Make real number encoding and decoding work under all locales

The decimal point '.' is changed to locale's decimal point
before/after JSON conversion to make C standard library's
locale-specific string conversion functions work correctly.

All the tests now call setlocale(LC_ALL, "") on startup to use the
locale set in the environment.

Fixes GH-32.
This commit is contained in:
Petri Lehtinen 2011-10-02 21:27:53 +03:00
parent b6d0191e51
commit d7ddbf3661
24 changed files with 199 additions and 59 deletions

View File

@ -14,6 +14,7 @@ AM_CONDITIONAL([GCC], [test x$GCC = xyes])
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([locale.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_INT32_T
@ -34,6 +35,12 @@ esac
AC_SUBST([json_inline])
# Checks for library functions.
AC_CHECK_FUNCS([setlocale localeconv])
case "$ac_cv_header_locale_h$ac_cv_func_localeconv" in
yesyes) json_have_localeconv=1;;
*) json_have_localeconv=0;;
esac
AC_SUBST([json_have_localeconv])
AC_CONFIG_FILES([
jansson.pc

View File

@ -110,3 +110,25 @@ types, ``long double``, etc. Obviously, shorter types like ``short``,
are implicitly handled via the ordinary C type coercion rules (subject
to overflow semantics). Also, no support or hooks are provided for any
supplemental "bignum" type add-on packages.
Locale
======
Jansson works fine under any locale.
However, if the host program is multithreaded and uses ``setlocale()``
to switch the locale in one thread while Jansson is currently encoding
or decoding JSON data in another thread, the result may be wrong or
the program may even crash.
Jansson uses locale specific functions for certain string conversions
in the encoder and decoder, and then converts the locale specific
values to/from the JSON representation. This fails if the locale
changes between the string conversion and the locale-to-JSON
conversion. This can only happen in multithreaded programs that use
``setlocale()``, that switches the locale for all running threads, not
only the thread that calls ``setlocale()``.
If your program uses ``setlocale()`` as described above, consider
using the thread-safe ``uselocale()`` instead.

View File

@ -12,6 +12,7 @@ libjansson_la_SOURCES = \
pack_unpack.c \
strbuffer.c \
strbuffer.h \
strconv.c \
utf.c \
utf.h \
value.c

View File

@ -196,26 +196,12 @@ static int do_dump(const json_t *json, size_t flags, int depth,
{
char buffer[MAX_REAL_STR_LENGTH];
int size;
double value = json_real_value(json);
size = snprintf(buffer, MAX_REAL_STR_LENGTH, "%.17g",
json_real_value(json));
if(size >= MAX_REAL_STR_LENGTH)
size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value);
if(size < 0)
return -1;
/* Make sure there's a dot or 'e' in the output. Otherwise
a real is converted to an integer when decoding */
if(strchr(buffer, '.') == NULL &&
strchr(buffer, 'e') == NULL)
{
if(size + 2 >= MAX_REAL_STR_LENGTH) {
/* No space to append ".0" */
return -1;
}
buffer[size] = '.';
buffer[size + 1] = '0';
size += 2;
}
return dump(buffer, size, data);
}

View File

@ -31,4 +31,8 @@
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@
/* If locale.h and localeconv() are available, define to 1,
otherwise to 0. */
#define JSON_HAVE_LOCALECONV @json_have_localeconv@
#endif

View File

@ -31,4 +31,8 @@
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
#define JSON_INTEGER_IS_LONG_LONG 1
/* If locale.h and localeconv() are available, define to 1,
otherwise to 0. */
#define JSON_HAVE_LOCALECONV 1
#endif

View File

@ -11,6 +11,7 @@
#include <stddef.h>
#include "jansson.h"
#include "hashtable.h"
#include "strbuffer.h"
#define container_of(ptr_, type_, member_) \
((type_ *)((char *)ptr_ - offsetof(type_, member_)))
@ -83,6 +84,10 @@ void jsonp_error_set(json_error_t *error, int line, int column,
void jsonp_error_vset(json_error_t *error, int line, int column,
size_t position, const char *msg, va_list ap);
/* Locale independent string<->double conversions */
int jsonp_strtod(strbuffer_t *strbuffer, double *out);
int jsonp_dtostr(char *buffer, size_t size, double value);
/* Wrappers for custom memory functions */
void* jsonp_malloc(size_t size);
void jsonp_free(void *ptr);

View File

@ -524,12 +524,7 @@ static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
lex_unget_unsave(lex, c);
saved_text = strbuffer_value(&lex->saved_text);
errno = 0;
value = strtod(saved_text, &end);
assert(end == saved_text + lex->saved_text.length);
if(errno == ERANGE && value != 0) {
if(jsonp_strtod(&lex->saved_text, &value)) {
error_set(error, lex, "real number overflow");
goto out;
}

108
src/strconv.c Normal file
View File

@ -0,0 +1,108 @@
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "jansson_private.h"
#include "strbuffer.h"
#if JSON_HAVE_LOCALECONV
#include <locale.h>
/*
- This code assumes that the decimal separator is exactly one
character.
- If setlocale() is called by another thread between the call to
localeconv() and the call to sprintf() or strtod(), the result may
be wrong. setlocale() is not thread-safe and should not be used
this way. Multi-threaded programs should use uselocale() instead.
*/
static void to_locale(strbuffer_t *strbuffer)
{
const char *point;
char *pos;
point = localeconv()->decimal_point;
if(*point == '.') {
/* No conversion needed */
return;
}
pos = strstr(strbuffer->value, ".");
if(pos)
*pos = *point;
}
static void from_locale(char *buffer)
{
const char *point;
char *pos;
point = localeconv()->decimal_point;
if(*point == '.') {
/* No conversion needed */
return;
}
pos = strstr(buffer, point);
if(pos)
*pos = '.';
}
#endif
int jsonp_strtod(strbuffer_t *strbuffer, double *out)
{
double value;
char *end;
#if JSON_HAVE_LOCALECONV
to_locale(strbuffer);
#endif
errno = 0;
value = strtod(strbuffer->value, &end);
assert(end == strbuffer->value + strbuffer->length);
if(errno == ERANGE && value != 0) {
/* Overflow */
return -1;
}
*out = value;
return 0;
}
int jsonp_dtostr(char *buffer, size_t size, double value)
{
int ret;
size_t length;
ret = snprintf(buffer, size, "%.17g", value);
if(ret < 0)
return -1;
length = (size_t)ret;
if(length >= size)
return -1;
#if JSON_HAVE_LOCALECONV
from_locale(buffer);
#endif
/* Make sure there's a dot or 'e' in the output. Otherwise
a real is converted to an integer when decoding */
if(strchr(buffer, '.') == NULL &&
strchr(buffer, 'e') == NULL)
{
if(length + 2 >= size) {
/* No space to append ".0" */
return -1;
}
buffer[length] = '.';
buffer[length + 1] = '0';
length += 2;
}
return (int)length;
}

View File

@ -5,12 +5,20 @@
* it under the terms of the MIT license. See LICENSE for details.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <jansson.h>
#if HAVE_LOCALE_H
#include <locale.h>
#endif
static int getenv_int(const char *name)
{
char *value, *end;
@ -55,6 +63,10 @@ int main(int argc, char *argv[])
json_t *json;
json_error_t error;
#if HAVE_SETLOCALE
setlocale(LC_ALL, "");
#endif
if(argc != 1) {
fprintf(stderr, "usage: %s\n", argv[0]);
return 2;

View File

@ -387,7 +387,7 @@ static void test_circular()
}
int main()
static void run_tests()
{
test_misc();
test_insert();
@ -395,6 +395,4 @@ int main()
test_clear();
test_extend();
test_circular();
return 0;
}

View File

@ -307,7 +307,7 @@ static void test_deep_copy_object(void)
json_decref(copy);
}
int main()
static void run_tests()
{
test_copy_simple();
test_deep_copy_simple();
@ -315,5 +315,4 @@ int main()
test_deep_copy_array();
test_copy_object();
test_deep_copy_object();
return 0;
}

View File

@ -133,10 +133,9 @@ static void encode_other_than_array_or_object()
}
int main()
static void run_tests()
{
encode_twice();
circular_references();
encode_other_than_array_or_object();
return 0;
}

View File

@ -26,7 +26,7 @@ static int my_writer(const char *buffer, size_t len, void *data) {
return 0;
}
int main(void)
static void run_tests()
{
struct my_sink s;
json_t *json;
@ -78,6 +78,4 @@ int main(void)
json_decref(json);
free(dumped_to_string);
free(s.buf);
return EXIT_SUCCESS;
}

View File

@ -180,11 +180,10 @@ static void test_equal_complex()
/* TODO: There's no negative test case here */
}
int main()
static void run_tests()
{
test_equal_simple();
test_equal_array();
test_equal_object();
test_equal_complex();
return 0;
}

View File

@ -50,11 +50,9 @@ static void disable_eof_check()
json_decref(json);
}
int main()
static void run_tests()
{
file_not_found();
reject_duplicates();
disable_eof_check();
return 0;
}

View File

@ -9,7 +9,7 @@
#include <string.h>
#include "util.h"
int main()
static void run_tests()
{
json_t *json;
json_error_t error;
@ -33,6 +33,4 @@ int main()
if(strcmp(error.text, "']' expected near end of file") != 0) {
fail("json_loadb returned an invalid error message for an unclosed top-level array");
}
return 0;
}

View File

@ -75,10 +75,8 @@ static void test_secure_funcs(void)
create_and_free_complex_object();
}
int main()
static void run_tests()
{
test_simple();
test_secure_funcs();
return 0;
}

