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

Locale independence #146

Merged
merged 5 commits into from
Apr 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,9 @@ The maximum length of a floating point literal that cJSON supports is currently
In general cJSON is **not thread safe**.

However it is thread safe under the following conditions:
* You don't use `cJSON_GetErrorPtr` (you can use the `return_parse_end` parameter of `cJSON_ParseWithOpts` instead)
* You only ever call `cJSON_InitHooks` before using cJSON in any threads.
* `cJSON_GetErrorPtr` is never used (the `return_parse_end` parameter of `cJSON_ParseWithOpts` can be used instead)
* `cJSON_InitHooks` is only ever called before using cJSON in any threads.
* `setlocale` is never called before all calls to cJSON functions have returned.

# Enjoy cJSON!

Expand Down
134 changes: 95 additions & 39 deletions cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <float.h>
#include <limits.h>
#include <ctype.h>
#include <locale.h>
#pragma GCC visibility pop

#include "cJSON.h"
Expand Down Expand Up @@ -177,19 +178,63 @@ CJSON_PUBLIC(void) cJSON_Delete(cJSON *c)
}
}

/* get the decimal point character of the current locale */
static unsigned char get_decimal_point(void)
{
struct lconv *lconv = localeconv();
return (unsigned char) lconv->decimal_point[0];
}

/* Parse the input text to generate a number, and populate the result into item. */
static const unsigned char *parse_number(cJSON * const item, const unsigned char * const input)
{
double number = 0;
unsigned char *after_end = NULL;
unsigned char number_c_string[64];
unsigned char decimal_point = get_decimal_point();
size_t i = 0;

if (input == NULL)
{
return NULL;
}

number = strtod((const char*)input, (char**)&after_end);
if (input == after_end)
/* copy the number into a temporary buffer and replace '.' with the decimal point
* of the current locale (for strtod) */
for (i = 0; (i < (sizeof(number_c_string) - 1)) && (input[i] != '\0'); i++)
{
switch (input[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '+':
case '-':
case 'e':
case 'E':
number_c_string[i] = input[i];
break;

case '.':
number_c_string[i] = decimal_point;
break;

default:
goto loop_end;
}
}
loop_end:
number_c_string[i] = '\0';

number = strtod((const char*)number_c_string, (char**)&after_end);
if (number_c_string == after_end)
{
return NULL; /* parse_error */
}
Expand All @@ -212,7 +257,7 @@ static const unsigned char *parse_number(cJSON * const item, const unsigned char

item->type = cJSON_Number;

return after_end;
return input + (after_end - number_c_string);
}

/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */
Expand Down Expand Up @@ -336,34 +381,24 @@ static void update_offset(printbuffer * const buffer)
}

/* Removes trailing zeroes from the end of a printed number */
static cJSON_bool trim_trailing_zeroes(printbuffer * const buffer)
static int trim_trailing_zeroes(const unsigned char * const number, int length, const unsigned char decimal_point)
{
size_t offset = 0;
unsigned char *content = NULL;

if ((buffer == NULL) || (buffer->buffer == NULL) || (buffer->offset < 1))
if ((number == NULL) || (length <= 0))
{
return false;
return -1;
}

offset = buffer->offset - 1;
content = buffer->buffer;

while ((offset > 0) && (content[offset] == '0'))
while ((length > 0) && (number[length - 1] == '0'))
{
offset--;
length--;
}
if ((offset > 0) && (content[offset] == '.'))
if ((length > 0) && (number[length - 1] == decimal_point))
{
offset--;
/* remove trailing decimal_point */
length--;
}

offset++;
content[offset] = '\0';

buffer->offset = offset;

return true;
return length;
}

/* Render the number nicely from the given item into a string. */
Expand All @@ -372,53 +407,74 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
unsigned char *output_pointer = NULL;
double d = item->valuedouble;
int length = 0;
cJSON_bool trim_zeroes = true; /* should at the end be removed? */
size_t i = 0;
cJSON_bool trim_zeroes = true; /* should zeroes at the end be removed? */
unsigned char number_buffer[64]; /* temporary buffer to print the number into */
unsigned char decimal_point = get_decimal_point();

if (output_buffer == NULL)
{
return false;
}

/* This is a nice tradeoff. */
output_pointer = ensure(output_buffer, 64, hooks);
if (output_pointer == NULL)
{
return false;
}

/* This checks for NaN and Infinity */
if ((d * 0) != 0)
{
length = sprintf((char*)output_pointer, "null");
length = sprintf((char*)number_buffer, "null");
}
else if ((fabs(floor(d) - d) <= DBL_EPSILON) && (fabs(d) < 1.0e60))
{
/* integer */
length = sprintf((char*)output_pointer, "%.0f", d);
length = sprintf((char*)number_buffer, "%.0f", d);
trim_zeroes = false; /* don't remove zeroes for "big integers" */
}
else if ((fabs(d) < 1.0e-6) || (fabs(d) > 1.0e9))
{
length = sprintf((char*)output_pointer, "%e", d);
length = sprintf((char*)number_buffer, "%e", d);
trim_zeroes = false; /* don't remove zeroes in engineering notation */
}
else
{
length = sprintf((char*)output_pointer, "%f", d);
length = sprintf((char*)number_buffer, "%f", d);
}

/* sprintf failed */
if (length < 0)
/* sprintf failed or buffer overrun occured */
if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
{
return false;
}

output_buffer->offset += (size_t)length;

if (trim_zeroes)
{
return trim_trailing_zeroes(output_buffer);
length = trim_trailing_zeroes(number_buffer, length, decimal_point);
if (length <= 0)
{
return false;
}
}

/* reserve appropriate space in the output */
output_pointer = ensure(output_buffer, (size_t)length, hooks);
if (output_pointer == NULL)
{
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)
{
output_pointer[i] = '.';
continue;
}

output_pointer[i] = number_buffer[i];
}
output_pointer[i] = '\0';

