You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

421 lines
11 KiB

/* -*- coding: utf-8 -*-
* ----------------------------------------------------------------------
* Copyright © 2011-2014, RedJack, LLC.
* All rights reserved.
*
* Please see the COPYING file in this distribution for license details.
* ----------------------------------------------------------------------
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "libcork/core/allocator.h"
#include "libcork/core/attributes.h"
#include "libcork/core/error.h"
#include "libcork/core/types.h"
#include "libcork/os/process.h"
/*-----------------------------------------------------------------------
* Allocator interface
*/
struct cork_alloc_priv {
struct cork_alloc public;
struct cork_alloc_priv *next;
};
static void *
cork_alloc__default_calloc(const struct cork_alloc *alloc,
size_t count, size_t size)
{
void *result = cork_alloc_xcalloc(alloc, count, size);
if (CORK_UNLIKELY(result == NULL)) {
abort();
}
return result;
}
static void *
cork_alloc__default_malloc(const struct cork_alloc *alloc, size_t size)
{
void *result = cork_alloc_xmalloc(alloc, size);
if (CORK_UNLIKELY(result == NULL)) {
abort();
}
return result;
}
static void *
cork_alloc__default_realloc(const struct cork_alloc *alloc, void *ptr,
size_t old_size, size_t new_size)
{
void *result = cork_alloc_xrealloc(alloc, ptr, old_size, new_size);
if (CORK_UNLIKELY(result == NULL)) {
abort();
}
return result;
}
static void *
cork_alloc__default_xcalloc(const struct cork_alloc *alloc,
size_t count, size_t size)
{
void *result;
assert(count < (SIZE_MAX / size));
result = cork_alloc_xmalloc(alloc, count * size);
if (result != NULL) {
memset(result, 0, count * size);
}
return result;
}
static void *
cork_alloc__default_xmalloc(const struct cork_alloc *alloc, size_t size)
{
cork_abort("%s isn't defined", "cork_alloc:xmalloc");
}
static void *
cork_alloc__default_xrealloc(const struct cork_alloc *alloc, void *ptr,
size_t old_size, size_t new_size)
{
void *result = cork_alloc_xmalloc(alloc, new_size);
if (CORK_LIKELY(result != NULL) && ptr != NULL) {
size_t min_size = (new_size < old_size)? new_size: old_size;
memcpy(result, ptr, min_size);
cork_alloc_free(alloc, ptr, old_size);
}
return result;
}
static void
cork_alloc__default_free(const struct cork_alloc *alloc, void *ptr, size_t size)
{
cork_abort("%s isn't defined", "cork_alloc:free");
}
static bool cleanup_registered = false;
static struct cork_alloc_priv *all_allocs = NULL;
static void
cork_alloc_free_alloc(struct cork_alloc_priv *alloc)
{
cork_free_user_data(&alloc->public);
cork_alloc_delete(alloc->public.parent, struct cork_alloc_priv, alloc);
}
static void
cork_alloc_free_all(void)
{
struct cork_alloc_priv *curr;
struct cork_alloc_priv *next;
for (curr = all_allocs; curr != NULL; curr = next) {
next = curr->next;
cork_alloc_free_alloc(curr);
}
}
static void
cork_alloc_register_cleanup(void)
{
if (CORK_UNLIKELY(!cleanup_registered)) {
/* We don't use cork_cleanup because that requires the allocators to
* have already been set up! (atexit calls its functions in reverse
* order, and this one will be registered before cork_cleanup's, which
* makes it safe for cork_cleanup functions to still use the allocator,
* since the allocator atexit function will be called last.) */
atexit(cork_alloc_free_all);
cleanup_registered = true;
}
}
struct cork_alloc *
cork_alloc_new_alloc(const struct cork_alloc *parent)
{
struct cork_alloc_priv *alloc =
cork_alloc_new(parent, struct cork_alloc_priv);
alloc->public.parent = parent;
alloc->public.user_data = NULL;
alloc->public.free_user_data = NULL;
alloc->public.calloc = cork_alloc__default_calloc;
alloc->public.malloc = cork_alloc__default_malloc;
alloc->public.realloc = cork_alloc__default_realloc;
alloc->public.xcalloc = cork_alloc__default_xcalloc;
alloc->public.xmalloc = cork_alloc__default_xmalloc;
alloc->public.xrealloc = cork_alloc__default_xrealloc;
alloc->public.free = cork_alloc__default_free;
cork_alloc_register_cleanup();
alloc->next = all_allocs;
all_allocs = alloc;
return &alloc->public;
}
void
cork_alloc_set_user_data(struct cork_alloc *alloc,
void *user_data, cork_free_f free_user_data)
{
cork_free_user_data(alloc);
alloc->user_data = user_data;
alloc->free_user_data = free_user_data;
}
void
cork_alloc_set_calloc(struct cork_alloc *alloc, cork_alloc_calloc_f calloc)
{
alloc->calloc = calloc;
}
void
cork_alloc_set_malloc(struct cork_alloc *alloc, cork_alloc_malloc_f malloc)
{
alloc->malloc = malloc;
}
void
cork_alloc_set_realloc(struct cork_alloc *alloc, cork_alloc_realloc_f realloc)
{
alloc->realloc = realloc;
}
void
cork_alloc_set_xcalloc(struct cork_alloc *alloc, cork_alloc_calloc_f xcalloc)
{
alloc->xcalloc = xcalloc;
}
void
cork_alloc_set_xmalloc(struct cork_alloc *alloc, cork_alloc_malloc_f xmalloc)
{
alloc->xmalloc = xmalloc;
}
void
cork_alloc_set_xrealloc(struct cork_alloc *alloc,
cork_alloc_realloc_f xrealloc)
{
alloc->xrealloc = xrealloc;
}
void
cork_alloc_set_free(struct cork_alloc *alloc, cork_alloc_free_f free)
{
alloc->free = free;
}
/*-----------------------------------------------------------------------
* Allocating strings
*/
static inline const char *
strndup_internal(const struct cork_alloc *alloc,
const char *str, size_t len)
{
char *dest;
size_t allocated_size = len + sizeof(size_t) + 1;
size_t *new_str = cork_alloc_malloc(alloc, allocated_size);
*new_str = allocated_size;
dest = (char *) (void *) (new_str + 1);
memcpy(dest, str, len);
dest[len] = '\0';
return dest;
}
const char *
cork_alloc_strdup(const struct cork_alloc *alloc, const char *str)
{
return strndup_internal(alloc, str, strlen(str));
}
const char *
cork_alloc_strndup(const struct cork_alloc *alloc,
const char *str, size_t size)
{
return strndup_internal(alloc, str, size);
}
static inline const char *
xstrndup_internal(const struct cork_alloc *alloc,
const char *str, size_t len)
{
size_t allocated_size = len + sizeof(size_t) + 1;
size_t *new_str = cork_alloc_xmalloc(alloc, allocated_size);
if (CORK_UNLIKELY(new_str == NULL)) {
return NULL;
} else {
char *dest;
*new_str = allocated_size;
dest = (char *) (void *) (new_str + 1);
memcpy(dest, str, len);
dest[len] = '\0';
return dest;
}
}
const char *
cork_alloc_xstrdup(const struct cork_alloc *alloc, const char *str)
{
return xstrndup_internal(alloc, str, strlen(str));
}
const char *
cork_alloc_xstrndup(const struct cork_alloc *alloc,
const char *str, size_t size)
{
return xstrndup_internal(alloc, str, size);
}
void
cork_alloc_strfree(const struct cork_alloc *alloc, const char *str)
{
size_t *base = ((size_t *) str) - 1;
cork_alloc_free(alloc, base, *base);
}
/*-----------------------------------------------------------------------
* stdlib allocator
*/
static void *
cork_stdlib_alloc__calloc(const struct cork_alloc *alloc,
size_t count, size_t size)
{
void *result = calloc(count, size);
if (CORK_UNLIKELY(result == NULL)) {
abort();
}
return result;
}
static void *
cork_stdlib_alloc__malloc(const struct cork_alloc *alloc, size_t size)
{
void *result = malloc(size);
if (CORK_UNLIKELY(result == NULL)) {
abort();
}
return result;
}
static void *
cork_stdlib_alloc__realloc(const struct cork_alloc *alloc, void *ptr,
size_t old_size, size_t new_size)
{
/* Technically we don't really need to free `ptr` if the reallocation fails,
* since we'll abort the process immediately after. But my sense of
* cleanliness makes me do it anyway. */
#if CORK_HAVE_REALLOCF
void *result = reallocf(ptr, new_size);
if (result == NULL) {
abort();
}
return result;
#else
void *result = realloc(ptr, new_size);
if (result == NULL) {
free(ptr);
abort();
}
return result;
#endif
}
static void *
cork_stdlib_alloc__xcalloc(const struct cork_alloc *alloc,
size_t count, size_t size)
{
return calloc(count, size);
}
static void *
cork_stdlib_alloc__xmalloc(const struct cork_alloc *alloc, size_t size)
{
return malloc(size);
}
static void *
cork_stdlib_alloc__xrealloc(const struct cork_alloc *alloc, void *ptr,
size_t old_size, size_t new_size)
{
return realloc(ptr, new_size);
}
static void
cork_stdlib_alloc__free(const struct cork_alloc *alloc, void *ptr, size_t size)
{
free(ptr);
}
static const struct cork_alloc default_allocator = {
NULL,
NULL,
NULL,
cork_stdlib_alloc__calloc,
cork_stdlib_alloc__malloc,
cork_stdlib_alloc__realloc,
cork_stdlib_alloc__xcalloc,
cork_stdlib_alloc__xmalloc,
cork_stdlib_alloc__xrealloc,
cork_stdlib_alloc__free
};
/*-----------------------------------------------------------------------
* Customizing libcork's allocator
*/
const struct cork_alloc *cork_allocator = &default_allocator;
void
cork_set_allocator(const struct cork_alloc *alloc)
{
cork_allocator = alloc;
}
/*-----------------------------------------------------------------------
* Debugging allocator
*/
static void *
cork_debug_alloc__xmalloc(const struct cork_alloc *alloc, size_t size)
{
size_t real_size = size + sizeof(size_t);
size_t *base = cork_alloc_xmalloc(alloc->parent, real_size);
*base = size;
return base + 1;
}
static void
cork_debug_alloc__free(const struct cork_alloc *alloc, void *ptr,
size_t expected_size)
{
size_t *base = ((size_t *) ptr) - 1;
size_t actual_size = *base;
size_t real_size = actual_size + sizeof(size_t);
if (CORK_UNLIKELY(actual_size != expected_size)) {
cork_abort
("Incorrect size when freeing pointer (got %zu, expected %zu)",
expected_size, actual_size);
}
cork_alloc_free(alloc->parent, base, real_size);
}
struct cork_alloc *
cork_debug_alloc_new(const struct cork_alloc *parent)
{
struct cork_alloc *debug = cork_alloc_new_alloc(parent);
cork_alloc_set_xmalloc(debug, cork_debug_alloc__xmalloc);
cork_alloc_set_free(debug, cork_debug_alloc__free);
return debug;
}