From patchwork Wed Jun 21 15:46:34 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 21169 Received: (qmail 68959 invoked by alias); 21 Jun 2017 15:46:46 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 68333 invoked by uid 89); 21 Jun 2017 15:46:45 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-23.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_LAZY_DOMAIN_SECURITY, NORMAL_HTTP_TO_IP, SPF_HELO_PASS, T_RP_MATCHES_RCVD, UNSUBSCRIBE_BODY autolearn=ham version=3.3.2 spammy=recieve X-HELO: mx1.redhat.com DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 1E9EE7486C Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=fweimer@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com 1E9EE7486C Subject: Re: [PATCH] Allocation buffers for NSS result construction To: Adhemerval Zanella , libc-alpha@sourceware.org References: <2526706e-18e7-1c30-84b3-812663c43b98@redhat.com> <0b2344dc-0e44-e11e-ffce-d4ceed9eeced@linaro.org> From: Florian Weimer Message-ID: <9533de1c-986a-7872-856d-0d3cc354356c@redhat.com> Date: Wed, 21 Jun 2017 17:46:34 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.1.0 MIME-Version: 1.0 In-Reply-To: <0b2344dc-0e44-e11e-ffce-d4ceed9eeced@linaro.org> 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? >> +/* 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. >> +/* 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. >> +/* 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. >> + >> +/* 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. >> +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. Thanks, Florian 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 * 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 + . */ + +/* 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 + 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); + _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 +#include +#include +#include +#include + +/* 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 + . */ + +#include +#include +#include + +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 + . */ + +#include + +#include + +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 + . */ + +#include + +#include + +/* 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 + . */ + +#include + +#include + +/* 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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