]> git.ipfire.org Git - thirdparty/glibc.git/blob - malloc/tst-dynarray-fail.c
e4a786282c42e0a77aee1800ea713eb299c23358
[thirdparty/glibc.git] / malloc / tst-dynarray-fail.c
1 /* Test allocation failures with dynamic arrays.
2 Copyright (C) 2017-2019 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>