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. 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 ### Example

303
cJSON.c
View File

@ -521,19 +521,6 @@ static unsigned char* ensure(printbuffer * const p, size_t needed)
return newbuffer + p->offset; 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 */ /* securely comparison of floating-point variables */
static cJSON_bool compare_double(double a, double b) 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; return false;
} }
/* reserve appropriate space in the output */ if (output_buffer->buffer || output_buffer->length)
output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
if (output_pointer == NULL)
{ {
return false; /* reserve appropriate space in the output */
} output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
if (output_pointer == NULL)
/* 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] = '.'; return false;
continue;
} }
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; output_buffer->offset += (size_t)length;
@ -912,12 +902,16 @@ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffe
/* empty string */ /* empty string */
if (input == NULL) if (input == NULL)
{ {
output = ensure(output_buffer, sizeof("\"\"")); if (output_buffer->buffer || output_buffer->length)
if (output == NULL)
{ {
return false; output = ensure(output_buffer, sizeof("\"\""));
if (output == NULL)
{
return false;
}
strcpy((char*)output, "\"\"");
} }
strcpy((char*)output, "\"\""); output_buffer->offset += strlen("\"\"");
return true; 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; 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("\"\"")); output = ensure(output_buffer, output_length + sizeof("\"\""));
if (output == NULL) 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); memcpy(output + 1, input, output_length);
output[output_length + 1] = '\"'; output[output_length + 1] = '\"';
output[output_length + 2] = '\0'; output[output_length + 2] = '\0';
output_buffer->offset += output_length + strlen("\"\"");
return true; 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 + 1] = '\"';
output[output_length + 2] = '\0'; output[output_length + 2] = '\0';
output_buffer->offset += output_length + strlen("\"\"");
return true; return true;
} }
@ -1203,7 +1206,6 @@ static unsigned char *print(const cJSON * const item, cJSON_bool format, const i
{ {
goto fail; goto fail;
} }
update_offset(buffer);
/* check if reallocate is available */ /* check if reallocate is available */
if (hooks->reallocate != NULL) if (hooks->reallocate != NULL)
@ -1285,13 +1287,13 @@ CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON
return (char*)p.buffer; 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 } }; 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; p.buffer = (unsigned char*)buffer;
@ -1301,7 +1303,12 @@ CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, cons
p.format = format; p.format = format;
p.hooks = global_hooks; 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. */ /* 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) static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
{ {
unsigned char *output = NULL; unsigned char *output = NULL;
size_t length;
if ((item == NULL) || (output_buffer == NULL)) 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) switch ((item->type) & 0xFF)
{ {
case cJSON_NULL: case cJSON_NULL:
output = ensure(output_buffer, 5); length = strlen("null");
if (output == 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; return true;
case cJSON_False: case cJSON_False:
output = ensure(output_buffer, 6); length = strlen("false");
if (output == NULL) 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; return true;
case cJSON_True: case cJSON_True:
output = ensure(output_buffer, 5); length = strlen("true");
if (output == NULL) 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; return true;
case cJSON_Number: case cJSON_Number:
return print_number(item, output_buffer); return print_number(item, output_buffer);
case cJSON_Raw: case cJSON_Raw:
{
size_t raw_length = 0;
if (item->valuestring == NULL) if (item->valuestring == NULL)
{ {
return false; return false;
} }
raw_length = strlen(item->valuestring) + sizeof(""); length = strlen(item->valuestring);
output = ensure(output_buffer, raw_length); if (output_buffer->buffer || output_buffer->length)
if (output == NULL)
{ {
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; return true;
}
case cJSON_String: case cJSON_String:
return print_string(item, output_buffer); 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. */ /* Compose the output array. */
/* opening square bracket */ /* opening square bracket */
output_pointer = ensure(output_buffer, 1); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
return false; output_pointer = ensure(output_buffer, 1);
} if (output_pointer == NULL)
{
return false;
}
*output_pointer = '['; *output_pointer = '[';
}
output_buffer->offset++; output_buffer->offset++;
output_buffer->depth++; output_buffer->depth++;
@ -1561,33 +1588,40 @@ static cJSON_bool print_array(const cJSON * const item, printbuffer * const outp
{ {
return false; return false;
} }
update_offset(output_buffer);
if (current_element->next) if (current_element->next)
{ {
length = (size_t) (output_buffer->format ? 2 : 1); length = (size_t) (output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length + 1); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
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; output_buffer->offset += length;
} }
current_element = current_element->next; current_element = current_element->next;
} }
output_pointer = ensure(output_buffer, 2); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
return false; output_pointer = ensure(output_buffer, 2);
if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = ']';
*output_pointer = '\0';
} }
*output_pointer++ = ']'; output_buffer->offset++;
*output_pointer = '\0';
output_buffer->depth--; output_buffer->depth--;
return true; return true;
@ -1720,33 +1754,39 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out
/* Compose the output: */ /* Compose the output: */
length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */
output_pointer = ensure(output_buffer, length + 1); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
return false; output_pointer = ensure(output_buffer, length + 1);
} if (output_pointer == NULL)
{
return false;
}
*output_pointer++ = '{'; *output_pointer++ = '{';
output_buffer->depth++; if (output_buffer->format)
if (output_buffer->format) {
{ *output_pointer++ = '\n';
*output_pointer++ = '\n'; }
} }
output_buffer->offset += length; output_buffer->offset += length;
output_buffer->depth++;
while (current_item) while (current_item)
{ {
if (output_buffer->format) if (output_buffer->format)
{ {
size_t i; if (output_buffer->buffer || output_buffer->length)
output_pointer = ensure(output_buffer, output_buffer->depth);
if (output_pointer == NULL)
{ {
return false; size_t i;
} output_pointer = ensure(output_buffer, output_buffer->depth);
for (i = 0; i < output_buffer->depth; i++) if (output_pointer == NULL)
{ {
*output_pointer++ = '\t'; return false;
}
for (i = 0; i < output_buffer->depth; i++)
{
*output_pointer++ = '\t';
}
} }
output_buffer->offset += output_buffer->depth; output_buffer->offset += output_buffer->depth;
} }
@ -1756,18 +1796,20 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out
{ {
return false; return false;
} }
update_offset(output_buffer);
length = (size_t) (output_buffer->format ? 2 : 1); length = (size_t) (output_buffer->format ? 2 : 1);
output_pointer = ensure(output_buffer, length); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
return false; output_pointer = ensure(output_buffer, length);
} if (output_pointer == NULL)
*output_pointer++ = ':'; {
if (output_buffer->format) return false;
{ }
*output_pointer++ = '\t'; *output_pointer++ = ':';
if (output_buffer->format)
{
*output_pointer++ = '\t';
}
} }
output_buffer->offset += length; output_buffer->offset += length;
@ -1776,45 +1818,52 @@ static cJSON_bool print_object(const cJSON * const item, printbuffer * const out
{ {
return false; return false;
} }
update_offset(output_buffer);
/* print comma if not last */ /* print comma if not last */
length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0));
output_pointer = ensure(output_buffer, length + 1); if (output_buffer->buffer || output_buffer->length)
if (output_pointer == NULL)
{ {
return false; output_pointer = ensure(output_buffer, length + 1);
} if (output_pointer == NULL)
if (current_item->next) {
{ return false;
*output_pointer++ = ','; }
} if (current_item->next)
{
*output_pointer++ = ',';
}
if (output_buffer->format) if (output_buffer->format)
{ {
*output_pointer++ = '\n'; *output_pointer++ = '\n';
}
*output_pointer = '\0';
} }
*output_pointer = '\0';
output_buffer->offset += length; output_buffer->offset += length;
current_item = current_item->next; current_item = current_item->next;
} }
output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); length = output_buffer->format ? output_buffer->depth : 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)
if (output_buffer->format)
{
size_t i;
for (i = 0; i < (output_buffer->depth - 1); i++)
{ {
*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_buffer->offset += length;
*output_pointer = '\0';
output_buffer->depth--; output_buffer->depth--;
return true; return true;

View File

@ -158,9 +158,10 @@ CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
CJSON_PUBLIC(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 */ /* 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); 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 */ /* 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. */ /* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); 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; char *buf_fail = NULL;
size_t len = 0; size_t len = 0;
size_t len_fail = 0; size_t len_fail = 0;
size_t len_ret = 0;
/* formatted print */ /* formatted print */
out = cJSON_Print(root); out = cJSON_Print(root);
/* create buffer to succeed */ /* create buffer to succeed */
/* the extra 5 bytes are because of inaccuracies when reserving memory */ /* the extra 5 bytes are because of inaccuracies when reserving memory */
len = strlen(out) + 5; len = strlen(out);
buf = (char*)malloc(len); buf = (char*)malloc(len + 5);
if (buf == NULL) if (buf == NULL)
{ {
printf("Failed to allocate memory.\n"); printf("Failed to allocate memory.\n");
@ -71,8 +72,16 @@ static int print_preallocated(cJSON *root)
exit(1); 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 */ /* 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"); printf("cJSON_PrintPreallocated failed!\n");
if (strcmp(out, buf) != 0) { if (strcmp(out, buf) != 0) {
printf("cJSON_PrintPreallocated not the same as cJSON_Print!\n"); printf("cJSON_PrintPreallocated not the same as cJSON_Print!\n");