💥 implemented new handling of NaN and INF #70 #329 #388

- If an overflow occurs during parsing a number from a JSON text, an
exception (std::out_of_range for the moment, to be replaced by a
user-defined exception #244) is thrown so that the overflow is detected
early and roundtripping is guaranteed.
- NaN and INF floating-point values can be stored in a JSON value and
are not replaced by null. That is, the basic_json class behaves like
double in this regard (no exception occurs). However, NaN and INF are
serialized to “null”.
- Adjusted test cases appropriately.
This commit is contained in:
Niels Lohmann 2017-03-12 18:38:05 +01:00
parent 9355f05888
commit 8feaf8dc94
No known key found for this signature in database
GPG key ID: 7F3CEA63AE251B69
8 changed files with 100 additions and 46 deletions

View file

@ -263,16 +263,8 @@ struct external_constructor<value_t::number_float>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
{ {
// replace infinity and NAN by null j.m_type = value_t::number_float;
if (not std::isfinite(val)) j.m_value = val;
{
j = BasicJsonType{};
}
else
{
j.m_type = value_t::number_float;
j.m_value = val;
}
j.assert_invariant(); j.assert_invariant();
} }
}; };
@ -6653,6 +6645,13 @@ class basic_json
*/ */
void dump_float(number_float_t x) void dump_float(number_float_t x)
{ {
// NaN / inf
if (not std::isfinite(x) or std::isnan(x))
{
o.write("null", 4);
return;
}
// special case for 0.0 and -0.0 // special case for 0.0 and -0.0
if (x == 0) if (x == 0)
{ {
@ -11425,11 +11424,10 @@ basic_json_parser_74:
result.m_type = value_t::number_float; result.m_type = value_t::number_float;
result.m_value = val; result.m_value = val;
// replace infinity and NAN by null // throw in case of infinity or NAN
if (not std::isfinite(result.m_value.number_float)) if (not std::isfinite(result.m_value.number_float))
{ {
result.m_type = value_t::null; JSON_THROW(std::out_of_range("number overflow: " + get_token_string()));
result.m_value = basic_json::json_value();
} }
return true; return true;

View file

@ -263,16 +263,8 @@ struct external_constructor<value_t::number_float>
template<typename BasicJsonType> template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
{ {
// replace infinity and NAN by null j.m_type = value_t::number_float;
if (not std::isfinite(val)) j.m_value = val;
{
j = BasicJsonType{};
}
else
{
j.m_type = value_t::number_float;
j.m_value = val;
}
j.assert_invariant(); j.assert_invariant();
} }
}; };
@ -6653,6 +6645,13 @@ class basic_json
*/ */
void dump_float(number_float_t x) void dump_float(number_float_t x)
{ {
// NaN / inf
if (not std::isfinite(x) or std::isnan(x))
{
o.write("null", 4);
return;
}
// special case for 0.0 and -0.0 // special case for 0.0 and -0.0
if (x == 0) if (x == 0)
{ {
@ -10459,11 +10458,10 @@ class basic_json
result.m_type = value_t::number_float; result.m_type = value_t::number_float;
result.m_value = val; result.m_value = val;
// replace infinity and NAN by null // throw in case of infinity or NAN
if (not std::isfinite(result.m_value.number_float)) if (not std::isfinite(result.m_value.number_float))
{ {
result.m_type = value_t::null; JSON_THROW(std::out_of_range("number overflow: " + get_token_string()));
result.m_value = basic_json::json_value();
} }
return true; return true;

View file

@ -33,6 +33,7 @@ SOFTWARE.
using nlohmann::json; using nlohmann::json;
#include <fstream> #include <fstream>
#include <cmath>
TEST_CASE("CBOR") TEST_CASE("CBOR")
{ {
@ -744,13 +745,17 @@ TEST_CASE("CBOR")
SECTION("infinity") SECTION("infinity")
{ {
json j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00})); json j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00}));
CHECK(j == nullptr); json::number_float_t d = j;
CHECK(not std::isfinite(d));
CHECK(j.dump() == "null");
} }
SECTION("NaN") SECTION("NaN")
{ {
json j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x01})); json j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x01}));
CHECK(j == nullptr); json::number_float_t d = j;
CHECK(std::isnan(d));
CHECK(j.dump() == "null");
} }
} }
} }
@ -1344,7 +1349,7 @@ TEST_CASE("CBOR roundtrips", "[hide]")
"test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json", //"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json",
@ -1356,9 +1361,9 @@ TEST_CASE("CBOR roundtrips", "[hide]")
"test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json", //"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json", //"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json",

View file

@ -272,7 +272,9 @@ TEST_CASE("parser class")
SECTION("overflow") SECTION("overflow")
{ {
CHECK(json::parser("1.18973e+4932").parse() == json()); // overflows during parsing yield an exception
CHECK_THROWS_AS(json::parser("1.18973e+4932").parse() == json(), std::out_of_range);
CHECK_THROWS_WITH(json::parser("1.18973e+4932").parse() == json(), "number overflow: 1.18973e+4932");
} }
SECTION("invalid numbers") SECTION("invalid numbers")

