Added custom implementation of strtod() (issue #453)

This commit is contained in:
Benoit Blanchon
2017-03-19 15:23:06 +01:00
parent 13409c433a
commit c4567bac18
15 changed files with 593 additions and 83 deletions

View File

@ -3,7 +3,7 @@ Thanks for using ArduinoJson :-)
Before opening an issue, please make sure you've read these: Before opening an issue, please make sure you've read these:
https://bblanchon.github.io/ArduinoJson/faq/ https://bblanchon.github.io/ArduinoJson/faq/
https://github.com/bblanchon/ArduinoJson/wiki/Avoiding-pitfalls https://bblanchon.github.io/ArduinoJson/doc/pitfalls/
Next, make sure you provide all the relevant information: platform, code snippet, and error messages. Next, make sure you provide all the relevant information: platform, code snippet, and error messages.

View File

@ -1,6 +1,11 @@
ArduinoJson: change log ArduinoJson: change log
======================= =======================
HEAD
----
* Added custom implementation of `strtod()` (issue #453)
v5.8.3 v5.8.3
------ ------

View File

@ -7,7 +7,7 @@ all: \
$(OUT)/json_fuzzer_seed_corpus.zip \ $(OUT)/json_fuzzer_seed_corpus.zip \
$(OUT)/json_fuzzer.options $(OUT)/json_fuzzer.options
$(OUT)/json_fuzzer: fuzzer.cpp $(OUT)/json_fuzzer: fuzzer.cpp $(shell find ../include -type f)
$(CXX) $(CXXFLAGS) $< -o$@ $(LIB_FUZZING_ENGINE) $(CXX) $(CXXFLAGS) $< -o$@ $(LIB_FUZZING_ENGINE)
$(OUT)/json_fuzzer_seed_corpus.zip: seed_corpus/* $(OUT)/json_fuzzer_seed_corpus.zip: seed_corpus/*

View File

@ -14,5 +14,11 @@
12.34e+56, 12.34e+56,
12.34E56, 12.34E56,
12.34E-56, 12.34E-56,
12.34E+56 12.34E+56,
NaN,
-NaN,
+NaN,
Infinity,
+Infinity,
-Infinity
] ]

View File

@ -1,66 +0,0 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include <stdlib.h>
namespace ArduinoJson {
namespace Internals {
template <typename TFloat>
TFloat parse(const char *);
template <>
inline float parse<float>(const char *s) {
return static_cast<float>(strtod(s, NULL));
}
template <>
inline double parse<double>(const char *s) {
return strtod(s, NULL);
}
template <>
inline long parse<long>(const char *s) {
return strtol(s, NULL, 10);
}
template <>
inline unsigned long parse<unsigned long>(const char *s) {
return strtoul(s, NULL, 10);
}
template <>
inline int parse<int>(const char *s) {
return atoi(s);
}
#if ARDUINOJSON_USE_LONG_LONG
template <>
inline long long parse<long long>(const char *s) {
return strtoll(s, NULL, 10);
}
template <>
inline unsigned long long parse<unsigned long long>(const char *s) {
return strtoull(s, NULL, 10);
}
#endif
#if ARDUINOJSON_USE_INT64
template <>
inline __int64 parse<__int64>(const char *s) {
return _strtoi64(s, NULL, 10);
}
template <>
inline unsigned __int64 parse<unsigned __int64>(const char *s) {
return _strtoui64(s, NULL, 10);
}
#endif
}
}

View File

@ -8,10 +8,12 @@
#pragma once #pragma once
#include "Configuration.hpp" #include "Configuration.hpp"
#include "Data/Parse.hpp"
#include "JsonArray.hpp" #include "JsonArray.hpp"
#include "JsonObject.hpp" #include "JsonObject.hpp"
#include "JsonVariant.hpp" #include "JsonVariant.hpp"
#include "Polyfills/isFloat.hpp"
#include "Polyfills/parseFloat.hpp"
#include "Polyfills/parseInteger.hpp"
#include <errno.h> // for errno #include <errno.h> // for errno
#include <stdlib.h> // for strtol, strtod #include <stdlib.h> // for strtol, strtod
@ -56,14 +58,14 @@ inline Internals::JsonInteger JsonVariant::variantAsInteger() const {
case JSON_BOOLEAN: case JSON_BOOLEAN:
return _content.asInteger; return _content.asInteger;
case JSON_NEGATIVE_INTEGER: case JSON_NEGATIVE_INTEGER:
return -static_cast<Internals::JsonInteger>(_content.asInteger); return -static_cast<JsonInteger>(_content.asInteger);
case JSON_STRING: case JSON_STRING:
case JSON_UNPARSED: case JSON_UNPARSED:
if (!_content.asString) return 0; if (!_content.asString) return 0;
if (!strcmp("true", _content.asString)) return 1; if (!strcmp("true", _content.asString)) return 1;
return parse<Internals::JsonInteger>(_content.asString); return Polyfills::parseInteger<JsonInteger>(_content.asString);
default: default:
return static_cast<Internals::JsonInteger>(_content.asFloat); return static_cast<JsonInteger>(_content.asFloat);
} }
} }
@ -80,9 +82,9 @@ inline Internals::JsonUInt JsonVariant::asUnsignedInteger() const {
case JSON_UNPARSED: case JSON_UNPARSED:
if (!_content.asString) return 0; if (!_content.asString) return 0;
if (!strcmp("true", _content.asString)) return 1; if (!strcmp("true", _content.asString)) return 1;
return parse<Internals::JsonUInt>(_content.asString); return Polyfills::parseInteger<JsonUInt>(_content.asString);
default: default:
return static_cast<Internals::JsonUInt>(_content.asFloat); return static_cast<JsonUInt>(_content.asFloat);
} }
} }
@ -107,7 +109,9 @@ inline Internals::JsonFloat JsonVariant::variantAsFloat() const {
return -static_cast<JsonFloat>(_content.asInteger); return -static_cast<JsonFloat>(_content.asInteger);
case JSON_STRING: case JSON_STRING:
case JSON_UNPARSED: case JSON_UNPARSED:
return _content.asString ? parse<JsonFloat>(_content.asString) : 0; return _content.asString
? Polyfills::parseFloat<JsonFloat>(_content.asString)
: 0;
default: default:
return _content.asFloat; return _content.asFloat;
} }
@ -143,11 +147,7 @@ inline bool JsonVariant::isFloat() const {
if (_type != JSON_UNPARSED || _content.asString == NULL) return false; if (_type != JSON_UNPARSED || _content.asString == NULL) return false;
char *end; return Polyfills::isFloat(_content.asString);
errno = 0;
strtod(_content.asString, &end);
return *end == '\0' && errno == 0 && !is<long>();
} }
#if ARDUINOJSON_ENABLE_STD_STREAM #if ARDUINOJSON_ENABLE_STD_STREAM

View File

@ -0,0 +1,21 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
namespace ArduinoJson {
namespace Polyfills {
inline bool isdigit(char c) {
return '0' <= c && c <= '9';
}
inline bool issign(char c) {
return '-' == c || c == '+';
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include "./ctype.hpp"
#include "./math.hpp"
namespace ArduinoJson {
namespace Polyfills {
inline bool isFloat(const char* s) {
if (!strcmp(s, "NaN")) return true;
if (issign(*s)) s++;
if (!strcmp(s, "Infinity")) return true;
while (isdigit(*s)) s++;
bool has_dot = *s == '.';
if (has_dot) {
s++;
while (isdigit(*s)) s++;
}
bool has_exponent = *s == 'e' || *s == 'E';
if (has_exponent) {
s++;
if (issign(*s)) s++;
if (!isdigit(*s)) return false;
while (isdigit(*s)) s++;
}
return (has_dot || has_exponent) && *s == '\0';
}
}
}

View File

@ -7,10 +7,11 @@
#pragma once #pragma once
// If Visual Studo <= 2012 // If Visual Studo
#if defined(_MSC_VER) && _MSC_VER <= 1700 #if defined(_MSC_VER)
#include <float.h> #include <float.h>
#include <limits>
namespace ArduinoJson { namespace ArduinoJson {
namespace Polyfills { namespace Polyfills {
@ -23,6 +24,16 @@ template <typename T>
bool isInfinity(T x) { bool isInfinity(T x) {
return !_finite(x); return !_finite(x);
} }
template <typename T>
T nan() {
return std::numeric_limits<T>::quiet_NaN();
}
template <typename T>
T inf() {
return std::numeric_limits<T>::infinity();
}
} }
} }
@ -101,6 +112,16 @@ inline bool isInfinity<float>(float x) {
} }
#endif #endif
template <typename T>
T nan() {
return static_cast<T>(NAN);
}
template <typename T>
T inf() {
return static_cast<T>(INFINITY);
}
#if defined(__GNUC__) #if defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

View File

@ -0,0 +1,87 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include "../TypeTraits/FloatTraits.hpp"
#include "./ctype.hpp"
#include "./math.hpp"
namespace ArduinoJson {
namespace Polyfills {
template <typename T>
inline T parseFloat(const char* s) {
typedef TypeTraits::FloatTraits<T> traits;
typedef typename traits::mantissa_type mantissa_t;
typedef typename traits::exponent_type exponent_t;
bool negative_result = false;
switch (*s) {
case '-':
negative_result = true;
case '+':
s++;
}
if (*s == 'n' || *s == 'N') return traits::nan();
if (*s == 'i' || *s == 'I')
return negative_result ? -traits::inf() : traits::inf();
mantissa_t mantissa = 0;
exponent_t exponent_offset = 0;
while (isdigit(*s)) {
if (mantissa < traits::mantissa_max / 10)
mantissa = mantissa * 10 + (*s - '0');
else
exponent_offset++;
s++;
}
if (*s == '.') {
s++;
while (isdigit(*s)) {
if (mantissa < traits::mantissa_max / 10) {
mantissa = mantissa * 10 + (*s - '0');
exponent_offset--;
}
s++;
}
}
int exponent = 0;
if (*s == 'e' || *s == 'E') {
s++;
bool negative_exponent = false;
if (*s == '-') {
negative_exponent = true;
s++;
} else if (*s == '+') {
s++;
}
while (isdigit(*s)) {
exponent = exponent * 10 + (*s - '0');
if (exponent + exponent_offset > traits::exponent_max) {
if (negative_exponent)
return negative_result ? -0.0f : 0.0f;
else
return negative_result ? -traits::inf() : traits::inf();
}
s++;
}
if (negative_exponent) exponent = -exponent;
}
exponent += exponent_offset;
T result = traits::make_float(static_cast<T>(mantissa), exponent);
return negative_result ? -result : result;
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include <stdlib.h>
namespace ArduinoJson {
namespace Polyfills {
template <typename T>
T parseInteger(const char *s);
template <>
inline long parseInteger<long>(const char *s) {
return ::strtol(s, NULL, 10);
}
template <>
inline unsigned long parseInteger<unsigned long>(const char *s) {
return ::strtoul(s, NULL, 10);
}
template <>
inline int parseInteger<int>(const char *s) {
return ::atoi(s);
}
#if ARDUINOJSON_USE_LONG_LONG
template <>
inline long long parseInteger<long long>(const char *s) {
return ::strtoll(s, NULL, 10);
}
template <>
inline unsigned long long parseInteger<unsigned long long>(const char *s) {
return ::strtoull(s, NULL, 10);
}
#endif
#if ARDUINOJSON_USE_INT64
template <>
inline __int64 parseInteger<__int64>(const char *s) {
return ::_strtoi64(s, NULL, 10);
}
template <>
inline unsigned __int64 parseInteger<unsigned __int64>(const char *s) {
return ::_strtoui64(s, NULL, 10);
}
#endif
}
}

View File

@ -0,0 +1,83 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include <stdint.h>
#include "../Polyfills/math.hpp"
namespace ArduinoJson {
namespace TypeTraits {
template <typename T, size_t = sizeof(T)>
struct FloatTraits {};
#ifndef ARDUINO_ARCH_AVR // double is 32 bits, so 1e64 gives a warning
template <typename T>
struct FloatTraits<T, 8 /*64bits*/> {
typedef int64_t mantissa_type;
static const short mantissa_bits = 52;
static const mantissa_type mantissa_max =
(static_cast<mantissa_type>(1) << mantissa_bits) - 1;
typedef int16_t exponent_type;
static const exponent_type exponent_max = 308;
template <typename TExponent>
static T make_float(T m, TExponent e) {
if (e >= 0)
return m * (e & 1 ? 1e1 : 1) * (e & 2 ? 1e2 : 1) * (e & 4 ? 1e4 : 1) *
(e & 8 ? 1e8 : 1) * (e & 16 ? 1e16 : 1) * (e & 32 ? 1e32 : 1) *
(e & 64 ? 1e64 : 1) * (e & 128 ? 1e128 : 1) *
(e & 256 ? 1e256 : 1);
e = -e;
return m * (e & 1 ? 1e-1 : 1) * (e & 2 ? 1e-2 : 1) * (e & 4 ? 1e-4 : 1) *
(e & 8 ? 1e-8 : 1) * (e & 16 ? 1e-16 : 1) * (e & 32 ? 1e-32 : 1) *
(e & 64 ? 1e-64 : 1) * (e & 128 ? 1e-128 : 1) *
(e & 256 ? 1e-256 : 1);
}
static T nan() {
return Polyfills::nan<T>();
}
static T inf() {
return Polyfills::inf<T>();
}
};
#endif
template <typename T>
struct FloatTraits<T, 4 /*32bits*/> {
typedef int32_t mantissa_type;
static const short mantissa_bits = 23;
static const mantissa_type mantissa_max =
(static_cast<mantissa_type>(1) << mantissa_bits) - 1;
typedef int8_t exponent_type;
static const exponent_type exponent_max = 38;
template <typename TExponent>
static T make_float(T m, TExponent e) {
if (e > 0)
return m * (e & 1 ? 1e1f : 1) * (e & 2 ? 1e2f : 1) * (e & 4 ? 1e4f : 1) *
(e & 8 ? 1e8f : 1) * (e & 16 ? 1e16f : 1) * (e & 32 ? 1e32f : 1);
e = -e;
return m * (e & 1 ? 1e-1f : 1) * (e & 2 ? 1e-2f : 1) * (e & 4 ? 1e-4f : 1) *
(e & 8 ? 1e-8f : 1) * (e & 16 ? 1e-16f : 1) * (e & 32 ? 1e-32f : 1);
}
static T nan() {
return Polyfills::nan<T>();
}
static T inf() {
return Polyfills::inf<T>();
}
};
}
}

