Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a function to print to pre-allocated buffer #72

Closed
wants to merge 12 commits into from
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ UTILS_LIBNAME = libcjson_utils
CJSON_TEST = cJSON_test
UTILS_TEST = cJSON_test_utils

CJSON_TEST_SRC = cJSON.c test.c
UTILS_TEST_SRC = cJSON.c cJSON_Utils.c test_utils.c

LDLIBS = -lm

LIBVERSION = 1.0.2
Expand Down Expand Up @@ -66,11 +69,11 @@ test: tests

#tests
#cJSON
$(CJSON_TEST): cJSON.c cJSON.h test.c
$(CC) $^ -o $@ $(LDLIBS) -I.
$(CJSON_TEST): $(CJSON_TEST_SRC) cJSON.h
$(CC) $(CJSON_TEST_SRC) -o $@ $(LDLIBS) -I.
#cJSON_Utils
$(UTILS_TEST): cJSON.c cJSON.h test.c
$(CC) $^ -o $@ $(LDLIBS) -I.
$(UTILS_TEST): $(UTILS_TEST_SRC) cJSON.h cJSON_Utils.h
$(CC) $(UTILS_TEST_SRC) -o $@ $(LDLIBS) -I.

#static libraries
#cJSON
Expand All @@ -85,8 +88,8 @@ $(UTILS_STATIC): $(UTILS_OBJ)
$(CJSON_SHARED_VERSION): $(CJSON_OBJ)
$(CC) -shared -o $@ $< $(LDFLAGS)
#cJSON_Utils
$(UTILS_SHARED_VERSION): $(UTILS_OBJ)
$(CC) -shared -o $@ $< $(LDFLAGS)
$(UTILS_SHARED_VERSION): $(UTILS_OBJ) $(CJSON_OBJ)
$(CC) -shared -o $@ $< $(CJSON_OBJ) $(LDFLAGS)

#objects
#cJSON
Expand Down
25 changes: 24 additions & 1 deletion cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ typedef struct
char *buffer;
int length;
int offset;
int noalloc;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use cjbool and use true and false when assigning to it.

} printbuffer;


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add random whitespace.

/* realloc printbuffer if necessary to have at least "needed" bytes more */
static char* ensure(printbuffer *p, int needed)
{
Expand All @@ -261,12 +263,17 @@ static char* ensure(printbuffer *p, int needed)
return p->buffer + p->offset;
}

if (p->noalloc) {
return NULL;
}

newsize = pow2gt(needed);
newbuffer = (char*)cJSON_malloc(newsize);
if (!newbuffer)
{
cJSON_free(p->buffer);
p->length = 0;
p->noalloc = 0;
p->buffer = NULL;

return NULL;
Expand Down Expand Up @@ -882,10 +889,22 @@ char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, cjbool fmt)
}
p.length = prebuffer;
p.offset = 0;
p.noalloc = 0;

return print_value(item, 0, fmt, &p);
}

int cJSON_PrintPreallocated(cJSON *item,char *buf, const size_t len, const cjbool fmt)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the size_t. I agree that size_t should be used everywhere instead of int, and it will be in v2, but currently ensure and printbuffer and all the other internal functions use int for sizes, so using size_t can create the illusion that numbers exceeding the integer range are supported, but they're not.

So please change to int 😢.

