123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- /* Test allocation failures with dynamic arrays.
- Copyright (C) 2017-2019 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <http://www.gnu.org/licenses/>. */
- /* This test is separate from tst-dynarray because it cannot run under
- valgrind. */
- #include "tst-dynarray-shared.h"
- #include <mcheck.h>
- #include <stdio.h>
- #include <support/check.h>
- #include <support/support.h>
- #include <support/xunistd.h>
- #include <sys/mman.h>
- #include <sys/resource.h>
- #include <unistd.h>
- /* Data structure to fill up the heap. */
- struct heap_filler
- {
- struct heap_filler *next;
- };
- /* Allocate objects until the heap is full. */
- static struct heap_filler *
- fill_heap (void)
- {
- size_t pad = 4096;
- struct heap_filler *head = NULL;
- while (true)
- {
- struct heap_filler *new_head = malloc (sizeof (*new_head) + pad);
- if (new_head == NULL)
- {
- if (pad > 0)
- {
- /* Try again with smaller allocations. */
- pad = 0;
- continue;
- }
- else
- break;
- }
- new_head->next = head;
- head = new_head;
- }
- return head;
- }
- /* Free the heap-filling allocations, so that we can continue testing
- and detect memory leaks elsewhere. */
- static void
- free_fill_heap (struct heap_filler *head)
- {
- while (head != NULL)
- {
- struct heap_filler *next = head->next;
- free (head);
- head = next;
- }
- }
- /* Check allocation failures for int arrays (without an element free
- function). */
- static void
- test_int_fail (void)
- {
- /* Exercise failure in add/emplace.
- do_add: Use emplace (false) or add (true) to add elements.
- do_finalize: Perform finalization at the end (instead of free). */
- for (int do_add = 0; do_add < 2; ++do_add)
- for (int do_finalize = 0; do_finalize < 2; ++do_finalize)
- {
- struct dynarray_int dyn;
- dynarray_int_init (&dyn);
- size_t count = 0;
- while (true)
- {
- if (do_add)
- {
- dynarray_int_add (&dyn, 0);
- if (dynarray_int_has_failed (&dyn))
- break;
- }
- else
- {
- int *place = dynarray_int_emplace (&dyn);
- if (place == NULL)
- break;
- TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
- *place = 0;
- }
- ++count;
- }
- printf ("info: %s: failure after %zu elements\n", __func__, count);
- TEST_VERIFY_EXIT (dynarray_int_has_failed (&dyn));
- if (do_finalize)
- {
- struct int_array result = { (int *) (uintptr_t) -1, -1 };
- TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result));
- TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1);
- TEST_VERIFY_EXIT (result.length == (size_t) -1);
- }
- else
- dynarray_int_free (&dyn);
- CHECK_INIT_STATE (int, &dyn);
- }
- /* Exercise failure in finalize. */
- for (int do_add = 0; do_add < 2; ++do_add)
- {
- struct dynarray_int dyn;
- dynarray_int_init (&dyn);
- for (unsigned int i = 0; i < 10000; ++i)
- {
- if (do_add)
- {
- dynarray_int_add (&dyn, i);
- TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
- }
- else
- {
- int *place = dynarray_int_emplace (&dyn);
- TEST_VERIFY_EXIT (place != NULL);
- *place = i;
- }
- }
- TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
- struct heap_filler *heap_filler = fill_heap ();
- struct int_array result = { (int *) (uintptr_t) -1, -1 };
- TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result));
- TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1);
- TEST_VERIFY_EXIT (result.length == (size_t) -1);
- CHECK_INIT_STATE (int, &dyn);
- free_fill_heap (heap_filler);
- }
- /* Exercise failure in resize. */
- {
- struct dynarray_int dyn;
- dynarray_int_init (&dyn);
- struct heap_filler *heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_int_resize (&dyn, 1000));
- TEST_VERIFY (dynarray_int_has_failed (&dyn));
- free_fill_heap (heap_filler);
- dynarray_int_init (&dyn);
- TEST_VERIFY (dynarray_int_resize (&dyn, 1));
- heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_int_resize (&dyn, 1000));
- TEST_VERIFY (dynarray_int_has_failed (&dyn));
- free_fill_heap (heap_filler);
- dynarray_int_init (&dyn);
- TEST_VERIFY (dynarray_int_resize (&dyn, 1000));
- heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_int_resize (&dyn, 2000));
- TEST_VERIFY (dynarray_int_has_failed (&dyn));
- free_fill_heap (heap_filler);
- }
- }
- /* Check allocation failures for char * arrays (which automatically
- free the pointed-to strings). */
- static void
- test_str_fail (void)
- {
- /* Exercise failure in add/emplace.
- do_add: Use emplace (false) or add (true) to add elements.
- do_finalize: Perform finalization at the end (instead of free). */
- for (int do_add = 0; do_add < 2; ++do_add)
- for (int do_finalize = 0; do_finalize < 2; ++do_finalize)
- {
- struct dynarray_str dyn;
- dynarray_str_init (&dyn);
- size_t count = 0;
- while (true)
- {
- char **place;
- if (do_add)
- {
- dynarray_str_add (&dyn, NULL);
- if (dynarray_str_has_failed (&dyn))
- break;
- else
- place = dynarray_str_at (&dyn, dynarray_str_size (&dyn) - 1);
- }
- else
- {
- place = dynarray_str_emplace (&dyn);
- if (place == NULL)
- break;
- }
- TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
- TEST_VERIFY_EXIT (*place == NULL);
- *place = strdup ("placeholder");
- if (*place == NULL)
- {
- /* Second loop to wait for failure of
- dynarray_str_emplace. */
- while (true)
- {
- if (do_add)
- {
- dynarray_str_add (&dyn, NULL);
- if (dynarray_str_has_failed (&dyn))
- break;
- }
- else
- {
- char **place = dynarray_str_emplace (&dyn);
- if (place == NULL)
- break;
- TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
- *place = NULL;
- }
- ++count;
- }
- break;
- }
- ++count;
- }
- printf ("info: %s: failure after %zu elements\n", __func__, count);
- TEST_VERIFY_EXIT (dynarray_str_has_failed (&dyn));
- if (do_finalize)
- {
- struct str_array result = { (char **) (uintptr_t) -1, -1 };
- TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result));
- TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1);
- TEST_VERIFY_EXIT (result.length == (size_t) -1);
- }
- else
- dynarray_str_free (&dyn);
- TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
- TEST_VERIFY_EXIT (dyn.dynarray_header.array == dyn.scratch);
- TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
- TEST_VERIFY_EXIT (dyn.dynarray_header.allocated > 0);
- }
- /* Exercise failure in finalize. */
- for (int do_add = 0; do_add < 2; ++do_add)
- {
- struct dynarray_str dyn;
- dynarray_str_init (&dyn);
- for (unsigned int i = 0; i < 1000; ++i)
- {
- if (do_add)
- dynarray_str_add (&dyn, xstrdup ("placeholder"));
- else
- {
- char **place = dynarray_str_emplace (&dyn);
- TEST_VERIFY_EXIT (place != NULL);
- TEST_VERIFY_EXIT (*place == NULL);
- *place = xstrdup ("placeholder");
- }
- }
- TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
- struct heap_filler *heap_filler = fill_heap ();
- struct str_array result = { (char **) (uintptr_t) -1, -1 };
- TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result));
- TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1);
- TEST_VERIFY_EXIT (result.length == (size_t) -1);
- TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
- TEST_VERIFY_EXIT (dyn.dynarray_header.array == dyn.scratch);
- TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
- TEST_VERIFY_EXIT (dyn.dynarray_header.allocated > 0);
- free_fill_heap (heap_filler);
- }
- /* Exercise failure in resize. */
- {
- struct dynarray_str dyn;
- dynarray_str_init (&dyn);
- struct heap_filler *heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_str_resize (&dyn, 1000));
- TEST_VERIFY (dynarray_str_has_failed (&dyn));
- free_fill_heap (heap_filler);
- dynarray_str_init (&dyn);
- TEST_VERIFY (dynarray_str_resize (&dyn, 1));
- *dynarray_str_at (&dyn, 0) = xstrdup ("allocated");
- heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_str_resize (&dyn, 1000));
- TEST_VERIFY (dynarray_str_has_failed (&dyn));
- free_fill_heap (heap_filler);
- dynarray_str_init (&dyn);
- TEST_VERIFY (dynarray_str_resize (&dyn, 1000));
- *dynarray_str_at (&dyn, 0) = xstrdup ("allocated");
- heap_filler = fill_heap ();
- TEST_VERIFY (!dynarray_str_resize (&dyn, 2000));
- TEST_VERIFY (dynarray_str_has_failed (&dyn));
- free_fill_heap (heap_filler);
- }
- }
- /* Test if mmap can allocate a page. This is necessary because
- setrlimit does not fail even if it reduces the RLIMIT_AS limit
- below what is currently needed by the process. */
- static bool
- mmap_works (void)
- {
- void *ptr = mmap (NULL, 1, PROT_READ | PROT_WRITE,
- MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
- if (ptr == MAP_FAILED)
- return false;
- xmunmap (ptr, 1);
- return true;
- }
- /* Set the RLIMIT_AS limit to the value in *LIMIT. */
- static void
- xsetrlimit_as (const struct rlimit *limit)
- {
- if (setrlimit (RLIMIT_AS, limit) != 0)
- FAIL_EXIT1 ("setrlimit (RLIMIT_AS, %lu): %m",
- (unsigned long) limit->rlim_cur);
- }
- /* Approximately this many bytes can be allocated after
- reduce_rlimit_as has run. */
- enum { as_limit_reserve = 2 * 1024 * 1024 };
- /* Limit the size of the process, so that memory allocation in
- allocate_thread will eventually fail, without impacting the entire
- system. By default, a dynamic limit which leaves room for 2 MiB is
- activated. The TEST_RLIMIT_AS environment variable overrides
- it. */
- static void
- reduce_rlimit_as (void)
- {
- struct rlimit limit;
- if (getrlimit (RLIMIT_AS, &limit) != 0)
- FAIL_EXIT1 ("getrlimit (RLIMIT_AS) failed: %m");
- /* Use the TEST_RLIMIT_AS setting if available. */
- {
- long target = 0;
- const char *variable = "TEST_RLIMIT_AS";
- const char *target_str = getenv (variable);
- if (target_str != NULL)
- {
- target = atoi (target_str);
- if (target <= 0)
- FAIL_EXIT1 ("invalid %s value: \"%s\"", variable, target_str);
- printf ("info: setting RLIMIT_AS to %ld MiB\n", target);
- target *= 1024 * 1024; /* Convert to megabytes. */
- limit.rlim_cur = target;
- xsetrlimit_as (&limit);
- return;
- }
- }
- /* Otherwise, try to find the limit with a binary search. */
- unsigned long low = 1 << 20;
- limit.rlim_cur = low;
- xsetrlimit_as (&limit);
- /* Find working upper limit. */
- unsigned long high = 1 << 30;
- while (true)
- {
- limit.rlim_cur = high;
- xsetrlimit_as (&limit);
- if (mmap_works ())
- break;
- if (2 * high < high)
- FAIL_EXIT1 ("cannot find upper AS limit");
- high *= 2;
- }
- /* Perform binary search. */
- while ((high - low) > 128 * 1024)
- {
- unsigned long middle = (low + high) / 2;
- limit.rlim_cur = middle;
- xsetrlimit_as (&limit);
- if (mmap_works ())
- high = middle;
- else
- low = middle;
- }
- unsigned long target = high + as_limit_reserve;
- limit.rlim_cur = target;
- xsetrlimit_as (&limit);
- printf ("info: RLIMIT_AS limit: %lu bytes\n", target);
- }
- static int
- do_test (void)
- {
- mtrace ();
- reduce_rlimit_as ();
- test_int_fail ();
- test_str_fail ();
- return 0;
- }
- #define TIMEOUT 90
- #include <support/test-driver.c>
|