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
Closed

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

wants to merge 12 commits into from

Conversation

ChisholmKyle
Copy link
Contributor

I added a function called cJSON_PrintMallocedBuffer to print output to a buffer instead of allocating memory internally. I'm made it so there aren't any calls to malloc when printing out a cJSON item.

I also fixed the Makefile to have the appropriate dependencies and only source files (no headers) being compiled for the tests. Otherwise, clang on Mac (and perhaps elsewhere) complains.

p.buffer = buf;
p.length = len;
p.offset = 0;
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.

@FSMaxB
Copy link
Collaborator

FSMaxB commented Nov 25, 2016

I understand why a function like that can be useful, but the implementation should be more carefully considered.

The name should be more like cJSON_PrintPreallocated or something like that. Also we haven't done the switch to size_t, but it is on it's way in cJSON 2.0.

If you don't do multithreading, there is a (quite hacky, I admit that) workaround to do what you want on the current version of cJSON btw.

/* consider this as pseudo code because I haven't actually compiled and tested it */
static int run_already = 0;
static char *buffer = NULL;
static size_t buffer_length = 0;

void *allocator(size_t size)
{
    if (run_already || (size > buffer_length))
    {
        return NULL;
    }
    run_already = 1;

    return buffer;
}

void deallocator(void *buffer)
{
    return;
}

cJSON_Hooks static_allocators = { allocator, deallocator };

int main(void)
{
    /*... */
    char local_buffer[100];
    buffer = local_buffer;
    buffer_length = 100;
    run_already = 0;

    cJSON_InitHooks(&static_allocators);

    cJSON_PrintBuffered(item, 100, 0);

    buffer = NULL;
    buffer_length = 0;
    run_already = 0;
    /* ... */

    return 0;
}

Yes, it's really ugly ...

If one would actually implement this functionality in cJSON, it would probably be best to add a flag to printbuffer that stores if the buffer is to be considered constant and then modifying ensure to handle that case.

We can have a discussion at least!

@ChisholmKyle
Copy link
Contributor Author

ChisholmKyle commented Nov 25, 2016

Seems like quite a bit of of user code to perform what seems like a simple idea. I really like idea of adding a flag to the printbuffer struct. I made the change and added a small test.

I found a bug: If the supplied buffer was too small by 1 or 2 bytes, the last closing curly bracket is omitted and no error is detected. I made a change to the source in the last commit which adds a check for a NULL pointer returned from print_value.
This seems to fix the problem, but the tests are not automated and I'm not sure it's worth inspecting the output manually. I'm also not as familiar with the source.

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

p.offset = 0;
p.noalloc = 1;
out = print_value(item,0,fmt,&p);
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.

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.


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 😢.


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).

@ChisholmKyle
Copy link
Contributor Author

Thanks for the thorough feedback, I'll make the requested changes soon :)

@FSMaxB FSMaxB removed the undecided label Nov 27, 2016
@ChisholmKyle
Copy link
Contributor Author

Okay made the changes. Let me know what you think.

@@ -23,7 +23,7 @@ INSTALL_LIBRARY_PATH = $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)

INSTALL ?= cp -a

R_CFLAGS = -fPIC -std=c89 -pedantic -Wall -Werror -Wstrict-prototypes -Wwrite-strings $(CFLAGS)
R_CFLAGS = -fPIC -std=c89 -pedantic -Wall -Werror -Wstrict-prototypes -Wwrite-strings -Wshadow -Winit-self -Wcast-align -Wformat=2 -Wmissing-prototypes $(CFLAGS)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't try to readd changes that I merged to the master branch already. I will handle merge conflicts myself.

p.length = len;
p.offset = 0;
p.noalloc = true;
return (print_value(item,0,fmt,&p) != NULL ? 0 : -1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant it exactly the other way around. Think of the return value as a bool. So return print_value(...) != NULL. It returns 1 if it succeeds and 0 if it doesn't.

@FSMaxB
Copy link
Collaborator

FSMaxB commented Nov 28, 2016

I think I will manually merge in your changes.

@FSMaxB
Copy link
Collaborator

FSMaxB commented Nov 28, 2016

I merged your changes, see bf17703

@FSMaxB FSMaxB closed this Nov 28, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants