]>
Commit | Line | Data |
---|---|---|
91b6eb11 FW |
1 | /* Test allocation failures with dynamic arrays. |
2 | Copyright (C) 2017 Free Software Foundation, Inc. | |
3 | This file is part of the GNU C Library. | |
4 | ||
5 | The GNU C Library is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU Lesser General Public | |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
9 | ||
10 | The GNU C Library is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | Lesser General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU Lesser General Public | |
16 | License along with the GNU C Library; if not, see | |
17 | <http://www.gnu.org/licenses/>. */ | |
18 | ||
19 | /* This test is separate from tst-dynarray because it cannot run under | |
20 | valgrind. */ | |
21 | ||
22 | #include "tst-dynarray-shared.h" | |
23 | ||
24 | #include <mcheck.h> | |
25 | #include <stdio.h> | |
26 | #include <support/check.h> | |
27 | #include <support/support.h> | |
28 | #include <support/xunistd.h> | |
29 | #include <sys/mman.h> | |
30 | #include <sys/resource.h> | |
31 | #include <unistd.h> | |
32 | ||
33 | /* Data structure to fill up the heap. */ | |
34 | struct heap_filler | |
35 | { | |
36 | struct heap_filler *next; | |
37 | }; | |
38 | ||
39 | /* Allocate objects until the heap is full. */ | |
40 | static struct heap_filler * | |
41 | fill_heap (void) | |
42 | { | |
43 | size_t pad = 4096; | |
44 | struct heap_filler *head = NULL; | |
45 | while (true) | |
46 | { | |
47 | struct heap_filler *new_head = malloc (sizeof (*new_head) + pad); | |
48 | if (new_head == NULL) | |
49 | { | |
50 | if (pad > 0) | |
51 | { | |
52 | /* Try again with smaller allocations. */ | |
53 | pad = 0; | |
54 | continue; | |
55 | } | |
56 | else | |
57 | break; | |
58 | } | |
59 | new_head->next = head; | |
60 | head = new_head; | |
61 | } | |
62 | return head; | |
63 | } | |
64 | ||
65 | /* Free the heap-filling allocations, so that we can continue testing | |
66 | and detect memory leaks elsewhere. */ | |
67 | static void | |
68 | free_fill_heap (struct heap_filler *head) | |
69 | { | |
70 | while (head != NULL) | |
71 | { | |
72 | struct heap_filler *next = head->next; | |
73 | free (head); | |
74 | head = next; | |
75 | } | |
76 | } | |
77 | ||
78 | /* Check allocation failures for int arrays (without an element free | |
79 | function). */ | |
80 | static void | |
81 | test_int_fail (void) | |
82 | { | |
83 | /* Exercise failure in add/emplace. | |
84 | ||
85 | do_add: Use emplace (false) or add (true) to add elements. | |
86 | do_finalize: Perform finalization at the end (instead of free). */ | |
87 | for (int do_add = 0; do_add < 2; ++do_add) | |
88 | for (int do_finalize = 0; do_finalize < 2; ++do_finalize) | |
89 | { | |
90 | struct dynarray_int dyn; | |
91 | dynarray_int_init (&dyn); | |
92 | size_t count = 0; | |
93 | while (true) | |
94 | { | |
95 | if (do_add) | |
96 | { | |
97 | dynarray_int_add (&dyn, 0); | |
98 | if (dynarray_int_has_failed (&dyn)) | |
99 | break; | |
100 | } | |
101 | else | |
102 | { | |
103 | int *place = dynarray_int_emplace (&dyn); | |
104 | if (place == NULL) | |
105 | break; | |
106 | TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn)); | |
107 | *place = 0; | |
108 | } | |
109 | ++count; | |
110 | } | |
111 | printf ("info: %s: failure after %zu elements\n", __func__, count); | |
112 | TEST_VERIFY_EXIT (dynarray_int_has_failed (&dyn)); | |
113 | if (do_finalize) | |
114 | { | |
115 | struct int_array result = { (int *) (uintptr_t) -1, -1 }; | |
116 | TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result)); | |
117 | TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1); | |
118 | TEST_VERIFY_EXIT (result.length == (size_t) -1); | |
119 | } | |
120 | else | |
121 | dynarray_int_free (&dyn); | |
122 | CHECK_INIT_STATE (int, &dyn); | |
123 | } | |
124 | ||
125 | /* Exercise failure in finalize. */ | |
126 | for (int do_add = 0; do_add < 2; ++do_add) | |
127 | { | |
128 | struct dynarray_int dyn; | |
129 | dynarray_int_init (&dyn); | |
130 | for (unsigned int i = 0; i < 10000; ++i) | |
131 | { | |
132 | if (do_add) | |
133 | { | |
134 | dynarray_int_add (&dyn, i); | |
135 | TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn)); | |
136 | } | |
137 | else | |
138 | { | |
139 | int *place = dynarray_int_emplace (&dyn); | |
140 | TEST_VERIFY_EXIT (place != NULL); | |
141 | *place = i; | |
142 | } | |
143 | } | |
144 | TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn)); | |
145 | struct heap_filler *heap_filler = fill_heap (); | |
146 | struct int_array result = { (int *) (uintptr_t) -1, -1 }; | |
147 | TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result)); | |
148 | TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1); | |
149 | TEST_VERIFY_EXIT (result.length == (size_t) -1); | |
150 | CHECK_INIT_STATE (int, &dyn); | |
151 | free_fill_heap (heap_filler); | |
152 | } | |
153 | ||
154 | /* Exercise failure in resize. */ | |
155 | { | |
156 | struct dynarray_int dyn; | |
157 | dynarray_int_init (&dyn); | |
158 | struct heap_filler *heap_filler = fill_heap (); | |
159 | TEST_VERIFY (!dynarray_int_resize (&dyn, 1000)); | |
160 | TEST_VERIFY (dynarray_int_has_failed (&dyn)); | |
161 | free_fill_heap (heap_filler); | |
162 | ||
163 | dynarray_int_init (&dyn); | |
164 | TEST_VERIFY (dynarray_int_resize (&dyn, 1)); | |
165 | heap_filler = fill_heap (); | |
166 | TEST_VERIFY (!dynarray_int_resize (&dyn, 1000)); | |
167 | TEST_VERIFY (dynarray_int_has_failed (&dyn)); | |
168 | free_fill_heap (heap_filler); | |
169 | ||
170 | dynarray_int_init (&dyn); | |
171 | TEST_VERIFY (dynarray_int_resize (&dyn, 1000)); | |
172 | heap_filler = fill_heap (); | |
173 | TEST_VERIFY (!dynarray_int_resize (&dyn, 2000)); | |
174 | TEST_VERIFY (dynarray_int_has_failed (&dyn)); | |
175 | free_fill_heap (heap_filler); | |
176 | } | |
177 | } | |
178 | ||
179 | /* Check allocation failures for char * arrays (which automatically | |
180 | free the pointed-to strings). */ | |
181 | static void | |
182 | test_str_fail (void) | |
183 | { | |
184 | /* Exercise failure in add/emplace. | |
185 | ||
186 | do_add: Use emplace (false) or add (true) to add elements. | |
187 | do_finalize: Perform finalization at the end (instead of free). */ | |
188 | for (int do_add = 0; do_add < 2; ++do_add) | |
189 | for (int do_finalize = 0; do_finalize < 2; ++do_finalize) | |
190 | { | |
191 | struct dynarray_str dyn; | |
192 | dynarray_str_init (&dyn); | |
193 | size_t count = 0; | |
194 | while (true) | |
195 | { | |
196 | char **place; | |
197 | if (do_add) | |
198 | { | |
199 | dynarray_str_add (&dyn, NULL); | |
200 | if (dynarray_str_has_failed (&dyn)) | |
201 | break; | |
202 | else | |
203 | place = dynarray_str_at (&dyn, dynarray_str_size (&dyn) - 1); | |
204 | } | |
205 | else | |
206 | { | |
207 | place = dynarray_str_emplace (&dyn); | |
208 | if (place == NULL) | |
209 | break; | |
210 | } | |
211 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); | |
212 | TEST_VERIFY_EXIT (*place == NULL); | |
213 | *place = strdup ("placeholder"); | |
214 | if (*place == NULL) | |
215 | { | |
216 | /* Second loop to wait for failure of | |
217 | dynarray_str_emplace. */ | |
218 | while (true) | |
219 | { | |
220 | if (do_add) | |
221 | { | |
222 | dynarray_str_add (&dyn, NULL); | |
223 | if (dynarray_str_has_failed (&dyn)) | |
224 | break; | |
225 | } | |
226 | else | |
227 | { | |
228 | char **place = dynarray_str_emplace (&dyn); | |
229 | if (place == NULL) | |
230 | break; | |
231 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); | |
232 | *place = NULL; | |
233 | } | |
234 | ++count; | |
235 | } | |
236 | break; | |
237 | } | |
238 | ++count; | |
239 | } | |
240 | printf ("info: %s: failure after %zu elements\n", __func__, count); | |
241 | TEST_VERIFY_EXIT (dynarray_str_has_failed (&dyn)); | |
242 | if (do_finalize) | |
243 | { | |
244 | struct str_array result = { (char **) (uintptr_t) -1, -1 }; | |
245 | TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result)); | |
246 | TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1); | |
247 | TEST_VERIFY_EXIT (result.length == (size_t) -1); | |
248 | } | |
249 | else | |
250 | dynarray_str_free (&dyn); | |
251 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); | |
252 | TEST_VERIFY_EXIT (dyn.dynarray_header.array == dyn.scratch); | |
253 | TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0); | |
254 | TEST_VERIFY_EXIT (dyn.dynarray_header.allocated > 0); | |
255 | } | |
256 | ||
257 | /* Exercise failure in finalize. */ | |
258 | for (int do_add = 0; do_add < 2; ++do_add) | |
259 | { | |
260 | struct dynarray_str dyn; | |
261 | dynarray_str_init (&dyn); | |
262 | for (unsigned int i = 0; i < 1000; ++i) | |
263 | { | |
264 | if (do_add) | |
265 | dynarray_str_add (&dyn, xstrdup ("placeholder")); | |
266 | else | |
267 | { | |
268 | char **place = dynarray_str_emplace (&dyn); | |
269 | TEST_VERIFY_EXIT (place != NULL); | |
270 | TEST_VERIFY_EXIT (*place == NULL); | |
271 | *place = xstrdup ("placeholder"); | |
272 | } | |
273 | } | |
274 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); | |
275 | struct heap_filler *heap_filler = fill_heap (); | |
276 | struct str_array result = { (char **) (uintptr_t) -1, -1 }; | |
277 | TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result)); | |
278 | TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1); | |
279 | TEST_VERIFY_EXIT (result.length == (size_t) -1); | |
280 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); | |
281 | TEST_VERIFY_EXIT (dyn.dynarray_header.array == dyn.scratch); | |
282 | TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0); | |
283 | TEST_VERIFY_EXIT (dyn.dynarray_header.allocated > 0); | |
284 | free_fill_heap (heap_filler); | |
285 | } | |
286 | ||
287 | /* Exercise failure in resize. */ | |
288 | { | |
289 | struct dynarray_str dyn; | |
290 | dynarray_str_init (&dyn); | |
291 | struct heap_filler *heap_filler = fill_heap (); | |
292 | TEST_VERIFY (!dynarray_str_resize (&dyn, 1000)); | |
293 | TEST_VERIFY (dynarray_str_has_failed (&dyn)); | |
294 | free_fill_heap (heap_filler); | |
295 | ||
296 | dynarray_str_init (&dyn); | |
297 | TEST_VERIFY (dynarray_str_resize (&dyn, 1)); | |
298 | *dynarray_str_at (&dyn, 0) = xstrdup ("allocated"); | |
299 | heap_filler = fill_heap (); | |
300 | TEST_VERIFY (!dynarray_str_resize (&dyn, 1000)); | |
301 | TEST_VERIFY (dynarray_str_has_failed (&dyn)); | |
302 | free_fill_heap (heap_filler); | |
303 | ||
304 | dynarray_str_init (&dyn); | |
305 | TEST_VERIFY (dynarray_str_resize (&dyn, 1000)); | |
306 | *dynarray_str_at (&dyn, 0) = xstrdup ("allocated"); | |
307 | heap_filler = fill_heap (); | |
308 | TEST_VERIFY (!dynarray_str_resize (&dyn, 2000)); | |
309 | TEST_VERIFY (dynarray_str_has_failed (&dyn)); | |
310 | free_fill_heap (heap_filler); | |
311 | } | |
312 | } | |
313 | ||
314 | /* Test if mmap can allocate a page. This is necessary because | |
315 | setrlimit does not fail even if it reduces the RLIMIT_AS limit | |
316 | below what is currently needed by the process. */ | |
317 | static bool | |
318 | mmap_works (void) | |
319 | { | |
320 | void *ptr = mmap (NULL, 1, PROT_READ | PROT_WRITE, | |
321 | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); | |
322 | if (ptr == MAP_FAILED) | |
323 | return false; | |
324 | xmunmap (ptr, 1); | |
325 | return true; | |
326 | } | |
327 | ||
328 | /* Set the RLIMIT_AS limit to the value in *LIMIT. */ | |
329 | static void | |
330 | xsetrlimit_as (const struct rlimit *limit) | |
331 | { | |
332 | if (setrlimit (RLIMIT_AS, limit) != 0) | |
333 | FAIL_EXIT1 ("setrlimit (RLIMIT_AS, %lu): %m", | |
334 | (unsigned long) limit->rlim_cur); | |
335 | } | |
336 | ||
337 | /* Approximately this many bytes can be allocated after | |
338 | reduce_rlimit_as has run. */ | |
339 | enum { as_limit_reserve = 2 * 1024 * 1024 }; | |
340 | ||
341 | /* Limit the size of the process, so that memory allocation in | |
342 | allocate_thread will eventually fail, without impacting the entire | |
343 | system. By default, a dynamic limit which leaves room for 2 MiB is | |
344 | activated. The TEST_RLIMIT_AS environment variable overrides | |
345 | it. */ | |
346 | static void | |
347 | reduce_rlimit_as (void) | |
348 | { | |
349 | struct rlimit limit; | |
350 | if (getrlimit (RLIMIT_AS, &limit) != 0) | |
351 | FAIL_EXIT1 ("getrlimit (RLIMIT_AS) failed: %m"); | |
352 | ||
353 | /* Use the TEST_RLIMIT_AS setting if available. */ | |
354 | { | |
355 | long target = 0; | |
356 | const char *variable = "TEST_RLIMIT_AS"; | |
357 | const char *target_str = getenv (variable); | |
358 | if (target_str != NULL) | |
359 | { | |
360 | target = atoi (target_str); | |
361 | if (target <= 0) | |
362 | FAIL_EXIT1 ("invalid %s value: \"%s\"", variable, target_str); | |
363 | printf ("info: setting RLIMIT_AS to %ld MiB\n", target); | |
364 | target *= 1024 * 1024; /* Convert to megabytes. */ | |
365 | limit.rlim_cur = target; | |
366 | xsetrlimit_as (&limit); | |
367 | return; | |
368 | } | |
369 | } | |
370 | ||
371 | /* Otherwise, try to find the limit with a binary search. */ | |
372 | unsigned long low = 1 << 20; | |
373 | limit.rlim_cur = low; | |
374 | xsetrlimit_as (&limit); | |
375 | ||
376 | /* Find working upper limit. */ | |
377 | unsigned long high = 1 << 30; | |
378 | while (true) | |
379 | { | |
380 | limit.rlim_cur = high; | |
381 | xsetrlimit_as (&limit); | |
382 | if (mmap_works ()) | |
383 | break; | |
384 | if (2 * high < high) | |
385 | FAIL_EXIT1 ("cannot find upper AS limit"); | |
386 | high *= 2; | |
387 | } | |
388 | ||
389 | /* Perform binary search. */ | |
390 | while ((high - low) > 128 * 1024) | |
391 | { | |
392 | unsigned long middle = (low + high) / 2; | |
393 | limit.rlim_cur = middle; | |
394 | xsetrlimit_as (&limit); | |
395 | if (mmap_works ()) | |
396 | high = middle; | |
397 | else | |
398 | low = middle; | |
399 | } | |
400 | ||
401 | unsigned long target = high + as_limit_reserve; | |
402 | limit.rlim_cur = target; | |
403 | xsetrlimit_as (&limit); | |
404 | printf ("info: RLIMIT_AS limit: %lu bytes\n", target); | |
405 | } | |
406 | ||
407 | static int | |
408 | do_test (void) | |
409 | { | |
410 | mtrace (); | |
411 | reduce_rlimit_as (); | |
412 | test_int_fail (); | |
413 | test_str_fail (); | |
414 | return 0; | |
415 | } | |
416 | ||
417 | #define TIMEOUT 90 | |
418 | #include <support/test-driver.c> |