From 2d1c13224f757e780763e205e22fdf0517db82d0 Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Thu, 7 Mar 2024 21:02:55 +0200 Subject: [PATCH] Use sprintf() to determine locale's decimal point This should fix thread safety of encoding and decoding, since localeconv() is not tread safe after all. --- CHANGES | 11 +++++++++++ CMakeLists.txt | 11 ----------- android/jansson_config.h | 4 ---- cmake/jansson_config.h.cmake | 3 --- configure.ac | 8 +------- src/jansson_config.h.in | 4 ---- src/strconv.c | 36 +++++++++++++++++------------------- 7 files changed, 29 insertions(+), 48 deletions(-) diff --git a/CHANGES b/CHANGES index f2d26aca..c5853c7e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ +Version 2.14.1 +============== + +Work in progress + +* Fixes: + + - Fix thread safety of encoding and decoding when `uselocale` or `newlocale` + is used to switch locales inside the threads (#674, #675, #677. Thanks to + Bruno Haible the report and help with fixing.) + Version 2.14 ============ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8788afe4..4a93a711 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,18 +215,7 @@ if (NOT DEFINED JSON_INT_T) endif () endif () - -# If locale.h and localeconv() are available, define to 1, otherwise to 0. check_include_files (locale.h HAVE_LOCALE_H) -check_function_exists (localeconv HAVE_LOCALECONV) - -if (HAVE_LOCALECONV AND HAVE_LOCALE_H) - set (JSON_HAVE_LOCALECONV 1) -else () - set (JSON_HAVE_LOCALECONV 0) -endif() - -# check if we have setlocale check_function_exists(setlocale HAVE_SETLOCALE) # Check what the inline keyword is. diff --git a/android/jansson_config.h b/android/jansson_config.h index 618a0da7..448010a4 100644 --- a/android/jansson_config.h +++ b/android/jansson_config.h @@ -32,10 +32,6 @@ 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 0 - /* Maximum recursion depth for parsing JSON input. This limits the depth of e.g. array-within-array constructions. */ #define JSON_PARSER_MAX_DEPTH 2048 diff --git a/cmake/jansson_config.h.cmake b/cmake/jansson_config.h.cmake index 2f248cbc..542e57b4 100644 --- a/cmake/jansson_config.h.cmake +++ b/cmake/jansson_config.h.cmake @@ -56,9 +56,6 @@ #define JSON_INTEGER_FORMAT @JSON_INTEGER_FORMAT@ -/* If locale.h and localeconv() are available, define to 1, otherwise to 0. */ -#define JSON_HAVE_LOCALECONV @JSON_HAVE_LOCALECONV@ - /* If __atomic builtins are available they will be used to manage reference counts of json_t. */ #define JSON_HAVE_ATOMIC_BUILTINS @JSON_HAVE_ATOMIC_BUILTINS@ diff --git a/configure.ac b/configure.ac index f022eb72..15721f5e 100644 --- a/configure.ac +++ b/configure.ac @@ -34,7 +34,7 @@ esac AC_SUBST([json_inline]) # Checks for library functions. -AC_CHECK_FUNCS([close getpid gettimeofday localeconv open read sched_yield strtoll]) +AC_CHECK_FUNCS([close getpid gettimeofday open read setlocale sched_yield strtoll]) AC_MSG_CHECKING([for gcc __sync builtins]) have_sync_builtins=no @@ -74,12 +74,6 @@ case "$ac_cv_type_long_long_int$ac_cv_func_strtoll" in esac AC_SUBST([json_have_long_long]) -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]) - # Features AC_ARG_ENABLE([urandom], [AS_HELP_STRING([--disable-urandom], diff --git a/src/jansson_config.h.in b/src/jansson_config.h.in index fe692ab4..791f60d0 100644 --- a/src/jansson_config.h.in +++ b/src/jansson_config.h.in @@ -32,10 +32,6 @@ 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@ - /* If __atomic builtins are available they will be used to manage reference counts of json_t. */ #define JSON_HAVE_ATOMIC_BUILTINS @json_have_atomic_builtins@ diff --git a/src/strconv.c b/src/strconv.c index c6f4fd16..7d317dd9 100644 --- a/src/strconv.c +++ b/src/strconv.c @@ -11,57 +11,57 @@ #include #endif -#if JSON_HAVE_LOCALECONV -#include - /* - 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. + get_decimal_point() 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 char get_decimal_point() { + char buf[3]; + sprintf(buf, "%#.0f", 1.0); // "1." in the current locale + return buf[1]; +} static void to_locale(strbuffer_t *strbuffer) { - const char *point; + char point; char *pos; - point = localeconv()->decimal_point; - if (*point == '.') { + point = get_decimal_point(); + if (point == '.') { /* No conversion needed */ return; } pos = strchr(strbuffer->value, '.'); if (pos) - *pos = *point; + *pos = point; } static void from_locale(char *buffer) { - const char *point; + char point; char *pos; - point = localeconv()->decimal_point; - if (*point == '.') { + point = get_decimal_point(); + if (point == '.') { /* No conversion needed */ return; } - pos = strchr(buffer, *point); + pos = strchr(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); @@ -92,9 +92,7 @@ int jsonp_dtostr(char *buffer, size_t size, double value, int precision) { 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 */