Merge remote-tracking branch 'origin/develop' into claude/fix-issue-3989-db7e45

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

# Conflicts:
#	include/nlohmann/detail/input/lexer.hpp
#	include/nlohmann/detail/input/parser.hpp
#	single_include/nlohmann/json.hpp
#	tests/src/fuzzer-parse_json.cpp
This commit is contained in:
Niels Lohmann committed 2026-10-01 07:36:30 +02:00
commit 5e93415d91
242 files changed
+2975 -12803

No files matched your search

+9 -11
View File
@@ -11,15 +11,15 @@ This file implements a parser test suitable for fuzz testing. Given a byte
array data, it performs the following steps:
- j1 = from_bjdata(data)
- vec = to_bjdata(j1)
- j2 = from_bjdata(vec)
- assert(j1 == j2)
- vec2 = to_bjdata(j1, use_size = true, use_type = false)
- j3 = from_bjdata(vec2)
- assert(j1 == j3)
- vec3 = to_bjdata(j1, use_size = true, use_type = true)
- j4 = from_bjdata(vec3)
- assert(j1 == j4)
- vec2 = to_bjdata(j1, use_size = false, use_type = false)
- vec3 = to_bjdata(j1, use_size = true, use_type = false)
- vec4 = to_bjdata(j1, use_size = true, use_type = true)
- j2 = from_bjdata(vec2)
- j3 = from_bjdata(vec3)
- j4 = from_bjdata(vec4)
- assert(from_bjdata(to_bjdata(j2, use_size = false, use_type = false)) is value-stable with j2)
- assert(from_bjdata(to_bjdata(j3, use_size = true, use_type = false)) is value-stable with j3)
- assert(from_bjdata(to_bjdata(j4, use_size = true, use_type = true)) is value-stable with j4)
Re-serializing j2/j3/j4 with the same use_size/use_type settings is checked
for value-stability rather than byte-exact stability: from_bjdata(to_bjdata(j2))
@@ -54,8 +54,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+1 -2
View File
@@ -13,7 +13,7 @@ array data, it performs the following steps:
- j1 = from_bon8(data)
- vec = to_bon8(j1)
- j2 = from_bon8(vec)
- assert(j1 == j2)
- assert(to_bon8(j2) == vec)
It also checks that reading the data from a stream, which reads strings byte by
byte, gives the same value or error as reading it from contiguous memory, which
@@ -28,7 +28,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
+1 -8
View File
@@ -13,7 +13,7 @@ array data, it performs the following steps:
- j1 = from_bson(data)
- vec = to_bson(j1)
- j2 = from_bson(vec)
- assert(j1 == j2)
- assert(to_bson(j2) == vec)
Furthermore, it reads data with a SAX parser that recovers from every error
and checks that the events are balanced, that reading ends, and that it
@@ -24,8 +24,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
@@ -50,11 +48,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
json const j1 = json::from_bson(vec1);
assert(recovered_without_errors);
if (j1.is_discarded())
{
return 0;
}
try
{
// step 2: round trip
+1 -3
View File
@@ -13,7 +13,7 @@ array data, it performs the following steps:
- j1 = from_cbor(data)
- vec = to_cbor(j1)
- j2 = from_cbor(vec)
- assert(j1 == j2)
- assert(to_cbor(j2) == vec)
Furthermore, it reads data with a SAX parser that recovers from every error
and checks that the events are balanced, that reading ends, and that it
@@ -24,8 +24,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
-3
View File
@@ -25,9 +25,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <string>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+1 -3
View File
@@ -13,7 +13,7 @@ array data, it performs the following steps:
- j1 = from_msgpack(data)
- vec = to_msgpack(j1)
- j2 = from_msgpack(vec)
- assert(j1 == j2)
- assert(to_msgpack(j2) == vec)
Furthermore, it reads data with a SAX parser that recovers from every error
and checks that the events are balanced, that reading ends, and that it
@@ -24,8 +24,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+9 -11
View File
@@ -11,15 +11,15 @@ This file implements a parser test suitable for fuzz testing. Given a byte
array data, it performs the following steps:
- j1 = from_ubjson(data)
- vec = to_ubjson(j1)
- j2 = from_ubjson(vec)
- assert(j1 == j2)
- vec2 = to_ubjson(j1, use_size = true, use_type = false)
- j3 = from_ubjson(vec2)
- assert(j1 == j3)
- vec3 = to_ubjson(j1, use_size = true, use_type = true)
- j4 = from_ubjson(vec3)
- assert(j1 == j4)
- vec2 = to_ubjson(j1, use_size = false, use_type = false)
- vec3 = to_ubjson(j1, use_size = true, use_type = false)
- vec4 = to_ubjson(j1, use_size = true, use_type = true)
- j2 = from_ubjson(vec2)
- j3 = from_ubjson(vec3)
- j4 = from_ubjson(vec4)
- assert(to_ubjson(j2, use_size = false, use_type = false) == vec2)
- assert(to_ubjson(j3, use_size = true, use_type = false) == vec3)
- assert(to_ubjson(j4, use_size = true, use_type = true) == vec4)
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
invariants" test case), so keep both in sync.
@@ -33,8 +33,6 @@ drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
+7 -6
View File
@@ -19,13 +19,14 @@
#include <nlohmann/json.hpp>
// Values for the round-trip property tests of the UBJSON and BJData writers.
// Values for the round-trip property tests of the binary format writers
// (BJData, BON8, BSON, CBOR, MessagePack and UBJSON).
//
// The fuzzer drivers (tests/src/fuzzer-parse_ubjson.cpp and
// fuzzer-parse_bjdata.cpp) check that anything the library parses can be
// serialized, parsed back, and serialized again without loss. Those checks
// only run at OSS-Fuzz, so a regression used to surface days later as an
// external report. The unit tests run the same checks on this corpus in CI.
// The fuzzer drivers (tests/src/fuzzer-parse_*.cpp) check that anything the
// library parses can be serialized, parsed back, and serialized again
// without loss. Those checks only run at OSS-Fuzz, so a regression used to
// surface days later as an external report. The unit tests run the same
// checks on this corpus in CI.
//
// The corpus is deterministic: std::mt19937's output sequence is fixed by
// the standard, and it is used directly rather than through a distribution
+99
View File
@@ -0,0 +1,99 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
namespace utils
{
/// a SAX event consumer that stops accepting events after a fixed count,
/// used by the binary-format tests to check behavior when the SAX consumer
/// rejects an event partway through parsing
class SaxCountdown
{
public:
using json = nlohmann::json;
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace utils
+4 -73
View File
@@ -12,80 +12,11 @@
using nlohmann::json;
#include <climits> // SIZE_MAX
#include <limits> // numeric_limits
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}
// JSON_32bitTest=ONLY builds only this file, so it must keep its own
// include of the shared trait/TEST_CASE_TEMPLATE_DEFINE rather than relying
// on unit-bjdata.cpp to provide it
#include "value_in_range_of_test.hpp"
TEST_CASE("32bit")
{
+5 -150
View File
@@ -21,158 +21,13 @@ using nlohmann::json;
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
// at some point in the future, a unit test dedicated to type traits might be a good idea
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}
// trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE
// are shared with unit-32bit.cpp
#include "value_in_range_of_test.hpp"
// NOLINTNEXTLINE(bugprone-throwing-static-initialization)
TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
+36 -75
View File
@@ -21,85 +21,13 @@ using nlohmann::json;
#include <string>
#include <vector>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
using bytes = std::vector<std::uint8_t>;
/// @return the string with the given bytes
@@ -817,6 +745,39 @@ TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
CHECK((parsed.is_object() || parsed.is_array()));
}
TEST_CASE("BON8 round-trip invariants")
{
// This checks what the parse_bon8_fuzzer driver checks (see
// tests/src/fuzzer-parse_bon8.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_bon8() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes. The stream-versus-contiguous input check the driver
// also performs is not covered here (see #5601).
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_bon8() returns it
j1 = json::from_bon8(json::to_bon8(j0));
}
catch (const json::exception&)
{
// BON8 cannot represent an unsigned integer above INT64_MAX, and
// the fuzzer driver only ever sees values from_bon8() actually
// produced, so skip such corpus values here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_bon8(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_bon8(vec));
CHECK(json::to_bon8(j2) == vec);
}
}
TEST_CASE("BON8 roundtrips" * doctest::skip())
{
SECTION("input from HikoGUI")
+41 -77
View File
@@ -17,7 +17,10 @@ using nlohmann::json;
#include <sstream>
#include <vector>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
@@ -861,83 +864,6 @@ TEST_CASE("BSON input/output_adapters")
}
}
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("Incomplete BSON Input")
{
@@ -1692,6 +1618,44 @@ TEST_CASE("Parse BSON directly from a file using iterator and sentinel")
CHECK(parsed == expected);
}
TEST_CASE("BSON round-trip invariants")
{
// This checks what the parse_bson_fuzzer driver checks (see
// tests/src/fuzzer-parse_bson.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_bson() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes. BSON only serializes objects, so non-object corpus
// values are skipped.
for (const auto& j0 : utils::round_trip_corpus::values())
{
if (!j0.is_object())
{
continue;
}
json j1;
try
{
// turn the corpus value into a value as from_bson() returns it
j1 = json::from_bson(json::to_bson(j0));
}
catch (const json::exception&)
{
// the fuzzer driver only ever sees values from_bson() actually
// produced, so skip corpus values that do not survive the
// round trip here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_bson(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_bson(vec));
CHECK(json::to_bson(j2) == vec);
}
}
TEST_CASE("BSON roundtrips" * doctest::skip())
{
SECTION("reference files")
+41 -94
View File
@@ -18,85 +18,11 @@ using nlohmann::json;
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("CBOR")
{
@@ -2415,6 +2341,39 @@ TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays")
}
}
TEST_CASE("CBOR round-trip invariants")
{
// This checks what the parse_cbor_fuzzer driver checks (see
// tests/src/fuzzer-parse_cbor.cpp), so that a regression shows up in CI
// rather than as an OSS-Fuzz report: anything from_cbor() returns (j1)
// can be serialized, parsed back (j2), and serialized again to reproduce
// the exact bytes.
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_cbor() returns it
j1 = json::from_cbor(json::to_cbor(j0));
}
catch (const json::exception&)
{
// not every corpus value survives a CBOR round trip (e.g., a
// binary subtype is written with a tag the default tag handler
// then rejects); the fuzzer driver only ever sees values
// from_cbor() actually produced, so skip those here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_cbor(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_cbor(vec));
CHECK(json::to_cbor(j2) == vec);
}
}
TEST_CASE("CBOR roundtrips" * doctest::skip())
{
SECTION("input from flynn")
@@ -2774,33 +2733,21 @@ TEST_CASE("examples from RFC 8949 Appendix A")
CHECK(json::to_cbor(json::parse("1.1")) == std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}));
CHECK(json::parse("1.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})));
// half-precision float
//CHECK(json::to_cbor(json::parse("1.5")) == std::vector<uint8_t>({0xf9, 0x3e, 0x00}));
// the writer never emits half-precision floats, so these can only be decoded, not encoded
CHECK(json::parse("1.5") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3e, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("65504.0")) == std::vector<uint8_t>({0xf9, 0x7b, 0xff}));
CHECK(json::parse("65504.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff})));
//CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
CHECK(json::parse("100000.0") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00})));
//CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff})));
CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}));
CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})));
// half-precision float
//CHECK(json::to_cbor(json::parse("5.960464477539063e-8")) == std::vector<uint8_t>({0xf9, 0x00, 0x01}));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("0.00006103515625")) == std::vector<uint8_t>({0xf9, 0x04, 0x00}));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
// half-precision float
//CHECK(json::to_cbor(json::parse("-4.0")) == std::vector<uint8_t>({0xf9, 0xc4, 0x00}));
CHECK(json::parse("5.960464477539063e-8") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x01})));
CHECK(json::parse("0.00006103515625") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x04, 0x00})));
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
CHECK(json::to_cbor(json::parse("-4.1")) == std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}));
+73 -63
View File
@@ -1380,21 +1380,22 @@ TEST_CASE("value conversion")
SECTION("std::map")
{
j1.get<std::map<std::string, int>>();
j2.get<std::map<std::string, unsigned int>>();
j3.get<std::map<std::string, double>>();
j4.get<std::map<std::string, bool>>();
j5.get<std::map<std::string, std::string>>();
CHECK(j1.get<std::map<std::string, int>>() == (std::map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::map<std::string, unsigned int>>() == (std::map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::map<std::string, double>>() == (std::map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::map<std::string, bool>>() == (std::map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
CHECK(j5.get<std::map<std::string, std::string>>() == (std::map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
}
SECTION("std::unordered_map")
{
j1.get<std::unordered_map<std::string, int>>();
j2.get<std::unordered_map<std::string, unsigned int>>();
j3.get<std::unordered_map<std::string, double>>();
j4.get<std::unordered_map<std::string, bool>>();
j5.get<std::unordered_map<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
CHECK(j1.get<std::unordered_map<std::string, int>>() == (std::unordered_map<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::unordered_map<std::string, unsigned int>>() == (std::unordered_map<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::unordered_map<std::string, double>>() == (std::unordered_map<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::unordered_map<std::string, bool>>() == (std::unordered_map<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::unordered_map<std::string, std::string>>();
CHECK(m5 == (std::unordered_map<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.at("one") == "eins");
}
SECTION("reserve is called on containers that support it (#5406)")
@@ -1430,22 +1431,24 @@ TEST_CASE("value conversion")
SECTION("std::multimap")
{
j1.get<std::multimap<std::string, int>>();
j2.get<std::multimap<std::string, unsigned int>>();
j3.get<std::multimap<std::string, double>>();
j4.get<std::multimap<std::string, bool>>();
j5.get<std::multimap<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
CHECK(j1.get<std::multimap<std::string, int>>() == (std::multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::multimap<std::string, unsigned int>>() == (std::multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::multimap<std::string, double>>() == (std::multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::multimap<std::string, bool>>() == (std::multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::multimap<std::string, std::string>>();
CHECK(m5 == (std::multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.find("one")->second == "eins");
}
SECTION("std::unordered_multimap")
{
j1.get<std::unordered_multimap<std::string, int>>();
j2.get<std::unordered_multimap<std::string, unsigned int>>();
j3.get<std::unordered_multimap<std::string, double>>();
j4.get<std::unordered_multimap<std::string, bool>>();
j5.get<std::unordered_multimap<std::string, std::string>>();
// CHECK(m5["one"] == "eins");
CHECK(j1.get<std::unordered_multimap<std::string, int>>() == (std::unordered_multimap<std::string, int> {{"one", 1}, {"two", 2}, {"three", 3}}));
CHECK(j2.get<std::unordered_multimap<std::string, unsigned int>>() == (std::unordered_multimap<std::string, unsigned int> {{"one", 1u}, {"two", 2u}, {"three", 3u}}));
CHECK(j3.get<std::unordered_multimap<std::string, double>>() == (std::unordered_multimap<std::string, double> {{"one", 1.1}, {"two", 2.2}, {"three", 3.3}}));
CHECK(j4.get<std::unordered_multimap<std::string, bool>>() == (std::unordered_multimap<std::string, bool> {{"one", true}, {"two", false}, {"three", true}}));
const auto m5 = j5.get<std::unordered_multimap<std::string, std::string>>();
CHECK(m5 == (std::unordered_multimap<std::string, std::string> {{"one", "eins"}, {"two", "zwei"}, {"three", "drei"}}));
CHECK(m5.find("one")->second == "eins");
}
SECTION("exception in case of a non-object type")
@@ -1466,29 +1469,30 @@ TEST_CASE("value conversion")
SECTION("std::list")
{
j1.get<std::list<int>>();
j2.get<std::list<unsigned int>>();
j3.get<std::list<double>>();
j4.get<std::list<bool>>();
j5.get<std::list<std::string>>();
CHECK(j1.get<std::list<int>>() == (std::list<int> {1, 2, 3, 4}));
CHECK(j2.get<std::list<unsigned int>>() == (std::list<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::list<double>>() == (std::list<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::list<bool>>() == (std::list<bool> {true, false, true}));
CHECK(j5.get<std::list<std::string>>() == (std::list<std::string> {"one", "two", "three"}));
}
SECTION("std::forward_list")
{
j1.get<std::forward_list<int>>();
j2.get<std::forward_list<unsigned int>>();
j3.get<std::forward_list<double>>();
j4.get<std::forward_list<bool>>();
j5.get<std::forward_list<std::string>>();
CHECK(j1.get<std::forward_list<int>>() == (std::forward_list<int> {1, 2, 3, 4}));
CHECK(j2.get<std::forward_list<unsigned int>>() == (std::forward_list<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::forward_list<double>>() == (std::forward_list<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::forward_list<bool>>() == (std::forward_list<bool> {true, false, true}));
CHECK(j5.get<std::forward_list<std::string>>() == (std::forward_list<std::string> {"one", "two", "three"}));
}
SECTION("std::array")
{
j1.get<std::array<int, 4>>();
j2.get<std::array<unsigned int, 3>>();
j3.get<std::array<double, 4>>();
j4.get<std::array<bool, 3>>();
j5.get<std::array<std::string, 3>>();
CHECK(j1.get<std::array<int, 4>>() == (std::array<int, 4> {{1, 2, 3, 4}}));
// only the first 3 elements of j2 are converted, since the target array is smaller
CHECK(j2.get<std::array<unsigned int, 3>>() == (std::array<unsigned int, 3> {{1u, 2u, 3u}}));
CHECK(j3.get<std::array<double, 4>>() == (std::array<double, 4> {{1.2, 2.3, 3.4, 4.5}}));
CHECK(j4.get<std::array<bool, 3>>() == (std::array<bool, 3> {{true, false, true}}));
CHECK(j5.get<std::array<std::string, 3>>() == (std::array<std::string, 3> {{"one", "two", "three"}}));
SECTION("std::array is larger than JSON")
{
@@ -1508,47 +1512,53 @@ TEST_CASE("value conversion")
SECTION("std::valarray")
{
j1.get<std::valarray<int>>();
j2.get<std::valarray<unsigned int>>();
j3.get<std::valarray<double>>();
j4.get<std::valarray<bool>>();
j5.get<std::valarray<std::string>>();
// valarray has no operator== that returns bool, so compare via a vector copy
const auto v1 = j1.get<std::valarray<int>>();
CHECK((std::vector<int>(std::begin(v1), std::end(v1)) == std::vector<int> {1, 2, 3, 4}));
const auto v2 = j2.get<std::valarray<unsigned int>>();
CHECK((std::vector<unsigned int>(std::begin(v2), std::end(v2)) == std::vector<unsigned int> {1u, 2u, 3u, 4u}));
const auto v3 = j3.get<std::valarray<double>>();
CHECK((std::vector<double>(std::begin(v3), std::end(v3)) == std::vector<double> {1.2, 2.3, 3.4, 4.5}));
const auto v4 = j4.get<std::valarray<bool>>();
CHECK((std::vector<bool>(std::begin(v4), std::end(v4)) == std::vector<bool> {true, false, true}));
const auto v5 = j5.get<std::valarray<std::string>>();
CHECK((std::vector<std::string>(std::begin(v5), std::end(v5)) == std::vector<std::string> {"one", "two", "three"}));
}
SECTION("std::vector")
{
j1.get<std::vector<int>>();
j2.get<std::vector<unsigned int>>();
j3.get<std::vector<double>>();
j4.get<std::vector<bool>>();
j5.get<std::vector<std::string>>();
CHECK(j1.get<std::vector<int>>() == (std::vector<int> {1, 2, 3, 4}));
CHECK(j2.get<std::vector<unsigned int>>() == (std::vector<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::vector<double>>() == (std::vector<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::vector<bool>>() == (std::vector<bool> {true, false, true}));
CHECK(j5.get<std::vector<std::string>>() == (std::vector<std::string> {"one", "two", "three"}));
}
SECTION("std::deque")
{
j1.get<std::deque<int>>();
j2.get<std::deque<unsigned int>>();
j2.get<std::deque<double>>();
j4.get<std::deque<bool>>();
j5.get<std::deque<std::string>>();
CHECK(j1.get<std::deque<int>>() == (std::deque<int> {1, 2, 3, 4}));
CHECK(j2.get<std::deque<unsigned int>>() == (std::deque<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::deque<double>>() == (std::deque<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::deque<bool>>() == (std::deque<bool> {true, false, true}));
CHECK(j5.get<std::deque<std::string>>() == (std::deque<std::string> {"one", "two", "three"}));
}
SECTION("std::set")
{
j1.get<std::set<int>>();
j2.get<std::set<unsigned int>>();
j3.get<std::set<double>>();
j4.get<std::set<bool>>();
j5.get<std::set<std::string>>();
CHECK(j1.get<std::set<int>>() == (std::set<int> {1, 2, 3, 4}));
CHECK(j2.get<std::set<unsigned int>>() == (std::set<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::set<double>>() == (std::set<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::set<bool>>() == (std::set<bool> {true, false, true}));
CHECK(j5.get<std::set<std::string>>() == (std::set<std::string> {"one", "two", "three"}));
}
SECTION("std::unordered_set")
{
j1.get<std::unordered_set<int>>();
j2.get<std::unordered_set<unsigned int>>();
j3.get<std::unordered_set<double>>();
j4.get<std::unordered_set<bool>>();
j5.get<std::unordered_set<std::string>>();
CHECK(j1.get<std::unordered_set<int>>() == (std::unordered_set<int> {1, 2, 3, 4}));
CHECK(j2.get<std::unordered_set<unsigned int>>() == (std::unordered_set<unsigned int> {1u, 2u, 3u, 4u}));
CHECK(j3.get<std::unordered_set<double>>() == (std::unordered_set<double> {1.2, 2.3, 3.4, 4.5}));
CHECK(j4.get<std::unordered_set<bool>>() == (std::unordered_set<bool> {true, false, true}));
CHECK(j5.get<std::unordered_set<std::string>>() == (std::unordered_set<std::string> {"one", "two", "three"}));
}
SECTION("std::map (array of pairs)")
+9 -31
View File
@@ -28,11 +28,6 @@ using nlohmann::json;
#include <string>
#include <valarray>
#if defined(_WIN32)
#define NOMINMAX
#include <windows.h> // for GetACP()
#endif
namespace
{
struct SaxEventLogger : public nlohmann::json_sax<json>
@@ -228,24 +223,6 @@ class proxy_iterator
iterator* m_it = nullptr;
};
// JSON_HAS_CPP_20
#if defined(__cpp_char8_t)
bool check_utf8()
{
#if defined(_WIN32)
// Runtime check of the active ANSI code page
// 65001 == UTF-8
return GetACP() == 65001;
#elif defined(__ICC) || defined(__INTEL_COMPILER)
// classic Intel ICC does not encode narrow string literals containing
// non-ASCII source characters as UTF-8, so comparing a decoded u8 literal
// against a narrow string literal containing the same characters fails
return false;
#else
return true;
#endif
}
#endif
} // namespace
TEST_CASE("deserialization")
@@ -1328,14 +1305,15 @@ TEST_CASE("deserialization")
CHECK(j1["key"] == "value");
CHECK(j1["num"] == 42);
// UTF-8 prefixed literal (C++20 and later);
// MSVC may not set /utf-8, so we need to check
if (check_utf8())
{
const auto j2 = u8R"({"emoji": "😀", "msg": "hello"})"_json;
CHECK(j2["emoji"] == "😀");
CHECK(j2["msg"] == "hello");
}
// UTF-8 prefixed literal (C++20 and later); the emoji is written as a
// \U escape rather than a raw multibyte character so this does not
// depend on the compiler's source-file encoding (e.g., MSVC without
// /utf-8, or classic ICC, which does not encode non-ASCII narrow
// string literals as UTF-8 - compare against a \x-escaped expectation
// for the same reason)
const auto j2 = u8"{\"emoji\": \"\U0001F600\", \"msg\": \"hello\"}"_json;
CHECK(j2["emoji"] == "\xF0\x9F\x98\x80");
CHECK(j2["msg"] == "hello");
const auto j3 = u8R"({"key": "value", "num": 42})"_json;
CHECK(j3["key"] == "value");
+25
View File
@@ -236,6 +236,31 @@ TEST_CASE("Regression tests for extended diagnostics")
}
}
SECTION("Regression test for issue #5641 - parent pointers after update()/merge_patch() with an aliasing argument")
{
// update()'s and merge_patch()'s argument may be *this or one of its
// descendants; the values moved out of the (temporary) copy must end
// up with their parent pointing at their new location in *this
{
json j = {{"a", {{"a", 1}, {"b", 2}}}};
j.update(j["a"]);
CHECK(j == json({{"a", 1}, {"b", 2}}));
// Must call operator[] on const element, otherwise m_parent gets updated.
auto const& constJ = j;
CHECK_THROWS_WITH_AS(constJ["a"].at(0), "[json.exception.type_error.304] (/a) cannot use at() with number", json::type_error);
}
{
json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
j.merge_patch(j["a"]);
CHECK(j == json({{"b", 2}}));
auto const& constJ = j;
CHECK_THROWS_WITH_AS(constJ["b"].at(0), "[json.exception.type_error.304] (/b) cannot use at() with number", json::type_error);
}
}
SECTION("Regression test for issue #3032 - Yet another assertion failure when inserting into arrays with JSON_DIAGNOSTICS set")
{
// reference operator[](size_type idx)
-7
View File
@@ -893,8 +893,6 @@ TEST_CASE("iterators 2")
CHECK(std::ranges::input_range<items_type>);
}
// libstdc++ algorithms don't work with Clang 15 (04/2022)
#if !DOCTEST_CLANG || (DOCTEST_CLANG && defined(__GLIBCXX__))
SECTION("algorithms")
{
SECTION("copy")
@@ -929,11 +927,7 @@ TEST_CASE("iterators 2")
CHECK(*it == 2);
}
}
#endif
// libstdc++ views don't work with Clang 15 (04/2022)
// libc++ hides limited ranges implementation behind guard macro
#if !(DOCTEST_CLANG && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)))
SECTION("views")
{
SECTION("reverse")
@@ -966,7 +960,6 @@ TEST_CASE("iterators 2")
CHECK(j_transformed == j_expected);
}
}
#endif
}
#endif
}
+153
View File
@@ -15,8 +15,65 @@ using nlohmann::json;
#endif
#include <fstream>
#include <string>
#include <vector>
#include "make_test_data_available.hpp"
namespace
{
// alternating objects and arrays nested `depth` levels deep, with members that
// depend on `variant` at some levels, so diffing two variants yields
// operations on many levels: replacing the innermost value, adding, removing,
// and (for ordered_json) reordering members, and changing array lengths
template<typename BasicJsonType>
BasicJsonType nested(const std::size_t depth, const int variant)
{
BasicJsonType value = variant;
for (std::size_t i = 0; i < depth; ++i)
{
if (i % 2 == 0)
{
BasicJsonType object = BasicJsonType::object();
if ((i + static_cast<std::size_t>(variant)) % 7 == 0)
{
object["x"] = i;
}
if (variant == 2 && i % 11 == 0)
{
object["z"] = "z";
}
object["a"] = std::move(value);
if (variant == 1 && i % 5 == 0)
{
object["y"] = 1;
}
value = std::move(object);
}
else
{
BasicJsonType array = BasicJsonType::array({std::move(value)});
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
{
array.push_back(i);
}
value = std::move(array);
}
}
return value;
}
// a path of `depth` reference tokens, as nested() nests its values
std::string nested_path(const std::size_t depth)
{
std::string path;
for (std::size_t i = depth; i > 0; --i)
{
path += (i - 1) % 2 == 0 ? "/a" : "/0";
}
return path;
}
} // namespace
TEST_CASE("JSON patch")
{
SECTION("examples from RFC 6902")
@@ -1752,6 +1809,102 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
}
}
TEST_CASE("JSON patch: diff of deeply nested values")
{
SECTION("the diff reproduces the target at every depth")
{
// depths on either side of the nesting depth up to which diff()
// recurses (detail::recursion_depth_limit(), 128); not every depth up
// to 300, as the test would then time out under Valgrind
std::vector<std::size_t> depths;
for (std::size_t depth = 0; depth <= 16; ++depth)
{
depths.push_back(depth);
}
for (std::size_t depth = 120; depth <= 136; ++depth)
{
depths.push_back(depth);
}
depths.push_back(300);
for (const auto depth : depths)
{
CAPTURE(depth);
for (int from = 0; from < 3; ++from)
{
for (int to = 0; to < 3; ++to)
{
CAPTURE(from);
CAPTURE(to);
const auto source = nested<json>(depth, from);
const auto target = nested<json>(depth, to);
const auto patch = json::diff(source, target);
CHECK(source.patch(patch) == target);
CHECK(patch.empty() == (from == to));
const auto ordered_source = nested<nlohmann::ordered_json>(depth, from);
const auto ordered_target = nested<nlohmann::ordered_json>(depth, to);
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
}
}
}
}
SECTION("a difference only in the innermost value is one replace operation")
{
for (std::size_t depth = 0; depth <= 300; ++depth)
{
CAPTURE(depth);
json source = 1;
json target = 2;
for (std::size_t i = 0; i < depth; ++i)
{
source = i % 2 == 0 ? json::object({{"a", std::move(source)}}) : json::array({std::move(source)});
target = i % 2 == 0 ? json::object({{"a", std::move(target)}}) : json::array({std::move(target)});
}
CHECK(json::diff(source, target, "/root") == json::array({{{"op", "replace"}, {"path", "/root" + nested_path(depth)}, {"value", 2}}}));
}
}
SECTION("values nested too deeply for the call stack (#5393)")
{
// diff() used to recurse once per nesting level, and compared the
// values with operator== on every level. The values are only
// parsed and diffed, never copied or compared, since those recurse
// too.
const std::size_t depth = 100000;
for (const bool objects :
{
false, true
})
{
CAPTURE(objects);
std::string source_text;
std::string target_text;
std::string equal_text;
std::string path;
for (std::size_t i = 0; i < depth; ++i)
{
source_text += objects ? "{\"a\":" : "[";
path += objects ? "/a" : "/0";
}
target_text = source_text + "2";
equal_text = source_text + "1";
source_text += "1";
const std::string closing(depth, objects ? '}' : ']');
const auto source = json::parse(source_text + closing);
const auto patch = json::diff(source, json::parse(target_text + closing));
REQUIRE(patch.size() == 1);
CHECK(patch[0]["op"] == "replace");
CHECK(patch[0]["path"] == path);
CHECK(patch[0]["value"] == 2);
CHECK(json::diff(source, json::parse(equal_text + closing)).empty());
}
}
}
TEST_CASE("JSON patch - diff() takes the fast path for non-reorderable object types (regression #5639)")
{
// #5465 added an order check to diff()'s object handling so a
+92
View File
@@ -14,7 +14,10 @@ using nlohmann::json;
#include <array>
#include <clocale>
#include <limits>
#include <map>
#include <ostream>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
@@ -385,3 +388,92 @@ TEST_CASE("locale with a multi-byte decimal point")
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
}
namespace
{
// a streambuf that switches LC_NUMERIC the first time anything is written to
// it, so a dump() in progress can be made to change locale mid-flight: after
// the serializer was constructed (and, before #5709 item 3, after it had
// cached std::localeconv() for the whole call) but before a later float is
// converted
struct LocaleSwitchingStreambuf final : std::streambuf
{
explicit LocaleSwitchingStreambuf(const char* switch_to)
: locale_after_first_write(switch_to)
{}
std::string data {}; // NOLINT(readability-redundant-member-init)
std::string locale_after_first_write;
bool switched = false;
std::streamsize xsputn(const char* s, std::streamsize n) override
{
if (!switched)
{
switched = std::setlocale(LC_NUMERIC, locale_after_first_write.c_str()) != nullptr;
}
data.append(s, static_cast<std::size_t>(n));
return n;
}
};
} // namespace
TEST_CASE("locale changes during a single dump() (#5709 item 3)")
{
// dump_float() only reads the locale on the snprintf path, taken for a
// number_float_t that is not an IEEE-754 single or double, i.e. not
// (is_iec559 && digits == 24 && max_exponent == 128) and not (is_iec559
// && digits == 53 && max_exponent == 1024) - see dump_float(). Checking
// is_iec559 alone is not enough: on x86_64, long double is a 64-bit
// (80-bit extended) format for which is_iec559 is also true, so it still
// takes the snprintf path this test means to exercise. Only a
// number_float_t whose digits/max_exponent match float or double (e.g.
// long double on 64-bit Arm, where it is IEEE-754 double) takes the
// locale-independent to_chars() path instead, and this test is a no-op
// there.
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
using ld_limits = std::numeric_limits<long_double_json::number_float_t>;
const bool is_ieee_single_or_double =
(ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) ||
(ld_limits::is_iec559 && ld_limits::digits == 53 && ld_limits::max_exponent == 1024);
if (is_ieee_single_or_double)
{
MESSAGE("long double is IEEE-754 single or double on this platform; dump_float()'s snprintf/locale path is not exercised here");
}
const char* de_DE_name = "de_DE.UTF-8";
if (std::setlocale(LC_NUMERIC, de_DE_name) == nullptr)
{
de_DE_name = "de_DE";
if (std::setlocale(LC_NUMERIC, de_DE_name) == nullptr)
{
MESSAGE("locale de_DE is not usable");
return;
}
}
const std::string decimal_point = std::localeconv()->decimal_point;
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
if (decimal_point != ",")
{
MESSAGE("de_DE's decimal point is not ',' on this platform, skipping");
return;
}
// a string long enough to overflow the serializer's internal write
// buffer, so that it is flushed to the output adapter - and the locale
// switched - before the number after it is converted
const std::string padding(5000, 'a');
const long_double_json j = { padding, 1234.5L };
LocaleSwitchingStreambuf buf(de_DE_name);
std::ostream os(&buf);
os << j;
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
REQUIRE(buf.switched);
// whatever locale was in effect when the float was actually converted,
// the output is normalized to use '.' as the decimal point: it must be
// looked up at conversion time, not once for the whole dump() - the same
// fix #5597 made on the parser side
CHECK(buf.data == "[\"" + padding + "\",1234.5]");
}
+49
View File
@@ -374,3 +374,52 @@ TEST_CASE("JSON Merge Patch and update on ordered_json")
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
}
}
TEST_CASE("merge_patch() with an argument that aliases *this (#5641)")
{
SECTION("j.merge_patch(j): erasing a member destroys the node the loop's iterator points to")
{
// reproduces issue #5641, case 1
json j = {{"a", nullptr}, {"b", 1}};
j.merge_patch(j);
CHECK(j == json({{"b", 1}}));
}
SECTION("j.merge_patch(j[\"a\"]): removing \"a\" destroys the patch while it is iterated")
{
// reproduces issue #5641, case 2
json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
j.merge_patch(j["a"]);
CHECK(j == json({{"b", 2}}));
}
SECTION("a patch nested past the iterative descent bound aliases *this")
{
// every depth on either side of where the iterative version takes
// over (detail::recursion_depth_limit(), 128); patching *this with
// itself is idempotent, aliased or not
for (const std::size_t depth :
{
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
})
{
CAPTURE(depth);
json j = json::parse(nested_objects(depth, 0));
const json expected = j;
j.merge_patch(j);
CHECK(j == expected);
}
}
SECTION("ordered_json")
{
using nlohmann::ordered_json;
SECTION("merge_patch with a member of *this")
{
ordered_json j = {{"a", {{"a", nullptr}, {"b", 2}}}};
j.merge_patch(j["a"]);
CHECK(j == ordered_json({{"b", 2}}));
}
}
}
+69
View File
@@ -1116,3 +1116,72 @@ TEST_CASE("update() on deeply nested values")
CHECK(p->at("y") == 2);
}
}
TEST_CASE("update() with an argument that aliases *this (#5641)")
{
SECTION("the target is checked before the argument, as before the copy")
{
json j = 1;
CHECK_THROWS_WITH_AS(j.update(json::array()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&);
CHECK_THROWS_WITH_AS(j.update(j.cbegin(), j.cend()), "[json.exception.type_error.312] cannot use update() with number", json::type_error&);
json k;
CHECK_THROWS_WITH_AS(k.update(json::array()), "[json.exception.type_error.312] cannot use update() with array", json::type_error&);
CHECK(k == json::object());
}
SECTION("const reference")
{
SECTION("j.update(j[\"a\"]): assigning into the argument's parent destroys it mid-iteration")
{
// reproduces issue #5641, case 3
json j = {{"a", {{"a", 1}, {"b", 2}}}};
j.update(j["a"]);
CHECK(j == json({{"a", 1}, {"b", 2}}));
}
SECTION("merge_objects with an argument that is a member of *this")
{
json j = {{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"b", 2}}}};
j.update(j["defaults"], true);
CHECK(j == json({{"defaults", {{"opts", {{"a", 1}}}}}, {"opts", {{"a", 1}, {"b", 2}}}}));
}
SECTION("ordered_json: inserting a new key relocates the vector behind the argument")
{
// reproduces issue #5641, case 4
using nlohmann::ordered_json;
ordered_json j = {{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}};
j.update(j["a"]);
CHECK(j == ordered_json({{"a", {{"x", 1}, {"y", 2}, {"z", 3}}}, {"x", 1}, {"y", 2}, {"z", 3}}));
}
}
SECTION("iterator range")
{
SECTION("range that is a member of *this")
{
json j = {{"a", {{"a", 1}, {"b", 2}}}};
j.update(j["a"].begin(), j["a"].end());
CHECK(j == json({{"a", 1}, {"b", 2}}));
}
}
SECTION("nested past the iterative descent bound aliases *this")
{
// every depth on either side of where the iterative version takes
// over (detail::recursion_depth_limit(), 128); merging *this into
// itself is idempotent, aliased or not
for (const std::size_t depth :
{
std::size_t{0}, std::size_t{127}, std::size_t{128}, std::size_t{300}
})
{
CAPTURE(depth);
json j = json::parse(nested_objects(depth, 0));
const json expected = j;
j.update(j, true);
CHECK(j == expected);
}
}
}
+35 -77
View File
@@ -21,85 +21,11 @@ using nlohmann::json;
#include <limits>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("MessagePack")
{
@@ -1930,6 +1856,38 @@ TEST_CASE("Parse MessagePack directly from a file using iterator and sentinel")
CHECK((parsed.is_object() || parsed.is_array()));
}
TEST_CASE("MessagePack round-trip invariants")
{
// This checks what the parse_msgpack_fuzzer driver checks (see
// tests/src/fuzzer-parse_msgpack.cpp), so that a regression shows up in
// CI rather than as an OSS-Fuzz report: anything from_msgpack() returns
// (j1) can be serialized, parsed back (j2), and serialized again to
// reproduce the exact bytes.
for (const auto& j0 : utils::round_trip_corpus::values())
{
json j1;
try
{
// turn the corpus value into a value as from_msgpack() returns it
j1 = json::from_msgpack(json::to_msgpack(j0));
}
catch (const json::exception&)
{
// the fuzzer driver only ever sees values from_msgpack() actually
// produced, so skip corpus values that do not survive the
// round trip here, too
continue;
}
INFO("j1 = " << j1.dump());
const std::vector<std::uint8_t> vec = json::to_msgpack(j1);
json j2;
// anything the library writes must be parsable by the library
REQUIRE_NOTHROW(j2 = json::from_msgpack(vec));
CHECK(json::to_msgpack(j2) == vec);
}
}
TEST_CASE("MessagePack roundtrips" * doctest::skip())
{
SECTION("input from msgpack-python")
+1 -1
View File
@@ -28,7 +28,7 @@ using nlohmann::json;
DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
DOCTEST_MSVC_SUPPRESS_WARNING(4189)
TEST_CASE("README" * doctest::skip())
TEST_CASE("README")
{
{
// redirect std::cout for the README file
+16
View File
@@ -1482,7 +1482,23 @@ TEST_CASE("regression tests 1")
SECTION("issue #972 - Segmentation fault on G++ when trying to assign json string literal to custom json type")
{
// this assignment used to crash outright
my_json const foo = R"([1, 2, 3])"_json;
// fifo_map is the adapter the docs recommend for keeping object keys
// in insertion order (see docs/mkdocs/docs/features/object_order.md
// and docs/mkdocs/docs/features/types/template_parameters.md); check
// that recommendation actually holds, including through erase() and
// inserting a new key. The comparator is stateful, so this avoids
// deep copies of "order" (see #1763, #5649).
my_json order = my_json::parse(R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
CHECK(order.dump() == R"({"z":1,"a":2,"m":{"y":1,"b":2}})");
order.erase("z");
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2}})");
order["new_key"] = 3;
CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2},"new_key":3})");
}
SECTION("issue #977 - Assigning between different json types")
-32
View File
@@ -213,28 +213,6 @@ struct adl_serializer<NonDefaultConstructible>
};
} // namespace nlohmann
/////////////////////////////////////////////////////////////////////
// for #2824
/////////////////////////////////////////////////////////////////////
class sax_no_exception : public nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>
{
public:
explicit sax_no_exception(json& j)
: nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type>(j, false)
{}
static bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& ex)
{
error_string = new std::string(ex.what()); // NOLINT(cppcoreguidelines-owning-memory)
return false;
}
static std::string* error_string;
};
std::string* sax_no_exception::error_string = nullptr;
/////////////////////////////////////////////////////////////////////
// for #2982
/////////////////////////////////////////////////////////////////////
@@ -751,16 +729,6 @@ TEST_CASE("regression tests 2")
}
}
SECTION("issue #2824 - encoding of json::exception::what()")
{
json j;
sax_no_exception sax(j);
CHECK(!json::sax_parse("xyz", &sax));
CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
}
SECTION("issue #2825 - Properly constrain the basic_json conversion operator")
{
static_assert(std::is_copy_assignable<nlohmann::ordered_json>::value, "ordered_json must be copy assignable");
+2 -77
View File
@@ -17,84 +17,9 @@ using nlohmann::json;
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
using utils::SaxCountdown;
namespace
{
class SaxCountdown
{
public:
explicit SaxCountdown(const int count) : events_left(count)
{}
bool null()
{
return events_left-- > 0;
}
bool boolean(bool /*unused*/)
{
return events_left-- > 0;
}
bool number_integer(json::number_integer_t /*unused*/)
{
return events_left-- > 0;
}
bool number_unsigned(json::number_unsigned_t /*unused*/)
{
return events_left-- > 0;
}
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
{
return events_left-- > 0;
}
bool string(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool binary(std::vector<std::uint8_t>& /*unused*/)
{
return events_left-- > 0;
}
bool start_object(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool key(std::string& /*unused*/)
{
return events_left-- > 0;
}
bool end_object()
{
return events_left-- > 0;
}
bool start_array(std::size_t /*unused*/)
{
return events_left-- > 0;
}
bool end_array()
{
return events_left-- > 0;
}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
{
return false;
}
private:
int events_left = 0;
};
} // namespace
TEST_CASE("UBJSON")
{
+63 -101
View File
@@ -11,135 +11,97 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
// ICPC errors out on multibyte character sequences in source files
#ifndef __INTEL_COMPILER
namespace
{
bool wstring_is_utf16();
bool wstring_is_utf16()
{
return (std::wstring(L"💩") == std::wstring(L"\U0001F4A9"));
}
bool u16string_is_utf16();
bool u16string_is_utf16()
{
return (std::u16string(u"💩") == std::u16string(u"\U0001F4A9"));
}
bool u32string_is_utf32();
bool u32string_is_utf32()
{
return (std::u32string(U"💩") == std::u32string(U"\U0001F4A9"));
}
} // namespace
TEST_CASE("wide strings")
{
SECTION("std::wstring")
{
if (wstring_is_utf16())
{
std::wstring const w = L"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
// U+10C5 U+0061(a) U+00E4 U+00F6 U+1F4A4 U+1F9E2, written with \u/\U
// escapes rather than as raw multibyte characters so this file
// compiles on toolchains (e.g. classic ICC) that error out on
// multibyte character sequences in source files
std::wstring const w = L"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
}
SECTION("invalid std::wstring")
{
if (wstring_is_utf16())
{
std::wstring const w = L"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
std::wstring const w = L"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[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: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[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: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[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: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[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: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(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<wchar_t>(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<wchar_t>(0xD800), static_cast<wchar_t>(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
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
}
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(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<wchar_t>(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<wchar_t>(0xD800), static_cast<wchar_t>(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
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
}
SECTION("std::u16string")
{
if (u16string_is_utf16())
{
std::u16string const w = u"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
std::u16string const w = u"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
}
SECTION("invalid std::u16string")
{
if (u16string_is_utf16())
{
std::u16string const w = u"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
std::u16string const w = u"\"\xDBFF";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a lone low surrogate cannot start a pair
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: '\"<U+0000>'", 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: '\"<U+0000>'", 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: '\"<U+0000>'", 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
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, 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: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
}
// a lone low surrogate cannot start a pair
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: '\"<U+0000>'", 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: '\"<U+0000>'", 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: '\"<U+0000>'", 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
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, 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: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
}
SECTION("std::u32string")
{
if (u32string_is_utf32())
{
std::u32string const w = U"[12.2,\"Ⴥaäö💤🧢\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"Ⴥaäö💤🧢\"]");
}
std::u32string const w = U"[12.2,\"\u10C5a\u00E4\u00F6\U0001F4A4\U0001F9E2\"]";
json const j = json::parse(w);
CHECK(j.dump() == "[12.2,\"" "\xE1\x83\x85" "a" "\xC3\xA4" "\xC3\xB6" "\xF0\x9F\x92\xA4" "\xF0\x9F\xA7\xA2" "\"]");
}
SECTION("invalid std::u32string")
{
if (u32string_is_utf32())
{
std::u32string const w = U"\"\x110000";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
std::u32string const w = U"\"\x110000";
json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a code unit above U+10FFFF must not be narrowed onto the EOF
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
// let everything following it pass the strict end-of-input check
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
// a code unit above U+10FFFF must not be narrowed onto the EOF
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
// let everything following it pass the strict end-of-input check
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
// the same unit inside a string is reported as an ill-formed byte
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), 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: '\"\xFF'", json::parse_error&);
}
// the same unit inside a string is reported as an ill-formed byte
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), 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: '\"\xFF'", json::parse_error&);
}
}
#endif
+18
View File
@@ -7,4 +7,22 @@
// SPDX-License-Identifier: MIT
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
// libc++ annotates std::mutex for Clang's thread safety analysis, so -Weverything
// reports -Wthread-safety-negative for the locks in doctest's reporters, which are
// only compiled in this file. __has_warning keeps older Clang versions from
// reporting an unknown warning group.
#if defined(__clang__) && defined(__has_warning)
#if __has_warning("-Wthread-safety-negative")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wthread-safety-negative"
#endif
#endif
#include "doctest_compatibility.h"
#if defined(__clang__) && defined(__has_warning)
#if __has_warning("-Wthread-safety-negative")
#pragma clang diagnostic pop
#endif
#endif
+92
View File
@@ -0,0 +1,92 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
// shared between unit-32bit.cpp (which must keep including this header,
// because JSON_32bitTest=ONLY builds only that file) and unit-bjdata.cpp
#include <climits> // CHAR_BIT
#include <limits>
#include <string>
#include <type_traits>
#include <nlohmann/json.hpp>
template <typename OfType, typename T, bool MinInRange, bool MaxInRange>
struct trait_test_arg
{
using of_type = OfType;
using type = T;
static constexpr bool min_in_range = MinInRange;
static constexpr bool max_in_range = MaxInRange;
};
TEST_CASE_TEMPLATE_DEFINE("value_in_range_of trait", T, value_in_range_of_test) // NOLINT(readability-math-missing-parentheses)
{
using nlohmann::detail::value_in_range_of;
using of_type = typename T::of_type;
using type = typename T::type;
constexpr bool min_in_range = T::min_in_range;
constexpr bool max_in_range = T::max_in_range;
type const val_min = std::numeric_limits<type>::min();
type const val_min2 = val_min + 1;
type const val_max = std::numeric_limits<type>::max();
type const val_max2 = val_max - 1;
REQUIRE(CHAR_BIT == 8);
std::string of_type_str;
if (std::is_unsigned<of_type>::value)
{
of_type_str += "u";
}
of_type_str += "int";
of_type_str += std::to_string(sizeof(of_type) * 8);
INFO("of_type := ", of_type_str);
std::string type_str;
if (std::is_unsigned<type>::value)
{
type_str += "u";
}
type_str += "int";
type_str += std::to_string(sizeof(type) * 8);
INFO("type := ", type_str);
CAPTURE(val_min);
CAPTURE(min_in_range);
CAPTURE(val_max);
CAPTURE(max_in_range);
if (min_in_range)
{
CHECK(value_in_range_of<of_type>(val_min));
CHECK(value_in_range_of<of_type>(val_min2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_min));
CHECK_FALSE(value_in_range_of<of_type>(val_min2));
}
if (max_in_range)
{
CHECK(value_in_range_of<of_type>(val_max));
CHECK(value_in_range_of<of_type>(val_max2));
}
else
{
CHECK_FALSE(value_in_range_of<of_type>(val_max));
CHECK_FALSE(value_in_range_of<of_type>(val_max2));
}
}