View File

@ -26,6 +26,7 @@ Vagrant.configure(2) do |config|
echo "export PROJECT_NAME='arduinojson'" >> $HOME/.profile echo "export PROJECT_NAME='arduinojson'" >> $HOME/.profile
echo "export CC='clang'" >> $HOME/.profile echo "export CC='clang'" >> $HOME/.profile
echo "export CXX='clang++'" >> $HOME/.profile echo "export CXX='clang++'" >> $HOME/.profile
echo "export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/" >> $HOME/.profile
echo "Run /host/ArduinoJson/fuzzing/fuzz.sh" | sudo tee /etc/motd echo "Run /host/ArduinoJson/fuzzing/fuzz.sh" | sudo tee /etc/motd
SHELL SHELL

View File

@ -0,0 +1,81 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <gtest/gtest.h>
#include <ArduinoJson/Polyfills/isFloat.hpp>
using namespace ArduinoJson::Polyfills;
struct Polyfills_IsFloat_Tests : testing::Test {
void check(bool expected, const char* input) {
bool actual = isFloat(input);
EXPECT_EQ(expected, actual) << input;
}
};
#define TEST_(X) TEST_F(Polyfills_IsFloat_Tests, X)
TEST_(NoExponent) {
check(true, "3.14");
check(true, "-3.14");
check(true, "+3.14");
}
TEST_(IntegralPartMissing) {
check(true, ".14");
check(true, "-.14");
check(true, "+.14");
}
TEST_(FractionalPartMissing) {
check(true, "3.");
check(true, "-3.e14");
check(true, "+3.e-14");
}
TEST_(NoDot) {
check(true, "3e14");
check(true, "3e-14");
check(true, "3e+14");
}
TEST_(Integer) {
check(false, "14");
check(false, "-14");
check(false, "+14");
}
TEST_(ExponentMissing) {
check(false, "3.14e");
check(false, "3.14e-");
check(false, "3.14e+");
}
TEST_(JustASign) {
check(false, "-");
check(false, "+");
}
TEST_(Empty) {
check(false, "");
}
TEST_(NaN) {
check(true, "NaN");
check(false, "n");
check(false, "N");
check(false, "nan");
check(false, "-NaN");
check(false, "+NaN");
}
TEST_(Infinity) {
check(true, "Infinity");
check(true, "+Infinity");
check(true, "-Infinity");
check(false, "infinity");
check(false, "Inf");
}

