// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT #pragma once #include // uint8_t #include // vector #include "doctest_compatibility.h" #include #include "round_trip_corpus.hpp" // The BJData round-trip invariants, split by the BJData version that turns a // corpus value into j1 (unit-bjdata_round_trip_draft2.cpp and // unit-bjdata_round_trip_draft3.cpp). They were part of unit-bjdata.cpp, but // took 80 % of its time; as separate tests, both halves run in parallel and // stay below the ctest timeout under Valgrind. namespace utils { // This checks what the parse_bjdata_fuzzer driver checks (see // tests/src/fuzzer-parse_bjdata.cpp), so that a regression shows up in CI // rather than as an OSS-Fuzz report: every value from_bjdata() returns // (j1) can be serialized with any combination of options, the result can // be parsed back (j2), and serializing j2 again with the same options // yields a value-equal result. // // Beyond the driver, this also checks that j2 equals j1 and that // serializing j2 reproduces the exact bytes, both except for values that // contain a binary value: a binary value is only written as a binary // value with Draft 3's optimized binary array, and otherwise read back as // an array of integers, for which the writer may choose different (but // equally valid) type markers when it is serialized again (see #5494). // // Values are compared with dump() rather than operator==, because a NaN // never compares equal to itself. // // Only the corpus values written with @a initial_version become j1; each j1 // is then serialized with every combination of options, both versions // included. inline void check_bjdata_round_trip(const nlohmann::json::bjdata_version_t initial_version) { using nlohmann::json; struct options { bool use_size; bool use_type; json::bjdata_version_t version; }; const std::vector all_options = { {false, false, json::bjdata_version_t::draft2}, {true, false, json::bjdata_version_t::draft2}, {true, true, json::bjdata_version_t::draft2}, {false, false, json::bjdata_version_t::draft3}, {true, false, json::bjdata_version_t::draft3}, {true, true, json::bjdata_version_t::draft3}, }; for (const auto& j0 : utils::round_trip_corpus::values()) { // turn the corpus value into a value as from_bjdata() returns it for (const auto& initial : all_options) { if (initial.version != initial_version) { continue; } const json j1 = json::from_bjdata(json::to_bjdata(j0, initial.use_size, initial.use_type, initial.version)); const bool has_binary = utils::round_trip_corpus::contains_binary(j1); for (const auto& o : all_options) { INFO("j1 = " << j1.dump() << ", use_size = " << o.use_size << ", use_type = " << o.use_type << ", draft3 = " << (o.version == json::bjdata_version_t::draft3)); const std::vector vec = json::to_bjdata(j1, o.use_size, o.use_type, o.version); json j2; // anything the library writes must be parsable by the library REQUIRE_NOTHROW(j2 = json::from_bjdata(vec)); const std::vector vec2 = json::to_bjdata(j2, o.use_size, o.use_type, o.version); CHECK(json::from_bjdata(vec2).dump() == j2.dump()); if (!has_binary) { CHECK(j2.dump() == j1.dump()); CHECK(vec2 == vec); } } } } } } // namespace utils