diff options
author | Eric S. Raymond <esr@thyrsus.com> | 2011-08-27 08:17:45 -0400 |
---|---|---|
committer | Eric S. Raymond <esr@thyrsus.com> | 2011-08-27 08:17:45 -0400 |
commit | b638d52e495c49930187f8825c8ff73c165152e3 (patch) | |
tree | f203d4a074f9cc1a61e9b4b95fa9778c3891e9b3 /json.c | |
parent | 17aebfd0662c0db283a916b70c811b378e966b84 (diff) | |
download | gpsd-b638d52e495c49930187f8825c8ff73c165152e3.tar.gz |
Avoid locale problems by inlining an old BSD version of strtod().
This solves Berlios tracker bug #18328: Wrong/bad locale handling in
json_read_object. All regression tests (including the JSON unit test) pass.
Diffstat (limited to 'json.c')
-rw-r--r-- | json.c | 222 |
1 files changed, 216 insertions, 6 deletions
@@ -63,7 +63,7 @@ PERMISSIONS #include <stdlib.h> #include <stdbool.h> #include <stdarg.h> -#include <locale.h> +#include <errno.h> #include <ctype.h> #include "gpsd_config.h" /* for strlcpy() prototype */ @@ -148,6 +148,220 @@ static /*@null@*/ char *json_target_address(const struct json_attr_t *cursor, return targetaddr; } + +/* + * Berkeley implementation of strtod(), inlined to avoid locale problems + * with the decimal point. + */ + +static int maxExponent = 511; /* Largest possible base 10 exponent. Any + * exponent larger than this will already + * produce underflow or overflow, so there's + * no need to worry about additional digits. + */ +static double powersOf10[] = { /* Table giving binary powers of 10. Entry */ + 10., /* is 10^2^i. Used to convert decimal */ + 100., /* exponents into floating-point numbers. */ + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 +}; + +static double +c_strtod(string, endPtr) + const char *string; /* A decimal ASCII floating-point number, + * optionally preceded by white space. + * Must have form "-I.FE-X", where I is the + * integer part of the mantissa, F is the + * fractional part of the mantissa, and X + * is the exponent. Either of the signs + * may be "+", "-", or omitted. Either I + * or F may be omitted, or both. The decimal + * point isn't necessary unless F is present. + * The "E" may actually be an "e". E and X + * may both be omitted (but not just one). + */ + char **endPtr; /* If non-NULL, store terminating character's + * address here. */ +{ + int sign, expSign = false; + double fraction, dblExp, *d; + register const char *p; + register int c; + int exp = 0; /* Exponent read from "EX" field. */ + int fracExp = 0; /* Exponent that derives from the fractional + * part. Under normal circumstatnces, it is + * the negative of the number of digits in F. + * However, if I is very long, the last digits + * of I get dropped (otherwise a long I with a + * large negative exponent could cause an + * unnecessary overflow on I alone). In this + * case, fracExp is incremented one for each + * dropped digit. */ + int mantSize; /* Number of digits in mantissa. */ + int decPt; /* Number of mantissa digits BEFORE decimal + * point. */ + const char *pExp; /* Temporarily holds location of exponent + * in string. */ + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (isspace(*p)) { + p += 1; + } + if (*p == '-') { + sign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = false; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0; ; mantSize += 1) + { + c = *p; + if (!isdigit(c)) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to + * collect 9 digits each (this is faster than using floating-point). + * If the mantissa has more than 18 digits, ignore the extras, since + * they can't affect the value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + p = string; + goto done; + } else { + int frac1, frac2; + frac1 = 0; + for ( ; mantSize > 9; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10*frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) + { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10*frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = false; + } + while (isdigit(*p)) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. + * Do this by processing the exponent one bit at a time to combine + * many powers of 2 of 10. Then combine the exponent with the + * fraction. + */ + + if (exp < 0) { + expSign = true; + exp = -exp; + } else { + expSign = false; + } + if (exp > maxExponent) { + exp = maxExponent; + errno = ERANGE; + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, d += 1) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + +done: + if (endPtr != NULL) { + *endPtr = (char *) p; + } + + if (sign) { + return -fraction; + } + return fraction; +} + /*@-immediatetrans -dependenttrans +usereleased +compdef@*/ static int json_internal_read_object(const char *cp, @@ -445,7 +659,7 @@ static int json_internal_read_object(const char *cp, *((double *)lptr) = iso8601_to_unix(valbuf); break; case t_real: - *((double *)lptr) = atof(valbuf); + *((double *)lptr) = c_strtod(valbuf, (char **)NULL); break; case t_string: if (parent != NULL @@ -604,14 +818,10 @@ int json_read_array(const char *cp, const struct json_array_t *arr, int json_read_object(const char *cp, const struct json_attr_t *attrs, /*@null@*/ const char **end) { - char *savedlocale = setlocale(LC_ALL, NULL); int st; - /* temporary locale setting is required wherever ',' is the decimal point */ - (void)setlocale(LC_ALL, "C"); json_debug_trace((1, "json_read_object() sees '%s'\n", cp)); st = json_internal_read_object(cp, attrs, NULL, 0, end); - (void)setlocale(LC_ALL, savedlocale); return st; } |