glibc/malloc/tst-dynarray-fail.c

419 lines
13 KiB
C
Raw Permalink Normal View History

/* Test allocation failures with dynamic arrays.
Copyright (C) 2017-2024 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
Prefer https to http for gnu.org and fsf.org URLs Also, change sources.redhat.com to sourceware.org. This patch was automatically generated by running the following shell script, which uses GNU sed, and which avoids modifying files imported from upstream: sed -ri ' s,(http|ftp)(://(.*\.)?(gnu|fsf|sourceware)\.org($|[^.]|\.[^a-z])),https\2,g s,(http|ftp)(://(.*\.)?)sources\.redhat\.com($|[^.]|\.[^a-z]),https\2sourceware.org\4,g ' \ $(find $(git ls-files) -prune -type f \ ! -name '*.po' \ ! -name 'ChangeLog*' \ ! -path COPYING ! -path COPYING.LIB \ ! -path manual/fdl-1.3.texi ! -path manual/lgpl-2.1.texi \ ! -path manual/texinfo.tex ! -path scripts/config.guess \ ! -path scripts/config.sub ! -path scripts/install-sh \ ! -path scripts/mkinstalldirs ! -path scripts/move-if-change \ ! -path INSTALL ! -path locale/programs/charmap-kw.h \ ! -path po/libc.pot ! -path sysdeps/gnu/errlist.c \ ! '(' -name configure \ -execdir test -f configure.ac -o -f configure.in ';' ')' \ ! '(' -name preconfigure \ -execdir test -f preconfigure.ac ';' ')' \ -print) and then by running 'make dist-prepare' to regenerate files built from the altered files, and then executing the following to cleanup: chmod a+x sysdeps/unix/sysv/linux/riscv/configure # Omit irrelevant whitespace and comment-only changes, # perhaps from a slightly-different Autoconf version. git checkout -f \ sysdeps/csky/configure \ sysdeps/hppa/configure \ sysdeps/riscv/configure \ sysdeps/unix/sysv/linux/csky/configure # Omit changes that caused a pre-commit check to fail like this: # remote: *** error: sysdeps/powerpc/powerpc64/ppc-mcount.S: trailing lines git checkout -f \ sysdeps/powerpc/powerpc64/ppc-mcount.S \ sysdeps/unix/sysv/linux/s390/s390-64/syscall.S # Omit change that caused a pre-commit check to fail like this: # remote: *** error: sysdeps/sparc/sparc64/multiarch/memcpy-ultra3.S: last line does not end in newline git checkout -f sysdeps/sparc/sparc64/multiarch/memcpy-ultra3.S
2019-09-07 13:40:42 +08:00
<https://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.u.dynarray_header.array == dyn.scratch);
TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
TEST_VERIFY_EXIT (dyn.u.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.u.dynarray_header.array == dyn.scratch);
TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
TEST_VERIFY_EXIT (dyn.u.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>