about summary refs log tree commit diff
path: root/REORG.TODO/malloc/tst-dynarray-fail.c
diff options
context:
space:
mode:
authorZack Weinberg <zackw@panix.com>2017-06-08 15:39:03 -0400
committerZack Weinberg <zackw@panix.com>2017-06-08 15:39:03 -0400
commit5046dbb4a7eba5eccfd258f92f4735c9ffc8d069 (patch)
tree4470480d904b65cf14ca524f96f79eca818c3eaf /REORG.TODO/malloc/tst-dynarray-fail.c
parent199fc19d3aaaf57944ef036e15904febe877fc93 (diff)
downloadglibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.tar.gz
glibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.tar.xz
glibc-5046dbb4a7eba5eccfd258f92f4735c9ffc8d069.zip
Prepare for radical source tree reorganization. zack/build-layout-experiment
All top-level files and directories are moved into a temporary storage
directory, REORG.TODO, except for files that will certainly still
exist in their current form at top level when we're done (COPYING,
COPYING.LIB, LICENSES, NEWS, README), all old ChangeLog files (which
are moved to the new directory OldChangeLogs, instead), and the
generated file INSTALL (which is just deleted; in the new order, there
will be no generated files checked into version control).
Diffstat (limited to 'REORG.TODO/malloc/tst-dynarray-fail.c')
-rw-r--r--REORG.TODO/malloc/tst-dynarray-fail.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/REORG.TODO/malloc/tst-dynarray-fail.c b/REORG.TODO/malloc/tst-dynarray-fail.c
new file mode 100644
index 0000000000..508dbae93e
--- /dev/null
+++ b/REORG.TODO/malloc/tst-dynarray-fail.c
@@ -0,0 +1,418 @@
+/* Test allocation failures with dynamic arrays.
+   Copyright (C) 2017 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>