This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] Allocation buffers for NSS result construction



On 21/06/2017 12:46, Florian Weimer wrote:
> On 06/16/2017 04:42 PM, Adhemerval Zanella wrote:
> 
>> I would prefer to split the patch in two, one for the alloc_buffer adition
>> and another one for its use in NSS result construction.
> Okay, continuing with the allocation buffers only for now
> 
>>> +/* struct alloc_buffer objects refer to a region of bytes in memory of a
>>> +   fixed size.  The functions below can be used to allocate single
>>> +   objects and arrays from this memory region, or write to its end.
>>> +   On allocation failure (or if an attempt to write beyond the end of
>>> +   the buffer with one of the copy functions), the buffer enters a
>>> +   failed state.
>>> +
>>> +   struct alloc_buffer objects can be copied.  The backing buffer will
>>> +   be shared, but the current write position will be independent.
>>> +
>>> +   Conceptually, the memory region consists of a current write pointer
>>> +   and a limit, beyond which the write pointer cannot move.  */
>> A new line maybe?
> What do you mean?

My mistake here, nvm.

>>> +/* Create a new allocation buffer.  The byte range from START to START
>>> +   + SIZE - 1 must be valid, and the allocation buffer allocates
>>> +   objects from that range.  If START is NULL (so that SIZE must be
>>> +   0), the buffer is marked as failed immediately.  */
>>> +static inline struct alloc_buffer
>>> +alloc_buffer_create (void *start, size_t size)
>>> +{
>>> +  return (struct alloc_buffer)
>>> +    {
>>> +      .__alloc_buffer_current = (uintptr_t) start,
>>> +      .__alloc_buffer_end = (uintptr_t) start + size
>>> +    };
>>> +}
>> Should we add an overflow test for sanity tests?
> I don't think it's worthwhile to do that because the memory range is
> already invalid at this point.

Some functions like 'alloc_buffer_add_byte' does have a check to mark
the buffer failed, but 'alloc_buffer_size' will return a bogus value if
'start' plus 'size' overflows.  It might not get caught in
'alloc_buffer_alloc_bytes' for instance, where the 'length' can be
potentially lower than the returned value of 'alloc_buffer_size'
(since a signed ptrdiff_t will be converted to size_t).  Maybe to add
the same check from 'alloc_buffer_add_byte' might be suffice.

> 
>>> +/* Internal function.  See alloc_buffer_allocate below.  */
>>> +struct alloc_buffer __libc_alloc_buffer_allocate (size_t size);
>>> +libc_hidden_proto (__libc_alloc_buffer_allocate)
>> I am getting this while trying to build malloc/tst-alloc_buffer.o:
>>
>> ../include/alloc_buffer.h:125:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
>>  libc_hidden_proto (__libc_alloc_buffer_allocate)
>>
>> This is due 7c3018f9 (Suppress internal declarations for most of the
>> testsuite) which suppress libc_hidden_proto for testsuite.  I think we
>> need either to make them empty macros in this case (move their definition
>> outside the _ISOMAC) or move the libc_hidden_proto to another internal
>> header.
> Yes, fixed in the attached patch.
> 
>>> +/* Deallocate the buffer and mark it as failed.  The buffer must be in
>>> +   its initial state; if data has been added to it, an invocation of
>>> +   alloc_buffer_free results in undefined behavior.  This means that
>>> +   callers need to make a copy of the buffer if they need to free it
>>> +   later.  Deallocating a failed buffer is allowed; it has no
>>> +   effect.  */
>>> +static inline void
>>> +alloc_buffer_free (struct alloc_buffer *buf)
>>> +{
>>> +  _Static_assert (__ALLOC_BUFFER_INVALID_POINTER == 0,
>>> +		  "free can be called on __ALLOC_BUFFER_INVALID_POINTER");
>>> +  free ((void *) buf->__alloc_buffer_current);
>>> +  alloc_buffer_mark_failed (buf);
>>> +}
>> No need to cast to void *.
> buf->__alloc_buffer_current is uinptr_t, to help with the alignment
> operations.

Ack.

