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).
This commit is contained in:
Bowe Neuenschwander 2020-08-04 23:32:48 -05:00 committed by Bowe Neuenschwander
parent 203a0dec6f
commit ed411f00e0
4 changed files with 192 additions and 133 deletions

View File

@ -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

303
cJSON.c
View File

@ -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;

View File

@ -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);

15
test.c
View File

@ -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");