Merge pull request #930 from dxbjavid/get-double-locale-radix

fix locale-dependent strtod in json_object_get_double
This commit is contained in:
Eric Hawicz
2026-06-21 19:46:36 -04:00
committed by GitHub
2 changed files with 50 additions and 2 deletions
+38 -2
View File
@@ -21,6 +21,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif /* HAVE_LOCALE_H */
#include "arraylist.h" #include "arraylist.h"
#include "debug.h" #include "debug.h"
@@ -1381,12 +1384,42 @@ double json_object_get_double(const struct json_object *jso)
} }
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean; case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
case json_type_string: case json_type_string:
{
const char *cstr = get_string_component(jso);
const char *parse = cstr;
char *radixconv = NULL;
#ifdef HAVE_LOCALE_H
/* A json_type_string holds the value with a '.' radix, as it
* appears in JSON, but strtod() honours the current locale. In a
* locale whose decimal point is not '.' the '.' is treated as a
* stray character, so e.g. "19.95" is read as 0. Parse a copy
* with the radix translated so the value comes back unchanged. */
const char *decimal_point = localeconv()->decimal_point;
if (decimal_point && decimal_point[0] != '.' && decimal_point[1] == '\0')
{
const char *dot = strchr(cstr, '.');
if (dot)
{
size_t slen = strlen(cstr);
radixconv = (char *)malloc(slen + 1);
if (radixconv == NULL)
{
errno = ENOMEM;
return 0.0;
}
memcpy(radixconv, cstr, slen + 1);
radixconv[dot - cstr] = decimal_point[0];
parse = radixconv;
}
}
#endif
errno = 0; errno = 0;
cdouble = strtod(get_string_component(jso), &errPtr); cdouble = strtod(parse, &errPtr);
/* if conversion stopped at the first character, return 0.0 */ /* if conversion stopped at the first character, return 0.0 */
if (errPtr == get_string_component(jso)) if (errPtr == parse)
{ {
free(radixconv);
errno = EINVAL; errno = EINVAL;
return 0.0; return 0.0;
} }
@@ -1398,6 +1431,7 @@ double json_object_get_double(const struct json_object *jso)
*/ */
if (*errPtr != '\0') if (*errPtr != '\0')
{ {
free(radixconv);
errno = EINVAL; errno = EINVAL;
return 0.0; return 0.0;
} }
@@ -1415,7 +1449,9 @@ double json_object_get_double(const struct json_object *jso)
*/ */
if ((HUGE_VAL == cdouble || -HUGE_VAL == cdouble) && (ERANGE == errno)) if ((HUGE_VAL == cdouble || -HUGE_VAL == cdouble) && (ERANGE == errno))
cdouble = 0.0; cdouble = 0.0;
free(radixconv);
return cdouble; return cdouble;
}
default: errno = EINVAL; return 0.0; default: errno = EINVAL; return 0.0;
} }
} }
+12
View File
@@ -40,6 +40,18 @@ int main(int argc, char **argv)
if (strcmp(buf1, buf2) != 0) if (strcmp(buf1, buf2) != 0)
printf("ERROR: Original locale not restored \"%s\" != \"%s\"", buf1, buf2); printf("ERROR: Original locale not restored \"%s\" != \"%s\"", buf1, buf2);
// json_object_get_double() on a json_type_string must not depend on the
// locale's decimal point. Under a comma-radix locale such as de_DE the
// '.' used to be treated as a stray char, so "19.95" came back as 0.
// (If the de_DE locale isn't installed we're still in "C" here, where
// this passes anyway, so the check is safe to always run.)
{
json_object *numstr = json_object_new_string("19.95");
double d = json_object_get_double(numstr);
assert(d > 19.94 && d < 19.96);
json_object_put(numstr);
}
#ifdef HAVE_SETLOCALE #ifdef HAVE_SETLOCALE
setlocale(LC_NUMERIC, "C"); setlocale(LC_NUMERIC, "C");
#endif #endif