> 
>>> +/* Internal function.  Obtain a pointer to an object.  */
>>> +static inline void *
>>> +__alloc_buffer_alloc (struct alloc_buffer *buf, size_t size, size_t align)
>>> +{
>>> +  if (size == 1 && align == 1)
>>> +    return alloc_buffer_alloc_bytes (buf, size);
>>> +
>>> +  size_t current = buf->__alloc_buffer_current;
>>> +  size_t aligned = roundup (current, align);
>>> +  size_t new_current = aligned + size;
>>> +  if (aligned >= current        /* No overflow in align step.  */
>>> +      && new_current >= size    /* No overflow in size computation.  */
>>> +      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
>>> +    {
>>> +      buf->__alloc_buffer_current = new_current;
>>> +      return (void *) aligned;
>>> +    }
>>> +  else
>>> +    {
>>> +      alloc_buffer_mark_failed (buf);
>>> +      return NULL;
>>> +    }
>>> +}
>> Maybe use/add the check_add_wrapv_size_t from my char_array patch (also
>> for the other occurences)?
> Hmm.  I think the comparison idiom is sufficiently common to use it this
> way.

Indeed, but check_add_wrapv_size_t (or check_add_overflow_size_t as you
suggested) might use a GCC builtin for newer compiler (which might use
more optimized instructions).

> 
>>> +
>>> +/* Obtain a TYPE * pointer to an object in BUF of TYPE.  Consume these
>>> +   bytes from the buffer.  Return NULL and mark the buffer as failed
>>> +   if if there is not enough room in the buffer, or if the buffer has
>>> +   failed before.  */
>>> +#define alloc_buffer_alloc(buf, type)				\
>>> +  ((type *) __alloc_buffer_alloc				\
>>> +   (buf, __alloc_buffer_assert_size (sizeof (type)),		\
>>> +    __alloc_buffer_assert_align (__alignof__ (type))))
>> I would prefer to use a static inline function to type check, but we
>> can like with it (same for other occurencies).
> It would need C++ because TYPE is a type parameter.  Everything that can
> be a function already is.

Ack.

> 
>>> +void *
>>> +__libc_alloc_buffer_alloc_array (struct alloc_buffer *buf, size_t element_size,
>>> +                                 size_t align, size_t count)
>>> +{
>>> +  size_t current = buf->__alloc_buffer_current;
>>> +  /* The caller asserts that align is a power of two.  */
>>> +  size_t aligned = (current + align - 1) & ~(align - 1);
>> Maybe use ALIGN_UP?
> Good idea.
> 
>>> +  struct alloc_buffer buf = alloc_buffer_create (dst, dstsiz);
>>> +  if (__ns_name_ntop_buffer (&buf, src) == NULL)
>>> +    {
>>> +      __set_errno (EMSGSIZE);
>>> +      return -1;
>>> +    }
>>> +  return alloc_buffer_next (&buf, void) - (const void *) dst;
>> I am not sure how safe is convert a ptrdiff_t to a int at this point.  Does
>> it worth add a check for it?
> It's a bit ugly, yes.  I should probably check for INT_MAX overflow, too.
> 
> In the attached patch, I changed the signature for alloc_buffer_allocate
> and removed alloc_buffer_free because the latter encouraged incorrect
> use.  Now, alloc_buffer_allocate produces a copy of the initial buffer
> pointer which can be used directly with free.

Ack.  The rest of the patch looks ok with 2 nits below.

> 
> Thanks,
> Florian
> 
> 
> alloc_buffer.patch
> 
> 
> Implement allocation buffers for internal use
> 
> This commit adds fixed-size allocation buffers.  The primary use
> case is in NSS modules, where dynamically sized data is stored
> in a fixed-size buffer provided by the caller.
> 
> Other uses include a replacement of mempcpy cascades (which is
> safer due to the size checking inherent to allocation buffers).
> 
> 2017-04-22  Florian Weimer  <fweimer@redhat.com>
> 
> 	* malloc/Makefile (tests-internal): Add tst-alloc_buffer.
> 	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
> 	alloc_buffer_copy_bytes, alloc_buffer_copy_string.
> 	* malloc/Versions (__libc_alloc_buffer_alloc_array)
> 	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes)
> 	(__libc_alloc_buffer_copy_string): Export as GLIBC_PRIVATE.
> 	* malloc/alloc_buffer_alloc_array.c: New file.
> 	* malloc/alloc_buffer_allocate.c: Likewise.
> 	* malloc/alloc_buffer_copy_bytes.c: Likewise.
> 	* malloc/alloc_buffer_copy_string.c: Likewise.
> 	* malloc/tst-alloc_buffer.c: Likewise.
> 
> diff --git a/include/alloc_buffer.h b/include/alloc_buffer.h
> new file mode 100644
> index 0000000..298bd72
> --- /dev/null
> +++ b/include/alloc_buffer.h
> @@ -0,0 +1,358 @@
> +/* Allocation from a fixed-size buffer.
> +   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/>.  */
> +
> +/* Allocation buffers are used to carve out sub-allocations from a
> +   larger allocation.  Their primary application is in writing NSS
> +   modules, which recieve a caller-allocated buffer in which they are

s/recieve/receive

> +   expected to store variable-length results:
> +
> +     void *buffer = ...;
> +     size_t buffer_size = ...;
> +
> +     struct alloc_buffer buf = alloc_buffer_create (buffer, buffer_size);
> +     result->gr_name = alloc_buffer_copy_string (&buf, name);
> +
> +     // Allocate a list of group_count groups and copy strings into it.
> +     char **group_list = alloc_buffer_alloc_array
> +       (&buf, char *, group_count  + 1);
> +     if (group_list == NULL)
> +       return ...; // Request a larger buffer.
> +     for (int i = 0; i < group_count; ++i)
> +       group_list[i] = alloc_buffer_copy_string (&buf, group_list_src[i]);
> +     group_list[group_count] = NULL;
> +     ...
> +
> +     if (alloc_buffer_has_failed (&buf))
> +       return ...; // Request a larger buffer.
> +     result->gr_mem = group_list;
> +     ...
> +
> +   Note that it is not necessary to check the results of individual
> +   allocation operations if the returned pointer is not dereferenced.
> +   Allocation failure is sticky, so one check using
> +   alloc_buffer_has_failed at the end covers all previous failures.
> +
> +   A different use case involves combining multiple heap allocations
> +   into a single, large one.  In the following example, an array of
> +   doubles and an array of ints is allocated:
> +
> +     size_t double_array_size = ...;
> +     size_t int_array_size = ...;
> +
> +     void *heap_ptr;
> +     struct alloc_buffer buf = alloc_buffer_allocate
> +       (double_array_size * sizeof (double) + int_array_size * sizeof (int),
> +        &heap_ptr);

I am not very found of this example because it does not check for
potentially overflow the size calculation.

> +     _Static_assert (__alignof__ (double) >= __alignof__ (int),
> +                     "no padding after double array");
> +     double *double_array = alloc_buffer_alloc_array
> +       (&buf, double, double_array_size);
> +     int *int_array = alloc_buffer_alloc_array (&buf, int, int_array_size);
> +     if (alloc_buffer_has_failed (&buf))
> +       return ...; // Report error.
> +     ...
> +     free (heap_ptr);
> +
> +   The advantage over manual coding is that the computation of the
> +   allocation size does not need an overflow check.  The size
> +   computation is checked for consistency at run time, too.  */
> +
> +#ifndef _ALLOC_BUFFER_H
> +#define _ALLOC_BUFFER_H
> +
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdlib.h>
> +#include <sys/param.h>
> +
> +/* struct alloc_buffer objects refer to a region of bytes in memory of a
> +   fixed size.  The functions below can be used to allocate single
> +   objects and arrays from this memory region, or write to its end.
> +   On allocation failure (or if an attempt to write beyond the end of
> +   the buffer with one of the copy functions), the buffer enters a
> +   failed state.
> +
> +   struct alloc_buffer objects can be copied.  The backing buffer will
> +   be shared, but the current write position will be independent.
> +
> +   Conceptually, the memory region consists of a current write pointer
> +   and a limit, beyond which the write pointer cannot move.  */
> +struct alloc_buffer
> +{
> +  /* uintptr_t is used here to simplify the alignment code, and to
> +     avoid issues undefined subtractions if the buffer covers more
> +     than half of the address space (which would result in differences
> +     which could not be represented as a ptrdiff_t value).  */
> +  uintptr_t __alloc_buffer_current;
> +  uintptr_t __alloc_buffer_end;
> +};
> +
> +enum
> +  {
> +    /* The value for the __alloc_buffer_current member which marks the
> +       buffer as invalid (together with a zero-length buffer).  */
> +    __ALLOC_BUFFER_INVALID_POINTER = 0,
> +  };
> +
> +/* Create a new allocation buffer.  The byte range from START to START
> +   + SIZE - 1 must be valid, and the allocation buffer allocates
> +   objects from that range.  If START is NULL (so that SIZE must be
> +   0), the buffer is marked as failed immediately.  */
> +static inline struct alloc_buffer
> +alloc_buffer_create (void *start, size_t size)
> +{
> +  return (struct alloc_buffer)
> +    {
> +      .__alloc_buffer_current = (uintptr_t) start,
> +      .__alloc_buffer_end = (uintptr_t) start + size
> +    };
> +}
> +
> +/* Internal function.  See alloc_buffer_allocate below.  */
> +struct alloc_buffer __libc_alloc_buffer_allocate (size_t size, void **pptr)
> +  __attribute__ ((nonnull (2)));
> +libc_hidden_proto (__libc_alloc_buffer_allocate)
> +
> +/* Allocate a buffer of SIZE bytes using malloc.  The returned buffer
> +   is in a failed state if malloc fails.  *PPTR points to the start of
> +   the buffer and can be used to free it later, after the returned
> +   buffer has been freed.  */
> +static __always_inline __attribute__ ((nonnull (2)))
> +struct alloc_buffer alloc_buffer_allocate (size_t size, void **pptr)
> +{
> +  return __libc_alloc_buffer_allocate (size, pptr);
> +}
> +
> +/* Mark the buffer as failed.  */
> +static inline void __attribute__ ((nonnull (1)))
> +alloc_buffer_mark_failed (struct alloc_buffer *buf)
> +{
> +  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
> +  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
> +}
> +
> +/* Return the remaining number of bytes in the buffer.  */
> +static __always_inline __attribute__ ((nonnull (1))) size_t
> +alloc_buffer_size (const struct alloc_buffer *buf)
> +{
> +  return buf->__alloc_buffer_end - buf->__alloc_buffer_current;
> +}
> +
> +/* Return true if the buffer has been marked as failed.  */
> +static inline bool __attribute__ ((nonnull (1)))
> +alloc_buffer_has_failed (const struct alloc_buffer *buf)
> +{
> +  return buf->__alloc_buffer_current == __ALLOC_BUFFER_INVALID_POINTER;
> +}
> +
> +/* Add a single byte to the buffer (consuming the space for this
> +   byte).  Mark the buffer as failed if there is not enough room.  */
> +static inline void __attribute__ ((nonnull (1)))
> +alloc_buffer_add_byte (struct alloc_buffer *buf, unsigned char b)
> +{
> +  if (__glibc_likely (buf->__alloc_buffer_current < buf->__alloc_buffer_end))
> +    {
> +      *(unsigned char *) buf->__alloc_buffer_current = b;
> +      ++buf->__alloc_buffer_current;
> +    }
> +  else
> +    alloc_buffer_mark_failed (buf);
> +}
> +
> +/* Obtain a pointer to LENGTH bytes in BUF, and consume these bytes.
> +   NULL is returned if there is not enough room, and the buffer is
> +   marked as failed, or if the buffer has already failed.
> +   (Zero-length allocations from an empty buffer which has not yet
> +   failed succeed.)  */
> +static inline __attribute__ ((nonnull (1))) void *
> +alloc_buffer_alloc_bytes (struct alloc_buffer *buf, size_t length)
> +{
> +  if (length <= alloc_buffer_size (buf))
> +    {
> +      void *result = (void *) buf->__alloc_buffer_current;
> +      buf->__alloc_buffer_current += length;
> +      return result;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +
> +/* Internal function.  Statically assert that the type size is
> +   constant and valid.  */
> +static __always_inline size_t
> +__alloc_buffer_assert_size (size_t size)
> +{
> +  if (!__builtin_constant_p (size))
> +    {
> +      __errordecl (error, "type size is not constant");
> +      error ();
> +    }
> +  else if (size == 0)
> +    {
> +      __errordecl (error, "type size is zero");
> +      error ();
> +    }
> +  return size;
> +}
> +
> +/* Internal function.  Statically assert that the type alignment is
> +   constant and valid.  */
> +static __always_inline size_t
> +__alloc_buffer_assert_align (size_t align)
> +{
> +  if (!__builtin_constant_p (align))
> +    {
> +      __errordecl (error, "type alignment is not constant");
> +      error ();
> +    }
> +  else if (align == 0)
> +    {
> +      __errordecl (error, "type alignment is zero");
> +      error ();
> +    }
> +  else if (!powerof2 (align))
> +    {
> +      __errordecl (error, "type alignment is not a power of two");
> +      error ();
> +    }
> +  return align;
> +}
> +
> +/* Internal function.  Obtain a pointer to an object.  */
> +static inline __attribute__ ((nonnull (1))) void *
> +__alloc_buffer_alloc (struct alloc_buffer *buf, size_t size, size_t align)
> +{
> +  if (size == 1 && align == 1)
> +    return alloc_buffer_alloc_bytes (buf, size);
> +
> +  size_t current = buf->__alloc_buffer_current;
> +  size_t aligned = roundup (current, align);
> +  size_t new_current = aligned + size;
> +  if (aligned >= current        /* No overflow in align step.  */
> +      && new_current >= size    /* No overflow in size computation.  */
> +      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = new_current;
> +      return (void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +
> +/* Obtain a TYPE * pointer to an object in BUF of TYPE.  Consume these
> +   bytes from the buffer.  Return NULL and mark the buffer as failed
> +   if if there is not enough room in the buffer, or if the buffer has
> +   failed before.  */
> +#define alloc_buffer_alloc(buf, type)				\
> +  ((type *) __alloc_buffer_alloc				\
> +   (buf, __alloc_buffer_assert_size (sizeof (type)),		\
> +    __alloc_buffer_assert_align (__alignof__ (type))))
> +
> +/* Internal function.  Obtain a pointer to an object which is
> +   subsequently added.  */
> +static inline const __attribute__ ((nonnull (1))) void *
> +__alloc_buffer_next (struct alloc_buffer *buf, size_t align)
> +{
> +  if (align == 1)
> +    return (const void *) buf->__alloc_buffer_current;
> +
> +  size_t current = buf->__alloc_buffer_current;
> +  size_t aligned = roundup (current, align);
> +  if (aligned >= current        /* No overflow in align step.  */
> +      && aligned <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = aligned;
> +      return (const void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +
> +/* Like alloc_buffer_alloc, but do not advance the pointer beyond the
> +   object (so a subseqent call to alloc_buffer_next or
> +   alloc_buffer_alloc returns the same pointer).  Note that the buffer
> +   is still aligned according to the requirements of TYPE.  The effect
> +   of this function is similar to allocating a zero-length array from
> +   the buffer.  */
> +#define alloc_buffer_next(buf, type)				\
> +  ((const type *) __alloc_buffer_next				\
> +   (buf, __alloc_buffer_assert_align (__alignof__ (type))))
> +
> +/* Internal function.  Allocate an array.  */
> +void * __libc_alloc_buffer_alloc_array (struct alloc_buffer *buf,
> +					size_t size, size_t align,
> +					size_t count)
> +  __attribute__ ((nonnull (1)));
> +libc_hidden_proto (__libc_alloc_buffer_alloc_array)
> +
> +/* Obtain a TYPE * pointer to an array of COUNT objects in BUF of
> +   TYPE.  Consume these bytes from the buffer.  Return NULL and mark
> +   the buffer as failed if if there is not enough room in the buffer,
> +   or if the buffer has failed before.  (Zero-length allocations from
> +   an empty buffer which has not yet failed succeed.)  */
> +#define alloc_buffer_alloc_array(buf, type, count)       \
> +  ((type *) __libc_alloc_buffer_alloc_array		 \
> +   (buf, __alloc_buffer_assert_size (sizeof (type)),	 \
> +    __alloc_buffer_assert_align (__alignof__ (type)),	 \
> +    count))
> +
> +/* Internal function.  See alloc_buffer_copy_bytes below.  */
> +struct alloc_buffer __libc_alloc_buffer_copy_bytes (struct alloc_buffer,
> +						    const void *, size_t)
> +  __attribute__ ((nonnull (2)));
> +libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
> +
> +/* Copy SIZE bytes starting at SRC into the buffer.  If there is not
> +   enough room in the buffer, the buffer is marked as failed.  No
> +   alignment of the buffer is performed.  */
> +static inline __attribute__ ((nonnull (1, 2))) void
> +alloc_buffer_copy_bytes (struct alloc_buffer *buf, const void *src, size_t size)
> +{
> +  *buf = __libc_alloc_buffer_copy_bytes (*buf, src, size);
> +}
> +
> +/* Internal function.  See alloc_buffer_copy_string below.  */
> +struct alloc_buffer __libc_alloc_buffer_copy_string (struct alloc_buffer,
> +						     const char *)
> +  __attribute__ ((nonnull (2)));
> +libc_hidden_proto (__libc_alloc_buffer_copy_string)
> +
> +/* Copy the string at SRC into the buffer, including its null
> +   terminator.  If there is not enough room in the buffer, the buffer
> +   is marked as failed.  Return a pointer to the string.  */
> +static inline __attribute__ ((nonnull (1, 2))) char *
> +alloc_buffer_copy_string (struct alloc_buffer *buf, const char *src)
> +{
> +  char *result = (char *) buf->__alloc_buffer_current;
> +  *buf = __libc_alloc_buffer_copy_string (*buf, src);
> +  if (alloc_buffer_has_failed (buf))
> +    result = NULL;
> +  return result;
> +}
> +
> +#endif /* _ALLOC_BUFFER_H */
> diff --git a/malloc/Makefile b/malloc/Makefile
> index 14c13f1..dd5d1dc 100644
> --- a/malloc/Makefile
> +++ b/malloc/Makefile
> @@ -39,7 +39,7 @@ tests-static := \
>  	 tst-interpose-static-thread \
>  	 tst-malloc-usable-static \
>  
> -tests-internal := tst-mallocstate tst-scratch_buffer
> +tests-internal := tst-mallocstate tst-scratch_buffer tst-alloc_buffer
>  
>  # The dynarray framework is only available inside glibc.
>  tests-internal += \
> @@ -63,6 +63,10 @@ routines = malloc morecore mcheck mtrace obstack reallocarray \
>    dynarray_finalize \
>    dynarray_resize \
>    dynarray_resize_clear \
> +  alloc_buffer_alloc_array \
> +  alloc_buffer_allocate \
> +  alloc_buffer_copy_bytes  \
> +  alloc_buffer_copy_string \
>  
>  install-lib := libmcheck.a
>  non-lib.a := libmcheck.a
> diff --git a/malloc/Versions b/malloc/Versions
> index 5b54306..4ead6c2 100644
> --- a/malloc/Versions
> +++ b/malloc/Versions
> @@ -76,7 +76,6 @@ libc {
>      __libc_scratch_buffer_grow_preserve;
>      __libc_scratch_buffer_set_array_size;
>  
> -
>      # Internal name for reallocarray
>      __libc_reallocarray;
>  
> @@ -86,5 +85,11 @@ libc {
>      __libc_dynarray_finalize;
>      __libc_dynarray_resize;
>      __libc_dynarray_resize_clear;
> +
> +    # struct alloc_buffer support
> +    __libc_alloc_buffer_alloc_array;
> +    __libc_alloc_buffer_allocate;
> +    __libc_alloc_buffer_copy_bytes;
> +    __libc_alloc_buffer_copy_string;
>    }
>  }
> diff --git a/malloc/alloc_buffer_alloc_array.c b/malloc/alloc_buffer_alloc_array.c
> new file mode 100644
> index 0000000..68e14da
> --- /dev/null
> +++ b/malloc/alloc_buffer_alloc_array.c
> @@ -0,0 +1,47 @@
> +/* Array allocation from a fixed-size buffer.
> +   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/>.  */
> +
> +#include <alloc_buffer.h>
> +#include <malloc-internal.h>
> +#include <libc-pointer-arith.h>
> +
> +void *
> +__libc_alloc_buffer_alloc_array (struct alloc_buffer *buf, size_t element_size,
> +                                 size_t align, size_t count)
> +{
> +  size_t current = buf->__alloc_buffer_current;
> +  /* The caller asserts that align is a power of two.  */
> +  size_t aligned = ALIGN_UP (current, align);
> +  size_t size;
> +  bool overflow = check_mul_overflow_size_t (element_size, count, &size);
> +  size_t new_current = aligned + size;
> +  if (!overflow                /* Multiplication did not overflow.  */
> +      && aligned >= current    /* No overflow in align step.  */
> +      && new_current >= size   /* No overflow in size computation.  */
> +      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = new_current;
> +      return (void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +libc_hidden_def (__libc_alloc_buffer_alloc_array)
> diff --git a/malloc/alloc_buffer_allocate.c b/malloc/alloc_buffer_allocate.c
> new file mode 100644
> index 0000000..cbde72b
> --- /dev/null
> +++ b/malloc/alloc_buffer_allocate.c
> @@ -0,0 +1,36 @@
> +/* Allocate a fixed-size allocation buffer using malloc.
> +   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/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <stdlib.h>
> +
> +struct alloc_buffer
> +__libc_alloc_buffer_allocate (size_t size, void **pptr)
> +{
> +  *pptr = malloc (size);
> +  if (*pptr == NULL)
> +    return (struct alloc_buffer)
> +      {
> +        .__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER,
> +        .__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER
> +      };
> +  else
> +    return alloc_buffer_create (*pptr, size);
> +}
> +libc_hidden_def (__libc_alloc_buffer_allocate)
> diff --git a/malloc/alloc_buffer_copy_bytes.c b/malloc/alloc_buffer_copy_bytes.c
> new file mode 100644
> index 0000000..66196f1
> --- /dev/null
> +++ b/malloc/alloc_buffer_copy_bytes.c
> @@ -0,0 +1,34 @@
> +/* Copy an array of bytes into the buffer.
> +   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/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <string.h>
> +
> +/* This function works on a copy of the buffer object, so that it can
> +   remain non-addressable in the caller.  */
> +struct alloc_buffer
> +__libc_alloc_buffer_copy_bytes (struct alloc_buffer buf,
> +                                const void *src, size_t len)
> +{
> +  void *ptr = alloc_buffer_alloc_bytes (&buf, len);
> +  if (ptr != NULL)
> +    memcpy (ptr, src, len);
> +  return buf;
> +}
> +libc_hidden_def (__libc_alloc_buffer_copy_bytes)
> diff --git a/malloc/alloc_buffer_copy_string.c b/malloc/alloc_buffer_copy_string.c
> new file mode 100644
> index 0000000..77c0023
> --- /dev/null
> +++ b/malloc/alloc_buffer_copy_string.c
> @@ -0,0 +1,30 @@
> +/* Copy a string into the allocation buffer.
> +   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/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <string.h>
> +
> +/* This function works on a copy of the buffer object, so that it can
> +   remain non-addressable in the caller.  */
> +struct alloc_buffer
> +__libc_alloc_buffer_copy_string (struct alloc_buffer buf, const char *src)
> +{
> +  return __libc_alloc_buffer_copy_bytes (buf, src, strlen (src) + 1);
> +}
> +libc_hidden_def (__libc_alloc_buffer_copy_string)
> diff --git a/malloc/tst-alloc_buffer.c b/malloc/tst-alloc_buffer.c
> new file mode 100644
> index 0000000..1c14399
> --- /dev/null
> +++ b/malloc/tst-alloc_buffer.c
> @@ -0,0 +1,665 @@
> +/* Tests for struct alloc_buffer.
> +   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/>.  */
> +
> +#include <arpa/inet.h>
> +#include <alloc_buffer.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/test-driver.h>
> +
> +/* Return true if PTR is sufficiently aligned for TYPE.  */
> +#define IS_ALIGNED(ptr, type) \
> +  ((((uintptr_t) ptr) & (__alloc_buffer_assert_align (__alignof (type)) - 1)) \
> +   == 0)
> +
> +/* Structure with non-power-of-two size.  */
> +struct twelve
> +{
> +  uint32_t buffer[3] __attribute__ ((aligned (4)));
> +};
> +_Static_assert (sizeof (struct twelve) == 12, "struct twelve");
> +_Static_assert (__alignof__ (struct twelve) == 4, "struct twelve");
> +
> +/* Check for success obtaining empty arrays.  Does not assume the
> +   buffer is empty.  */
> +static void
> +test_empty_array (struct alloc_buffer refbuf)
> +{
> +  bool refbuf_failed = alloc_buffer_has_failed (&refbuf);
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx refbuf_failed=%d\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end, refbuf_failed);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY ((alloc_buffer_alloc_bytes (&buf, 0) == NULL)
> +                 == refbuf_failed);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY ((alloc_buffer_alloc_array (&buf, char, 0) == NULL)
> +                 == refbuf_failed);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
> +  }
> +  /* The following tests can fail due to the need for aligning the
> +     returned pointer.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    bool expect_failure = refbuf_failed
> +      || !IS_ALIGNED (alloc_buffer_next (&buf, void), double);
> +    double *ptr = alloc_buffer_alloc_array (&buf, double, 0);
> +    TEST_VERIFY (IS_ALIGNED (ptr, double));
> +    TEST_VERIFY ((ptr == NULL) == expect_failure);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    bool expect_failure = refbuf_failed
> +      || !IS_ALIGNED (alloc_buffer_next (&buf, void), struct twelve);
> +    struct twelve *ptr = alloc_buffer_alloc_array (&buf, struct twelve, 0);
> +    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
> +    TEST_VERIFY ((ptr == NULL) == expect_failure);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
> +  }
> +}
> +
> +/* Test allocation of impossibly large arrays.  */
> +static void
> +test_impossible_array (struct alloc_buffer refbuf)
> +{
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end);
> +  static const size_t counts[] =
> +    { SIZE_MAX, SIZE_MAX - 1, SIZE_MAX - 2, SIZE_MAX - 3, SIZE_MAX - 4,
> +      SIZE_MAX / 2, SIZE_MAX / 2 + 1, SIZE_MAX / 2 - 1, 0};
> +
> +  for (int i = 0; counts[i] != 0; ++i)
> +    {
> +      size_t count = counts[i];
> +      if (test_verbose)
> +        printf ("info: %s: count=%zu\n", __func__, count);
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
> +                     == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +    }
> +}
> +
> +/* Check for failure to obtain anything from a failed buffer.  */
> +static void
> +test_after_failure (struct alloc_buffer refbuf)
> +{
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end);
> +  TEST_VERIFY (alloc_buffer_has_failed (&refbuf));
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  test_impossible_array (refbuf);
> +  for (int count = 0; count <= 4; ++count)
> +    {
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
> +                     == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +    }
> +}
> +
> +static void
> +test_empty (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 0);
> +  if (alloc_buffer_next (&refbuf, void) != NULL)
> +    TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Failure to obtain non-empty objects.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +}
> +
> +static void
> +test_size_1 (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 1);
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Success adding a single byte.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x11", 1) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 126;
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\176", 1) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = (char) 253;
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\xfd", 1) == 0);
> +
> +  /* Failure with larger objects.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, short) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +}
> +
> +static void
> +test_size_2 (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 2);
> +  TEST_VERIFY (IS_ALIGNED (alloc_buffer_next (&refbuf, void), short));
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Success adding two bytes.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, '@');
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "@\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 'A';
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "A\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 'B';
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "B\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    unsigned short *ptr = alloc_buffer_alloc (&buf, unsigned short);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = htons (0x12f4);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x12\xf4", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    unsigned short *ptr = alloc_buffer_alloc_array (&buf, unsigned short, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = htons (0x13f5);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x13\xf5", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 2);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    memcpy (ptr, "12", 2);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "12", 2) == 0);
> +}
> +
> +static void
> +test_misaligned (char pad)
> +{
> +  enum { SIZE = 23 };
> +  char *backing = xmalloc (SIZE + 2);
> +  backing[0] = ~pad;
> +  backing[SIZE + 1] = pad;
> +  struct alloc_buffer refbuf = alloc_buffer_create (backing + 1, SIZE);
> +
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    short *ptr = alloc_buffer_alloc_array (&buf, short, SIZE / sizeof (short));
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    for (int i = 0; i < SIZE / sizeof (short); ++i)
> +      ptr[i] = htons (0xff01 + i);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\xff\x01\xff\x02\xff\x03\xff\x04"
> +                         "\xff\x05\xff\x06\xff\x07\xff\x08"
> +                         "\xff\x09\xff\x0a\xff\x0b", 22) == 0);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    uint32_t *ptr = alloc_buffer_alloc_array
> +      (&buf, uint32_t, SIZE / sizeof (uint32_t));
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, uint32_t));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    for (int i = 0; i < SIZE / sizeof (uint32_t); ++i)
> +      ptr[i] = htonl (0xf1e2d301 + i);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\xf1\xe2\xd3\x01\xf1\xe2\xd3\x02"
> +                         "\xf1\xe2\xd3\x03\xf1\xe2\xd3\x04"
> +                         "\xf1\xe2\xd3\x05", 20) == 0);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    struct twelve *ptr = alloc_buffer_alloc (&buf, struct twelve);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    ptr->buffer[0] = htonl (0x11223344);
> +    ptr->buffer[1] = htonl (0x55667788);
> +    ptr->buffer[2] = htonl (0x99aabbcc);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\x11\x22\x33\x44"
> +                         "\x55\x66\x77\x88"
> +                         "\x99\xaa\xbb\xcc", 12) == 0);
> +  }
> +  {
> +    static const double nums[] = { 1, 2 };
> +    struct alloc_buffer buf = refbuf;
> +    double *ptr = alloc_buffer_alloc_array (&buf, double, 2);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, double));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    ptr[0] = nums[0];
> +    ptr[1] = nums[1];
> +    TEST_VERIFY (memcmp (ptr, nums, sizeof (nums)) == 0);
> +  }
> +
> +  /* Verify that padding was not overwritten.  */
> +  TEST_VERIFY (backing[0] == ~pad);
> +  TEST_VERIFY (backing[SIZE + 1] == pad);
> +  free (backing);
> +}
> +
> +/* Check that overflow during alignment is handled properly.  */
> +static void
> +test_large_misaligned (void)
> +{
> +  uintptr_t minus1 = -1;
> +  uintptr_t start = minus1 & ~0xfe;
> +  struct alloc_buffer refbuf = alloc_buffer_create ((void *) start, 16);
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +
> +  struct __attribute__ ((aligned (256))) align256
> +  {
> +    int dymmy;
> +  };
> +
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct align256) == NULL);
> +    test_after_failure (buf);
> +  }
> +  for (int count = 0; count < 3; ++count)
> +    {
> +      struct alloc_buffer buf = refbuf;
> +      TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct align256, count)
> +                   == NULL);
> +      test_after_failure (buf);
> +    }
> +}
> +
> +/* Check behavior of large allocations.  */
> +static void
> +test_large (void)
> +{
> +  {
> +    /* Allocation which wraps around.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, SIZE_MAX) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  {
> +    /* Successful very large allocation.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, char, SIZE_MAX - 1);
> +    TEST_VERIFY (val == 1);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_empty (buf);
> +  }
> +
> +  {
> +    typedef char __attribute__ ((aligned (2))) char2;
> +
> +    /* Overflow in array size computation.   */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, SIZE_MAX - 1) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +
> +    /* Successful allocation after alignment.  */
> +    buf = (struct alloc_buffer) { 1, SIZE_MAX };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, char2, SIZE_MAX - 2);
> +    TEST_VERIFY (val == 2);
> +    test_empty (buf);
> +
> +    /* Alignment behavior near the top of the address space.  */
> +    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_next (&buf, char2) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, 0) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  {
> +    typedef short __attribute__ ((aligned (2))) short2;
> +
> +    /* Test overflow in size computation.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short2, SIZE_MAX / 2)
> +                 == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +
> +    /* A slightly smaller array fits within the allocation.  */
> +    buf = (struct alloc_buffer) { 2, SIZE_MAX - 1 };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, short2, SIZE_MAX / 2 - 1);
> +    TEST_VERIFY (val == 2);
> +    test_empty (buf);
> +  }
> +}
> +
> +static void
> +test_copy_bytes (void)
> +{
> +  char backing[4];
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1", 1);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
> +    TEST_VERIFY (memcmp (backing, "1@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "12", 3);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
> +    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", 4);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
> +    TEST_VERIFY (memcmp (backing, "1234", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", 5);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", -1);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +}
> +
> +static void
> +test_copy_string (void)
> +{
> +  char backing[4];
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
> +    TEST_VERIFY (memcmp (backing, "\0@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "1");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "1") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 2);
> +    TEST_VERIFY (memcmp (backing, "1\0@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "12");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "12") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
> +    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "123");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "123") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
> +    TEST_VERIFY (memcmp (backing, "123", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    TEST_VERIFY (alloc_buffer_copy_string (&buf, "1234") == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    TEST_VERIFY (alloc_buffer_copy_string (&buf, "12345") == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  test_empty (alloc_buffer_create (NULL, 0));
> +  test_empty (alloc_buffer_create ((char *) "", 0));
> +  test_empty (alloc_buffer_create ((void *) 1, 0));
> +
> +  {
> +    void *ptr = (void *) "";    /* Cannot be freed. */
> +    struct alloc_buffer buf = alloc_buffer_allocate (1, &ptr);
> +    test_size_1 (buf);
> +    free (ptr);                 /* Should have been overwritten.  */
> +  }
> +
> +  {
> +    void *ptr= (void *) "";     /* Cannot be freed.  */
> +    struct alloc_buffer buf = alloc_buffer_allocate (2, &ptr);
> +    test_size_2 (buf);
> +    free (ptr);                 /* Should have been overwritten.  */
> +  }
> +
> +  test_misaligned (0);
> +  test_misaligned (0xc7);
> +  test_misaligned (0xff);
> +
> +  test_large_misaligned ();
> +  test_large ();
> +  test_copy_bytes ();
> +  test_copy_string ();
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> 


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]