From 9d801d64eacdc900da8daa7527c5f2a663cd2b89 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Thu, 1 Feb 2018 22:13:39 +0100 Subject: [PATCH] cJSON_CreateConfiguration, cJSON_ConfigurationChange{Allocators,Userdata} --- cJSON.c | 136 +++++++++++++++++++++++++++++++++- cJSON.h | 42 ++++++++++- tests/CMakeLists.txt | 1 + tests/configuration_tests.c | 143 ++++++++++++++++++++++++++++++++++++ 4 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 tests/configuration_tests.c diff --git a/cJSON.c b/cJSON.c index 97e9141..6386cc4 100644 --- a/cJSON.c +++ b/cJSON.c @@ -60,6 +60,10 @@ #define true ((cJSON_bool)1) #define false ((cJSON_bool)0) +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + typedef struct { const unsigned char *json; size_t position; @@ -124,8 +128,6 @@ typedef struct internal_configuration cJSON_bool case_sensitive; cJSON_Allocators allocators; void *userdata; - - } internal_configuration; #if defined(_MSC_VER) @@ -193,6 +195,9 @@ static void deallocate(const internal_configuration * const configuration, void NULL /* no userdata */\ } +/* this is necessary to assign the default configuration after initialization */ +static const internal_configuration global_default_configuration = default_configuration; + static internal_configuration global_configuration = default_configuration; static unsigned char* custom_strdup(const unsigned char* string, const internal_configuration * const configuration) @@ -2880,6 +2885,133 @@ CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) return (item->type & 0xFF) == cJSON_Raw; } +static size_t get_size_from_number(const cJSON * const number) +{ + if (number->valuedouble >= SIZE_MAX) + { + return SIZE_MAX; + } + + if (number->valuedouble <= 0) + { + return 0; + } + + return (size_t)number->valuedouble; +} + +CJSON_PUBLIC(cJSON_Configuration) cJSON_CreateConfiguration(const cJSON * const json, const cJSON_Allocators * const allocators, void *allocator_userdata) +{ + internal_configuration *configuration = NULL; + cJSON *option = NULL; + const cJSON_Allocators *local_allocators = &global_configuration.allocators; + + if (allocators != NULL) + { + if ((allocators->allocate == NULL) || (allocators->deallocate == NULL)) + { + goto fail; + } + + local_allocators = allocators; + } + + if ((json != NULL) && !cJSON_IsObject(json)) + { + goto fail; + } + + configuration = (internal_configuration*)local_allocators->allocate(sizeof(internal_configuration), allocator_userdata); + if (configuration == NULL) + { + goto fail; + } + + /* initialize with the default */ + *configuration = global_default_configuration; + configuration->userdata = allocator_userdata; + configuration->allocators = *local_allocators; + + if (json == NULL) + { + /* default configuration */ + return configuration; + } + + /* then overwrite with other options if they exist */ + + option = get_object_item(json, "buffer_size", &global_configuration); + if (cJSON_IsNumber(option)) + { + configuration->buffer_size = get_size_from_number(option); + } + + option = get_object_item(json, "format", &global_configuration); + if (cJSON_IsTrue(option)) + { + configuration->format = true; + } + else if (cJSON_IsFalse(option)) + { + configuration->format = false; + } + + option = get_object_item(json, "case_sensitive", &global_configuration); + if (cJSON_IsTrue(option)) + { + configuration->case_sensitive = true; + } + else if (cJSON_IsFalse(option)) + { + configuration->case_sensitive = false; + } + + option = get_object_item(json, "allow_data_after_json", &global_configuration); + if (cJSON_IsTrue(option)) + { + configuration->allow_data_after_json = true; + } + else if (cJSON_IsFalse(option)) + { + configuration->allow_data_after_json = false; + } + + return (cJSON_Configuration)configuration; + +fail: + if (configuration != NULL) + { + local_allocators->deallocate(configuration, allocator_userdata); + } + + return NULL; +} + +CJSON_PUBLIC(cJSON_Configuration) cJSON_ConfigurationChangeAllocators(cJSON_Configuration configuration, const cJSON_Allocators allocators) +{ + if ((configuration == NULL) || (allocators.allocate == NULL) || (allocators.deallocate == NULL)) + { + return NULL; + } + + ((internal_configuration*)configuration)->allocators = allocators; + ((internal_configuration*)configuration)->userdata = NULL; + + return configuration; +} + +/* Change the allocator userdata attached to a cJSON_Configuration */ +CJSON_PUBLIC(cJSON_Configuration) cJSON_ConfigurationChangeUserdata(cJSON_Configuration configuration, void *userdata) +{ + if (configuration == NULL) + { + return NULL; + } + + ((internal_configuration*)configuration)->userdata = userdata; + return configuration; +} + static cJSON_bool compare(const cJSON * const a, const cJSON * const b, const internal_configuration * const configuration) { if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) diff --git a/cJSON.h b/cJSON.h index 36974eb..8b434c4 100644 --- a/cJSON.h +++ b/cJSON.h @@ -87,6 +87,7 @@ typedef struct cJSON_Allocators } cJSON_Allocators; typedef int cJSON_bool; +typedef void* cJSON_Configuration; #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) #define __WINDOWS__ @@ -140,7 +141,46 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ /* returns the version of cJSON as a string */ CJSON_PUBLIC(const char*) cJSON_Version(void); -/* Supply malloc, realloc and free functions to cJSON */ +/* Create a configuration object that can be passed to several functions + * to configure their behavior. + * A configuration is given in JSON form (case sensitive) and can optionally contain any + * of the following options: + * - buffer_size: number of bytes that the printbuffer should be initially + * - format: boolean that indicates if the output should be formatted + * - case_sensitive: boolean that indicates if object keys should be considered case_sensitive + * - allow_data_after_json: boolean that indicates if parsing succeeds if the JSON in the + * input is followed by non JSON data + * + * + * If NULL is passed to a function that expects an object of type cJSON_Configuration, + * the following default configuration is used: + * { + * "buffer_size": 256, + * "format": true, + * "case_sensitive": true, + * "allow_data_after_json": true + * } + * + * A cJSON_Configuration object is dynamically allocated and you are responsible to free it + * after use. + * + * If allocators is a NULL pointer, the global default allocators are used (the one that is set + * by cJSON_InitHooks, malloc/free by default). + * The allocator is automatically attached to the configuration, so it will be used by functions + * that the configuration is passed to. This can be changed later with + * cJSON_ConfigurationChangeAllocator. + * + * allocator_userdata can be used to pass custom data to your allocator. It also gets attached to + * the configuration automatically. This can later be changed with + * cJSON_ConfigurationChangeUserdata. + * */ +CJSON_PUBLIC(cJSON_Configuration) cJSON_CreateConfiguration(const cJSON * const json, const cJSON_Allocators * const allocators, void *allocator_userdata); +/* Change the allocators of a cJSON_Configuration and reset the userdata */ +CJSON_PUBLIC(cJSON_Configuration) cJSON_ConfigurationChangeAllocators(cJSON_Configuration configuration, const cJSON_Allocators allocators); +/* Change the allocator userdata attached to a cJSON_Configuration */ +CJSON_PUBLIC(cJSON_Configuration) cJSON_ConfigurationChangeUserdata(cJSON_Configuration configuration, void *userdata); + +/* Supply malloc and free functions to cJSON globally */ CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7967292..14bdf34 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ if(ENABLE_CJSON_TEST) compare_tests cjson_add readme_examples + configuration_tests ) option(ENABLE_VALGRIND OFF "Enable the valgrind memory checker for the tests.") diff --git a/tests/configuration_tests.c b/tests/configuration_tests.c new file mode 100644 index 0000000..e4489a9 --- /dev/null +++ b/tests/configuration_tests.c @@ -0,0 +1,143 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include +#include +#include + +#include "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static void create_configuration_should_create_a_configuration(void) +{ + cJSON *json = NULL; + internal_configuration *configuration = NULL; + int userdata = 1; + + json = cJSON_Parse("{\"buffer_size\":1024,\"format\":false,\"case_sensitive\":false,\"allow_data_after_json\":false}"); + TEST_ASSERT_NOT_NULL(json); + configuration = (internal_configuration*)cJSON_CreateConfiguration(json, NULL, &userdata); + cJSON_Delete(json); + json = NULL; + TEST_ASSERT_NOT_NULL(configuration); + TEST_ASSERT_EQUAL_MESSAGE(configuration->buffer_size, 1024, "buffer_size has an incorrect value."); + TEST_ASSERT_FALSE_MESSAGE(configuration->format, "format has an incorrect value."); + TEST_ASSERT_FALSE_MESSAGE(configuration->case_sensitive, "case_sensitive has an incorrect value."); + TEST_ASSERT_FALSE_MESSAGE(configuration->allow_data_after_json, "allow_data_after_json has an incorrect value."); + TEST_ASSERT_TRUE_MESSAGE(configuration->userdata == &userdata, "Incorrect userdata"); + TEST_ASSERT_TRUE_MESSAGE(global_allocate_wrapper == configuration->allocators.allocate, "Wrong malloc."); + TEST_ASSERT_TRUE_MESSAGE(global_reallocate_wrapper == configuration->allocators.reallocate, "Wrong realloc."); + TEST_ASSERT_TRUE_MESSAGE(global_deallocate_wrapper == configuration->allocators.deallocate, "Wrong realloc."); + + free(configuration); +} + +static void create_configuration_should_work_with_an_empty_object(void) +{ + internal_configuration *configuration = NULL; + int userdata = 1; + + configuration = (internal_configuration*)cJSON_CreateConfiguration(NULL, NULL, &userdata); + TEST_ASSERT_NOT_NULL(configuration); + TEST_ASSERT_EQUAL_MESSAGE(configuration->buffer_size, 256, "buffer_size has an incorrect value."); + TEST_ASSERT_TRUE_MESSAGE(configuration->format, "format has an incorrect value."); + TEST_ASSERT_TRUE_MESSAGE(configuration->case_sensitive, "case_sensitive has an incorrect value."); + TEST_ASSERT_TRUE_MESSAGE(configuration->allow_data_after_json, "allow_data_after_json has an incorrect value."); + TEST_ASSERT_TRUE_MESSAGE(configuration->userdata == &userdata, "Incorrect userdata"); + TEST_ASSERT_TRUE_MESSAGE(global_allocate_wrapper == configuration->allocators.allocate, "Wrong malloc."); + TEST_ASSERT_TRUE_MESSAGE(global_reallocate_wrapper == configuration->allocators.reallocate, "Wrong realloc."); + TEST_ASSERT_TRUE_MESSAGE(global_deallocate_wrapper == configuration->allocators.deallocate, "Wrong free."); + + free(configuration); +} + +static void* custom_allocator(size_t size, void *userdata) +{ + *((size_t*)userdata) = size; + return malloc(size); +} +static void custom_deallocator(void *pointer, void *userdata) +{ + *((size_t*)userdata) = (size_t)pointer; + free(pointer); +} + +static void create_configuration_should_take_custom_allocators(void) +{ + internal_configuration *configuration = NULL; + cJSON_Allocators allocators = {custom_allocator, custom_deallocator, NULL}; + size_t userdata = 0; + + configuration = (internal_configuration*)cJSON_CreateConfiguration(NULL, &allocators, &userdata); + TEST_ASSERT_NOT_NULL(configuration); + TEST_ASSERT_EQUAL_MESSAGE(userdata, sizeof(internal_configuration), "custom allocator wasn't run properly."); + TEST_ASSERT_TRUE_MESSAGE(custom_allocator == configuration->allocators.allocate, "Wrong allocator."); + TEST_ASSERT_TRUE_MESSAGE(custom_deallocator == configuration->allocators.deallocate, "Wrong deallocator."); + TEST_ASSERT_NULL_MESSAGE(configuration->allocators.reallocate, "Reallocator is not null"); + + custom_deallocator(configuration, &userdata); +} + +static void configuration_change_allocators_should_change_allocators(void) +{ + internal_configuration *configuration = NULL; + cJSON_Allocators allocators = {custom_allocator, custom_deallocator, NULL}; + size_t userdata = 0; + + configuration = (internal_configuration*)cJSON_CreateConfiguration(NULL, &allocators, &userdata); + TEST_ASSERT_NOT_NULL(configuration); + + configuration = (internal_configuration*)cJSON_ConfigurationChangeAllocators(configuration, allocators); + TEST_ASSERT_NOT_NULL(configuration); + TEST_ASSERT_TRUE_MESSAGE(custom_allocator == configuration->allocators.allocate, "Wrong allocator."); + TEST_ASSERT_TRUE_MESSAGE(custom_deallocator == configuration->allocators.deallocate, "Wrong deallocator."); + TEST_ASSERT_NULL_MESSAGE(configuration->allocators.reallocate, "Reallocator is not null"); + + custom_deallocator(configuration, &userdata); +} + +static void configuration_change_userdata_should_change_userdata(void) +{ + internal_configuration *configuration = NULL; + size_t userdata = 0; + configuration = (internal_configuration*)cJSON_CreateConfiguration(NULL, NULL, NULL); + TEST_ASSERT_NOT_NULL(configuration); + + configuration = (internal_configuration*)cJSON_ConfigurationChangeUserdata(configuration, &userdata); + TEST_ASSERT_TRUE_MESSAGE(configuration->userdata == &userdata, "Userdata is incorrect."); + + free(configuration); +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(create_configuration_should_create_a_configuration); + RUN_TEST(create_configuration_should_work_with_an_empty_object); + RUN_TEST(create_configuration_should_take_custom_allocators); + RUN_TEST(configuration_change_allocators_should_change_allocators); + RUN_TEST(configuration_change_userdata_should_change_userdata); + + return UNITY_END(); +}