Blame malloc/tst-dynarray-fail.c

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