1 | /* Test allocation failures with dynamic arrays. |
2 | Copyright (C) 2017-2022 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 | <https://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 (size: 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 (ptr: 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 (list: &dyn); |
92 | size_t count = 0; |
93 | while (true) |
94 | { |
95 | if (do_add) |
96 | { |
97 | dynarray_int_add (list: &dyn, item: 0); |
98 | if (dynarray_int_has_failed (list: &dyn)) |
99 | break; |
100 | } |
101 | else |
102 | { |
103 | int *place = dynarray_int_emplace (list: &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 (format: "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 (list: &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 (list: &dyn); |
130 | for (unsigned int i = 0; i < 10000; ++i) |
131 | { |
132 | if (do_add) |
133 | { |
134 | dynarray_int_add (list: &dyn, item: i); |
135 | TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn)); |
136 | } |
137 | else |
138 | { |
139 | int *place = dynarray_int_emplace (list: &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 (head: heap_filler); |
152 | } |
153 | |
154 | /* Exercise failure in resize. */ |
155 | { |
156 | struct dynarray_int dyn; |
157 | dynarray_int_init (list: &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 (head: heap_filler); |
162 | |
163 | dynarray_int_init (list: &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 (head: heap_filler); |
169 | |
170 | dynarray_int_init (list: &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 (head: 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 (list: &dyn); |
193 | size_t count = 0; |
194 | while (true) |
195 | { |
196 | char **place; |
197 | if (do_add) |
198 | { |
199 | dynarray_str_add (list: &dyn, NULL); |
200 | if (dynarray_str_has_failed (list: &dyn)) |
201 | break; |
202 | else |
203 | place = dynarray_str_at (list: &dyn, index: dynarray_str_size (list: &dyn) - 1); |
204 | } |
205 | else |
206 | { |
207 | place = dynarray_str_emplace (list: &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 (s: "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 (list: &dyn, NULL); |
223 | if (dynarray_str_has_failed (list: &dyn)) |
224 | break; |
225 | } |
226 | else |
227 | { |
228 | char **place = dynarray_str_emplace (list: &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 (format: "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 (list: &dyn); |
251 | TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn)); |
252 | TEST_VERIFY_EXIT (dyn.u.dynarray_header.array == dyn.scratch); |
253 | TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0); |
254 | TEST_VERIFY_EXIT (dyn.u.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 (list: &dyn); |
262 | for (unsigned int i = 0; i < 1000; ++i) |
263 | { |
264 | if (do_add) |
265 | dynarray_str_add (list: &dyn, item: xstrdup ("placeholder" )); |
266 | else |
267 | { |
268 | char **place = dynarray_str_emplace (list: &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.u.dynarray_header.array == dyn.scratch); |
282 | TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0); |
283 | TEST_VERIFY_EXIT (dyn.u.dynarray_header.allocated > 0); |
284 | free_fill_heap (head: heap_filler); |
285 | } |
286 | |
287 | /* Exercise failure in resize. */ |
288 | { |
289 | struct dynarray_str dyn; |
290 | dynarray_str_init (list: &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 (head: heap_filler); |
295 | |
296 | dynarray_str_init (list: &dyn); |
297 | TEST_VERIFY (dynarray_str_resize (&dyn, 1)); |
298 | *dynarray_str_at (list: &dyn, index: 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 (head: heap_filler); |
303 | |
304 | dynarray_str_init (list: &dyn); |
305 | TEST_VERIFY (dynarray_str_resize (&dyn, 1000)); |
306 | *dynarray_str_at (list: &dyn, index: 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 (head: 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, len: 1, PROT_READ | PROT_WRITE, |
321 | MAP_ANONYMOUS | MAP_PRIVATE, fd: -1, offset: 0); |
322 | if (ptr == MAP_FAILED) |
323 | return false; |
324 | xmunmap (addr: ptr, length: 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, rlimits: 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, rlimits: &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 (format: "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: &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: &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: &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: &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: &limit); |
404 | printf (format: "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> |
419 | |