2017-06-02 17:59:28 +08:00
|
|
|
/* Test allocation failures with dynamic arrays.
|
2020-01-01 08:14:33 +08:00
|
|
|
Copyright (C) 2017-2020 Free Software Foundation, Inc.
|
2017-06-02 17:59:28 +08:00
|
|
|
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/>. */
|
2017-06-02 17:59:28 +08:00
|
|
|
|
|
|
|
/* 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>
|