View File

@ -8,7 +8,7 @@
#include <jansson.h>
#include "util.h"
int main()
static void run_tests()
{
json_t *integer, *real;
int i;
@ -39,6 +39,4 @@ int main()
json_decref(integer);
json_decref(real);
return 0;
}

View File

@ -437,7 +437,7 @@ static void test_preserve_order()
json_decref(object);
}
int main()
static void run_tests()
{
test_misc();
test_clear();
@ -446,6 +446,4 @@ int main()
test_set_nocheck();
test_iterators();
test_preserve_order();
return 0;
}

View File

@ -11,7 +11,7 @@
#include <stdio.h>
#include "util.h"
int main()
static void run_tests()
{
json_t *value;
int i;
@ -227,6 +227,4 @@ int main()
if(json_pack_ex(&error, 0, "{s:s}", "foo", "\xff\xff"))
fail("json_pack failed to catch invalid UTF-8 in a string");
check_error("Invalid UTF-8 string", "<args>", 1, 4, 4);
return 0;
}

View File

@ -10,7 +10,7 @@
#include "util.h"
/* Call the simple functions not covered by other tests of the public API */
int main()
static void run_tests()
{
json_t *value;
@ -180,6 +180,4 @@ int main()
json_incref(value);
if(value->refcount != (size_t)-1)
fail("refcounting null works incorrectly");
return 0;
}

View File

@ -11,7 +11,7 @@
#include <stdio.h>
#include "util.h"
int main()
static void run_tests()
{
json_t *j, *j2;
int i1, i2, i3;
@ -336,6 +336,4 @@ int main()
fail("json_unpack nested array with strict validation failed");
check_error("1 array item(s) left unpacked", "<validation>", 1, 5, 5);
json_decref(j);
return 0;
}

View File

@ -8,8 +8,16 @@
#ifndef UTIL_H
#define UTIL_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#if HAVE_LOCALE_H
#include <locale.h>
#endif
#include <jansson.h>
#define failhdr fprintf(stderr, "%s:%s:%d: ", __FILE__, __FUNCTION__, __LINE__)
@ -52,4 +60,15 @@
} \
} while(0)
static void run_tests();
int main() {
#ifdef HAVE_SETLOCALE
setlocale(LC_ALL, "");
#endif
run_tests();
return 0;
}
#endif