// __ _____ _____ _____ // __| | __| | | | 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 /* This file implements a parser test suitable for fuzz testing. Given a byte array data, it performs the following steps: - j0 = from_bjdata(data, allow_exceptions = false) - j1 = from_bjdata(data) - assert(j0 is discarded if parsing j1 fails, and j0 == j1 otherwise) - 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)) must equal j2 (and likewise for j3, j4). Byte-exact stability does not hold in general, because a BJData value can lose type fidelity across a round trip (e.g. a binary_t value serialized without the optimized "$U#" array header is parsed back as a plain array of numbers, see #5398 and the discussion on PR #5494) - the numeric value is preserved, but the writer's smallest-type selection for the now-plain numbers may legitimately pick a different, but equally valid, single-byte type marker than the dedicated binary-data writer would have. Both encodings are valid BJData and both decode to the same value, so this is not treated as a round-trip failure here. "Value-stable" is checked by comparing dump()s rather than with operator== directly: a BJData/UBJSON payload can decode to a non-finite double (NaN or +-Infinity), and IEEE 754 NaN is never equal to itself, so operator== would report two structurally-identical trees as different whenever a NaN is involved -- not a round-trip bug, just NaN's ordinary (non-)reflexivity. dump() serializes any non-finite double the same deterministic way (as JSON `null`, since JSON itself cannot represent NaN/Infinity), so comparing dumps is stable under exactly the same values that break operator==. The unit tests run the same checks on a fixed corpus (see the "BJData round-trip invariants" test case), so keep both in sync. Finally, it reads the data from a stream, a non-contiguous iterator range, and a FILE*, which read byte by byte, and checks that each gives the same value or error (id, message, byte position) as the pointer range, which takes the bulk fast paths (see check_input_adapters()). The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ #include "fuzzer_common.hpp" namespace { // json::from_bjdata for check_input_adapters() struct read_bjdata { template json operator()(Args&& ... args) const { return json::from_bjdata(std::forward(args)...); } }; } // namespace // value-stable comparison for the round-trip checks below; see the note // above on why this compares dump()s rather than the json values directly static bool is_value_stable(const json& lhs, const json& rhs) { return lhs.dump() == rhs.dump(); } // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); // step 0: parse input without exceptions bool noexcept_threw = false; json const j_noexcept = parse_without_exceptions([&] { return json::from_bjdata(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; try { // step 1: parse input json const j1 = json::from_bjdata(vec1); parsed = true; // without exceptions, the same input must give the same value assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1)); try { // step 2.1: round trip without adding size annotations to container types std::vector const vec2 = json::to_bjdata(j1, false, false); // step 2.2: round trip with adding size annotations but without adding type annotations to container types std::vector const vec3 = json::to_bjdata(j1, true, false); // step 2.3: round trip with adding size as well as type annotations to container types std::vector const vec4 = json::to_bjdata(j1, true, true); // parse serialization json const j2 = json::from_bjdata(vec2); json const j3 = json::from_bjdata(vec3); json const j4 = json::from_bjdata(vec4); // re-serializing must be value-stable (see the notes above on // why byte-exact stability is not guaranteed in general, and // why this compares dump()s rather than the values directly) assert(is_value_stable(json::from_bjdata(json::to_bjdata(j2, false, false)), j2)); assert(is_value_stable(json::from_bjdata(json::to_bjdata(j3, true, false)), j3)); assert(is_value_stable(json::from_bjdata(json::to_bjdata(j4, true, true)), j4)); } catch (const json::parse_error&) { // parsing a BJData serialization must not fail assert(false); } } catch (const json::parse_error&) { // parse errors are ok, because input may be random bytes assert(parsed || noexcept_threw || j_noexcept.is_discarded()); } catch (const json::type_error&) { // type errors can occur during parsing, too assert(parsed || noexcept_threw || j_noexcept.is_discarded()); } catch (const json::out_of_range&) { // out of range errors may happen if provided sizes are excessive assert(parsed || noexcept_threw || j_noexcept.is_discarded()); } // step 3: the other input adapters must read the data alike check_input_adapters(data, size, read_bjdata()); // return 0 - non-zero return values are reserved for future use return 0; }