View File

@ -0,0 +1,175 @@
// Copyright Benoit Blanchon 2014-2017
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <gtest/gtest.h>
#include <ArduinoJson/Polyfills/parseFloat.hpp>
using namespace ArduinoJson::Polyfills;
struct Polyfills_ParseFloat_Float_Tests : testing::Test {
void check(const char* input, float expected) {
float actual = parseFloat<float>(input);
EXPECT_FLOAT_EQ(expected, actual) << input;
}
void checkNaN(const char* input) {
float result = parseFloat<float>(input);
EXPECT_TRUE(result != result) << input;
}
void checkInf(const char* input, bool negative) {
float x = parseFloat<float>(input);
if (negative)
EXPECT_TRUE(x < 0) << input;
else
EXPECT_TRUE(x > 0) << input;
EXPECT_TRUE(x == x && x * 2 == x) << input;
}
};
#define TEST_FLOAT(X) TEST_F(Polyfills_ParseFloat_Float_Tests, X)
struct Polyfills_ParseFloat_Double_Tests : testing::Test {
void check(const char* input, double expected) {
double actual = parseFloat<double>(input);
EXPECT_DOUBLE_EQ(expected, actual) << input;
}
void checkNaN(const char* input) {
double result = parseFloat<double>(input);
EXPECT_TRUE(result != result) << input;
}
void checkInf(const char* input, bool negative) {
double x = parseFloat<double>(input);
if (negative)
EXPECT_TRUE(x < 0) << input;
else
EXPECT_TRUE(x > 0) << input;
EXPECT_TRUE(x == x && x * 2 == x) << input;
}
};
#define TEST_DOUBLE(X) TEST_F(Polyfills_ParseFloat_Double_Tests, X)
TEST_DOUBLE(Short_NoExponent) {
check("3.14", 3.14);
check("-3.14", -3.14);
check("+3.14", +3.14);
}
TEST_FLOAT(Float_Short_NoExponent) {
check("3.14", 3.14f);
check("-3.14", -3.14f);
check("+3.14", +3.14f);
}
TEST_DOUBLE(Short_NoDot) {
check("1E+308", 1E+308);
check("-1E+308", -1E+308);
check("+1E-308", +1E-308);
check("+1e+308", +1e+308);
check("-1e-308", -1e-308);
}
TEST_FLOAT(Short_NoDot) {
check("1E+38", 1E+38f);
check("-1E+38", -1E+38f);
check("+1E-38", +1E-38f);
check("+1e+38", +1e+38f);
check("-1e-38", -1e-38f);
}
TEST_FLOAT(Max) {
check("340.2823e+36", 3.402823e+38f);
check("34.02823e+37", 3.402823e+38f);
check("3.402823e+38", 3.402823e+38f);
check("0.3402823e+39", 3.402823e+38f);
check("00.3402823e+40", 3.402823e+38f);
check("000.3402823e+41", 3.402823e+38f);
}
TEST_DOUBLE(Max) {
check(".017976931348623147e+310", 1.7976931348623147e+308);
check(".17976931348623147e+309", 1.7976931348623147e+308);
check("1.7976931348623147e+308", 1.7976931348623147e+308);
check("17.976931348623147e+307", 1.7976931348623147e+308);
check("179.76931348623147e+306", 1.7976931348623147e+308);
}
TEST_DOUBLE(Min) {
check(".022250738585072014e-306", 2.2250738585072014e-308);
check(".22250738585072014e-307", 2.2250738585072014e-308);
check("2.2250738585072014e-308", 2.2250738585072014e-308);
check("22.250738585072014e-309", 2.2250738585072014e-308);
check("222.50738585072014e-310", 2.2250738585072014e-308);
}
TEST_DOUBLE(VeryLong) {
check("0.00000000000000000000000000000001", 1e-32);
check("100000000000000000000000000000000.0", 1e+32);
check("100000000000000000000000000000000.00000000000000000000000000000",
1e+32);
}
TEST_FLOAT(VeryLong) {
check("0.00000000000000000000000000000001", 1e-32f);
check("100000000000000000000000000000000.0", 1e+32f);
check("100000000000000000000000000000000.00000000000000000000000000000",
1e+32f);
}
TEST_DOUBLE(MantissaTooLongToFit) {
check("0.179769313486231571111111111111", 0.17976931348623157);
check("17976931348623157.11111111111111", 17976931348623157.0);
check("1797693.134862315711111111111111", 1797693.1348623157);
check("-0.179769313486231571111111111111", -0.17976931348623157);
check("-17976931348623157.11111111111111", -17976931348623157.0);
check("-1797693.134862315711111111111111", -1797693.1348623157);
}
TEST_FLOAT(MantissaTooLongToFit) {
check("0.340282346638528861111111111111", 0.34028234663852886f);
check("34028234663852886.11111111111111", 34028234663852886.0f);
check("34028234.66385288611111111111111", 34028234.663852886f);
check("-0.340282346638528861111111111111", -0.34028234663852886f);
check("-34028234663852886.11111111111111", -34028234663852886.0f);
check("-34028234.66385288611111111111111", -34028234.663852886f);
}
TEST_DOUBLE(ExponentTooBig) {
checkInf("1e309", false);
checkInf("-1e309", true);
checkInf("1e65535", false);
check("1e-65535", 0.0);
}
TEST_FLOAT(ExponentTooBig) {
checkInf("1e39", false);
checkInf("-1e39", true);
checkInf("1e255", false);
check("1e-255", 0.0f);
}
TEST_DOUBLE(NaN) {
checkNaN("NaN");
checkNaN("nan");
}
TEST_FLOAT(NaN) {
checkNaN("NaN");
checkNaN("nan");
}
TEST_FLOAT(Infinity) {
checkInf("Infinity", false);
checkInf("+Infinity", false);
checkInf("-Infinity", true);
checkInf("inf", false);
checkInf("+inf", false);
checkInf("-inf", true);
}