View file

@ -702,11 +702,17 @@ TEST_CASE("constructors")
SECTION("infinity") SECTION("infinity")
{ {
// infinity is stored as null // infinity is stored properly, but serialized to null
// should change in the future: https://github.com/nlohmann/json/issues/388
json::number_float_t n(std::numeric_limits<json::number_float_t>::infinity()); json::number_float_t n(std::numeric_limits<json::number_float_t>::infinity());
json j(n); json j(n);
CHECK(j.type() == json::value_t::null); CHECK(j.type() == json::value_t::number_float);
// check round trip of infinity
json::number_float_t d = j;
CHECK(d == n);
// check that inf is serialized to null
CHECK(j.dump() == "null");
} }
} }

View file

@ -1116,7 +1116,7 @@ TEST_CASE("MessagePack roundtrips", "[hide]")
"test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json", //"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json",
@ -1128,9 +1128,9 @@ TEST_CASE("MessagePack roundtrips", "[hide]")
"test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json", //"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json", //"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json",

View file

@ -49,6 +49,7 @@ TEST_CASE("regression tests")
SECTION("issue #70 - Handle infinity and NaN cases") SECTION("issue #70 - Handle infinity and NaN cases")
{ {
/*
SECTION("NAN value") SECTION("NAN value")
{ {
CHECK(json(NAN) == json()); CHECK(json(NAN) == json());
@ -60,6 +61,36 @@ TEST_CASE("regression tests")
CHECK(json(INFINITY) == json()); CHECK(json(INFINITY) == json());
CHECK(json(json::number_float_t(INFINITY)) == json()); CHECK(json(json::number_float_t(INFINITY)) == json());
} }
*/
// With 3.0.0, the semantics of this changed: NAN and infinity are
// stored properly inside the JSON value (no exception or conversion
// to null), but are serialized as null.
SECTION("NAN value")
{
json j1 = NAN;
CHECK(j1.is_number_float());
json::number_float_t f1 = j1;
CHECK(std::isnan(f1));
json j2 = json::number_float_t(NAN);
CHECK(j2.is_number_float());
json::number_float_t f2 = j2;
CHECK(std::isnan(f2));
}
SECTION("infinity")
{
json j1 = INFINITY;
CHECK(j1.is_number_float());
json::number_float_t f1 = j1;
CHECK(not std::isfinite(f1));
json j2 = json::number_float_t(INFINITY);
CHECK(j2.is_number_float());
json::number_float_t f2 = j2;
CHECK(not std::isfinite(f2));
}
} }
SECTION("pull request #71 - handle enum type") SECTION("pull request #71 - handle enum type")
@ -559,8 +590,8 @@ TEST_CASE("regression tests")
SECTION("issue #329 - serialized value not always can be parsed") SECTION("issue #329 - serialized value not always can be parsed")
{ {
json j = json::parse("22e2222"); CHECK_THROWS_AS(json::parse("22e2222"), std::out_of_range);
CHECK(j == json()); CHECK_THROWS_WITH(json::parse("22e2222"), "number overflow: 22e2222");
} }
SECTION("issue #366 - json::parse on failed stream gets stuck") SECTION("issue #366 - json::parse on failed stream gets stuck")

View file

@ -460,7 +460,6 @@ TEST_CASE("nst's JSONTestSuite")
"test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
"test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json",
@ -472,9 +471,7 @@ TEST_CASE("nst's JSONTestSuite")
"test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json",
"test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json",
@ -765,9 +762,6 @@ TEST_CASE("nst's JSONTestSuite")
{ {
for (auto filename : for (auto filename :
{ {
// we currently do not limit exponents
"test/data/nst_json_testsuite/test_parsing/i_number_neg_int_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/i_number_pos_double_huge_exp.json",
// we do not pose a limit on nesting // we do not pose a limit on nesting
"test/data/nst_json_testsuite/test_parsing/i_structure_500_nested_arrays.json", "test/data/nst_json_testsuite/test_parsing/i_structure_500_nested_arrays.json",
// we silently ignore BOMs // we silently ignore BOMs
@ -787,6 +781,26 @@ TEST_CASE("nst's JSONTestSuite")
} }
} }
// numbers that overflow during parsing
SECTION("i/y -> n (out of range)")
{
for (auto filename :
{
"test/data/nst_json_testsuite/test_parsing/i_number_neg_int_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/i_number_pos_double_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
"test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json"
}
)
{
CAPTURE(filename);
std::ifstream f(filename);
json j;
CHECK_THROWS_AS(j << f, std::out_of_range);
}
}
SECTION("i -> n") SECTION("i -> n")
{ {
for (auto filename : for (auto filename :