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).
This commit is contained in:
parent
11ffcacb64
commit
4dd8e7c0ce
17
ChangeLog
17
ChangeLog
|
@ -1,3 +1,20 @@
|
||||||
|
2017-04-21 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,
|
||||||
|
alloc_buffer_create_failure.
|
||||||
|
* malloc/Versions (__libc_alloc_buffer_alloc_array)
|
||||||
|
(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes)
|
||||||
|
(__libc_alloc_buffer_copy_string)
|
||||||
|
(__libc_alloc_buffer_create_failure): 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/alloc_buffer_create_failure.c: Likewise.
|
||||||
|
* malloc/tst-alloc_buffer.c: Likewise.
|
||||||
|
|
||||||
2017-06-21 H.J. Lu <hongjiu.lu@intel.com>
|
2017-06-21 H.J. Lu <hongjiu.lu@intel.com>
|
||||||
|
|
||||||
* sysdeps/x86_64/multiarch/Makefile (sysdep_routines): Add
|
* sysdeps/x86_64/multiarch/Makefile (sysdep_routines): Add
|
||||||
|
|
|
@ -0,0 +1,367 @@
|
||||||
|
/* 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 receive 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. In case of an
|
||||||
|
overflow, one of the subsequent allocations from the buffer will
|
||||||
|
fail. The initial 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Internal function. Terminate the process using __libc_fatal. */
|
||||||
|
void __libc_alloc_buffer_create_failure (void *start, size_t size);
|
||||||
|
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
uintptr_t current = (uintptr_t) start;
|
||||||
|
uintptr_t end = (uintptr_t) start + size;
|
||||||
|
if (end < current)
|
||||||
|
__libc_alloc_buffer_create_failure (start, size);
|
||||||
|
return (struct alloc_buffer) { current, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internal function. See alloc_buffer_allocate below. */
|
||||||
|
struct alloc_buffer __libc_alloc_buffer_allocate (size_t size, void **pptr)
|
||||||
|
__attribute__ ((nonnull (2)));
|
||||||
|
|
||||||
|
/* 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)));
|
||||||
|
|
||||||
|
/* 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)));
|
||||||
|
|
||||||
|
/* 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)));
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _ISOMAC
|
||||||
|
libc_hidden_proto (__libc_alloc_buffer_alloc_array)
|
||||||
|
libc_hidden_proto (__libc_alloc_buffer_allocate)
|
||||||
|
libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
|
||||||
|
libc_hidden_proto (__libc_alloc_buffer_copy_string)
|
||||||
|
libc_hidden_proto (__libc_alloc_buffer_create_failure)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ALLOC_BUFFER_H */
|
|
@ -33,6 +33,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
|
||||||
tst-mallocfork2 \
|
tst-mallocfork2 \
|
||||||
tst-interpose-nothread \
|
tst-interpose-nothread \
|
||||||
tst-interpose-thread \
|
tst-interpose-thread \
|
||||||
|
tst-alloc_buffer \
|
||||||
|
|
||||||
tests-static := \
|
tests-static := \
|
||||||
tst-interpose-static-nothread \
|
tst-interpose-static-nothread \
|
||||||
|
@ -63,6 +64,11 @@ routines = malloc morecore mcheck mtrace obstack reallocarray \
|
||||||
dynarray_finalize \
|
dynarray_finalize \
|
||||||
dynarray_resize \
|
dynarray_resize \
|
||||||
dynarray_resize_clear \
|
dynarray_resize_clear \
|
||||||
|
alloc_buffer_alloc_array \
|
||||||
|
alloc_buffer_allocate \
|
||||||
|
alloc_buffer_copy_bytes \
|
||||||
|
alloc_buffer_copy_string \
|
||||||
|
alloc_buffer_create_failure \
|
||||||
|
|
||||||
install-lib := libmcheck.a
|
install-lib := libmcheck.a
|
||||||
non-lib.a := libmcheck.a
|
non-lib.a := libmcheck.a
|
||||||
|
|
|
@ -76,7 +76,6 @@ libc {
|
||||||
__libc_scratch_buffer_grow_preserve;
|
__libc_scratch_buffer_grow_preserve;
|
||||||
__libc_scratch_buffer_set_array_size;
|
__libc_scratch_buffer_set_array_size;
|
||||||
|
|
||||||
|
|
||||||
# Internal name for reallocarray
|
# Internal name for reallocarray
|
||||||
__libc_reallocarray;
|
__libc_reallocarray;
|
||||||
|
|
||||||
|
@ -86,5 +85,12 @@ libc {
|
||||||
__libc_dynarray_finalize;
|
__libc_dynarray_finalize;
|
||||||
__libc_dynarray_resize;
|
__libc_dynarray_resize;
|
||||||
__libc_dynarray_resize_clear;
|
__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;
|
||||||
|
__libc_alloc_buffer_create_failure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* Terminate the process as the result of an invalid 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 <stdio.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
__libc_alloc_buffer_create_failure (void *start, size_t size)
|
||||||
|
{
|
||||||
|
char buf[200];
|
||||||
|
__snprintf (buf, sizeof (buf), "Fatal glibc error: "
|
||||||
|
"invalid allocation buffer of size %zu\n",
|
||||||
|
size);
|
||||||
|
__libc_fatal (buf);
|
||||||
|
}
|
||||||
|
libc_hidden_def (__libc_alloc_buffer_create_failure)
|
|
@ -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>
|
Loading…
Reference in New Issue