output_buffer->offset += (size_t)length;

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion cJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ 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. */
/* NOTE: If you are printing numbers, the buffer hat to be 63 bytes bigger then the printed JSON (worst case) */
/* 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);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);
Expand Down
4 changes: 2 additions & 2 deletions test.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ static int print_preallocated(cJSON *root)
out = cJSON_Print(root);

/* create buffer to succeed */
/* the extra 64 bytes are in case a floating point value is printed */
len = strlen(out) + 64;
/* the extra 5 bytes are because of inaccuracies when reserving memory */
len = strlen(out) + 5;
buf = (char*)malloc(len);
if (buf == NULL)
{
Expand Down
16 changes: 5 additions & 11 deletions tests/print_number.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,11 @@ static void print_number_should_print_non_number(void)

static void trim_trailing_zeroes_should_trim_trailing_zeroes(void)
{
printbuffer buffer;
unsigned char number[100];
buffer.length = sizeof(number);
buffer.buffer = number;

strcpy((char*)number, "10.00");
buffer.offset = sizeof("10.00") - 1;
TEST_ASSERT_TRUE(trim_trailing_zeroes(&buffer));
TEST_ASSERT_EQUAL_UINT8('\0', buffer.buffer[buffer.offset]);
TEST_ASSERT_EQUAL_STRING("10", number);
TEST_ASSERT_EQUAL_UINT(sizeof("10") - 1, buffer.offset);
TEST_ASSERT_EQUAL_INT(2, trim_trailing_zeroes((const unsigned char*)"10.00", (int)(sizeof("10.00") - 1), '.'));
TEST_ASSERT_EQUAL_INT(0, trim_trailing_zeroes((const unsigned char*)".00", (int)(sizeof(".00") - 1), '.'));
TEST_ASSERT_EQUAL_INT(0, trim_trailing_zeroes((const unsigned char*)"00", (int)(sizeof("00") - 1), '.'));
TEST_ASSERT_EQUAL_INT(-1, trim_trailing_zeroes(NULL, 10, '.'));
TEST_ASSERT_EQUAL_INT(-1, trim_trailing_zeroes((const unsigned char*)"", 0, '.'));
}

int main(void)
Expand Down