// __ _____ _____ _____ // __| | __| | | | 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. It checks that json_document (the zero-copy, read-only view of a parsed JSON text declared in json_view.hpp) agrees with basic_json on every input, for the parse options selected by the low bits of the first input byte (bit 0: ignore_comments, bit 1: ignore_trailing_commas; the byte stays part of the text): - json_document::accept(data) must equal json::accept(data) - if the input is accepted, json_document::parse(data).root().materialize() must equal json::parse(data) - if the input is rejected, json_document::parse(data) (with exceptions enabled) must throw a json::parse_error or json::out_of_range whose what() is identical to the one json::parse(data) throws This is checked for two kinds of input: a std::string, which the document borrows and which ends in the NUL the parser uses as sentinel, and an exact-size byte vector, which has no NUL after its last byte and takes the parser's bounds-checked path (AddressSanitizer reports any read past the end). The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ #include #include #include #include #include #include #include // the checks below are assertions; NDEBUG would compile them away #ifdef NDEBUG #error "the fuzzer drivers must be built without NDEBUG" #endif using json = nlohmann::json; using json_document = nlohmann::json_document; namespace { // what json::parse does with a text: the value, or the message of the exception struct reference_result { bool accepted = false; json value{}; std::string what{}; // NOLINT(readability-redundant-member-init) }; reference_result parse_reference(const std::uint8_t* data, std::size_t size, bool comments, bool trailing_commas) { reference_result r; r.accepted = json::accept(data, data + size, comments, trailing_commas); bool json_threw = false; try { r.value = json::parse(data, data + size, nullptr, true, comments, trailing_commas); } catch (const json::parse_error& e) { r.what = e.what(); json_threw = true; } catch (const json::out_of_range& e) { r.what = e.what(); json_threw = true; } // json::accept and json::parse must agree assert(json_threw == !r.accepted); static_cast(json_threw); return r; } // json_document must agree with the reference for this input (a container // that json_document::parse borrows) template void check_input(const Input& input, const reference_result& expected, bool comments, bool trailing_commas) { // json_document::accept must agree with json::accept on every input const bool accepted_by_view = json_document::accept(input, comments, trailing_commas); assert(expected.accepted == accepted_by_view); static_cast(accepted_by_view); if (expected.accepted) { // both parsers must agree on the resulting value json_document const doc = json_document::parse(input, true, comments, trailing_commas); assert(!doc.is_discarded()); json const j2 = doc.root().materialize(); assert(expected.value == j2); static_cast(j2); // (without exceptions, the same document) json_document const quiet = json_document::parse(input, false, comments, trailing_commas); assert(!quiet.is_discarded()); assert(quiet.node_count() == doc.node_count()); } else { // both parsers must reject the input the same way when exceptions are used bool view_threw = false; try { static_cast(json_document::parse(input, true, comments, trailing_commas)); } catch (const json::parse_error& e) { assert(e.what() == expected.what); view_threw = true; } catch (const json::out_of_range& e) { assert(e.what() == expected.what); view_threw = true; } assert(view_threw); static_cast(view_threw); // and without exceptions, the document is discarded json_document const quiet = json_document::parse(input, false, comments, trailing_commas); assert(quiet.is_discarded()); static_cast(quiet); } } } // namespace // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // the parse options are taken from the low bits of the first byte const bool comments = size > 0 && (data[0] & 1U) != 0; const bool trailing_commas = size > 0 && (data[0] & 2U) != 0; const reference_result expected = parse_reference(data, size, comments, trailing_commas); // a std::string: borrowed, with the NUL of std::string as sentinel const std::string input(reinterpret_cast(data), size); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) check_input(input, expected, comments, trailing_commas); // an exact-size byte vector: borrowed, with nothing after its last byte const std::vector exact(data, data + size); check_input(exact, expected, comments, trailing_commas); // return 0 - non-zero return values are reserved for future use return 0; }