From ed411f00e0f142bf577ad34d93e652ba8dff39e4 Mon Sep 17 00:00:00 2001 From: Bowe Neuenschwander Date: Tue, 4 Aug 2020 23:32:48 -0500 Subject: [PATCH] Return output length from cJSON_PrintPreallocated Adjust cJSON_PrintPreallocated() to return a size_t that provides the length of the output string. On error, it will return 0; so legacy behavior of interpreting its return as a boolean will still work as expected. In addition, a NULL buffer and zero length can be passed in to get the length of what would be the resulting string, so the needed buffer size is this plus 5 (extra that cJSON needs for printing). --- README.md | 2 +- cJSON.c | 303 +++++++++++++++++++++++++++++++----------------------- cJSON.h | 5 +- test.c | 15 ++- 4 files changed, 192 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index cd18c7c..effea8e 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ It will allocate a string and print a JSON representation of the tree into it. O If you have a rough idea of how big your resulting string will be, you can use `cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)`. `fmt` is a boolean to turn formatting with whitespace on and off. `prebuffer` specifies the first buffer size to use for printing. `cJSON_Print` currently uses 256 bytes for its first buffer size. Once printing runs out of space, a new buffer is allocated and the old gets copied over before printing is continued. -These dynamic buffer allocations can be completely avoided by using `cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)`. It takes a buffer to a pointer to print to and its length. If the length is reached, printing will fail and it returns `0`. In case of success, `1` is returned. Note that you should provide 5 bytes more than is actually needed, because cJSON is not 100% accurate in estimating if the provided memory is enough. +These dynamic buffer allocations can be completely avoided by using `cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)`. It takes a pointer to a buffer to which to print and the length of that buffer. If the length is reached, printing will fail and it returns `0`. In case of success, the length of the resulting string is returned. Note that you should provide 5 bytes more than is actually needed, because cJSON is not 100% accurate in estimating if the provided memory is enough. In addition, a NULL buffer pointer and length of zero can be provided to calculate and return the length of the resulting string without actually outputing to a buffer. ### Example diff --git a/cJSON.c b/cJSON.c index 3063f74..804c6d0 100644 --- a/cJSON.c +++ b/cJSON.c @@ -521,19 +521,6 @@ static unsigned char* ensure(printbuffer * const p, size_t needed) return newbuffer + p->offset; } -/* 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); -} - /* securely comparison of floating-point variables */ static cJSON_bool compare_double(double a, double b) { @@ -581,26 +568,29 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out return false; } - /* reserve appropriate space in the output */ - output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } - - /* copy the printed number to the output and replace locale - * dependent decimal point with '.' */ - for (i = 0; i < ((size_t)length); i++) - { - if (number_buffer[i] == decimal_point) + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) { - output_pointer[i] = '.'; - continue; + return false; } - output_pointer[i] = number_buffer[i]; + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; } - output_pointer[i] = '\0'; output_buffer->offset += (size_t)length; @@ -912,12 +902,16 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe /* empty string */ if (input == NULL) { - output = ensure(output_buffer, sizeof("\"\"")); - if (output == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); } - strcpy((char*)output, "\"\""); + output_buffer->offset += strlen("\"\""); return true; } @@ -948,6 +942,13 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe } output_length = (size_t)(input_pointer - input) + escape_characters; + /* if just counting length, then do not need to actually copy string */ + if (!output_buffer->buffer && !output_buffer->length) + { + output_buffer->offset += output_length + strlen("\"\""); + return true; + } + output = ensure(output_buffer, output_length + sizeof("\"\"")); if (output == NULL) { @@ -961,6 +962,7 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe memcpy(output + 1, input, output_length); output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; + output_buffer->offset += output_length + strlen("\"\""); return true; } @@ -1012,6 +1014,7 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe } output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; + output_buffer->offset += output_length + strlen("\"\""); return true; } @@ -1203,7 +1206,6 @@ static unsigned char *print(const cJSON * const item, cJSON_bool format, const i { goto fail; } - update_offset(buffer); /* check if reallocate is available */ if (hooks->reallocate != NULL) @@ -1285,13 +1287,13 @@ CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON return (char*)p.buffer; } -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +CJSON_PUBLIC(size_t) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - if ((length < 0) || (buffer == NULL)) + if (length < 0) { - return false; + return 0; } p.buffer = (unsigned char*)buffer; @@ -1301,7 +1303,12 @@ CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, cons p.format = format; p.hooks = global_hooks; - return print_value(item, &p); + if (!print_value(item, &p)) + { + return 0; + } + + return p.offset; } /* Parser core - when encountering text, process appropriately. */ @@ -1363,6 +1370,7 @@ static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buf static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output = NULL; + size_t length; if ((item == NULL) || (output_buffer == NULL)) { @@ -1372,52 +1380,68 @@ static cJSON_bool print_value(const cJSON * const item, printbuffer * const outp switch ((item->type) & 0xFF) { case cJSON_NULL: - output = ensure(output_buffer, 5); - if (output == NULL) + length = strlen("null"); + if (output_buffer->buffer || output_buffer->length) { - return false; + output = ensure(output_buffer, length + sizeof("")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); } - strcpy((char*)output, "null"); + output_buffer->offset += length; return true; case cJSON_False: - output = ensure(output_buffer, 6); - if (output == NULL) + length = strlen("false"); + if (output_buffer->buffer || output_buffer->length) { - return false; + output = ensure(output_buffer, length + sizeof("")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); } - strcpy((char*)output, "false"); + output_buffer->offset += length; return true; case cJSON_True: - output = ensure(output_buffer, 5); - if (output == NULL) + length = strlen("true"); + if (output_buffer->buffer || output_buffer->length) { - return false; + output = ensure(output_buffer, length + sizeof("")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); } - strcpy((char*)output, "true"); + output_buffer->offset += length; return true; case cJSON_Number: return print_number(item, output_buffer); case cJSON_Raw: - { - size_t raw_length = 0; if (item->valuestring == NULL) { return false; } - raw_length = strlen(item->valuestring) + sizeof(""); - output = ensure(output_buffer, raw_length); - if (output == NULL) + length = strlen(item->valuestring); + if (output_buffer->buffer || output_buffer->length) { - return false; + output = ensure(output_buffer, length + sizeof("")); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, length + sizeof("")); } - memcpy(output, item->valuestring, raw_length); + output_buffer->offset += length; return true; - } case cJSON_String: return print_string(item, output_buffer); @@ -1545,13 +1569,16 @@ static cJSON_bool print_array(const cJSON * const item, printbuffer * const outp /* Compose the output array. */ /* opening square bracket */ - output_pointer = ensure(output_buffer, 1); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } - *output_pointer = '['; + *output_pointer = '['; + } output_buffer->offset++; output_buffer->depth++; @@ -1561,33 +1588,40 @@ static cJSON_bool print_array(const cJSON * const item, printbuffer * const outp { return false; } - update_offset(output_buffer); + if (current_element->next) { length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; + output_pointer = ensure(output_buffer, length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; } - *output_pointer++ = ','; - if(output_buffer->format) - { - *output_pointer++ = ' '; - } - *output_pointer = '\0'; output_buffer->offset += length; } current_element = current_element->next; } - output_pointer = ensure(output_buffer, 2); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; } - *output_pointer++ = ']'; - *output_pointer = '\0'; + output_buffer->offset++; output_buffer->depth--; return true; @@ -1720,33 +1754,39 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out /* Compose the output: */ length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } - *output_pointer++ = '{'; - output_buffer->depth++; - if (output_buffer->format) - { - *output_pointer++ = '\n'; + *output_pointer++ = '{'; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } } output_buffer->offset += length; + output_buffer->depth++; while (current_item) { if (output_buffer->format) { - size_t i; - output_pointer = ensure(output_buffer, output_buffer->depth); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } - for (i = 0; i < output_buffer->depth; i++) - { - *output_pointer++ = '\t'; + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } } output_buffer->offset += output_buffer->depth; } @@ -1756,18 +1796,20 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out { return false; } - update_offset(output_buffer); length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } - *output_pointer++ = ':'; - if (output_buffer->format) - { - *output_pointer++ = '\t'; + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } } output_buffer->offset += length; @@ -1776,45 +1818,52 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out { return false; } - update_offset(output_buffer); /* print comma if not last */ length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) + if (output_buffer->buffer || output_buffer->length) { - return false; - } - if (current_item->next) - { - *output_pointer++ = ','; - } + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } - if (output_buffer->format) - { - *output_pointer++ = '\n'; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; } - *output_pointer = '\0'; output_buffer->offset += length; current_item = current_item->next; } - output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); - if (output_pointer == NULL) + length = output_buffer->format ? output_buffer->depth : 1; + if (output_buffer->buffer || output_buffer->length) { - return false; - } - if (output_buffer->format) - { - size_t i; - for (i = 0; i < (output_buffer->depth - 1); i++) + output_pointer = ensure(output_buffer, length + sizeof("")); + if (output_pointer == NULL) { - *output_pointer++ = '\t'; + return false; } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; } - *output_pointer++ = '}'; - *output_pointer = '\0'; + output_buffer->offset += length; output_buffer->depth--; return true; diff --git a/cJSON.h b/cJSON.h index 92907a2..115daca 100644 --- a/cJSON.h +++ b/cJSON.h @@ -158,9 +158,10 @@ CJSON_PUBLIC(char *) cJSON_Print(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 */ 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 given length. Returns 1 on success and 0 on failure. */ +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns length of resulting string on success and 0 on failure. */ +/* A NULL buffer and zero length can also be pass in to this function to calculate the resulting string length (excluding null terminator), so the needed buffer size is this plus 5 (see next line). */ /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +CJSON_PUBLIC(size_t) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); /* Delete a cJSON entity and all subentities. */ CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); diff --git a/test.c b/test.c index 986fc6e..135ce3a 100644 --- a/test.c +++ b/test.c @@ -48,14 +48,15 @@ static int print_preallocated(cJSON *root) char *buf_fail = NULL; size_t len = 0; size_t len_fail = 0; + size_t len_ret = 0; /* formatted print */ out = cJSON_Print(root); /* create buffer to succeed */ /* the extra 5 bytes are because of inaccuracies when reserving memory */ - len = strlen(out) + 5; - buf = (char*)malloc(len); + len = strlen(out); + buf = (char*)malloc(len + 5); if (buf == NULL) { printf("Failed to allocate memory.\n"); @@ -71,8 +72,16 @@ static int print_preallocated(cJSON *root) exit(1); } + len_ret = cJSON_PrintPreallocated(root, NULL, 0, 1); + if (len_ret != len) { + printf("cJSON_PrintPreallocated length calculation failed!\n"); + printf("cJSON_Print result length:%d\n", (int)(len)); + printf("cJSON_PrintPreallocated length:%d\n", (int)(len_ret)); + return -1; + } + /* Print to buffer */ - if (!cJSON_PrintPreallocated(root, buf, (int)len, 1)) { + if (!cJSON_PrintPreallocated(root, buf, (int)(len + 5), 1)) { printf("cJSON_PrintPreallocated failed!\n"); if (strcmp(out, buf) != 0) { printf("cJSON_PrintPreallocated not the same as cJSON_Print!\n");