{
char *out;
printbuffer p;
p.buffer = buf;
p.length = len;
p.offset = 0;
p.noalloc = 1;
out = print_value(item,0,fmt,&p);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By doing this you risk crashes, because print_value will try to reallocate the printbuffer when it is too short.

return (out != buf ? -1 : 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return out like the other print functions. It will be NULL if it failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this function returns a char pointer (effectively providing an alias to the input char *buf or a NULL pointer), it might falsely imply a new dynamically allocated string was created. Do you think it might be more intuitive to use the pointer to char* buf after the function is called if no error was returned, or is consistency of return values with all the cJSON_Print functions more important?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Good point. Considering this, your way of returning true or false might actually be better. An API should be hard to misuse in a dangerous way (like having a double free because of the alias).
The question is how far one is willing to protect the users from themselves.

This will then not allow the use of cJSON_PrintPreallocated as an argument to another function (do_something(cJSON_PrintPreallocated(...)))

If you do it the way you suggested, you can just return print_value(...) != NULL;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be more concise. Made the change.

}

/* Parser core - when encountering text, process appropriately. */
static const char *parse_value(cJSON *item, const char *value, const char **ep)
Expand Down Expand Up @@ -1137,7 +1156,11 @@ static char *print_array(const cJSON *item, int depth, cjbool fmt, printbuffer *
child = item->child;
while (child && !fail)
{
print_value(child, depth + 1, fmt, p);
ptr = print_value(child, depth + 1, fmt, p);
if (!ptr)
{
return NULL;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to:

if (!print_value(child, depth + 1, fmt, p))
{
    return NULL;
}

Thank you for finding this problem btw. after that I was taking a closer look to the rest of the library and there are quite some cases where out of memory errors are not handled properly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will also fix these other cases in the next release.

p->offset = update(p);
if (child->next)
{
Expand Down
2 changes: 2 additions & 0 deletions cJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ extern char *cJSON_Print(const cJSON *item);
extern 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);
/* Render a cJSON entity to text using a buffer already allocated in memory with length buf_len */
extern int cJSON_PrintPreallocated(cJSON *item, char *buf, const size_t len, const int fmt);
/* Delete a cJSON entity and all subentities. */
extern void cJSON_Delete(cJSON *c);

Expand Down
49 changes: 47 additions & 2 deletions test.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"

/* Parse text to JSON, then render back to text, and print! */
Expand Down Expand Up @@ -219,16 +220,60 @@ void create_objects(void)
/* cJSON_ReplaceItemInObject(cJSON_GetArrayItem(root, 1), "City", cJSON_CreateIntArray(ids, 4)); */

out = cJSON_Print(root);
cJSON_Delete(root);
printf("%s\n", out);

printf("Test cJSON_PrintPreallocated:\n");
/* create buffer */
size_t len = strlen(out) + 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not allowed to mix code and declarations in C89. I have a bug in the Makefile and CMakelists.txt so that the compiler option is not enforced, sorry for that.

I suggest you create a new function just for testing preallocated printing and call it from main (you can pass one of the texts to it).

char *buf = malloc(len);

/* create buffer to fail */
size_t len_fail = strlen(out);
char *buf_fail = malloc(len_fail);

free(out);

/* Print to buffer */
if (cJSON_PrintPreallocated(root, buf, len, 1) != 0) {
printf("cJSON_PrintPreallocated failed (but it should not have!)\n");
free(buf_fail);
free(buf);
exit(EXIT_FAILURE);
} else {
printf("cJSON_PrintPreallocated:\n%s\n", buf);
}

/* unformatted output */
if (cJSON_PrintPreallocated(root, buf, len, 0) != 0) {
printf("cJSON_PrintPreallocated failed (but it should not have!)\n");
free(buf_fail);
free(buf);
exit(EXIT_FAILURE);
} else {
printf("cJSON_PrintPreallocated (unformatted):\n%s\n", buf);
}

free(buf);

/* force it to fail */
if (cJSON_PrintPreallocated(root, buf_fail, len_fail, 1) != 0) {
printf("cJSON_PrintPreallocated failed (as expected)\n");
} else {
printf("cJSON_PrintPreallocated worked (but it should have failed!)\n");
printf("cJSON_PrintPreallocated:\n%s\n", buf_fail);
}

free(buf_fail);
cJSON_Delete(root);

root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "number", 1.0 / zero);
out = cJSON_Print(root);
cJSON_Delete(root);
printf("%s\n", out);
cJSON_Delete(root);

free(out);

}

int main(void)
Expand Down