summaryrefslogtreecommitdiff
path: root/json.c
diff options
context:
space:
mode:
authorEric S. Raymond <esr@thyrsus.com>2011-08-27 08:17:45 -0400
committerEric S. Raymond <esr@thyrsus.com>2011-08-27 08:17:45 -0400
commitb638d52e495c49930187f8825c8ff73c165152e3 (patch)
treef203d4a074f9cc1a61e9b4b95fa9778c3891e9b3 /json.c
parent17aebfd0662c0db283a916b70c811b378e966b84 (diff)
downloadgpsd-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.c222
1 files changed, 216 insertions, 6 deletions
diff --git a/json.c b/json.c
index 42cfbc9a..cd9f85b4 100644
--- a/json.c
+++ b/json.c
@@ -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;
}