]> icculus.org git repositories - icculus/xz.git/blob - src/liblzma/common/memory_limitter.c
Prevent LZ encoder from hanging with known uncompressed
[icculus/xz.git] / src / liblzma / common / memory_limitter.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       memory_limitter.c
4 /// \brief      Limitting memory usage
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
8 //  This library is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU Lesser General Public
10 //  License as published by the Free Software Foundation; either
11 //  version 2.1 of the License, or (at your option) any later version.
12 //
13 //  This library is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 //  Lesser General Public License for more details.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "common.h"
21
22
23 /// Rounds an unsigned integer upwards to the next multiple.
24 #define my_ceil(num, multiple) \
25         ((num) + (((multiple) - ((num) % (multiple))) % (multiple)))
26
27
28 /// Add approximated overhead of malloc() to size and round upwards to the
29 /// next multiple of 2 * sizeof(size_t). I suppose that most malloc()
30 /// implementations align small allocations this way, but the overhead
31 /// varies due to several reasons (free lists, mmap() usage etc.).
32 ///
33 /// This doesn't need to be exact at all. It's enough to take into account
34 /// that there is some overhead. That way our memory usage count won't be
35 /// horribly wrong if we are used to allocate lots of small memory chunks.
36 #define malloc_ceil(size) \
37         my_ceil((size) + 2 * sizeof(void *), 2 * sizeof(size_t))
38
39
40 typedef struct lzma_memlimit_list_s lzma_memlimit_list;
41 struct lzma_memlimit_list_s {
42         lzma_memlimit_list *next;
43         void *ptr;
44         size_t size;
45 };
46
47
48 struct lzma_memlimit_s {
49         /// List of allocated memory chunks
50         lzma_memlimit_list *list;
51
52         /// Number of bytes currently allocated; this includes the memory
53         /// needed for the helper structures.
54         size_t used;
55
56         /// Memory usage limit
57         size_t limit;
58
59         /// Maximum amount of memory that have been or would have been needed.
60         /// That is, this is updated also if memory allocation fails, letting
61         /// the application check how much memory was tried to be allocated
62         /// in total.
63         size_t max;
64
65         /// True if lzma_memlimit_alloc() has returned NULL due to memory
66         /// usage limit.
67         bool limit_reached;
68 };
69
70
71 extern LZMA_API lzma_memlimit *
72 lzma_memlimit_create(size_t limit)
73 {
74         const size_t base_size = malloc_ceil(sizeof(lzma_memlimit));
75
76         if (limit < base_size)
77                 return NULL;
78
79         lzma_memlimit *mem = malloc(sizeof(lzma_memlimit));
80
81         if (mem != NULL) {
82                 mem->list = NULL;
83                 mem->used = base_size;
84                 mem->limit = limit;
85                 mem->max = base_size;
86                 mem->limit_reached = false;
87         }
88
89         return mem;
90 }
91
92
93 extern LZMA_API void
94 lzma_memlimit_set(lzma_memlimit *mem, size_t limit)
95 {
96         mem->limit = limit;
97         return;
98 }
99
100
101 extern LZMA_API size_t
102 lzma_memlimit_get(const lzma_memlimit *mem)
103 {
104         return mem->limit;
105 }
106
107
108 extern LZMA_API size_t
109 lzma_memlimit_used(const lzma_memlimit *mem)
110 {
111         return mem->used;
112 }
113
114
115 extern LZMA_API size_t
116 lzma_memlimit_max(lzma_memlimit *mem, lzma_bool clear)
117 {
118         const size_t ret = mem->max;
119
120         if (clear)
121                 mem->max = mem->used;
122
123         return ret;
124 }
125
126
127 extern LZMA_API lzma_bool
128 lzma_memlimit_reached(lzma_memlimit *mem, lzma_bool clear)
129 {
130         const bool ret = mem->limit_reached;
131
132         if (clear)
133                 mem->limit_reached = false;
134
135         return ret;
136 }
137
138
139 extern LZMA_API size_t
140 lzma_memlimit_count(const lzma_memlimit *mem)
141 {
142         // This is slow; we could have a counter in lzma_memlimit
143         // for fast version. I expect the primary use of this
144         // function to be limited to easy checking of memory leaks,
145         // in which this implementation is just fine.
146         size_t count = 0;
147         const lzma_memlimit_list *record = mem->list;
148
149         while (record != NULL) {
150                 ++count;
151                 record = record->next;
152         }
153
154         return count;
155 }
156
157
158 extern LZMA_API void
159 lzma_memlimit_end(lzma_memlimit *mem, lzma_bool free_allocated)
160 {
161         if (mem == NULL)
162                 return;
163
164         lzma_memlimit_list *record = mem->list;
165         while (record != NULL) {
166                 if (free_allocated)
167                         free(record->ptr);
168
169                 lzma_memlimit_list *tmp = record;
170                 record = record->next;
171                 free(tmp);
172         }
173
174         free(mem);
175
176         return;
177 }
178
179
180 extern LZMA_API void *
181 lzma_memlimit_alloc(lzma_memlimit *mem, size_t nmemb, size_t size)
182 {
183         // While liblzma always sets nmemb to one, do this multiplication
184         // to make these functions usable e.g. with zlib and libbzip2.
185         // Making sure that this doesn't overflow is up to the application.
186         size *= nmemb;
187
188         // Some malloc() implementations return NULL on malloc(0). We like
189         // to get a non-NULL value.
190         if (size == 0)
191                 size = 1;
192
193         // Calculate how much memory we are going to allocate in reality.
194         const size_t total_size = malloc_ceil(size)
195                         + malloc_ceil(sizeof(lzma_memlimit_list));
196
197         // Integer overflow protection for total_size and mem->used.
198         if (total_size <= size || SIZE_MAX - total_size < mem->used) {
199                 mem->max = SIZE_MAX;
200                 mem->limit_reached = true;
201                 return NULL;
202         }
203
204         // Update the maximum memory requirement counter if needed. This
205         // is updated even if memory allocation would fail or limit would
206         // be reached.
207         if (mem->used + total_size > mem->max)
208                 mem->max = mem->used + total_size;
209
210         // Check if we would stay in the memory usage limits. We need to
211         // check also that the current usage is in the limits, because
212         // the application could have decreased the limit between calls
213         // to this function.
214         if (mem->limit < mem->used || mem->limit - mem->used < total_size) {
215                 mem->limit_reached = true;
216                 return NULL;
217         }
218
219         // Allocate separate memory chunks for lzma_memlimit_list and the
220         // actual requested memory. Optimizing this to use only one
221         // allocation is not a good idea, because applications may want to
222         // detach lzma_extra structures that have been allocated with
223         // lzma_memlimit_alloc().
224         lzma_memlimit_list *record = malloc(sizeof(lzma_memlimit_list));
225         void *ptr = malloc(size);
226
227         if (record == NULL || ptr == NULL) {
228                 free(record);
229                 free(ptr);
230                 return NULL;
231         }
232
233         // Add the new entry to the beginning of the list. This should be
234         // more efficient when freeing memory, because usually it is
235         // "last allocated, first freed".
236         record->next = mem->list;
237         record->ptr = ptr;
238         record->size = total_size;
239
240         mem->list = record;
241         mem->used += total_size;
242
243         return ptr;
244 }
245
246
247 extern LZMA_API void
248 lzma_memlimit_detach(lzma_memlimit *mem, void *ptr)
249 {
250         if (ptr == NULL || mem->list == NULL)
251                 return;
252
253         lzma_memlimit_list *record = mem->list;
254         lzma_memlimit_list *prev = NULL;
255
256         while (record->ptr != ptr) {
257                 prev = record;
258                 record = record->next;
259                 if (record == NULL)
260                         return;
261         }
262
263         if (prev != NULL)
264                 prev->next = record->next;
265         else
266                 mem->list = record->next;
267
268         assert(mem->used >= record->size);
269         mem->used -= record->size;
270
271         free(record);
272
273         return;
274 }
275
276
277 extern LZMA_API void
278 lzma_memlimit_free(lzma_memlimit *mem, void *ptr)
279 {
280         if (ptr == NULL)
281                 return;
282
283         lzma_memlimit_detach(mem, ptr);
284
285         free(ptr);
286
287         return;
288 }