diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index b2bfe3304..7305c9c07 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -15,6 +15,7 @@ using nlohmann::json; #include #include #include +#include #include #include "make_test_data_available.hpp" #include "test_utils.hpp" @@ -2133,6 +2134,13 @@ TEST_CASE("CBOR input that cannot be read is discarded by every overload") CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded()); CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded()); + + // a string that ends early, read through iterators that are not + // contiguous and have to be copied from one element at a time + const std::list truncated_string = {0x63, 'a', 'b'}; + CHECK(json::from_cbor(truncated_string.begin(), truncated_string.end(), true, false).is_discarded()); + const std::list complete_string = {0x63, 'a', 'b', 'c'}; + CHECK(json::from_cbor(complete_string.begin(), complete_string.end()) == "abc"); } TEST_CASE("CBOR SAX parsing stops at every event") diff --git a/tests/src/unit-class_const_iterator.cpp b/tests/src/unit-class_const_iterator.cpp index 9e5de42c7..b17e2df1a 100644 --- a/tests/src/unit-class_const_iterator.cpp +++ b/tests/src/unit-class_const_iterator.cpp @@ -43,6 +43,13 @@ TEST_CASE("const_iterator class") json::const_iterator const it(&j); json::const_iterator it2(&j); it2 = it; + + // assigning an iterator to itself leaves it unchanged + json const a = {1, 2, 3}; + json::const_iterator it3 = a.cbegin() + 1; + json::const_iterator& same = it3; + it3 = same; + CHECK(*it3 == 2); } SECTION("copy constructor from non-const iterator") diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index e89497738..cb8ceed6b 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -12,6 +12,7 @@ #include using nlohmann::json; +#include // FLT_EVAL_METHOD #include // strtod #include // stringstream #include // string @@ -657,3 +658,45 @@ TEST_CASE("lexer string fast path") } } } + +TEST_CASE("parse_float_fast declines what it cannot convert exactly") +{ + // The lexer only hands well-formed numbers to parse_float_fast, so the + // malformed ones below can only be passed to it directly. Declining is + // always safe: the caller then falls back to a slower, exact conversion. + const auto fast = [](const std::string & s, double & out) + { + return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out); + }; + double out = 0; + +#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 + // without true double precision, the fast path declines everything + CHECK_FALSE(fast("1.5", out)); +#else + CHECK(fast("1.5", out)); + CHECK(out == 1.5); + CHECK(fast("+2.5e1", out)); + CHECK(out == 25.0); + CHECK(fast("-25E-1", out)); + CHECK(out == -2.5); + CHECK(fast("1e", out)); + CHECK(out == 1.0); +#endif + + // not a number + CHECK_FALSE(fast("", out)); + CHECK_FALSE(fast("-", out)); + CHECK_FALSE(fast(".", out)); + CHECK_FALSE(fast("1.2.3", out)); + CHECK_FALSE(fast("1x", out)); + CHECK_FALSE(fast("1e+", out)); + CHECK_FALSE(fast("1e1x", out)); + + // numbers that are not represented exactly on the fast path + CHECK_FALSE(fast("12345678901234567890", out)); + CHECK_FALSE(fast("1e10000", out)); + CHECK_FALSE(fast("9007199254740993", out)); + CHECK_FALSE(fast("1e23", out)); + CHECK_FALSE(fast("1e-23", out)); +} diff --git a/tests/src/unit-diagnostic-positions.cpp b/tests/src/unit-diagnostic-positions.cpp index d607e935c..5326094c7 100644 --- a/tests/src/unit-diagnostic-positions.cpp +++ b/tests/src/unit-diagnostic-positions.cpp @@ -156,3 +156,38 @@ TEST_CASE("Better diagnostics with positions") #endif } } + +TEST_CASE("values read from a binary format have no positions") +{ + // only the JSON lexer knows where a value started and ended + const json source = {{"a", {1, "x", json::binary({1})}}, {"b", {{"c", true}}}, {"d", nullptr}, {"e", 1.5}}; + const std::vector cbor = json::to_cbor(source); + + const auto check_no_positions = [](const json & j) + { + CHECK(j.start_pos() == std::string::npos); + CHECK(j.end_pos() == std::string::npos); + CHECK(j.at("a").start_pos() == std::string::npos); + CHECK(j.at("a").at(1).end_pos() == std::string::npos); + CHECK(j.at("b").at("c").start_pos() == std::string::npos); + }; + + SECTION("DOM parser") + { + const json j = json::from_cbor(cbor); + CHECK(j == source); + check_no_positions(j); + } + + SECTION("DOM parser with a callback") + { + json j; + nlohmann::detail::json_sax_dom_callback_parser sdp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept + { + return true; + }); + CHECK(json::sax_parse(cbor, &sdp, json::input_format_t::cbor)); + CHECK(j == source); + check_no_positions(j); + } +} diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 0019d3b92..c2a113d06 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp @@ -158,6 +158,17 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)") json::sax_parse("12.34", &sax); CHECK(sax.float_string_copy == "12.34"); } + + SECTION("serializing a long double") + { + // a floating-point type that is not a float or a double is written + // with snprintf, whose locale-specific decimal point and thousands + // separator are undone afterwards + using long_double_json = nlohmann::basic_json; + CHECK(long_double_json(12345.5L).dump() == "12345.5"); + CHECK(long_double_json(1.0L).dump() == "1.0"); + CHECK(long_double_json(-0.25L).dump() == "-0.25"); + } } else { diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index a38df3aaa..6e0aff29e 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -70,6 +70,8 @@ TEST_CASE("wide strings") CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast(0xDC00), L'"'}), error_low_surrogate, json::parse_error&); // a high surrogate followed by a non-low-surrogate unit is invalid CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&); + // ... also when the unit is above the low surrogates + CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast(0xD800), static_cast(0xE000), L'"'}), error_high_surrogate, json::parse_error&); // a lone low surrogate must not swallow the following unit: pairing // it with any second unit would produce valid UTF-8, so the error // has to report an ill-formed byte at the surrogate's own position @@ -99,6 +101,8 @@ TEST_CASE("wide strings") CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); // a high surrogate followed by a non-low-surrogate unit is invalid CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); + // ... also when the unit is above the low surrogates + CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"'", json::parse_error&); // a lone low surrogate must not swallow the following unit: pairing // it with any second unit would produce valid UTF-8, so the error // has to report an ill-formed byte at the surrogate's own position