diff --git a/Makefile.am b/Makefile.am index 1df4143107..029078a41d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -135,7 +135,7 @@ endif ### Tests (make check) -TESTS = tests/mantest tests/jqtest tests/shtest tests/utf8test tests/base64test tests/uritest +TESTS = tests/mantest tests/jqtest tests/shtest tests/utf8test tests/base64test tests/uritest tests/alloctest if !WIN32 TESTS += tests/optionaltest endif diff --git a/src/jv_alloc.c b/src/jv_alloc.c index 4a701d57bb..4366bf4349 100644 --- a/src/jv_alloc.c +++ b/src/jv_alloc.c @@ -3,12 +3,19 @@ #include #include #include "jv.h" +#include "jv_alloc.h" struct nomem_handler { jv_nomem_handler_f handler; void *data; }; +struct custom_allocator { + jv_malloc_t malloc_fn; + jv_free_t free_fn; + jv_realloc_t realloc_fn; +}; + #if !defined(HAVE_PTHREAD_KEY_CREATE) || \ !defined(HAVE_PTHREAD_ONCE) || \ !defined(HAVE_ATEXIT) @@ -18,11 +25,13 @@ struct nomem_handler { #ifdef _MSC_VER /* Visual C++: yes */ static __declspec(thread) struct nomem_handler nomem_handler; +static __declspec(thread) struct custom_allocator custom_allocator; #define USE_TLS #else #ifdef HAVE___THREAD /* GCC and friends: yes */ static __thread struct nomem_handler nomem_handler; +static __thread struct custom_allocator custom_allocator; #define USE_TLS #endif /* HAVE___THREAD */ #endif /* _MSC_VER */ @@ -47,17 +56,26 @@ static void memory_exhausted(void) { #include static pthread_key_t nomem_handler_key; +static pthread_key_t custom_allocator_key; static pthread_once_t mem_once = PTHREAD_ONCE_INIT; /* tsd_fini is called on application exit */ -/* it clears the nomem_handler allocated in the main thread */ +/* it clears the nomem_handler and custom_allocator allocated in the main thread */ static void tsd_fini(void) { struct nomem_handler *nomem_handler; + struct custom_allocator *custom_alloc; + nomem_handler = pthread_getspecific(nomem_handler_key); if (nomem_handler) { (void) pthread_setspecific(nomem_handler_key, NULL); free(nomem_handler); } + + custom_alloc = pthread_getspecific(custom_allocator_key); + if (custom_alloc) { + (void) pthread_setspecific(custom_allocator_key, NULL); + free(custom_alloc); + } } /* The tsd_fini_thread is a destructor set by calling */ @@ -67,11 +85,20 @@ static void tsd_fini_thread(void *nomem_handler) { free(nomem_handler); } +/* Destructor for custom allocator thread-specific data */ +static void tsd_fini_custom_alloc(void *custom_alloc) { + free(custom_alloc); +} + static void tsd_init(void) { if (pthread_key_create(&nomem_handler_key, tsd_fini_thread) != 0) { fprintf(stderr, "jq: error: cannot create thread specific key"); abort(); } + if (pthread_key_create(&custom_allocator_key, tsd_fini_custom_alloc) != 0) { + fprintf(stderr, "jq: error: cannot create thread specific key"); + abort(); + } if (atexit(tsd_fini) != 0) { fprintf(stderr, "jq: error: cannot set an exit handler"); abort(); @@ -89,6 +116,17 @@ static void tsd_init_nomem_handler(void) } } +static void tsd_init_custom_allocator(void) +{ + if (pthread_getspecific(custom_allocator_key) == NULL) { + struct custom_allocator *custom_alloc = calloc(1, sizeof(struct custom_allocator)); + if (pthread_setspecific(custom_allocator_key, custom_alloc) != 0) { + fprintf(stderr, "jq: error: cannot set thread specific data"); + abort(); + } + } +} + void jv_nomem_handler(jv_nomem_handler_f handler, void *data) { pthread_once(&mem_once, tsd_init); // cannot fail tsd_init_nomem_handler(); @@ -124,6 +162,8 @@ static void memory_exhausted(void) { /* No thread-local storage of any kind that we know how to handle */ static struct nomem_handler nomem_handler; +static struct custom_allocator custom_allocator; + void jv_nomem_handler(jv_nomem_handler_f handler, void *data) { nomem_handler.handler = handler; nomem_handler.data = data; @@ -138,8 +178,35 @@ static void memory_exhausted(void) { #endif /* USE_TLS */ +// Helper functions to get custom allocator based on threading model +#ifdef USE_TLS +static struct custom_allocator* get_custom_allocator(void) { + return &custom_allocator; +} +#else +#ifdef HAVE_PTHREAD_KEY_CREATE +static struct custom_allocator* get_custom_allocator(void) { + pthread_once(&mem_once, tsd_init); + tsd_init_custom_allocator(); + return pthread_getspecific(custom_allocator_key); +} +#else +static struct custom_allocator* get_custom_allocator(void) { + return &custom_allocator; +} +#endif +#endif + void* jv_mem_alloc(size_t sz) { - void* p = malloc(sz); + struct custom_allocator* custom_alloc = get_custom_allocator(); + void* p; + + if (custom_alloc && custom_alloc->malloc_fn) { + p = custom_alloc->malloc_fn(sz); + } else { + p = malloc(sz); + } + if (!p) { memory_exhausted(); } @@ -147,12 +214,29 @@ void* jv_mem_alloc(size_t sz) { } void* jv_mem_alloc_unguarded(size_t sz) { - return malloc(sz); + struct custom_allocator* custom_alloc = get_custom_allocator(); + + if (custom_alloc && custom_alloc->malloc_fn) { + return custom_alloc->malloc_fn(sz); + } else { + return malloc(sz); + } } void* jv_mem_calloc(size_t nemb, size_t sz) { assert(nemb > 0 && sz > 0); - void* p = calloc(nemb, sz); + struct custom_allocator* custom_alloc = get_custom_allocator(); + void* p; + + if (custom_alloc && custom_alloc->malloc_fn) { + p = custom_alloc->malloc_fn(nemb * sz); + if (p) { + memset(p, 0, nemb * sz); + } + } else { + p = calloc(nemb, sz); + } + if (!p) { memory_exhausted(); } @@ -161,11 +245,33 @@ void* jv_mem_calloc(size_t nemb, size_t sz) { void* jv_mem_calloc_unguarded(size_t nemb, size_t sz) { assert(nemb > 0 && sz > 0); - return calloc(nemb, sz); + struct custom_allocator* custom_alloc = get_custom_allocator(); + + if (custom_alloc && custom_alloc->malloc_fn) { + void* p = custom_alloc->malloc_fn(nemb * sz); + if (p) { + memset(p, 0, nemb * sz); + } + return p; + } else { + return calloc(nemb, sz); + } } char* jv_mem_strdup(const char *s) { - char *p = strdup(s); + struct custom_allocator* custom_alloc = get_custom_allocator(); + char *p; + + if (custom_alloc && custom_alloc->malloc_fn) { + size_t len = strlen(s) + 1; + p = custom_alloc->malloc_fn(len); + if (p) { + memcpy(p, s, len); + } + } else { + p = strdup(s); + } + if (!p) { memory_exhausted(); } @@ -173,17 +279,100 @@ char* jv_mem_strdup(const char *s) { } char* jv_mem_strdup_unguarded(const char *s) { - return strdup(s); + struct custom_allocator* custom_alloc = get_custom_allocator(); + + if (custom_alloc && custom_alloc->malloc_fn) { + size_t len = strlen(s) + 1; + char *p = custom_alloc->malloc_fn(len); + if (p) { + memcpy(p, s, len); + } + return p; + } else { + return strdup(s); + } } void jv_mem_free(void* p) { - free(p); + struct custom_allocator* custom_alloc = get_custom_allocator(); + + if (custom_alloc && custom_alloc->free_fn) { + custom_alloc->free_fn(p); + } else { + free(p); + } } void* jv_mem_realloc(void* p, size_t sz) { - p = realloc(p, sz); + struct custom_allocator* custom_alloc = get_custom_allocator(); + + if (custom_alloc && custom_alloc->realloc_fn) { + p = custom_alloc->realloc_fn(p, sz); + } else { + p = realloc(p, sz); + } + if (!p) { memory_exhausted(); } return p; } + +#ifdef USE_TLS +// Thread-local storage implementation +void jv_set_alloc_funcs(jv_malloc_t malloc_fn, jv_free_t free_fn) { + custom_allocator.malloc_fn = malloc_fn; + custom_allocator.free_fn = free_fn; + custom_allocator.realloc_fn = NULL; +} + +void jv_set_alloc_funcs_ex(jv_malloc_t malloc_fn, jv_free_t free_fn, jv_realloc_t realloc_fn) { + custom_allocator.malloc_fn = malloc_fn; + custom_allocator.free_fn = free_fn; + custom_allocator.realloc_fn = realloc_fn; +} + +#else + +#ifdef HAVE_PTHREAD_KEY_CREATE +// Pthread-based implementation +void jv_set_alloc_funcs(jv_malloc_t malloc_fn, jv_free_t free_fn) { + pthread_once(&mem_once, tsd_init); + tsd_init_custom_allocator(); + + struct custom_allocator *custom_alloc = pthread_getspecific(custom_allocator_key); + if (custom_alloc) { + custom_alloc->malloc_fn = malloc_fn; + custom_alloc->free_fn = free_fn; + custom_alloc->realloc_fn = NULL; + } +} + +void jv_set_alloc_funcs_ex(jv_malloc_t malloc_fn, jv_free_t free_fn, jv_realloc_t realloc_fn) { + pthread_once(&mem_once, tsd_init); + tsd_init_custom_allocator(); + + struct custom_allocator *custom_alloc = pthread_getspecific(custom_allocator_key); + if (custom_alloc) { + custom_alloc->malloc_fn = malloc_fn; + custom_alloc->free_fn = free_fn; + custom_alloc->realloc_fn = realloc_fn; + } +} + +#else +// Global variable implementation +void jv_set_alloc_funcs(jv_malloc_t malloc_fn, jv_free_t free_fn) { + custom_allocator.malloc_fn = malloc_fn; + custom_allocator.free_fn = free_fn; + custom_allocator.realloc_fn = NULL; +} + +void jv_set_alloc_funcs_ex(jv_malloc_t malloc_fn, jv_free_t free_fn, jv_realloc_t realloc_fn) { + custom_allocator.malloc_fn = malloc_fn; + custom_allocator.free_fn = free_fn; + custom_allocator.realloc_fn = realloc_fn; +} + +#endif /* HAVE_PTHREAD_KEY_CREATE */ +#endif /* USE_TLS */ diff --git a/src/jv_alloc.h b/src/jv_alloc.h index 0ec391d221..43f889b60e 100644 --- a/src/jv_alloc.h +++ b/src/jv_alloc.h @@ -3,6 +3,10 @@ #include +typedef void* (*jv_malloc_t)(size_t size); +typedef void (*jv_free_t)(void* ptr); +typedef void* (*jv_realloc_t)(void* ptr, size_t size); + void* jv_mem_alloc(size_t); void* jv_mem_alloc_unguarded(size_t); void* jv_mem_calloc(size_t, size_t); @@ -12,4 +16,7 @@ char* jv_mem_strdup_unguarded(const char *); void jv_mem_free(void*); __attribute__((warn_unused_result)) void* jv_mem_realloc(void*, size_t); +void jv_set_alloc_funcs(jv_malloc_t malloc_fn, jv_free_t free_fn); +void jv_set_alloc_funcs_ex(jv_malloc_t malloc_fn, jv_free_t free_fn, jv_realloc_t realloc_fn); + #endif diff --git a/tests/alloctest b/tests/alloctest new file mode 100755 index 0000000000..598803395a --- /dev/null +++ b/tests/alloctest @@ -0,0 +1,97 @@ +#!/bin/sh + +. "${0%/*}/setup" "$@" + +# Test custom allocator functionality +echo "Testing custom allocator functionality..." + +# Create a simple test program +cat > "$d/test_custom_alloc.c" << 'EOF' +#include +#include +#include +#include +#include "src/jv_alloc.h" +#include "src/jv.h" + +static size_t alloc_count = 0; +static size_t free_count = 0; + +void* test_malloc(size_t size) { + alloc_count++; + return malloc(size); +} + +void test_free(void* ptr) { + if (ptr) free_count++; + free(ptr); +} + +int main() { + // Test 1: Basic custom allocator functionality + jv_set_alloc_funcs(test_malloc, test_free); + + jv str = jv_string("test"); + jv_free(str); + + if (alloc_count == 0) { + printf("ERROR: Custom malloc was not called\n"); + return 1; + } + + if (free_count == 0) { + printf("ERROR: Custom free was not called\n"); + return 1; + } + + // Test 2: Reset to default allocators + alloc_count = 0; + free_count = 0; + jv_set_alloc_funcs(NULL, NULL); + + jv str2 = jv_string("test2"); + jv_free(str2); + + // When reset to NULL, should use default allocators (our counters should be 0) + if (alloc_count != 0 || free_count != 0) { + printf("ERROR: Default allocators not properly restored\n"); + return 1; + } + + // Test 3: JSON parsing with custom allocator + jv_set_alloc_funcs(test_malloc, test_free); + alloc_count = 0; + free_count = 0; + + jv json = jv_parse("{\"key\": \"value\"}"); + if (!jv_is_valid(json)) { + printf("ERROR: JSON parsing failed\n"); + return 1; + } + jv_free(json); + + if (alloc_count == 0) { + printf("ERROR: Custom allocator not used during JSON parsing\n"); + return 1; + } + + printf("All custom allocator tests passed!\n"); + return 0; +} +EOF + +# Compile the test +cd "$d" +CC=${CC:-gcc} +if ! $CC -I$JQBASEDIR -L$JQBASEDIR/.libs -L$JQBASEDIR/vendor/oniguruma/src/.libs -o test_custom_alloc test_custom_alloc.c $JQBASEDIR/.libs/libjq.a $JQBASEDIR/vendor/oniguruma/src/.libs/libonig.a -lm; then + echo "FAIL: Could not compile custom allocator test" + exit 1 +fi + +# Run the test +if ! ./test_custom_alloc; then + echo "FAIL: Custom allocator test failed" + exit 1 +fi + +echo "PASS: Custom allocator tests completed successfully" \ No newline at end of file