diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..172cfcd3 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,54 @@ +Contribution Guidelines +======================= + +Contributions to cJSON are welcome. If you find a bug or want to improve cJSON in another way, pull requests are appreciated. + +For bigger changes, in order to avoid wasted effort, please open an issue to discuss the technical details before creating a pull request. + +The further sections explain the process in more detail and provides some guidelines on how contributions should look like. + +Branches +-------- +There are two branches to be aware of, the `master` and the `develop` branch. The `master` branch is reserved for the latest release, so only make pull requests to the `master` branch for small bug- or security fixes (these are usually just a few lines). In all other cases, please make a pull request to the `develop` branch. + +Coding Style +------------ +The coding style has been discussed in [#24](https://github.com/DaveGamble/cJSON/issues/24). The basics are: + +* Use 4 tabs for indentation +* No oneliners (conditions, loops, variable declarations ...) +* Always use parenthesis for control structures +* Don't implicitly rely on operator precedence, use round brackets in expressions. e.g. `(a > b) && (c < d)` instead of `a>b && cbuffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} +``` + +Unit Tests +---------- +cJSON uses the [Unity](https://github.com/ThrowTheSwitch/Unity) library for unit tests. The tests are located in the `tests` directory. In order to add a new test, either add it to one of the existing files (if it fits) or add a new C file for the test. That new file has to be added to the list of tests in `tests/CMakeLists.txt`. + +All new features have to be covered by unit tests. + +Other Notes +----------- +* Internal functions are to be declared static. +* Wrap the return type of external function in the `CJSON_PUBLIC` macro. diff --git a/CMakeLists.txt b/CMakeLists.txt index 38aab04a..01a1f3bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,15 @@ set(CMAKE_LEGACY_CYGWIN_WIN32 0) cmake_minimum_required(VERSION 2.8) -subdirs(tests) +subdirs(tests fuzzing) include(GNUInstallDirs) project(cJSON C) set(PROJECT_VERSION_MAJOR 1) -set(PROJECT_VERSION_MINOR 3) -set(PROJECT_VERSION_PATCH 2) +set(PROJECT_VERSION_MINOR 4) +set(PROJECT_VERSION_PATCH 0) set(CJSON_VERSION_SO 1) set(CJSON_UTILS_VERSION_SO 1) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") @@ -40,6 +40,9 @@ if (ENABLE_CUSTOM_COMPILER_FLAGS) -Wconversion -Wc++-compat -fstack-protector-strong + -Wcomma + -Wdouble-promotion + -Wparentheses ) endif() @@ -58,6 +61,16 @@ if (ENABLE_SANITIZERS) ) endif() +option(ENABLE_PUBLIC_SYMBOLS "Export library symbols." On) +if (ENABLE_PUBLIC_SYMBOLS) + list(APPEND custom_compiler_flags -fvisibility=hidden) + add_definitions(-DCJSON_EXPORT_SYMBOLS -DCJSON_API_VISIBILITY) +endif() +option(ENABLE_HIDDEN_SYMBOLS "Hide library symbols." Off) +if (ENABLE_HIDDEN_SYMBOLS) + add_definitions(-DCJSON_HIDE_SYMBOLS -UCJSON_API_VISIBILITY) +endif() + # apply custom compiler flags foreach(compiler_flag ${custom_compiler_flags}) #remove problematic characters diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6ab649b4..e0f4473a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,6 +21,7 @@ Contributors * [Linus Wallgren](https://github.com/ecksun) * [Max Bruckner](https://github.com/FSMaxB) * Mike Pontillo +* [Mike Jerris](https://github.com/mjerris) * Paulo Antonio Alvarez * [Rafael Leal Dias](https://github.com/rafaeldias) * [Rod Vagg](https://github.com/rvagg) diff --git a/Makefile b/Makefile index 99bfc586..2e1b1e11 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ UTILS_TEST_SRC = cJSON.c cJSON_Utils.c test_utils.c LDLIBS = -lm -LIBVERSION = 1.3.2 +LIBVERSION = 1.4.0 CJSON_SOVERSION = 1 UTILS_SOVERSION = 1 diff --git a/cJSON.c b/cJSON.c index e6e5d3ae..67804b20 100644 --- a/cJSON.c +++ b/cJSON.c @@ -23,6 +23,7 @@ /* cJSON */ /* JSON parser in C. */ +#pragma GCC visibility push(default) #include #include #include @@ -30,26 +31,27 @@ #include #include #include +#pragma GCC visibility pop + #include "cJSON.h" /* define our own boolean type */ -typedef int cjbool; -#define true ((cjbool)1) -#define false ((cjbool)0) +#define true ((cJSON_bool)1) +#define false ((cJSON_bool)0) static const unsigned char *global_ep = NULL; -const char *cJSON_GetErrorPtr(void) +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) { return (const char*) global_ep; } /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 3) || (CJSON_VERSION_PATCH != 2) +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 4) || (CJSON_VERSION_PATCH != 0) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif -extern const char* cJSON_Version(void) +CJSON_PUBLIC(const char*) cJSON_Version(void) { static char version[15]; sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); @@ -68,7 +70,7 @@ static int cJSON_strcasecmp(const unsigned char *s1, const unsigned char *s2) { return 1; } - for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) + for(; tolower(*s1) == tolower(*s2); (void)++s1, ++s2) { if (*s1 == '\0') { @@ -79,10 +81,16 @@ static int cJSON_strcasecmp(const unsigned char *s1, const unsigned char *s2) return tolower(*s1) - tolower(*s2); } -static void *(*cJSON_malloc)(size_t sz) = malloc; -static void (*cJSON_free)(void *ptr) = free; +typedef struct internal_hooks +{ + void *(*allocate)(size_t size); + void (*deallocate)(void *pointer); + void *(*reallocate)(void *pointer, size_t size); +} internal_hooks; + +static internal_hooks global_hooks = { malloc, free, realloc }; -static unsigned char* cJSON_strdup(const unsigned char* str) +static unsigned char* cJSON_strdup(const unsigned char* str, const internal_hooks * const hooks) { size_t len = 0; unsigned char *copy = NULL; @@ -93,7 +101,7 @@ static unsigned char* cJSON_strdup(const unsigned char* str) } len = strlen((const char*)str) + 1; - if (!(copy = (unsigned char*)cJSON_malloc(len))) + if (!(copy = (unsigned char*)hooks->allocate(len))) { return NULL; } @@ -102,24 +110,41 @@ static unsigned char* cJSON_strdup(const unsigned char* str) return copy; } -void cJSON_InitHooks(cJSON_Hooks* hooks) +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) { - if (!hooks) + if (hooks == NULL) { /* Reset hooks */ - cJSON_malloc = malloc; - cJSON_free = free; + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; return; } - cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; - cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } } /* Internal constructor. */ -static cJSON *cJSON_New_Item(void) +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) { - cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); if (node) { memset(node, '\0', sizeof(cJSON)); @@ -129,7 +154,7 @@ static cJSON *cJSON_New_Item(void) } /* Delete a cJSON structure. */ -void cJSON_Delete(cJSON *c) +CJSON_PUBLIC(void) cJSON_Delete(cJSON *c) { cJSON *next = NULL; while (c) @@ -141,13 +166,13 @@ void cJSON_Delete(cJSON *c) } if (!(c->type & cJSON_IsReference) && c->valuestring) { - cJSON_free(c->valuestring); + global_hooks.deallocate(c->valuestring); } if (!(c->type & cJSON_StringIsConst) && c->string) { - cJSON_free(c->string); + global_hooks.deallocate(c->string); } - cJSON_free(c); + global_hooks.deallocate(c); c = next; } } @@ -191,7 +216,7 @@ static const unsigned char *parse_number(cJSON * const item, const unsigned char } /* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ -double cJSON_SetNumberHelper(cJSON *object, double number) +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) { if (number >= INT_MAX) { @@ -214,25 +239,26 @@ typedef struct unsigned char *buffer; size_t length; size_t offset; - cjbool noalloc; + cJSON_bool noalloc; } printbuffer; /* realloc printbuffer if necessary to have at least "needed" bytes more */ -static unsigned char* ensure(printbuffer *p, size_t needed) +static unsigned char* ensure(printbuffer * const p, size_t needed, const internal_hooks * const hooks) { unsigned char *newbuffer = NULL; size_t newsize = 0; - if (needed > INT_MAX) + if ((p == NULL) || (p->buffer == NULL)) { - /* sizes bigger than INT_MAX are currently not supported */ return NULL; } - if (!p || !p->buffer) + if (needed > INT_MAX) { + /* sizes bigger than INT_MAX are currently not supported */ return NULL; } + needed += p->offset; if (needed <= p->length) { @@ -258,112 +284,132 @@ static unsigned char* ensure(printbuffer *p, size_t needed) } } - newbuffer = (unsigned char*)cJSON_malloc(newsize); - if (!newbuffer) + if (hooks->reallocate != NULL) { - cJSON_free(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)hooks->reallocate(p->buffer, newsize); } - if (newbuffer) + else { - memcpy(newbuffer, p->buffer, p->length); + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)hooks->allocate(newsize); + if (!newbuffer) + { + hooks->deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + hooks->deallocate(p->buffer); } - cJSON_free(p->buffer); p->length = newsize; p->buffer = newbuffer; return newbuffer + p->offset; } -/* calculate the new length of the string in a printbuffer */ -static size_t update(const printbuffer *p) +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* Removes trailing zeroes from the end of a printed number */ +static cJSON_bool trim_trailing_zeroes(printbuffer * const buffer) { - const unsigned char *str = NULL; - if (!p || !p->buffer) + size_t offset = 0; + unsigned char *content = NULL; + + if ((buffer == NULL) || (buffer->buffer == NULL) || (buffer->offset < 1)) + { + return false; + } + + offset = buffer->offset - 1; + content = buffer->buffer; + + while ((offset > 0) && (content[offset] == '0')) { - return 0; + offset--; } - str = p->buffer + p->offset; + if ((offset > 0) && (content[offset] == '.')) + { + offset--; + } + + offset++; + content[offset] = '\0'; - return p->offset + strlen((const char*)str); + buffer->offset = offset; + + return true; } /* Render the number nicely from the given item into a string. */ -static unsigned char *print_number(const cJSON *item, printbuffer *p) +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer, const internal_hooks * const hooks) { - unsigned char *str = NULL; + unsigned char *output_pointer = NULL; double d = item->valuedouble; - /* special case for 0. */ - if (d == 0) + int length = 0; + cJSON_bool trim_zeroes = true; /* should at the end be removed? */ + + if (output_buffer == NULL) { - if (p) - { - str = ensure(p, 2); - } - else - { - str = (unsigned char*)cJSON_malloc(2); - } - if (str) - { - strcpy((char*)str,"0"); - } + return false; } - /* value is an int */ - else if ((fabs(((double)item->valueint) - d) <= DBL_EPSILON) && (d <= INT_MAX) && (d >= INT_MIN)) + + /* This is a nice tradeoff. */ + output_pointer = ensure(output_buffer, 64, hooks); + if (output_pointer != NULL) { - if (p) - { - str = ensure(p, 21); - } - else + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) { - /* 2^64+1 can be represented in 21 chars. */ - str = (unsigned char*)cJSON_malloc(21); + length = sprintf((char*)output_pointer, "null"); } - if (str) + else if ((fabs(floor(d) - d) <= DBL_EPSILON) && (fabs(d) < 1.0e60)) { - sprintf((char*)str, "%d", item->valueint); + /* integer */ + length = sprintf((char*)output_pointer, "%.0f", d); + trim_zeroes = false; /* don't remove zeroes for "big integers" */ } - } - /* value is a floating point number */ - else - { - if (p) + else if ((fabs(d) < 1.0e-6) || (fabs(d) > 1.0e9)) { - /* This is a nice tradeoff. */ - str = ensure(p, 64); + length = sprintf((char*)output_pointer, "%e", d); + trim_zeroes = false; /* don't remove zeroes in engineering notation */ } else { - /* This is a nice tradeoff. */ - str = (unsigned char*)cJSON_malloc(64); - } - if (str) - { - /* This checks for NaN and Infinity */ - if ((d * 0) != 0) - { - sprintf((char*)str, "null"); - } - else if ((fabs(floor(d) - d) <= DBL_EPSILON) && (fabs(d) < 1.0e60)) - { - sprintf((char*)str, "%.0f", d); - } - else if ((fabs(d) < 1.0e-6) || (fabs(d) > 1.0e9)) - { - sprintf((char*)str, "%e", d); - } - else - { - sprintf((char*)str, "%f", d); - } + length = sprintf((char*)output_pointer, "%f", d); } } - return str; + + /* sprintf failed */ + if (length < 0) + { + return false; + } + + output_buffer->offset += (size_t)length; + + if (trim_zeroes) + { + return trim_trailing_zeroes(output_buffer); + } + + return true; } /* parse 4 digit hexadecimal number */ @@ -406,21 +452,13 @@ static unsigned parse_hex4(const unsigned char * const input) * A literal can be one or two sequences of the form \uXXXX */ static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer, const unsigned char **error_pointer) { - /* first bytes of UTF8 encoding for a given length in bytes */ - static const unsigned char firstByteMark[5] = - { - 0x00, /* should never happen */ - 0x00, /* 0xxxxxxx */ - 0xC0, /* 110xxxxx */ - 0xE0, /* 1110xxxx */ - 0xF0 /* 11110xxx */ - }; - long unsigned int codepoint = 0; unsigned int first_code = 0; const unsigned char *first_sequence = input_pointer; unsigned char utf8_length = 0; + unsigned char utf8_position = 0; unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; if ((input_end - first_sequence) < 6) { @@ -492,16 +530,19 @@ static unsigned char utf16_literal_to_utf8(const unsigned char * const input_poi { /* two bytes, encoding 110xxxxx 10xxxxxx */ utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ } else if (codepoint < 0x10000) { /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ } else if (codepoint <= 0x10FFFF) { /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ } else { @@ -511,28 +552,22 @@ static unsigned char utf16_literal_to_utf8(const unsigned char * const input_poi } /* encode as utf8 */ - switch (utf8_length) - { - case 4: - /* 10xxxxxx */ - (*output_pointer)[3] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - case 3: - /* 10xxxxxx */ - (*output_pointer)[2] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - case 2: - (*output_pointer)[1] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - case 1: - /* depending on the length in bytes this determines the - encoding of the first UTF8 byte */ - (*output_pointer)[0] = (unsigned char)((codepoint | firstByteMark[utf8_length]) & 0xFF); - break; - default: - *error_pointer = first_sequence; - goto fail; + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); } + *output_pointer += utf8_length; return sequence_length; @@ -542,7 +577,7 @@ static unsigned char utf16_literal_to_utf8(const unsigned char * const input_poi } /* Parse the input text into an unescaped cinput, and populate item. */ -static const unsigned char *parse_string(cJSON * const item, const unsigned char * const input, const unsigned char ** const error_pointer) +static const unsigned char *parse_string(cJSON * const item, const unsigned char * const input, const unsigned char ** const error_pointer, const internal_hooks * const hooks) { const unsigned char *input_pointer = input + 1; const unsigned char *input_end = input + 1; @@ -582,7 +617,7 @@ static const unsigned char *parse_string(cJSON * const item, const unsigned char /* This is at most how much we need for the output */ allocation_length = (size_t) (input_end - input) - skipped_bytes; - output = (unsigned char*)cJSON_malloc(allocation_length + sizeof('\0')); + output = (unsigned char*)hooks->allocate(allocation_length + sizeof('\0')); if (output == NULL) { goto fail; /* allocation failure */ @@ -653,174 +688,140 @@ static const unsigned char *parse_string(cJSON * const item, const unsigned char fail: if (output != NULL) { - cJSON_free(output); + hooks->deallocate(output); } return NULL; } /* Render the cstring provided to an escaped version that can be printed. */ -static unsigned char *print_string_ptr(const unsigned char *str, printbuffer *p) +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer, const internal_hooks * const hooks) { - const unsigned char *ptr = NULL; - unsigned char *ptr2 = NULL; - unsigned char *out = NULL; - size_t len = 0; - cjbool flag = false; - unsigned char token = '\0'; + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; - /* empty string */ - if (!str) + if (output_buffer == NULL) { - if (p) - { - out = ensure(p, 3); - } - else - { - out = (unsigned char*)cJSON_malloc(3); - } - if (!out) - { - return NULL; - } - strcpy((char*)out, "\"\""); - - return out; + return false; } - /* set "flag" to 1 if something needs to be escaped */ - for (ptr = str; *ptr; ptr++) - { - flag |= (((*ptr > 0) && (*ptr < 32)) /* unprintable characters */ - || (*ptr == '\"') /* double quote */ - || (*ptr == '\\')) /* backslash */ - ? 1 - : 0; - } - /* no characters have to be escaped */ - if (!flag) + /* empty string */ + if (input == NULL) { - len = (size_t)(ptr - str); - if (p) - { - out = ensure(p, len + 3); - } - else - { - out = (unsigned char*)cJSON_malloc(len + 3); - } - if (!out) + output = ensure(output_buffer, sizeof("\"\""), hooks); + if (output == NULL) { - return NULL; + return false; } + strcpy((char*)output, "\"\""); - ptr2 = out; - *ptr2++ = '\"'; - strcpy((char*)ptr2, (const char*)str); - ptr2[len] = '\"'; - ptr2[len + 1] = '\0'; - - return out; + return true; } - ptr = str; - /* calculate additional space that is needed for escaping */ - while ((token = *ptr)) + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) { - ++len; - if (strchr("\"\\\b\f\n\r\t", token)) + if (strchr("\"\\\b\f\n\r\t", *input_pointer)) { - len++; /* +1 for the backslash */ + /* one character escape sequence */ + escape_characters++; } - else if (token < 32) + else if (*input_pointer < 32) { - len += 5; /* +5 for \uXXXX */ + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; } - ptr++; } + output_length = (size_t)(input_pointer - input) + escape_characters; - if (p) - { - out = ensure(p, len + 3); - } - else + output = ensure(output_buffer, output_length + sizeof("\"\""), hooks); + if (output == NULL) { - out = (unsigned char*)cJSON_malloc(len + 3); + return false; } - if (!out) + + /* no characters have to be escaped */ + if (escape_characters == 0) { - return NULL; + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; } - ptr2 = out; - ptr = str; - *ptr2++ = '\"'; + output[0] = '\"'; + output_pointer = output + 1; /* copy the string */ - while (*ptr) + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) { - if ((*ptr > 31) && (*ptr != '\"') && (*ptr != '\\')) + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) { /* normal character, copy */ - *ptr2++ = *ptr++; + *output_pointer = *input_pointer; } else { /* character needs to be escaped */ - *ptr2++ = '\\'; - switch (token = *ptr++) + *output_pointer++ = '\\'; + switch (*input_pointer) { case '\\': - *ptr2++ = '\\'; + *output_pointer = '\\'; break; case '\"': - *ptr2++ = '\"'; + *output_pointer = '\"'; break; case '\b': - *ptr2++ = 'b'; + *output_pointer = 'b'; break; case '\f': - *ptr2++ = 'f'; + *output_pointer = 'f'; break; case '\n': - *ptr2++ = 'n'; + *output_pointer = 'n'; break; case '\r': - *ptr2++ = 'r'; + *output_pointer = 'r'; break; case '\t': - *ptr2++ = 't'; + *output_pointer = 't'; break; default: /* escape and print as unicode codepoint */ - sprintf((char*)ptr2, "u%04x", token); - ptr2 += 5; + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; break; } } } - *ptr2++ = '\"'; - *ptr2++ = '\0'; + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; - return out; + return true; } /* Invoke print_string_ptr (which is useful) on an item. */ -static unsigned char *print_string(const cJSON *item, printbuffer *p) +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p, const internal_hooks * const hooks) { - return print_string_ptr((unsigned char*)item->valuestring, p); + return print_string_ptr((unsigned char*)item->valuestring, p, hooks); } /* Predeclare these prototypes. */ -static const unsigned char *parse_value(cJSON * const item, const unsigned char * const input, const unsigned char ** const ep); -static unsigned char *print_value(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p); -static const unsigned char *parse_array(cJSON * const item, const unsigned char *input, const unsigned char ** const ep); -static unsigned char *print_array(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p); -static const unsigned char *parse_object(cJSON * const item, const unsigned char *input, const unsigned char ** const ep); -static unsigned char *print_object(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p); +static const unsigned char *parse_value(cJSON * const item, const unsigned char * const input, const unsigned char ** const ep, const internal_hooks * const hooks); +static cJSON_bool print_value(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks); +static const unsigned char *parse_array(cJSON * const item, const unsigned char *input, const unsigned char ** const ep, const internal_hooks * const hooks); +static cJSON_bool print_array(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks); +static const unsigned char *parse_object(cJSON * const item, const unsigned char *input, const unsigned char ** const ep, const internal_hooks * const hooks); +static cJSON_bool print_object(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks); /* Utility to jump whitespace and cr/lf */ -static const unsigned char *skip(const unsigned char *in) +static const unsigned char *skip_whitespace(const unsigned char *in) { while (in && *in && (*in <= 32)) { @@ -831,19 +832,19 @@ static const unsigned char *skip(const unsigned char *in) } /* Parse an object - create a new root, and populate. */ -cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cjbool require_null_terminated) +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) { const unsigned char *end = NULL; /* use global error pointer if no specific one was given */ const unsigned char **ep = return_parse_end ? (const unsigned char**)return_parse_end : &global_ep; - cJSON *c = cJSON_New_Item(); + cJSON *c = cJSON_New_Item(&global_hooks); *ep = NULL; if (!c) /* memory fail */ { return NULL; } - end = parse_value(c, skip((const unsigned char*)value), ep); + end = parse_value(c, skip_whitespace((const unsigned char*)value), ep, &global_hooks); if (!end) { /* parse failure. ep is set. */ @@ -854,7 +855,7 @@ cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cjb /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ if (require_null_terminated) { - end = skip(end); + end = skip_whitespace(end); if (*end) { cJSON_Delete(c); @@ -871,23 +872,74 @@ cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cjb } /* Default options for cJSON_Parse */ -cJSON *cJSON_Parse(const char *value) +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) { return cJSON_ParseWithOpts(value, 0, 0); } +#define min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(256); + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, 0, format, buffer, hooks)) + { + goto fail; + } + update_offset(buffer); + + /* copy the buffer over to a new one */ + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + strncpy((char*)printed, (char*)buffer->buffer, min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + /* Render a cJSON item/entity/structure to text. */ -char *cJSON_Print(const cJSON *item) +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) { - return (char*)print_value(item, 0, 1, 0); + return (char*)print(item, true, &global_hooks); } -char *cJSON_PrintUnformatted(const cJSON *item) +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) { - return (char*)print_value(item, 0, 0, 0); + return (char*)print(item, false, &global_hooks); } -char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cjbool fmt) +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) { printbuffer p; @@ -896,7 +948,7 @@ char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cjbool fmt) return NULL; } - p.buffer = (unsigned char*)cJSON_malloc((size_t)prebuffer); + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); if (!p.buffer) { return NULL; @@ -906,10 +958,15 @@ char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cjbool fmt) p.offset = 0; p.noalloc = false; - return (char*)print_value(item, 0, fmt, &p); + if (!print_value(item, 0, fmt, &p, &global_hooks)) + { + return NULL; + } + + return (char*)p.buffer; } -int cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cjbool fmt) +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) { printbuffer p; @@ -922,11 +979,11 @@ int cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cjbool p.length = (size_t)len; p.offset = 0; p.noalloc = true; - return print_value(item, 0, fmt, &p) != NULL; + return print_value(item, 0, fmt, &p, &global_hooks); } /* Parser core - when encountering text, process appropriately. */ -static const unsigned char *parse_value(cJSON * const item, const unsigned char * const input, const unsigned char ** const error_pointer) +static const unsigned char *parse_value(cJSON * const item, const unsigned char * const input, const unsigned char ** const error_pointer, const internal_hooks * const hooks) { if (input == NULL) { @@ -956,7 +1013,7 @@ static const unsigned char *parse_value(cJSON * const item, const unsigned char /* string */ if (*input == '\"') { - return parse_string(item, input, error_pointer); + return parse_string(item, input, error_pointer, hooks); } /* number */ if ((*input == '-') || ((*input >= '0') && (*input <= '9'))) @@ -966,12 +1023,12 @@ static const unsigned char *parse_value(cJSON * const item, const unsigned char /* array */ if (*input == '[') { - return parse_array(item, input, error_pointer); + return parse_array(item, input, error_pointer, hooks); } /* object */ if (*input == '{') { - return parse_object(item, input, error_pointer); + return parse_object(item, input, error_pointer, hooks); } /* failure. */ @@ -980,116 +1037,85 @@ static const unsigned char *parse_value(cJSON * const item, const unsigned char } /* Render a value to text. */ -static unsigned char *print_value(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p) +static cJSON_bool print_value(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks) { - unsigned char *out = NULL; + unsigned char *output = NULL; - if (!item) + if ((item == NULL) || (output_buffer == NULL)) { - return NULL; + return false; } - if (p) + + switch ((item->type) & 0xFF) { - switch ((item->type) & 0xFF) + case cJSON_NULL: + output = ensure(output_buffer, 5, hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6, hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5, hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer, hooks); + + case cJSON_Raw: { - case cJSON_NULL: - out = ensure(p, 5); - if (out) - { - strcpy((char*)out, "null"); - } - break; - case cJSON_False: - out = ensure(p, 6); - if (out) - { - strcpy((char*)out, "false"); - } - break; - case cJSON_True: - out = ensure(p, 5); - if (out) - { - strcpy((char*)out, "true"); - } - break; - case cJSON_Number: - out = print_number(item, p); - break; - case cJSON_Raw: + size_t raw_length = 0; + if (item->valuestring == NULL) { - size_t raw_length = 0; - if (item->valuestring == NULL) + if (!output_buffer->noalloc) { - if (!p->noalloc) - { - cJSON_free(p->buffer); - } - out = NULL; - break; + hooks->deallocate(output_buffer->buffer); } + return false; + } - raw_length = strlen(item->valuestring) + sizeof('\0'); - out = ensure(p, raw_length); - if (out) - { - memcpy(out, item->valuestring, raw_length); - } - break; + raw_length = strlen(item->valuestring) + sizeof('\0'); + output = ensure(output_buffer, raw_length, hooks); + if (output == NULL) + { + return false; } - case cJSON_String: - out = print_string(item, p); - break; - case cJSON_Array: - out = print_array(item, depth, fmt, p); - break; - case cJSON_Object: - out = print_object(item, depth, fmt, p); - break; - default: - out = NULL; - break; + memcpy(output, item->valuestring, raw_length); + return true; } + + case cJSON_String: + return print_string(item, output_buffer, hooks); + + case cJSON_Array: + return print_array(item, depth, format, output_buffer, hooks); + + case cJSON_Object: + return print_object(item, depth, format, output_buffer, hooks); + + default: + return false; } - else - { - switch ((item->type) & 0xFF) - { - case cJSON_NULL: - out = cJSON_strdup((const unsigned char*)"null"); - break; - case cJSON_False: - out = cJSON_strdup((const unsigned char*)"false"); - break; - case cJSON_True: - out = cJSON_strdup((const unsigned char*)"true"); - break; - case cJSON_Number: - out = print_number(item, 0); - break; - case cJSON_Raw: - out = cJSON_strdup((unsigned char*)item->valuestring); - break; - case cJSON_String: - out = print_string(item, 0); - break; - case cJSON_Array: - out = print_array(item, depth, fmt, 0); - break; - case cJSON_Object: - out = print_object(item, depth, fmt, 0); - break; - default: - out = NULL; - break; - } - } - - return out; } /* Build an array from input text. */ -static const unsigned char *parse_array(cJSON * const item, const unsigned char *input, const unsigned char ** const error_pointer) +static const unsigned char *parse_array(cJSON * const item, const unsigned char *input, const unsigned char ** const error_pointer, const internal_hooks * const hooks) { cJSON *head = NULL; /* head of the linked list */ cJSON *current_item = NULL; @@ -1101,7 +1127,7 @@ static const unsigned char *parse_array(cJSON * const item, const unsigned char goto fail; } - input = skip(input + 1); /* skip whitespace */ + input = skip_whitespace(input + 1); if (*input == ']') { /* empty array */ @@ -1114,7 +1140,7 @@ static const unsigned char *parse_array(cJSON * const item, const unsigned char do { /* allocate next item */ - cJSON *new_item = cJSON_New_Item(); + cJSON *new_item = cJSON_New_Item(hooks); if (new_item == NULL) { goto fail; /* allocation failure */ @@ -1135,9 +1161,9 @@ static const unsigned char *parse_array(cJSON * const item, const unsigned char } /* parse next value */ - input = skip(input + 1); /* skip whitespace before value */ - input = parse_value(current_item, input, error_pointer); - input = skip(input); /* skip whitespace after value */ + input = skip_whitespace(input + 1); + input = parse_value(current_item, input, error_pointer, hooks); + input = skip_whitespace(input); if (input == NULL) { goto fail; /* failed to parse value */ @@ -1167,176 +1193,67 @@ static const unsigned char *parse_array(cJSON * const item, const unsigned char } /* Render an array to text */ -static unsigned char *print_array(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p) -{ - unsigned char **entries; - unsigned char *out = NULL; - unsigned char *ptr = NULL; - unsigned char *ret = NULL; - size_t len = 5; - cJSON *child = item->child; - size_t numentries = 0; - size_t i = 0; - cjbool fail = false; - size_t tmplen = 0; +static cJSON_bool print_array(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; - /* How many entries in the array? */ - while (child) + if (output_buffer == NULL) { - numentries++; - child = child->next; + return false; } - /* Explicitly handle numentries == 0 */ - if (!numentries) + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1, hooks); + if (output_pointer == NULL) { - if (p) - { - out = ensure(p, 3); - } - else - { - out = (unsigned char*)cJSON_malloc(3); - } - if (out) - { - strcpy((char*)out, "[]"); - } - - return out; + return false; } - if (p) - { - /* Compose the output array. */ - /* opening square bracket */ - i = p->offset; - ptr = ensure(p, 1); - if (!ptr) - { - return NULL; - } - *ptr = '['; - p->offset++; + *output_pointer = '['; + output_buffer->offset++; - child = item->child; - while (child && !fail) - { - if (!print_value(child, depth + 1, fmt, p)) - { - return NULL; - } - p->offset = update(p); - if (child->next) - { - len = fmt ? 2 : 1; - ptr = ensure(p, len + 1); - if (!ptr) - { - return NULL; - } - *ptr++ = ','; - if(fmt) - { - *ptr++ = ' '; - } - *ptr = '\0'; - p->offset += len; - } - child = child->next; - } - ptr = ensure(p, 2); - if (!ptr) - { - return NULL; - } - *ptr++ = ']'; - *ptr = '\0'; - out = (p->buffer) + i; - } - else + while (current_element != NULL) { - /* Allocate an array to hold the pointers to all printed values */ - entries = (unsigned char**)cJSON_malloc(numentries * sizeof(unsigned char*)); - if (!entries) + if (!print_value(current_element, depth + 1, format, output_buffer, hooks)) { - return NULL; + return false; } - memset(entries, '\0', numentries * sizeof(unsigned char*)); - - /* Retrieve all the results: */ - child = item->child; - while (child && !fail) + update_offset(output_buffer); + if (current_element->next) { - ret = print_value(child, depth + 1, fmt, 0); - entries[i++] = ret; - if (ret) + length = format ? 2 : 1; + output_pointer = ensure(output_buffer, length + 1, hooks); + if (output_pointer == NULL) { - len += strlen((char*)ret) + 2 + (fmt ? 1 : 0); + return false; } - else + *output_pointer++ = ','; + if(format) { - fail = true; - } - child = child->next; - } - - /* If we didn't fail, try to malloc the output string */ - if (!fail) - { - out = (unsigned char*)cJSON_malloc(len); - } - /* If that fails, we fail. */ - if (!out) - { - fail = true; - } - - /* Handle failure. */ - if (fail) - { - /* free all the entries in the array */ - for (i = 0; i < numentries; i++) - { - if (entries[i]) - { - cJSON_free(entries[i]); - } + *output_pointer++ = ' '; } - cJSON_free(entries); - return NULL; + *output_pointer = '\0'; + output_buffer->offset += length; } + current_element = current_element->next; + } - /* Compose the output array. */ - *out='['; - ptr = out + 1; - *ptr = '\0'; - for (i = 0; i < numentries; i++) - { - tmplen = strlen((char*)entries[i]); - memcpy(ptr, entries[i], tmplen); - ptr += tmplen; - if (i != (numentries - 1)) - { - *ptr++ = ','; - if(fmt) - { - *ptr++ = ' '; - } - *ptr = '\0'; - } - cJSON_free(entries[i]); - } - cJSON_free(entries); - *ptr++ = ']'; - *ptr++ = '\0'; + output_pointer = ensure(output_buffer, 2, hooks); + if (output_pointer == NULL) + { + return false; } + *output_pointer++ = ']'; + *output_pointer = '\0'; - return out; + return true; } /* Build an object from the text. */ -static const unsigned char *parse_object(cJSON * const item, const unsigned char *input, const unsigned char ** const error_pointer) +static const unsigned char *parse_object(cJSON * const item, const unsigned char *input, const unsigned char ** const error_pointer, const internal_hooks * const hooks) { cJSON *head = NULL; /* linked list head */ cJSON *current_item = NULL; @@ -1347,7 +1264,7 @@ static const unsigned char *parse_object(cJSON * const item, const unsigned char goto fail; /* not an object */ } - input = skip(input + 1); /* skip whitespace */ + input = skip_whitespace(input + 1); if (*input == '}') { goto success; /* empty object */ @@ -1359,7 +1276,7 @@ static const unsigned char *parse_object(cJSON * const item, const unsigned char do { /* allocate next item */ - cJSON *new_item = cJSON_New_Item(); + cJSON *new_item = cJSON_New_Item(hooks); if (new_item == NULL) { goto fail; /* allocation failure */ @@ -1380,9 +1297,9 @@ static const unsigned char *parse_object(cJSON * const item, const unsigned char } /* parse the name of the child */ - input = skip(input + 1); /* skip whitespaces before name */ - input = parse_string(current_item, input, error_pointer); - input = skip(input); /* skip whitespaces after name */ + input = skip_whitespace(input + 1); + input = parse_string(current_item, input, error_pointer, hooks); + input = skip_whitespace(input); if (input == NULL) { goto fail; /* faile to parse name */ @@ -1399,9 +1316,9 @@ static const unsigned char *parse_object(cJSON * const item, const unsigned char } /* parse the value */ - input = skip(input + 1); /* skip whitespaces before value */ - input = parse_value(current_item, input, error_pointer); - input = skip(input); /* skip whitespaces after the value */ + input = skip_whitespace(input + 1); + input = parse_value(current_item, input, error_pointer, hooks); + input = skip_whitespace(input); if (input == NULL) { goto fail; /* failed to parse value */ @@ -1431,288 +1348,119 @@ static const unsigned char *parse_object(cJSON * const item, const unsigned char } /* Render an object to text. */ -static unsigned char *print_object(const cJSON *item, size_t depth, cjbool fmt, printbuffer *p) -{ - unsigned char **entries = NULL; - unsigned char **names = NULL; - unsigned char *out = NULL; - unsigned char *ptr = NULL; - unsigned char *ret = NULL; - unsigned char *str = NULL; - size_t len = 7; - size_t i = 0; - size_t j = 0; - cJSON *child = item->child; - size_t numentries = 0; - cjbool fail = false; - size_t tmplen = 0; +static cJSON_bool print_object(const cJSON * const item, const size_t depth, const cJSON_bool format, printbuffer * const output_buffer, const internal_hooks * const hooks) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; - /* Count the number of entries. */ - while (child) + if (output_buffer == NULL) { - numentries++; - child = child->next; + return false; } - /* Explicitly handle empty object case */ - if (!numentries) + /* Compose the output: */ + length = format ? 2 : 1; /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1, hooks); + if (output_pointer == NULL) { - if (p) - { - out = ensure(p, fmt ? depth + 4 : 3); - } - else - { - out = (unsigned char*)cJSON_malloc(fmt ? depth + 4 : 3); - } - if (!out) - { - return NULL; - } - ptr = out; - *ptr++ = '{'; - if (fmt) { - *ptr++ = '\n'; - for (i = 0; i < depth; i++) - { - *ptr++ = '\t'; - } - } - *ptr++ = '}'; - *ptr++ = '\0'; - - return out; + return false; } - if (p) + *output_pointer++ = '{'; + if (format) { - /* Compose the output: */ - i = p->offset; - len = fmt ? 2 : 1; /* fmt: {\n */ - ptr = ensure(p, len + 1); - if (!ptr) - { - return NULL; - } - - *ptr++ = '{'; - if (fmt) - { - *ptr++ = '\n'; - } - *ptr = '\0'; - p->offset += len; + *output_pointer++ = '\n'; + } + output_buffer->offset += length; - child = item->child; - depth++; - while (child) + while (current_item) + { + if (format) { - if (fmt) - { - ptr = ensure(p, depth); - if (!ptr) - { - return NULL; - } - for (j = 0; j < depth; j++) - { - *ptr++ = '\t'; - } - p->offset += depth; - } - - /* print key */ - if (!print_string_ptr((unsigned char*)child->string, p)) - { - return NULL; - } - p->offset = update(p); - - len = fmt ? 2 : 1; - ptr = ensure(p, len); - if (!ptr) - { - return NULL; - } - *ptr++ = ':'; - if (fmt) - { - *ptr++ = '\t'; - } - p->offset+=len; - - /* print value */ - if (!print_value(child, depth, fmt, p)) - { - return NULL; - }; - p->offset = update(p); - - /* print comma if not last */ - len = (size_t) (fmt ? 1 : 0) + (child->next ? 1 : 0); - ptr = ensure(p, len + 1); - if (!ptr) + size_t i; + output_pointer = ensure(output_buffer, depth + 1, hooks); + if (output_pointer == NULL) { - return NULL; + return false; } - if (child->next) + for (i = 0; i < depth + 1; i++) { - *ptr++ = ','; + *output_pointer++ = '\t'; } - - if (fmt) - { - *ptr++ = '\n'; - } - *ptr = '\0'; - p->offset += len; - - child = child->next; + output_buffer->offset += depth + 1; } - ptr = ensure(p, fmt ? (depth + 1) : 2); - if (!ptr) + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer, hooks)) { - return NULL; + return false; } - if (fmt) + update_offset(output_buffer); + + length = format ? 2 : 1; + output_pointer = ensure(output_buffer, length, hooks); + if (output_pointer == NULL) { - for (i = 0; i < (depth - 1); i++) - { - *ptr++ = '\t'; - } + return false; } - *ptr++ = '}'; - *ptr = '\0'; - out = (p->buffer) + i; - } - else - { - /* Allocate space for the names and the objects */ - entries = (unsigned char**)cJSON_malloc(numentries * sizeof(unsigned char*)); - if (!entries) + *output_pointer++ = ':'; + if (format) { - return NULL; + *output_pointer++ = '\t'; } - names = (unsigned char**)cJSON_malloc(numentries * sizeof(unsigned char*)); - if (!names) - { - cJSON_free(entries); - return NULL; - } - memset(entries, '\0', sizeof(unsigned char*) * numentries); - memset(names, '\0', sizeof(unsigned char*) * numentries); + output_buffer->offset += length; - /* Collect all the results into our arrays: */ - child = item->child; - depth++; - if (fmt) + /* print value */ + if (!print_value(current_item, depth + 1, format, output_buffer, hooks)) { - len += depth; - } - while (child && !fail) - { - names[i] = str = print_string_ptr((unsigned char*)child->string, 0); /* print key */ - entries[i++] = ret = print_value(child, depth, fmt, 0); - if (str && ret) - { - len += strlen((char*)ret) + strlen((char*)str) + 2 + (fmt ? 2 + depth : 0); - } - else - { - fail = true; - } - child = child->next; + return false; } + update_offset(output_buffer); - /* Try to allocate the output string */ - if (!fail) + /* print comma if not last */ + length = (size_t) (format ? 1 : 0) + (current_item->next ? 1 : 0); + output_pointer = ensure(output_buffer, length + 1, hooks); + if (output_pointer == NULL) { - out = (unsigned char*)cJSON_malloc(len); + return false; } - if (!out) + if (current_item->next) { - fail = true; + *output_pointer++ = ','; } - /* Handle failure */ - if (fail) + if (format) { - /* free all the printed keys and values */ - for (i = 0; i < numentries; i++) - { - if (names[i]) - { - cJSON_free(names[i]); - } - if (entries[i]) - { - cJSON_free(entries[i]); - } - } - cJSON_free(names); - cJSON_free(entries); - return NULL; + *output_pointer++ = '\n'; } + *output_pointer = '\0'; + output_buffer->offset += length; - /* Compose the output: */ - *out = '{'; - ptr = out + 1; - if (fmt) - { - *ptr++ = '\n'; - } - *ptr = '\0'; - for (i = 0; i < numentries; i++) - { - if (fmt) - { - for (j = 0; j < depth; j++) - { - *ptr++='\t'; - } - } - tmplen = strlen((char*)names[i]); - memcpy(ptr, names[i], tmplen); - ptr += tmplen; - *ptr++ = ':'; - if (fmt) - { - *ptr++ = '\t'; - } - strcpy((char*)ptr, (char*)entries[i]); - ptr += strlen((char*)entries[i]); - if (i != (numentries - 1)) - { - *ptr++ = ','; - } - if (fmt) - { - *ptr++ = '\n'; - } - *ptr = '\0'; - cJSON_free(names[i]); - cJSON_free(entries[i]); - } + current_item = current_item->next; + } - cJSON_free(names); - cJSON_free(entries); - if (fmt) + output_pointer = ensure(output_buffer, format ? (depth + 2) : 2, hooks); + if (output_pointer == NULL) + { + return false; + } + if (format) + { + size_t i; + for (i = 0; i < (depth); i++) { - for (i = 0; i < (depth - 1); i++) - { - *ptr++ = '\t'; - } + *output_pointer++ = '\t'; } - *ptr++ = '}'; - *ptr++ = '\0'; } + *output_pointer++ = '}'; + *output_pointer = '\0'; - return out; + return true; } /* Get Array size/item / object item. */ -int cJSON_GetArraySize(const cJSON *array) +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) { cJSON *c = array->child; size_t i = 0; @@ -1727,7 +1475,7 @@ int cJSON_GetArraySize(const cJSON *array) return (int)i; } -cJSON *cJSON_GetArrayItem(const cJSON *array, int item) +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int item) { cJSON *c = array ? array->child : NULL; while (c && item > 0) @@ -1739,7 +1487,7 @@ cJSON *cJSON_GetArrayItem(const cJSON *array, int item) return c; } -cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string) +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string) { cJSON *c = object ? object->child : NULL; while (c && cJSON_strcasecmp((unsigned char*)c->string, (const unsigned char*)string)) @@ -1749,7 +1497,25 @@ cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string) return c; } -cjbool cJSON_HasObjectItem(const cJSON *object, const char *string) +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (string == NULL)) + { + return NULL; + } + + current_element = object->child; + while ((current_element != NULL) && (strcmp(string, current_element->string) != 0)) + { + current_element = current_element->next; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) { return cJSON_GetObjectItem(object, string) ? 1 : 0; } @@ -1762,9 +1528,9 @@ static void suffix_object(cJSON *prev, cJSON *item) } /* Utility for handling references. */ -static cJSON *create_reference(const cJSON *item) +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) { - cJSON *ref = cJSON_New_Item(); + cJSON *ref = cJSON_New_Item(hooks); if (!ref) { return NULL; @@ -1777,7 +1543,7 @@ static cJSON *create_reference(const cJSON *item) } /* Add item to array/object. */ -void cJSON_AddItemToArray(cJSON *array, cJSON *item) +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) { cJSON *child = NULL; @@ -1804,16 +1570,16 @@ void cJSON_AddItemToArray(cJSON *array, cJSON *item) } } -void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) { /* call cJSON_AddItemToObjectCS for code reuse */ - cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string), item); + cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); /* remove cJSON_StringIsConst flag */ item->type &= ~cJSON_StringIsConst; } /* Add an item to an object with constant string as key */ -void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) { if (!item) { @@ -1821,7 +1587,7 @@ void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) } if (!(item->type & cJSON_StringIsConst) && item->string) { - cJSON_free(item->string); + global_hooks.deallocate(item->string); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" @@ -1831,14 +1597,14 @@ void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) cJSON_AddItemToArray(object, item); } -void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) { - cJSON_AddItemToArray(array, create_reference(item)); + cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); } -void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) { - cJSON_AddItemToObject(object, string, create_reference(item)); + cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); } static cJSON *DetachItemFromArray(cJSON *array, size_t which) @@ -1872,7 +1638,7 @@ static cJSON *DetachItemFromArray(cJSON *array, size_t which) return c; } -cJSON *cJSON_DetachItemFromArray(cJSON *array, int which) +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) { if (which < 0) { @@ -1882,12 +1648,12 @@ cJSON *cJSON_DetachItemFromArray(cJSON *array, int which) return DetachItemFromArray(array, (size_t)which); } -void cJSON_DeleteItemFromArray(cJSON *array, int which) +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) { cJSON_Delete(cJSON_DetachItemFromArray(array, which)); } -cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string) +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) { size_t i = 0; cJSON *c = object->child; @@ -1904,13 +1670,13 @@ cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string) return NULL; } -void cJSON_DeleteItemFromObject(cJSON *object, const char *string) +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) { cJSON_Delete(cJSON_DetachItemFromObject(object, string)); } /* Replace array/object items with new ones. */ -void cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) { cJSON *c = array->child; while (c && (which > 0)) @@ -1965,7 +1731,7 @@ static void ReplaceItemInArray(cJSON *array, size_t which, cJSON *newitem) c->next = c->prev = NULL; cJSON_Delete(c); } -void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) { if (which < 0) { @@ -1975,7 +1741,7 @@ void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) ReplaceItemInArray(array, (size_t)which, newitem); } -void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) { size_t i = 0; cJSON *c = object->child; @@ -1989,18 +1755,18 @@ void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem /* free the old string if not const */ if (!(newitem->type & cJSON_StringIsConst) && newitem->string) { - cJSON_free(newitem->string); + global_hooks.deallocate(newitem->string); } - newitem->string = (char*)cJSON_strdup((const unsigned char*)string); + newitem->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); ReplaceItemInArray(object, i, newitem); } } /* Create basic types: */ -cJSON *cJSON_CreateNull(void) +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_NULL; @@ -2009,9 +1775,9 @@ cJSON *cJSON_CreateNull(void) return item; } -cJSON *cJSON_CreateTrue(void) +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_True; @@ -2020,9 +1786,9 @@ cJSON *cJSON_CreateTrue(void) return item; } -cJSON *cJSON_CreateFalse(void) +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_False; @@ -2031,9 +1797,9 @@ cJSON *cJSON_CreateFalse(void) return item; } -cJSON *cJSON_CreateBool(cjbool b) +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = b ? cJSON_True : cJSON_False; @@ -2042,9 +1808,9 @@ cJSON *cJSON_CreateBool(cjbool b) return item; } -cJSON *cJSON_CreateNumber(double num) +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_Number; @@ -2068,13 +1834,13 @@ cJSON *cJSON_CreateNumber(double num) return item; } -cJSON *cJSON_CreateString(const char *string) +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_String; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)string); + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); if(!item->valuestring) { cJSON_Delete(item); @@ -2085,13 +1851,13 @@ cJSON *cJSON_CreateString(const char *string) return item; } -extern cJSON *cJSON_CreateRaw(const char *raw) +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_Raw; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw); + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); if(!item->valuestring) { cJSON_Delete(item); @@ -2102,9 +1868,9 @@ extern cJSON *cJSON_CreateRaw(const char *raw) return item; } -cJSON *cJSON_CreateArray(void) +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type=cJSON_Array; @@ -2113,9 +1879,9 @@ cJSON *cJSON_CreateArray(void) return item; } -cJSON *cJSON_CreateObject(void) +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) { - cJSON *item = cJSON_New_Item(); + cJSON *item = cJSON_New_Item(&global_hooks); if (item) { item->type = cJSON_Object; @@ -2125,7 +1891,7 @@ cJSON *cJSON_CreateObject(void) } /* Create Arrays: */ -cJSON *cJSON_CreateIntArray(const int *numbers, int count) +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) { size_t i = 0; cJSON *n = NULL; @@ -2160,7 +1926,7 @@ cJSON *cJSON_CreateIntArray(const int *numbers, int count) return a; } -cJSON *cJSON_CreateFloatArray(const float *numbers, int count) +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) { size_t i = 0; cJSON *n = NULL; @@ -2176,7 +1942,7 @@ cJSON *cJSON_CreateFloatArray(const float *numbers, int count) for(i = 0; a && (i < (size_t)count); i++) { - n = cJSON_CreateNumber(numbers[i]); + n = cJSON_CreateNumber((double)numbers[i]); if(!n) { cJSON_Delete(a); @@ -2196,7 +1962,7 @@ cJSON *cJSON_CreateFloatArray(const float *numbers, int count) return a; } -cJSON *cJSON_CreateDoubleArray(const double *numbers, int count) +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) { size_t i = 0; cJSON *n = NULL; @@ -2232,7 +1998,7 @@ cJSON *cJSON_CreateDoubleArray(const double *numbers, int count) return a; } -cJSON *cJSON_CreateStringArray(const char **strings, int count) +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) { size_t i = 0; cJSON *n = NULL; @@ -2269,7 +2035,7 @@ cJSON *cJSON_CreateStringArray(const char **strings, int count) } /* Duplication */ -cJSON *cJSON_Duplicate(const cJSON *item, cjbool recurse) +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) { cJSON *newitem = NULL; cJSON *child = NULL; @@ -2282,7 +2048,7 @@ cJSON *cJSON_Duplicate(const cJSON *item, cjbool recurse) goto fail; } /* Create new item */ - newitem = cJSON_New_Item(); + newitem = cJSON_New_Item(&global_hooks); if (!newitem) { goto fail; @@ -2293,7 +2059,7 @@ cJSON *cJSON_Duplicate(const cJSON *item, cjbool recurse) newitem->valuedouble = item->valuedouble; if (item->valuestring) { - newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring); + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); if (!newitem->valuestring) { goto fail; @@ -2301,7 +2067,7 @@ cJSON *cJSON_Duplicate(const cJSON *item, cjbool recurse) } if (item->string) { - newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string); + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); if (!newitem->string) { goto fail; @@ -2348,7 +2114,7 @@ cJSON *cJSON_Duplicate(const cJSON *item, cjbool recurse) return NULL; } -void cJSON_Minify(char *json) +CJSON_PUBLIC(void) cJSON_Minify(char *json) { unsigned char *into = (unsigned char*)json; while (*json) @@ -2411,3 +2177,103 @@ void cJSON_Minify(char *json) /* and null-terminate. */ *into = '\0'; } + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} diff --git a/cJSON.h b/cJSON.h index e251da09..e5918d67 100644 --- a/cJSON.h +++ b/cJSON.h @@ -30,11 +30,8 @@ extern "C" /* project version */ #define CJSON_VERSION_MAJOR 1 -#define CJSON_VERSION_MINOR 3 -#define CJSON_VERSION_PATCH 2 - -/* returns the version of cJSON as a string */ -extern const char* cJSON_Version(void); +#define CJSON_VERSION_MINOR 4 +#define CJSON_VERSION_PATCH 0 #include @@ -81,84 +78,139 @@ typedef struct cJSON_Hooks void (*free_fn)(void *ptr); } cJSON_Hooks; +typedef int cJSON_bool; + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type __stdcall +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall +#else +#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall +#endif +#else /* !WIN32 */ +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + /* Supply malloc, realloc and free functions to cJSON */ -extern void cJSON_InitHooks(cJSON_Hooks* hooks); +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); /* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ -extern cJSON *cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ -extern char *cJSON_Print(const cJSON *item); +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); /* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ -extern char *cJSON_PrintUnformatted(const cJSON *item); +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -extern char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, int fmt); +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); /* Render a cJSON entity to text using a buffer already allocated in memory with length buf_len. Returns 1 on success and 0 on failure. */ -extern int cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const int fmt); +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt); /* Delete a cJSON entity and all subentities. */ -extern void cJSON_Delete(cJSON *c); +CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); /* Returns the number of items in an array (or object). */ -extern int cJSON_GetArraySize(const cJSON *array); +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ -extern cJSON *cJSON_GetArrayItem(const cJSON *array, int item); +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int item); /* Get item "string" from object. Case insensitive. */ -extern cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string); -extern int cJSON_HasObjectItem(const cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -extern const char *cJSON_GetErrorPtr(void); +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); /* These calls create a cJSON item of the appropriate type. */ -extern cJSON *cJSON_CreateNull(void); -extern cJSON *cJSON_CreateTrue(void); -extern cJSON *cJSON_CreateFalse(void); -extern cJSON *cJSON_CreateBool(int b); -extern cJSON *cJSON_CreateNumber(double num); -extern cJSON *cJSON_CreateString(const char *string); +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); /* raw json */ -extern cJSON *cJSON_CreateRaw(const char *raw); -extern cJSON *cJSON_CreateArray(void); -extern cJSON *cJSON_CreateObject(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); /* These utilities create an Array of count items. */ -extern cJSON *cJSON_CreateIntArray(const int *numbers, int count); -extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count); -extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count); -extern cJSON *cJSON_CreateStringArray(const char **strings, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); /* Append item to the specified array/object. */ -extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); -extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before * writing to `item->string` */ -extern void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -extern void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); /* Remove/Detatch items from Arrays/Objects. */ -extern cJSON *cJSON_DetachItemFromArray(cJSON *array, int which); -extern void cJSON_DeleteItemFromArray(cJSON *array, int which); -extern cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string); -extern void cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); /* Update array items. */ -extern void cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ -extern void cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); /* Duplicate a cJSON item */ -extern cJSON *cJSON_Duplicate(const cJSON *item, int recurse); +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will need to be released. With recurse!=0, it will duplicate any children connected to the item. The item->next and ->prev pointers are always zero on return from Duplicate. */ /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ -extern cJSON *cJSON_ParseWithOpts(const char *value, const char **return_parse_end, int require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); -extern void cJSON_Minify(char *json); +CJSON_PUBLIC(void) cJSON_Minify(char *json); /* Macros for creating things quickly. */ #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) @@ -172,11 +224,11 @@ extern void cJSON_Minify(char *json); /* When assigning an integer value, it needs to be propagated to valuedouble too. */ #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) /* helper for the cJSON_SetNumberValue macro */ -extern double cJSON_SetNumberHelper(cJSON *object, double number); -#define cJSON_SetNumberValue(object, number) ((object) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) /* Macro for iterating over an array */ -#define cJSON_ArrayForEach(pos, head) for(pos = (head)->child; pos != NULL; pos = pos->next) +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) #ifdef __cplusplus } diff --git a/cJSON_Utils.c b/cJSON_Utils.c index d43b8e2a..b18def39 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -1,8 +1,10 @@ +#pragma GCC visibility push(default) #include #include #include #include #include +#pragma GCC visibility pop #include "cJSON_Utils.h" @@ -31,7 +33,7 @@ static int cJSONUtils_strcasecmp(const unsigned char *s1, const unsigned char *s { return 1; } - for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) + for(; tolower(*s1) == tolower(*s2); (void)++s1, ++s2) { if(*s1 == 0) { @@ -49,7 +51,7 @@ static int cJSONUtils_Pstrcasecmp(const unsigned char *a, const unsigned char *e { return (a == e) ? 0 : 1; /* both NULL? */ } - for (; *a && *e && (*e != '/'); a++, e++) /* compare until next '/' */ + for (; *a && *e && (*e != '/'); (void)a++, e++) /* compare until next '/' */ { if (*e == '~') { @@ -81,7 +83,7 @@ static int cJSONUtils_Pstrcasecmp(const unsigned char *a, const unsigned char *e static size_t cJSONUtils_PointerEncodedstrlen(const unsigned char *s) { size_t l = 0; - for (; *s; s++, l++) + for (; *s; (void)s++, l++) { if ((*s == '~') || (*s == '/')) { @@ -115,9 +117,8 @@ static void cJSONUtils_PointerEncodedstrcpy(unsigned char *d, const unsigned cha *d = '\0'; } -char *cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target) +CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target) { - int type = object->type; size_t c = 0; cJSON *obj = 0; @@ -128,12 +129,12 @@ char *cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target) } /* recursively search all children of the object */ - for (obj = object->child; obj; obj = obj->next, c++) + for (obj = object->child; obj; (void)(obj = obj->next), c++) { unsigned char *found = (unsigned char*)cJSONUtils_FindPointerFromObjectTo(obj, target); if (found) { - if ((type & 0xFF) == cJSON_Array) + if (cJSON_IsArray(object)) { /* reserve enough memory for a 64 bit integer + '/' and '\0' */ unsigned char *ret = (unsigned char*)malloc(strlen((char*)found) + 23); @@ -150,7 +151,7 @@ char *cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target) return (char*)ret; } - else if ((type & 0xFF) == cJSON_Object) + else if (cJSON_IsObject(object)) { unsigned char *ret = (unsigned char*)malloc(strlen((char*)found) + cJSONUtils_PointerEncodedstrlen((unsigned char*)obj->string) + 2); *ret = '/'; @@ -171,12 +172,12 @@ char *cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target) return NULL; } -cJSON *cJSONUtils_GetPointer(cJSON *object, const char *pointer) +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer) { /* follow path of the pointer */ while ((*pointer++ == '/') && object) { - if ((object->type & 0xFF) == cJSON_Array) + if (cJSON_IsArray(object)) { size_t which = 0; /* parse array index */ @@ -195,7 +196,7 @@ cJSON *cJSONUtils_GetPointer(cJSON *object, const char *pointer) } object = cJSON_GetArrayItem(object, (int)which); } - else if ((object->type & 0xFF) == cJSON_Object) + else if (cJSON_IsObject(object)) { object = object->child; /* GetObjectItem. */ @@ -227,7 +228,7 @@ static void cJSONUtils_InplaceDecodePointerString(unsigned char *string) return; } - for (; *string; s2++, string++) + for (; *string; (void)s2++, string++) { *s2 = (*string != '~') ? (*string) @@ -269,11 +270,11 @@ static cJSON *cJSONUtils_PatchDetach(cJSON *object, const unsigned char *path) /* Couldn't find object to remove child from. */ ret = NULL; } - else if ((parent->type & 0xFF) == cJSON_Array) + else if (cJSON_IsArray(parent)) { ret = cJSON_DetachItemFromArray(parent, atoi((char*)childptr)); } - else if ((parent->type & 0xFF) == cJSON_Object) + else if (cJSON_IsObject(parent)) { ret = cJSON_DetachItemFromObject(parent, (char*)childptr); } @@ -299,7 +300,7 @@ static int cJSONUtils_Compare(cJSON *a, cJSON *b) /* string mismatch. */ return (strcmp(a->valuestring, b->valuestring) != 0) ? -3 : 0; case cJSON_Array: - for (a = a->child, b = b->child; a && b; a = a->next, b = b->next) + for ((void)(a = a->child), b = b->child; a && b; (void)(a = a->next), b = b->next) { int err = cJSONUtils_Compare(a, b); if (err) @@ -474,7 +475,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) cJSON_Delete(value); return 9; } - else if ((parent->type & 0xFF) == cJSON_Array) + else if (cJSON_IsArray(parent)) { if (!strcmp((char*)childptr, "-")) { @@ -485,7 +486,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) cJSON_InsertItemInArray(parent, atoi((char*)childptr), value); } } - else if ((parent->type & 0xFF) == cJSON_Object) + else if (cJSON_IsObject(parent)) { cJSON_DeleteItemFromObject(parent, (char*)childptr); cJSON_AddItemToObject(parent, (char*)childptr, value); @@ -499,7 +500,7 @@ static int cJSONUtils_ApplyPatch(cJSON *object, cJSON *patch) return 0; } -int cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches) +CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches) { int err = 0; @@ -508,7 +509,7 @@ int cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches) return 1; } - if ((patches->type & 0xFF) != cJSON_Array) + if (cJSON_IsArray(patches)) { /* malformed patches. */ return 1; @@ -551,7 +552,7 @@ static void cJSONUtils_GeneratePatch(cJSON *patches, const unsigned char *op, co cJSON_AddItemToArray(patches, patch); } -void cJSONUtils_AddPatchToArray(cJSON *array, const char *op, const char *path, cJSON *val) +CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON *array, const char *op, const char *path, cJSON *val) { cJSONUtils_GeneratePatch(array, (const unsigned char*)op, (const unsigned char*)path, 0, val); } @@ -590,7 +591,7 @@ static void cJSONUtils_CompareToPatch(cJSON *patches, const unsigned char *path, size_t c = 0; unsigned char *newpath = (unsigned char*)malloc(strlen((const char*)path) + 23); /* Allow space for 64bit int. */ /* generate patches for all array elements that exist in "from" and "to" */ - for (c = 0, from = from->child, to = to->child; from && to; from = from->next, to = to->next, c++) + for ((void)(c = 0), (void)(from = from->child), to = to->child; from && to; (void)(from = from->next), (void)(to = to->next), c++) { /* check if conversion to unsigned long is valid * This should be eliminated at compile time by dead code elimination @@ -604,7 +605,7 @@ static void cJSONUtils_CompareToPatch(cJSON *patches, const unsigned char *path, cJSONUtils_CompareToPatch(patches, newpath, from, to); } /* remove leftover elements from 'from' that are not in 'to' */ - for (; from; from = from->next, c++) + for (; from; (void)(from = from->next), c++) { /* check if conversion to unsigned long is valid * This should be eliminated at compile time by dead code elimination @@ -618,7 +619,7 @@ static void cJSONUtils_CompareToPatch(cJSON *patches, const unsigned char *path, cJSONUtils_GeneratePatch(patches, (const unsigned char*)"remove", path, newpath, 0); } /* add new elements in 'to' that were not in 'from' */ - for (; to; to = to->next, c++) + for (; to; (void)(to = to->next), c++) { cJSONUtils_GeneratePatch(patches, (const unsigned char*)"add", path, (const unsigned char*)"-", to); } @@ -671,7 +672,7 @@ static void cJSONUtils_CompareToPatch(cJSON *patches, const unsigned char *path, } } -cJSON* cJSONUtils_GeneratePatches(cJSON *from, cJSON *to) +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON *from, cJSON *to) { cJSON *patches = cJSON_CreateArray(); cJSONUtils_CompareToPatch(patches, (const unsigned char*)"", from, to); @@ -786,21 +787,21 @@ static cJSON *cJSONUtils_SortList(cJSON *list) return list; } -void cJSONUtils_SortObject(cJSON *object) +CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON *object) { object->child = cJSONUtils_SortList(object->child); } -cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch) +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, cJSON *patch) { - if (!patch || ((patch->type & 0xFF) != cJSON_Object)) + if (!cJSON_IsObject(patch)) { /* scalar value, array or NULL, just duplicate */ cJSON_Delete(target); return cJSON_Duplicate(patch, 1); } - if (!target || ((target->type & 0xFF) != cJSON_Object)) + if (!cJSON_IsObject(target)) { cJSON_Delete(target); target = cJSON_CreateObject(); @@ -809,7 +810,7 @@ cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch) patch = patch->child; while (patch) { - if ((patch->type & 0xFF) == cJSON_NULL) + if (cJSON_IsNull(patch)) { /* NULL is the indicator to remove a value, see RFC7396 */ cJSON_DeleteItemFromObject(target, patch->string); @@ -824,7 +825,7 @@ cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch) return target; } -cJSON *cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to) +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to) { cJSON *patch = NULL; if (!to) @@ -832,7 +833,7 @@ cJSON *cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to) /* patch to delete everything */ return cJSON_CreateNull(); } - if (((to->type & 0xFF) != cJSON_Object) || !from || ((from->type & 0xFF) != cJSON_Object)) + if (!cJSON_IsObject(to) || !cJSON_IsObject(from)) { return cJSON_Duplicate(to, 1); } diff --git a/cJSON_Utils.h b/cJSON_Utils.h index cefda08b..6baf9c62 100644 --- a/cJSON_Utils.h +++ b/cJSON_Utils.h @@ -1,14 +1,14 @@ #include "cJSON.h" /* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */ -cJSON *cJSONUtils_GetPointer(cJSON *object, const char *pointer); +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON *object, const char *pointer); /* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */ -cJSON* cJSONUtils_GeneratePatches(cJSON *from, cJSON *to); +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON *from, cJSON *to); /* Utility for generating patch array entries. */ -void cJSONUtils_AddPatchToArray(cJSON *array, const char *op, const char *path, cJSON *val); +CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON *array, const char *op, const char *path, cJSON *val); /* Returns 0 for success. */ -int cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches); +CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches); /* // Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use: @@ -33,12 +33,12 @@ int cJSONUtils_ApplyPatches(cJSON *object, cJSON *patches); /* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */ /* target will be modified by patch. return value is new ptr for target. */ -cJSON* cJSONUtils_MergePatch(cJSON *target, cJSON *patch); +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, cJSON *patch); /* generates a patch to move from -> to */ -cJSON *cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to); +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON *from, cJSON *to); /* Given a root object and a target object, construct a pointer from one to the other. */ -char *cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target); +CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(cJSON *object, cJSON *target); /* Sorts the members of the object into alphabetical order. */ -void cJSONUtils_SortObject(cJSON *object); +CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON *object); diff --git a/fuzzing/.gitignore b/fuzzing/.gitignore new file mode 100644 index 00000000..c82a2620 --- /dev/null +++ b/fuzzing/.gitignore @@ -0,0 +1 @@ +afl-build diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt new file mode 100644 index 00000000..fdd7126e --- /dev/null +++ b/fuzzing/CMakeLists.txt @@ -0,0 +1,28 @@ +option(ENABLE_FUZZING "Create executables and targets for fuzzing cJSON with afl." Off) +if (ENABLE_FUZZING) + find_program(AFL_FUZZ afl-fuzz) + if ("${AFL_FUZZ}" MATCHES "AFL_FUZZ-NOTFOUND") + message(FATAL_ERROR "Couldn't find afl-fuzz.") + endif() + + add_executable(afl-main afl.c) + target_link_libraries(afl-main "${CJSON_LIB}") + + if (NOT ENABLE_SANITIZERS) + message(FATAL_ERROR "Enable sanitizers with -DENABLE_SANITIZERS=On to do fuzzing.") + endif() + + option(ENABLE_FUZZING_PRINT "Fuzz printing functions together with parser." On) + set(fuzz_print_parameter "no") + if (ENABLE_FUZZING_PRINT) + set(fuzz_print_parameter "yes") + endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error") + + add_custom_target(afl + COMMAND "${AFL_FUZZ}" -i "${CMAKE_CURRENT_SOURCE_DIR}/inputs" -o "${CMAKE_CURRENT_BINARY_DIR}/findings" -x "${CMAKE_CURRENT_SOURCE_DIR}/json.dict" -- "${CMAKE_CURRENT_BINARY_DIR}/afl-main" "@@" "${fuzz_print_parameter}" + DEPENDS afl-main) + + +endif() diff --git a/fuzzing/afl-prepare-linux.sh b/fuzzing/afl-prepare-linux.sh new file mode 100755 index 00000000..41c9b899 --- /dev/null +++ b/fuzzing/afl-prepare-linux.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -x +echo core | sudo tee /proc/sys/kernel/core_pattern +echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor diff --git a/fuzzing/afl.c b/fuzzing/afl.c new file mode 100644 index 00000000..ac4f2303 --- /dev/null +++ b/fuzzing/afl.c @@ -0,0 +1,176 @@ +/* + 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 "../cJSON.h" + +static char *read_file(const char *filename) +{ + FILE *file = NULL; + long length = 0; + char *content = NULL; + size_t read_chars = 0; + + /* open in read binary mode */ + file = fopen(filename, "rb"); + if (file == NULL) + { + goto cleanup; + } + + /* get the length */ + if (fseek(file, 0, SEEK_END) != 0) + { + goto cleanup; + } + length = ftell(file); + if (length < 0) + { + goto cleanup; + } + if (fseek(file, 0, SEEK_SET) != 0) + { + goto cleanup; + } + + /* allocate content buffer */ + content = (char*)malloc((size_t)length + sizeof('\0')); + if (content == NULL) + { + goto cleanup; + } + + /* read the file into memory */ + read_chars = fread(content, sizeof(char), (size_t)length, file); + if ((long)read_chars != length) + { + free(content); + content = NULL; + goto cleanup; + } + content[read_chars] = '\0'; + + +cleanup: + if (file != NULL) + { + fclose(file); + } + + return content; +} + +int main(int argc, char** argv) +{ + const char *filename = NULL; + cJSON *item = NULL; + char *json = NULL; + int status = EXIT_FAILURE; + char *printed_json = NULL; + + if ((argc < 2) || (argc > 3)) + { + printf("Usage:\n"); + printf("%s input_file [enable_printing]\n", argv[0]); + printf("\t input_file: file containing the test data\n"); + printf("\t enable_printing: print after parsing, 'yes' or 'no', defaults to 'no'\n"); + goto cleanup; + } + + filename = argv[1]; + +#if __AFL_HAVE_MANUAL_CONTROL + while (__AFL_LOOP(1000)) + { +#endif + status = EXIT_SUCCESS; + + json = read_file(filename); + if ((json == NULL) || (json[0] == '\0') || (json[1] == '\0')) + { + status = EXIT_FAILURE; + goto cleanup; + } + item = cJSON_Parse(json + 2); + if (item == NULL) + { + goto cleanup; + } + + if ((argc == 3) && (strncmp(argv[2], "yes", 3) == 0)) + { + int do_format = 0; + if (json[1] == 'f') + { + do_format = 1; + } + + if (json[0] == 'b') + { + /* buffered printing */ + printed_json = cJSON_PrintBuffered(item, 1, do_format); + } + else + { + /* unbuffered printing */ + if (do_format) + { + printed_json = cJSON_Print(item); + } + else + { + printed_json = cJSON_PrintUnformatted(item); + } + } + if (printed_json == NULL) + { + status = EXIT_FAILURE; + goto cleanup; + } + printf("%s\n", printed_json); + } + +cleanup: + if (item != NULL) + { + cJSON_Delete(item); + item = NULL; + } + if (json != NULL) + { + free(json); + json = NULL; + } + if (printed_json != NULL) + { + free(printed_json); + printed_json = NULL; + } +#if __AFL_HAVE_MANUAL_CONTROL + } +#endif + + return status; +} diff --git a/fuzzing/afl.sh b/fuzzing/afl.sh new file mode 100755 index 00000000..8f0f02fb --- /dev/null +++ b/fuzzing/afl.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +mkdir -p afl-build || exit 1 +cd afl-build || exit 1 +#cleanup +rm -r -- * + +CC=afl-clang-fast cmake ../.. -DENABLE_FUZZING=On -DENABLE_SANITIZERS=On -DBUILD_SHARED_LIBS=Off +make afl diff --git a/fuzzing/inputs/test1 b/fuzzing/inputs/test1 new file mode 100644 index 00000000..6a0c0d7c --- /dev/null +++ b/fuzzing/inputs/test1 @@ -0,0 +1,22 @@ +bf{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} diff --git a/fuzzing/inputs/test10 b/fuzzing/inputs/test10 new file mode 100644 index 00000000..01e9a82f --- /dev/null +++ b/fuzzing/inputs/test10 @@ -0,0 +1 @@ +bf["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] diff --git a/fuzzing/inputs/test11 b/fuzzing/inputs/test11 new file mode 100644 index 00000000..818c6e0f --- /dev/null +++ b/fuzzing/inputs/test11 @@ -0,0 +1,8 @@ +bf{ +"name": "Jack (\"Bee\") Nimble", +"format": {"type": "rect", +"width": 1920, +"height": 1080, +"interlace": false,"frame rate": 24 +} +} diff --git a/fuzzing/inputs/test2 b/fuzzing/inputs/test2 new file mode 100644 index 00000000..3fdf8cb7 --- /dev/null +++ b/fuzzing/inputs/test2 @@ -0,0 +1,11 @@ +bf{"menu": { + "id": "file", + "value": "File", + "popup": { + "menuitem": [ + {"value": "New", "onclick": "CreateNewDoc()"}, + {"value": "Open", "onclick": "OpenDoc()"}, + {"value": "Close", "onclick": "CloseDoc()"} + ] + } +}} diff --git a/fuzzing/inputs/test3 b/fuzzing/inputs/test3 new file mode 100644 index 00000000..7143163b --- /dev/null +++ b/fuzzing/inputs/test3 @@ -0,0 +1,26 @@ +bf{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}} diff --git a/fuzzing/inputs/test3.bu b/fuzzing/inputs/test3.bu new file mode 100644 index 00000000..6fc93d3c --- /dev/null +++ b/fuzzing/inputs/test3.bu @@ -0,0 +1,26 @@ +bu{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}} diff --git a/fuzzing/inputs/test3.uf b/fuzzing/inputs/test3.uf new file mode 100644 index 00000000..d48df612 --- /dev/null +++ b/fuzzing/inputs/test3.uf @@ -0,0 +1,26 @@ +uf{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}} diff --git a/fuzzing/inputs/test3.uu b/fuzzing/inputs/test3.uu new file mode 100644 index 00000000..ad6ae541 --- /dev/null +++ b/fuzzing/inputs/test3.uu @@ -0,0 +1,26 @@ +uu{"widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } +}} diff --git a/fuzzing/inputs/test4 b/fuzzing/inputs/test4 new file mode 100644 index 00000000..e24ae9b3 --- /dev/null +++ b/fuzzing/inputs/test4 @@ -0,0 +1,88 @@ +bf{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} diff --git a/fuzzing/inputs/test5 b/fuzzing/inputs/test5 new file mode 100644 index 00000000..f6cc84e1 --- /dev/null +++ b/fuzzing/inputs/test5 @@ -0,0 +1,27 @@ +bf{"menu": { + "header": "SVG Viewer", + "items": [ + {"id": "Open"}, + {"id": "OpenNew", "label": "Open New"}, + null, + {"id": "ZoomIn", "label": "Zoom In"}, + {"id": "ZoomOut", "label": "Zoom Out"}, + {"id": "OriginalView", "label": "Original View"}, + null, + {"id": "Quality"}, + {"id": "Pause"}, + {"id": "Mute"}, + null, + {"id": "Find", "label": "Find..."}, + {"id": "FindAgain", "label": "Find Again"}, + {"id": "Copy"}, + {"id": "CopyAgain", "label": "Copy Again"}, + {"id": "CopySVG", "label": "Copy SVG"}, + {"id": "ViewSVG", "label": "View SVG"}, + {"id": "ViewSource", "label": "View Source"}, + {"id": "SaveAs", "label": "Save As"}, + null, + {"id": "Help"}, + {"id": "About", "label": "About Adobe CVG Viewer..."} + ] +}} diff --git a/fuzzing/inputs/test6 b/fuzzing/inputs/test6 new file mode 100644 index 00000000..af279752 --- /dev/null +++ b/fuzzing/inputs/test6 @@ -0,0 +1,16 @@ +bf + + + + + Application Error + + + + + diff --git a/fuzzing/inputs/test7 b/fuzzing/inputs/test7 new file mode 100644 index 00000000..4a3c0b7a --- /dev/null +++ b/fuzzing/inputs/test7 @@ -0,0 +1,22 @@ +bf[ + { + "precision": "zip", + "Latitude": 37.7668, + "Longitude": -122.3959, + "Address": "", + "City": "SAN FRANCISCO", + "State": "CA", + "Zip": "94107", + "Country": "US" + }, + { + "precision": "zip", + "Latitude": 37.371991, + "Longitude": -122.026020, + "Address": "", + "City": "SUNNYVALE", + "State": "CA", + "Zip": "94085", + "Country": "US" + } + ] diff --git a/fuzzing/inputs/test8 b/fuzzing/inputs/test8 new file mode 100644 index 00000000..3ffe570c --- /dev/null +++ b/fuzzing/inputs/test8 @@ -0,0 +1,13 @@ +bf{ + "Image": { + "Width": 800, + "Height": 600, + "Title": "View from 15th Floor", + "Thumbnail": { + "Url": "http:/*www.example.com/image/481989943", + "Height": 125, + "Width": "100" + }, + "IDs": [116, 943, 234, 38793] + } + } diff --git a/fuzzing/inputs/test9 b/fuzzing/inputs/test9 new file mode 100644 index 00000000..28c9033a --- /dev/null +++ b/fuzzing/inputs/test9 @@ -0,0 +1,5 @@ +bf[ + [0, -1, 0], + [1, 0, 0], + [0, 0, 1] + ] diff --git a/fuzzing/json.dict b/fuzzing/json.dict new file mode 100644 index 00000000..ac9c08c8 --- /dev/null +++ b/fuzzing/json.dict @@ -0,0 +1,47 @@ +# +# AFL dictionary for JSON +# ----------------------------- +# + +object_start="{" +object_end="}" +object_empty="{}" +object_one_element="{\"one\":1}" +object_two_elements="{\"1\":1,\"2\":2}" +object_separator=":" + +array_start="[" +array_end="]" +array_empty="[]" +array_one_element="[1]" +array_two_elements="[1,2]" + +separator="," + +escape_sequence_b="\\b" +escape_sequence_f="\\f" +escape_sequence_n="\\n" +escape_sequence_r="\\r" +escape_sequence_t="\\t" +escape_sequence_quote="\\\"" +escape_sequence_backslash="\\\\" +escapce_sequence_slash="\\/" +escpae_sequence_utf16_base="\\u" +escape_sequence_utf16="\\u12ab" + +number_integer="1" +number_double="1.0" +number_negative_integer="-1" +number_negative_double="-1.0" +number_engineering1="1e1" +number_engineering2="1e-1" +number_positive_integer="+1" +number_positive_double="+1.0" +number_e="e" +number_plus="+" +number_minus="-" +number_separator="." + +null="null" +true="true" +false="false" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cd22fbc5..36c71ecc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,17 @@ if(ENABLE_CJSON_TEST) add_library(unity unity/src/unity.c) + # Disable -Werror for Unity + list(FIND custom_compiler_flags "-Werror" werror_found) + if (werror_found) + target_compile_options(unity PRIVATE "-Wno-error") + endif() + # Disable -fvisibility=hidden for Unity + list(FIND custom_compiler_flags "-fvisibility=hidden" visibility_found) + if (visibility_found) + target_compile_options(unity PRIVATE "-fvisibility=default") + endif() + #copy test files file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/inputs") file(GLOB test_files "inputs/*") @@ -14,6 +25,12 @@ if(ENABLE_CJSON_TEST) parse_array parse_object parse_value + print_string + print_number + print_array + print_object + print_value + misc_tests ) add_library(test-common common.c) @@ -29,6 +46,11 @@ if(ENABLE_CJSON_TEST) endif() endif() + #"check" target that automatically builds everything and runs the tests + add_custom_target(check + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS ${unity_tests}) + foreach(unity_test ${unity_tests}) add_executable("${unity_test}" "${unity_test}.c") target_link_libraries("${unity_test}" "${CJSON_LIB}" unity test-common) diff --git a/tests/common.c b/tests/common.c index 65ef97c0..3870896a 100644 --- a/tests/common.c +++ b/tests/common.c @@ -22,7 +22,7 @@ #include "common.h" -extern void reset(cJSON *item) +CJSON_PUBLIC(void) reset(cJSON *item) { if ((item != NULL) && (item->child != NULL)) { @@ -30,17 +30,17 @@ extern void reset(cJSON *item) } if ((item->valuestring != NULL) && !(item->type & cJSON_IsReference)) { - cJSON_free(item->valuestring); + global_hooks.deallocate(item->valuestring); } if ((item->string != NULL) && !(item->type & cJSON_StringIsConst)) { - cJSON_free(item->string); + global_hooks.deallocate(item->string); } memset(item, 0, sizeof(cJSON)); } -extern char *read_file(const char *filename) +CJSON_PUBLIC(char*) read_file(const char *filename) { FILE *file = NULL; long length = 0; diff --git a/tests/common.h b/tests/common.h index d5c8def6..97c09bf2 100644 --- a/tests/common.h +++ b/tests/common.h @@ -25,9 +25,8 @@ #include "../cJSON.c" -extern void reset(cJSON *item); -extern char *read_file(const char *filename); -extern cjbool assert_is_invalid(cJSON *item); +CJSON_PUBLIC(void) reset(cJSON *item); +CJSON_PUBLIC(char*) read_file(const char *filename); /* assertion helper macros */ #define assert_has_type(item, item_type) TEST_ASSERT_BITS_MESSAGE(0xFF, item_type, item->type, "Item doesn't have expected type.") diff --git a/tests/inputs/test7.expected b/tests/inputs/test7.expected index 6e2e0757..15adf793 100644 --- a/tests/inputs/test7.expected +++ b/tests/inputs/test7.expected @@ -1,7 +1,7 @@ [{ "precision": "zip", - "Latitude": 37.766800, - "Longitude": -122.395900, + "Latitude": 37.7668, + "Longitude": -122.3959, "Address": "", "City": "SAN FRANCISCO", "State": "CA", @@ -10,7 +10,7 @@ }, { "precision": "zip", "Latitude": 37.371991, - "Longitude": -122.026020, + "Longitude": -122.02602, "Address": "", "City": "SUNNYVALE", "State": "CA", diff --git a/tests/misc_tests.c b/tests/misc_tests.c new file mode 100644 index 00000000..47dff72e --- /dev/null +++ b/tests/misc_tests.c @@ -0,0 +1,197 @@ +/* + 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 cjson_array_foreach_should_loop_over_arrays(void) +{ + cJSON array[1]; + cJSON elements[10]; + cJSON *element_pointer = NULL; + size_t i = 0; + + memset(array, 0, sizeof(array)); + memset(elements, 0, sizeof(elements)); + + /* create array */ + array[0].child = &elements[0]; + elements[0].prev = NULL; + elements[9].next = NULL; + for (i = 0; i < 9; i++) + { + elements[i].next = &elements[i + 1]; + elements[i + 1].prev = &elements[i]; + } + + i = 0; + cJSON_ArrayForEach(element_pointer, array) + { + TEST_ASSERT_TRUE_MESSAGE(element_pointer == &elements[i], "Not iterating over array properly"); + i++; + } +} + +static void cjson_array_foreach_should_not_dereference_null_pointer(void) +{ + cJSON *array = NULL; + cJSON *element = NULL; + cJSON_ArrayForEach(element, array); +} + +static void cjson_get_object_item_should_get_object_items(void) +{ + cJSON *item = NULL; + cJSON *found = NULL; + + item = cJSON_Parse("{\"one\":1, \"Two\":2, \"tHree\":3}"); + + found = cJSON_GetObjectItem(NULL, "test"); + TEST_ASSERT_NULL_MESSAGE(found, "Failed to fail on NULL pointer."); + + found = cJSON_GetObjectItem(item, NULL); + TEST_ASSERT_NULL_MESSAGE(found, "Failed to fail on NULL string."); + + + found = cJSON_GetObjectItem(item, "one"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find first item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 1); + + found = cJSON_GetObjectItem(item, "tWo"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find first item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 2); + + found = cJSON_GetObjectItem(item, "three"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 3); + + found = cJSON_GetObjectItem(item, "four"); + TEST_ASSERT_NULL_MESSAGE(found, "Should not find something that isn't there."); + + cJSON_Delete(item); +} + +static void cjson_get_object_item_case_sensitive_should_get_object_items(void) +{ + cJSON *item = NULL; + cJSON *found = NULL; + + item = cJSON_Parse("{\"one\":1, \"Two\":2, \"tHree\":3}"); + + found = cJSON_GetObjectItemCaseSensitive(NULL, "test"); + TEST_ASSERT_NULL_MESSAGE(found, "Failed to fail on NULL pointer."); + + found = cJSON_GetObjectItemCaseSensitive(item, NULL); + TEST_ASSERT_NULL_MESSAGE(found, "Failed to fail on NULL string."); + + found = cJSON_GetObjectItemCaseSensitive(item, "one"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find first item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 1); + + found = cJSON_GetObjectItemCaseSensitive(item, "Two"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find first item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 2); + + found = cJSON_GetObjectItemCaseSensitive(item, "tHree"); + TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find item."); + TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 3); + + found = cJSON_GetObjectItemCaseSensitive(item, "One"); + TEST_ASSERT_NULL_MESSAGE(found, "Should not find something that isn't there."); + + cJSON_Delete(item); +} + +static void typecheck_functions_should_check_type(void) +{ + cJSON invalid[1]; + cJSON item[1]; + invalid->type = cJSON_Invalid; + invalid->type |= cJSON_StringIsConst; + item->type = cJSON_False; + item->type |= cJSON_StringIsConst; + + TEST_ASSERT_FALSE(cJSON_IsInvalid(NULL)); + TEST_ASSERT_FALSE(cJSON_IsInvalid(item)); + TEST_ASSERT_TRUE(cJSON_IsInvalid(invalid)); + + item->type = cJSON_False | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsFalse(NULL)); + TEST_ASSERT_FALSE(cJSON_IsFalse(invalid)); + TEST_ASSERT_TRUE(cJSON_IsFalse(item)); + TEST_ASSERT_TRUE(cJSON_IsBool(item)); + + item->type = cJSON_True | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsTrue(NULL)); + TEST_ASSERT_FALSE(cJSON_IsTrue(invalid)); + TEST_ASSERT_TRUE(cJSON_IsTrue(item)); + TEST_ASSERT_TRUE(cJSON_IsBool(item)); + + item->type = cJSON_NULL | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsNull(NULL)); + TEST_ASSERT_FALSE(cJSON_IsNull(invalid)); + TEST_ASSERT_TRUE(cJSON_IsNull(item)); + + item->type = cJSON_Number | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsNumber(NULL)); + TEST_ASSERT_FALSE(cJSON_IsNumber(invalid)); + TEST_ASSERT_TRUE(cJSON_IsNumber(item)); + + item->type = cJSON_String | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsString(NULL)); + TEST_ASSERT_FALSE(cJSON_IsString(invalid)); + TEST_ASSERT_TRUE(cJSON_IsString(item)); + + item->type = cJSON_Array | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsArray(NULL)); + TEST_ASSERT_FALSE(cJSON_IsArray(invalid)); + TEST_ASSERT_TRUE(cJSON_IsArray(item)); + + item->type = cJSON_Object | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsObject(NULL)); + TEST_ASSERT_FALSE(cJSON_IsObject(invalid)); + TEST_ASSERT_TRUE(cJSON_IsObject(item)); + + item->type = cJSON_Raw | cJSON_StringIsConst; + TEST_ASSERT_FALSE(cJSON_IsRaw(NULL)); + TEST_ASSERT_FALSE(cJSON_IsRaw(invalid)); + TEST_ASSERT_TRUE(cJSON_IsRaw(item)); +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(cjson_array_foreach_should_loop_over_arrays); + RUN_TEST(cjson_array_foreach_should_not_dereference_null_pointer); + RUN_TEST(cjson_get_object_item_should_get_object_items); + RUN_TEST(cjson_get_object_item_case_sensitive_should_get_object_items); + RUN_TEST(typecheck_functions_should_check_type); + + return UNITY_END(); +} diff --git a/tests/parse_array.c b/tests/parse_array.c index 5364739b..47dd44f8 100644 --- a/tests/parse_array.c +++ b/tests/parse_array.c @@ -46,13 +46,13 @@ static void assert_is_array(cJSON *array_item) static void assert_not_array(const char *json) { - TEST_ASSERT_NULL(parse_array(item, (const unsigned char*)json, &error_pointer)); + TEST_ASSERT_NULL(parse_array(item, (const unsigned char*)json, &error_pointer, &global_hooks)); assert_is_invalid(item); } static void assert_parse_array(const char *json) { - TEST_ASSERT_NOT_NULL(parse_array(item, (const unsigned char*)json, &error_pointer)); + TEST_ASSERT_NOT_NULL(parse_array(item, (const unsigned char*)json, &error_pointer, &global_hooks)); assert_is_array(item); } @@ -124,7 +124,7 @@ static void parse_array_should_parse_arrays_with_multiple_elements(void) i = 0; (i < (sizeof(expected_types)/sizeof(int))) && (node != NULL); - i++, node = node->next) + (void)i++, node = node->next) { TEST_ASSERT_BITS(0xFF, expected_types[i], node->type); } diff --git a/tests/parse_object.c b/tests/parse_object.c index e8ede1d1..0f8569cd 100644 --- a/tests/parse_object.c +++ b/tests/parse_object.c @@ -54,14 +54,14 @@ static void assert_is_child(cJSON *child_item, const char *name, int type) static void assert_not_object(const char *json) { - TEST_ASSERT_NULL(parse_object(item, (const unsigned char*)json, &error_pointer)); + TEST_ASSERT_NULL(parse_object(item, (const unsigned char*)json, &error_pointer, &global_hooks)); assert_is_invalid(item); reset(item); } static void assert_parse_object(const char *json) { - TEST_ASSERT_NOT_NULL(parse_object(item, (const unsigned char*)json, &error_pointer)); + TEST_ASSERT_NOT_NULL(parse_object(item, (const unsigned char*)json, &error_pointer, &global_hooks)); assert_is_object(item); } @@ -76,7 +76,7 @@ static void parse_object_should_parse_empty_objects(void) reset(item); } -static void parse_array_should_parse_arrays_with_one_element(void) +static void parse_object_should_parse_objects_with_one_element(void) { assert_parse_object("{\"one\":1}"); @@ -134,7 +134,7 @@ static void parse_object_should_parse_objects_with_multiple_elements(void) i = 0; (i < (sizeof(expected_types)/sizeof(int))) && (node != NULL); - i++, node = node->next) + (void)i++, node = node->next) { assert_is_child(node, expected_names[i], expected_types[i]); } @@ -163,6 +163,6 @@ int main(void) RUN_TEST(parse_object_should_parse_empty_objects); RUN_TEST(parse_object_should_not_parse_non_objects); RUN_TEST(parse_object_should_parse_objects_with_multiple_elements); - RUN_TEST(parse_array_should_parse_arrays_with_one_element); + RUN_TEST(parse_object_should_parse_objects_with_one_element); return UNITY_END(); } diff --git a/tests/parse_string.c b/tests/parse_string.c index 67d78c42..f1352a00 100644 --- a/tests/parse_string.c +++ b/tests/parse_string.c @@ -47,15 +47,15 @@ static void assert_is_string(cJSON *string_item) static void assert_parse_string(const char *string, const char *expected) { - TEST_ASSERT_NOT_NULL_MESSAGE(parse_string(item, (const unsigned char*)string, &error_pointer), "Couldn't parse string."); + TEST_ASSERT_NOT_NULL_MESSAGE(parse_string(item, (const unsigned char*)string, &error_pointer, &global_hooks), "Couldn't parse string."); assert_is_string(item); TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, item->valuestring, "The parsed result isn't as expected."); - cJSON_free(item->valuestring); + global_hooks.deallocate(item->valuestring); item->valuestring = NULL; } #define assert_not_parse_string(string) \ - TEST_ASSERT_NULL_MESSAGE(parse_string(item, (const unsigned char*)string, &error_pointer), "Malformed string should not be accepted");\ + TEST_ASSERT_NULL_MESSAGE(parse_string(item, (const unsigned char*)string, &error_pointer, &global_hooks), "Malformed string should not be accepted");\ assert_is_invalid(item) diff --git a/tests/parse_value.c b/tests/parse_value.c index bbd52c59..9db967f2 100644 --- a/tests/parse_value.c +++ b/tests/parse_value.c @@ -44,7 +44,7 @@ static void assert_is_value(cJSON *value_item, int type) static void assert_parse_value(const char *string, int type) { - TEST_ASSERT_NOT_NULL(parse_value(item, (const unsigned char*)string, &error_pointer)); + TEST_ASSERT_NOT_NULL(parse_value(item, (const unsigned char*)string, &error_pointer, &global_hooks)); assert_is_value(item, type); } diff --git a/tests/print_array.c b/tests/print_array.c new file mode 100644 index 00000000..df32be67 --- /dev/null +++ b/tests/print_array.c @@ -0,0 +1,92 @@ +/* + 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 "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static void assert_print_array(const char * const expected, const char * const input) +{ + unsigned char printed_unformatted[1024]; + unsigned char printed_formatted[1024]; + + const unsigned char *error_pointer; + cJSON item[1]; + + printbuffer formatted_buffer; + printbuffer unformatted_buffer; + + /* buffer for formatted printing */ + formatted_buffer.buffer = printed_formatted; + formatted_buffer.length = sizeof(printed_formatted); + formatted_buffer.offset = 0; + formatted_buffer.noalloc = true; + + /* buffer for unformatted printing */ + unformatted_buffer.buffer = printed_unformatted; + unformatted_buffer.length = sizeof(printed_unformatted); + unformatted_buffer.offset = 0; + unformatted_buffer.noalloc = true; + + memset(item, 0, sizeof(item)); + TEST_ASSERT_NOT_NULL_MESSAGE(parse_array(item, (const unsigned char*)input, &error_pointer, &global_hooks), "Failed to parse array."); + + TEST_ASSERT_TRUE_MESSAGE(print_array(item, 0, false, &unformatted_buffer, &global_hooks), "Failed to print unformatted string."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(input, printed_unformatted, "Unformatted array is not correct."); + + TEST_ASSERT_TRUE_MESSAGE(print_array(item, 0, true, &formatted_buffer, &global_hooks), "Failed to print formatted string."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, printed_formatted, "Formatted array is not correct."); + + reset(item); +} + +static void print_array_should_print_empty_arrays(void) +{ + assert_print_array("[]", "[]"); +} + +static void print_array_should_print_arrays_with_one_element(void) +{ + + assert_print_array("[1]", "[1]"); + assert_print_array("[\"hello!\"]", "[\"hello!\"]"); + assert_print_array("[[]]", "[[]]"); + assert_print_array("[null]", "[null]"); +} + +static void print_array_should_print_arrays_with_multiple_elements(void) +{ + assert_print_array("[1, 2, 3]", "[1,2,3]"); + assert_print_array("[1, null, true, false, [], \"hello\", {\n\t}]", "[1,null,true,false,[],\"hello\",{}]"); +} + +int main(void) +{ + /* initialize cJSON item */ + UNITY_BEGIN(); + + RUN_TEST(print_array_should_print_empty_arrays); + RUN_TEST(print_array_should_print_arrays_with_one_element); + RUN_TEST(print_array_should_print_arrays_with_multiple_elements); + + return UNITY_END(); +} diff --git a/tests/print_number.c b/tests/print_number.c new file mode 100644 index 00000000..b478049c --- /dev/null +++ b/tests/print_number.c @@ -0,0 +1,119 @@ +/* + 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 "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static void assert_print_number(const char *expected, double input) +{ + unsigned char printed[1024]; + cJSON item[1]; + printbuffer buffer; + buffer.buffer = printed; + buffer.length = sizeof(printed); + buffer.offset = 0; + buffer.noalloc = true; + + memset(item, 0, sizeof(item)); + cJSON_SetNumberValue(item, input); + + TEST_ASSERT_TRUE_MESSAGE(print_number(item, &buffer, &global_hooks), "Failed to print number."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buffer.buffer, "Printed number is not as expected."); +} + +static void print_number_should_print_zero(void) +{ + assert_print_number("0", 0); +} + +static void print_number_should_print_negative_integers(void) +{ + assert_print_number("-1", -1); + assert_print_number("-32768", -32768); + assert_print_number("-2147483648", -2147483648); +} + +static void print_number_should_print_positive_integers(void) +{ + assert_print_number("1", 1); + assert_print_number("32767", 32767); + assert_print_number("2147483647", 2147483647); +} + +static void print_number_should_print_positive_reals(void) +{ + assert_print_number("0.123", 0.123); + assert_print_number("1.000000e-09", 10e-10); + assert_print_number("1000000000000", 10e11); + assert_print_number("1.230000e+129", 123e+127); + assert_print_number("0", 123e-128); /* TODO: Maybe this shouldn't be 0 */ +} + +static void print_number_should_print_negative_reals(void) +{ + assert_print_number("-0.0123", -0.0123); + assert_print_number("-1.000000e-09", -10e-10); + assert_print_number("-1000000000000000000000", -10e20); + assert_print_number("-1.230000e+129", -123e+127); + assert_print_number("-1.230000e-126", -123e-128); +} + +static void print_number_should_print_non_number(void) +{ + TEST_IGNORE(); + /* FIXME: Cannot test this easily in C89! */ + /* assert_print_number("null", NaN); */ + /* assert_print_number("null", INFTY); */ + /* assert_print_number("null", -INFTY); */ +} + +static void trim_trailing_zeroes_should_trim_trailing_zeroes(void) +{ + printbuffer buffer; + unsigned char number[100]; + buffer.length = sizeof(number); + buffer.buffer = number; + + strcpy((char*)number, "10.00"); + buffer.offset = sizeof("10.00") - 1; + TEST_ASSERT_TRUE(trim_trailing_zeroes(&buffer)); + TEST_ASSERT_EQUAL_UINT8('\0', buffer.buffer[buffer.offset]); + TEST_ASSERT_EQUAL_STRING("10", number); + TEST_ASSERT_EQUAL_UINT(sizeof("10") - 1, buffer.offset); +} + +int main(void) +{ + /* initialize cJSON item */ + UNITY_BEGIN(); + + RUN_TEST(print_number_should_print_zero); + RUN_TEST(print_number_should_print_negative_integers); + RUN_TEST(print_number_should_print_positive_integers); + RUN_TEST(print_number_should_print_positive_reals); + RUN_TEST(print_number_should_print_negative_reals); + RUN_TEST(print_number_should_print_non_number); + RUN_TEST(trim_trailing_zeroes_should_trim_trailing_zeroes); + + return UNITY_END(); +} diff --git a/tests/print_object.c b/tests/print_object.c new file mode 100644 index 00000000..f7f53444 --- /dev/null +++ b/tests/print_object.c @@ -0,0 +1,92 @@ +/* + 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 "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static void assert_print_object(const char * const expected, const char * const input) +{ + unsigned char printed_unformatted[1024]; + unsigned char printed_formatted[1024]; + + const unsigned char *error_pointer; + cJSON item[1]; + + printbuffer formatted_buffer; + printbuffer unformatted_buffer; + + /* buffer for formatted printing */ + formatted_buffer.buffer = printed_formatted; + formatted_buffer.length = sizeof(printed_formatted); + formatted_buffer.offset = 0; + formatted_buffer.noalloc = true; + + /* buffer for unformatted printing */ + unformatted_buffer.buffer = printed_unformatted; + unformatted_buffer.length = sizeof(printed_unformatted); + unformatted_buffer.offset = 0; + unformatted_buffer.noalloc = true; + + memset(item, 0, sizeof(item)); + TEST_ASSERT_NOT_NULL_MESSAGE(parse_object(item, (const unsigned char*)input, &error_pointer, &global_hooks), "Failed to parse object."); + + TEST_ASSERT_TRUE_MESSAGE(print_object(item, 0, false, &unformatted_buffer, &global_hooks), "Failed to print unformatted string."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(input, printed_unformatted, "Unformatted object is not correct."); + + TEST_ASSERT_TRUE_MESSAGE(print_object(item, 0, true, &formatted_buffer, &global_hooks), "Failed to print formatted string."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, printed_formatted, "Formatted ojbect is not correct."); + + reset(item); +} + +static void print_object_should_print_empty_objects(void) +{ + assert_print_object("{\n}", "{}"); +} + +static void print_object_should_print_objects_with_one_element(void) +{ + + assert_print_object("{\n\t\"one\":\t1\n}", "{\"one\":1}"); + assert_print_object("{\n\t\"hello\":\t\"world!\"\n}", "{\"hello\":\"world!\"}"); + assert_print_object("{\n\t\"array\":\t[]\n}", "{\"array\":[]}"); + assert_print_object("{\n\t\"null\":\tnull\n}", "{\"null\":null}"); +} + +static void print_object_should_print_objects_with_multiple_elements(void) +{ + assert_print_object("{\n\t\"one\":\t1,\n\t\"two\":\t2,\n\t\"three\":\t3\n}", "{\"one\":1,\"two\":2,\"three\":3}"); + assert_print_object("{\n\t\"one\":\t1,\n\t\"NULL\":\tnull,\n\t\"TRUE\":\ttrue,\n\t\"FALSE\":\tfalse,\n\t\"array\":\t[],\n\t\"world\":\t\"hello\",\n\t\"object\":\t{\n\t}\n}", "{\"one\":1,\"NULL\":null,\"TRUE\":true,\"FALSE\":false,\"array\":[],\"world\":\"hello\",\"object\":{}}"); +} + +int main(void) +{ + /* initialize cJSON item */ + UNITY_BEGIN(); + + RUN_TEST(print_object_should_print_empty_objects); + RUN_TEST(print_object_should_print_objects_with_one_element); + RUN_TEST(print_object_should_print_objects_with_multiple_elements); + + return UNITY_END(); +} diff --git a/tests/print_string.c b/tests/print_string.c new file mode 100644 index 00000000..2e38eaa6 --- /dev/null +++ b/tests/print_string.c @@ -0,0 +1,77 @@ +/* + 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 "unity/examples/unity_config.h" +#include "unity/src/unity.h" +#include "common.h" + +static void assert_print_string(const char *expected, const char *input) +{ + unsigned char printed[1024]; + printbuffer buffer; + buffer.buffer = printed; + buffer.length = sizeof(printed); + buffer.offset = 0; + buffer.noalloc = true; + + TEST_ASSERT_TRUE_MESSAGE(print_string_ptr((const unsigned char*)input, &buffer, &global_hooks), "Failed to print string."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, printed, "The printed string isn't as expected."); +} + +static void print_string_should_print_empty_strings(void) +{ + assert_print_string("\"\"", ""); + assert_print_string("\"\"", NULL); +} + +static void print_string_should_print_ascii(void) +{ + char ascii[0x7F]; + size_t i = 1; + + /* create ascii table */ + for (i = 1; i < 0x7F; i++) + { + ascii[i-1] = (char)i; + } + ascii[0x7F-1] = '\0'; + + assert_print_string("\"\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", + ascii); +} + +static void print_string_should_print_utf8(void) +{ + assert_print_string("\"ü猫慕\"", "ü猫慕"); +} + +int main(void) +{ + /* initialize cJSON item */ + UNITY_BEGIN(); + + RUN_TEST(print_string_should_print_empty_strings); + RUN_TEST(print_string_should_print_ascii); + RUN_TEST(print_string_should_print_utf8); + + return UNITY_END(); +} diff --git a/tests/print_value.c b/tests/print_value.c new file mode 100644 index 00000000..7e0f52fe --- /dev/null +++ b/tests/print_value.c @@ -0,0 +1,102 @@ +/* + 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 assert_print_value(const char *input) +{ + unsigned char printed[1024]; + const unsigned char *error_pointer = NULL; + cJSON item[1]; + printbuffer buffer; + buffer.buffer = printed; + buffer.length = sizeof(printed); + buffer.offset = 0; + buffer.noalloc = true; + + memset(item, 0, sizeof(item)); + + TEST_ASSERT_NOT_NULL_MESSAGE(parse_value(item, (const unsigned char*)input, &error_pointer, &global_hooks), "Failed to parse value."); + + TEST_ASSERT_TRUE_MESSAGE(print_value(item, 0, false, &buffer, &global_hooks), "Failed to print value."); + TEST_ASSERT_EQUAL_STRING_MESSAGE(input, buffer.buffer, "Printed value is not as expected."); + + reset(item); +} + +static void print_value_should_print_null(void) +{ + assert_print_value("null"); +} + +static void print_value_should_print_true(void) +{ + assert_print_value("true"); +} + +static void print_value_should_print_false(void) +{ + assert_print_value("false"); +} + +static void print_value_should_print_number(void) +{ + assert_print_value("1.5"); +} + +static void print_value_should_print_string(void) +{ + assert_print_value("\"\""); + assert_print_value("\"hello\""); +} + +static void print_value_should_print_array(void) +{ + assert_print_value("[]"); +} + +static void print_value_should_print_object(void) +{ + assert_print_value("{}"); +} + +int main(void) +{ + /* initialize cJSON item */ + UNITY_BEGIN(); + + RUN_TEST(print_value_should_print_null); + RUN_TEST(print_value_should_print_true); + RUN_TEST(print_value_should_print_false); + RUN_TEST(print_value_should_print_number); + RUN_TEST(print_value_should_print_string); + RUN_TEST(print_value_should_print_array); + RUN_TEST(print_value_should_print_object); + + return UNITY_END(); +} diff --git a/tests/unity/README.md b/tests/unity/README.md index fef275c9..11759c84 100644 --- a/tests/unity/README.md +++ b/tests/unity/README.md @@ -41,13 +41,13 @@ Example: main() { - if (TEST_PROTECT() == 0) + if (TEST_PROTECT()) { MyTest(); } } -If MyTest calls `TEST_ABORT`, program control will immediately return to `TEST_PROTECT` with a non-zero return value. +If MyTest calls `TEST_ABORT`, program control will immediately return to `TEST_PROTECT` with a return value of zero. Unity Assertion Summary diff --git a/tests/unity/auto/parseOutput.rb b/tests/unity/auto/parseOutput.rb index 7ea180f4..e8dbf08b 100644 --- a/tests/unity/auto/parseOutput.rb +++ b/tests/unity/auto/parseOutput.rb @@ -65,6 +65,17 @@ def testPassed(array) @arrayList.push " " end end + +# Test was flagged as having passed so format the output. +# This is using the Unity fixture output and not the original Unity output. + def testPassedUnityFixture(array) + testSuite = array[0].sub("TEST(", "") + testSuite = testSuite.sub(",", "") + testName = array[1].sub(")", "") + if @xmlOut == true + @arrayList.push " " + end + end # Test was flagged as being ingored so format the output def testIgnored(array) @@ -73,6 +84,14 @@ def testIgnored(array) reason = array[lastItem].chomp testSuiteVerify(array[@className]) printf "%-40s IGNORED\n", testName + + if testName.start_with? "TEST(" + array2 = testName.split(" ") + @testSuite = array2[0].sub("TEST(", "") + @testSuite = @testSuite.sub(",", "") + testName = array2[1].sub(")", "") + end + if @xmlOut == true @arrayList.push " " @arrayList.push " " + reason + " " @@ -87,6 +106,14 @@ def testFailed(array) reason = array[lastItem].chomp + " at line: " + array[lastItem - 3] testSuiteVerify(array[@className]) printf "%-40s FAILED\n", testName + + if testName.start_with? "TEST(" + array2 = testName.split(" ") + @testSuite = array2[0].sub("TEST(", "") + @testSuite = @testSuite.sub(",", "") + testName = array2[1].sub(")", "") + end + if @xmlOut == true @arrayList.push " " @arrayList.push " " + reason + " " @@ -138,7 +165,7 @@ def process(name) lineSize = lineArray.size # If we were able to split the line then we can look to see if any of our target words # were found. Case is important. - if lineSize >= 4 + if ((lineSize >= 4) || (line.start_with? "TEST(")) # Determine if this test passed if line.include? ":PASS" testPassed(lineArray) @@ -149,6 +176,12 @@ def process(name) elsif line.include? ":IGNORE:" testIgnored(lineArray) testIgnore += 1 + elsif line.start_with? "TEST(" + if line.include? " PASS" + lineArray = line.split(" ") + testPassedUnityFixture(lineArray) + testPass += 1 + end # If none of the keywords are found there are no more tests for this suite so clear # the test flag else diff --git a/tests/unity/src/unity.c b/tests/unity/src/unity.c index ece0d9be..52663642 100644 --- a/tests/unity/src/unity.c +++ b/tests/unity/src/unity.c @@ -669,7 +669,7 @@ void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, UnityTestResultsFailBegin(lineNumber); UnityPrint(UnityStrElement); UnityPrintNumberUnsigned(num_elements - elements - 1); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); UnityAddMsgIfSpecified(msg); UNITY_FAIL_AND_BAIL; } @@ -691,7 +691,7 @@ void UnityAssertFloatsWithin(const UNITY_FLOAT delta, if (!UnityFloatsWithin(delta, expected, actual)) { UnityTestResultsFailBegin(lineNumber); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); UnityAddMsgIfSpecified(msg); UNITY_FAIL_AND_BAIL; } @@ -746,7 +746,7 @@ void UnityAssertFloatSpecial(const UNITY_FLOAT actual, UnityPrint(trait_names[trait_index]); UnityPrint(UnityStrWas); #ifndef UNITY_EXCLUDE_FLOAT_PRINT - UnityPrintFloat(actual); + UnityPrintFloat((UNITY_DOUBLE)actual); #else if (should_be_trait) UnityPrint(UnityStrNot);