mirror of
https://github.com/nlohmann/json.git
synced 2026-10-12 01:17:14 +00:00
Merge remote-tracking branch 'origin/develop' into custom-number-type-fixes
Signed-off-by: Niels Lohmann <mail@nlohmann.me> # Conflicts: # include/nlohmann/detail/output/binary_writer.hpp # single_include/nlohmann/json.hpp
This commit is contained in:
110 files changed
+3572
-2047
No files matched your search
@@ -0,0 +1,519 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Object types with a user-defined key type. The key types differ in what they
|
||||
// offer to the library: a conversion to std::string (implicit or explicit), a
|
||||
// comparison with ==, a to_json overload, or a c_str() member.
|
||||
|
||||
namespace custom_key_test
|
||||
{
|
||||
class key_base
|
||||
{
|
||||
public:
|
||||
key_base() = default;
|
||||
|
||||
key_base(const char* value)
|
||||
: m_value(value)
|
||||
{}
|
||||
|
||||
key_base(std::string value)
|
||||
: m_value(std::move(value))
|
||||
{}
|
||||
|
||||
// Required by JSON_DIAGNOSTICS, which reads object keys through data()
|
||||
// when building the path of an exception.
|
||||
const char* data() const noexcept
|
||||
{
|
||||
return m_value.data();
|
||||
}
|
||||
|
||||
friend bool operator<(const key_base& lhs, const key_base& rhs)
|
||||
{
|
||||
return lhs.m_value < rhs.m_value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const std::string& str() const noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
// implicit conversion to std::string and operator==
|
||||
class key_full : public key_base
|
||||
{
|
||||
public:
|
||||
key_full() = default;
|
||||
using key_base::key_base;
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
|
||||
friend bool operator==(const key_full& lhs, const key_full& rhs)
|
||||
{
|
||||
return lhs.str() == rhs.str();
|
||||
}
|
||||
};
|
||||
|
||||
// implicit conversion to std::string, but no operator==
|
||||
class key_no_eq : public key_base
|
||||
{
|
||||
public:
|
||||
key_no_eq() = default;
|
||||
using key_base::key_base;
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
};
|
||||
|
||||
// explicit conversion to std::string, no operator==
|
||||
class key_explicit : public key_base
|
||||
{
|
||||
public:
|
||||
key_explicit() = default;
|
||||
using key_base::key_base;
|
||||
|
||||
explicit operator std::string() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
};
|
||||
|
||||
// no conversion at all, only a to_json overload, no operator==
|
||||
class key_to_json : public key_base
|
||||
{
|
||||
public:
|
||||
key_to_json() = default;
|
||||
using key_base::key_base;
|
||||
|
||||
const std::string& value() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
void to_json(BasicJsonType& j, const key_to_json& k)
|
||||
{
|
||||
j = k.value();
|
||||
}
|
||||
|
||||
// like key_to_json, but with size() and c_str()
|
||||
class key_c_str : public key_base
|
||||
{
|
||||
public:
|
||||
key_c_str() = default;
|
||||
using key_base::key_base;
|
||||
|
||||
const std::string& value() const
|
||||
{
|
||||
return str();
|
||||
}
|
||||
|
||||
std::size_t size() const
|
||||
{
|
||||
return str().size();
|
||||
}
|
||||
|
||||
const char* c_str() const
|
||||
{
|
||||
return str().c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
void to_json(BasicJsonType& j, const key_c_str& k)
|
||||
{
|
||||
j = k.value();
|
||||
}
|
||||
|
||||
// std::map with key type K, ignoring the key type basic_json passes
|
||||
template<class K>
|
||||
struct object_for
|
||||
{
|
||||
template<class Key, class Value, class Compare, class Allocator>
|
||||
using pair_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<std::pair<const K, Value>>;
|
||||
|
||||
template<class Key, class Value, class Compare, class Allocator>
|
||||
using type = std::map<K, Value, std::less<K>, pair_allocator<Key, Value, Compare, Allocator>>; // NOLINT(modernize-use-transparent-functors)
|
||||
};
|
||||
|
||||
using json_full = nlohmann::json::with_object_t<object_for<key_full>::type>;
|
||||
using json_no_eq = nlohmann::json::with_object_t<object_for<key_no_eq>::type>;
|
||||
using json_explicit = nlohmann::json::with_object_t<object_for<key_explicit>::type>;
|
||||
using json_to_json = nlohmann::json::with_object_t<object_for<key_to_json>::type>;
|
||||
using json_c_str = nlohmann::json::with_object_t<object_for<key_c_str>::type>;
|
||||
|
||||
// a key that is long enough to need a length byte in CBOR and MessagePack
|
||||
inline const char* long_key_name(std::size_t i, std::string& storage)
|
||||
{
|
||||
storage = "a key longer than thirty-one characters " + std::to_string(i);
|
||||
return storage.c_str();
|
||||
}
|
||||
|
||||
// name of the key at nesting level i of a deep value
|
||||
inline std::string deep_name(std::size_t i, bool long_keys)
|
||||
{
|
||||
std::string storage;
|
||||
return (long_keys && i % 2 == 1) ? std::string(long_key_name(i, storage)) : "k" + std::to_string(i);
|
||||
}
|
||||
|
||||
// {"a": 1, "b": [true, null, "x"], "c": {"d": 2.5}, <keys of 23, 36, and 300 characters>}
|
||||
// 23 is the longest CBOR length stored in the initial byte; 36 needs one
|
||||
// length byte in CBOR and MessagePack, 300 needs two
|
||||
template<class J>
|
||||
J make_shallow()
|
||||
{
|
||||
using key_t = typename J::object_t::key_type;
|
||||
|
||||
J array = J::array();
|
||||
array.push_back(J(true));
|
||||
array.push_back(J(nullptr));
|
||||
array.push_back(J("x"));
|
||||
|
||||
typename J::object_t inner;
|
||||
inner.emplace(key_t("d"), J(2.5));
|
||||
|
||||
typename J::object_t object;
|
||||
object.emplace(key_t("a"), J(1));
|
||||
object.emplace(key_t("b"), std::move(array));
|
||||
object.emplace(key_t("c"), J(std::move(inner)));
|
||||
object.emplace(key_t(std::string(23, 'x')), J(2));
|
||||
object.emplace(key_t(std::string(36, 'y')), J(3));
|
||||
object.emplace(key_t(std::string(300, 'z')), J(4));
|
||||
return J(std::move(object));
|
||||
}
|
||||
|
||||
// {"k0": {"k1": {... {"k<depth-1>": 1} ...}}}
|
||||
template<class J>
|
||||
J make_deep(std::size_t depth, bool long_keys)
|
||||
{
|
||||
using key_t = typename J::object_t::key_type;
|
||||
|
||||
J value = 1;
|
||||
for (std::size_t i = depth; i > 0; --i)
|
||||
{
|
||||
typename J::object_t object;
|
||||
object.emplace(key_t(deep_name(i - 1, long_keys)), std::move(value));
|
||||
value = J(std::move(object));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
inline std::size_t deep_depth()
|
||||
{
|
||||
return nlohmann::detail::recursion_depth_limit() + 10;
|
||||
}
|
||||
|
||||
// walk down the nesting levels without recursion and check the leaf
|
||||
template<class J>
|
||||
bool check_deep(const J& value, std::size_t depth, bool long_keys)
|
||||
{
|
||||
using key_t = typename J::object_t::key_type;
|
||||
|
||||
const J* current = &value;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (!current->is_object() || current->size() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto it = current->find(key_t(deep_name(i, long_keys)));
|
||||
if (it == current->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
current = &it.value();
|
||||
}
|
||||
return current->is_number_integer() && current->template get<int>() == 1;
|
||||
}
|
||||
|
||||
template<class J>
|
||||
bool check_shallow(const J& value)
|
||||
{
|
||||
using key_t = typename J::object_t::key_type;
|
||||
|
||||
if (!value.is_object() || value.size() != 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto a = value.find(key_t("a"));
|
||||
const auto b = value.find(key_t("b"));
|
||||
const auto c = value.find(key_t("c"));
|
||||
if (a == value.end() || b == value.end() || c == value.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto d = c->find(key_t("d"));
|
||||
// basic_json::operator== needs operator== on the keys, which most of the
|
||||
// key types do not have, so the values are checked through get<>()
|
||||
return a->template get<int>() == 1
|
||||
&& b->is_array() && b->size() == 3 && (*b)[0].template get<bool>() && (*b)[1].is_null()
|
||||
&& (*b)[2].template get<std::string>() == "x"
|
||||
&& d != c->end() && std::abs(d->template get<double>() - 2.5) < 1e-9
|
||||
&& value.find(key_t(std::string(23, 'x')))->template get<int>() == 2
|
||||
&& value.find(key_t(std::string(36, 'y')))->template get<int>() == 3
|
||||
&& value.find(key_t(std::string(300, 'z')))->template get<int>() == 4;
|
||||
}
|
||||
|
||||
template<class J>
|
||||
bool is_missing(const J& value, const char* name)
|
||||
{
|
||||
return value.find(typename J::object_t::key_type(name)) == value.end();
|
||||
}
|
||||
|
||||
// member access through find(): at() does not compile for key types without
|
||||
// size() or a conversion to string_t (key_to_json), as in version 3.12.0
|
||||
template<class J>
|
||||
const J& member(const J& value, const char* name)
|
||||
{
|
||||
const auto it = value.find(typename J::object_t::key_type(name));
|
||||
REQUIRE(it != value.end());
|
||||
return *it;
|
||||
}
|
||||
|
||||
// The test cases, as function templates: each unit-custom-key-type-*.cpp file
|
||||
// runs them for one object type only. The MinGW linker fails on objects with
|
||||
// more than 65535 sections, and every basic_json specialization adds many
|
||||
// (see .github/workflows/windows.yml).
|
||||
|
||||
// copy
|
||||
template<class J>
|
||||
void test_copy()
|
||||
{
|
||||
SECTION("shallow")
|
||||
{
|
||||
const J original = custom_key_test::make_shallow<J>();
|
||||
REQUIRE(custom_key_test::check_shallow(original));
|
||||
|
||||
const J copy(original); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(custom_key_test::check_shallow(copy));
|
||||
|
||||
J assigned;
|
||||
assigned = original;
|
||||
CHECK(custom_key_test::check_shallow(assigned));
|
||||
|
||||
// the original is unchanged
|
||||
CHECK(custom_key_test::check_shallow(original));
|
||||
}
|
||||
|
||||
SECTION("deep")
|
||||
{
|
||||
const std::size_t depth = custom_key_test::deep_depth();
|
||||
|
||||
const J original = custom_key_test::make_deep<J>(depth, false);
|
||||
REQUIRE(custom_key_test::check_deep(original, depth, false));
|
||||
|
||||
const J copy(original); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(custom_key_test::check_deep(copy, depth, false));
|
||||
|
||||
J assigned;
|
||||
assigned = original;
|
||||
CHECK(custom_key_test::check_deep(assigned, depth, false));
|
||||
|
||||
CHECK(custom_key_test::check_deep(original, depth, false));
|
||||
}
|
||||
}
|
||||
|
||||
// parse
|
||||
template<class J>
|
||||
void test_parse()
|
||||
{
|
||||
const J j = J::parse(R"({"a":1,"b":{"c":[1,2]}})");
|
||||
|
||||
CHECK(j.size() == 2);
|
||||
CHECK(custom_key_test::member(j, "a").template get<int>() == 1);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "b"), "c").size() == 2);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "b"), "c")[1].template get<int>() == 2);
|
||||
|
||||
// a deeply nested document
|
||||
const std::size_t depth = custom_key_test::deep_depth();
|
||||
std::string text;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += "{\"k" + std::to_string(i) + "\":";
|
||||
}
|
||||
text += '1';
|
||||
text.append(depth, '}');
|
||||
CHECK(custom_key_test::check_deep(J::parse(text), depth, false));
|
||||
}
|
||||
|
||||
// merge_patch, update, and insert
|
||||
template<class J>
|
||||
void test_patch()
|
||||
{
|
||||
SECTION("merge_patch")
|
||||
{
|
||||
J j = J::parse(R"({"a":1,"b":2,"n":{"x":1,"y":2}})");
|
||||
j.merge_patch(J::parse(R"({"b":null,"c":3,"n":{"y":null,"z":3}})"));
|
||||
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(custom_key_test::member(j, "a").template get<int>() == 1);
|
||||
CHECK(custom_key_test::is_missing(j, "b"));
|
||||
CHECK(custom_key_test::member(j, "c").template get<int>() == 3);
|
||||
CHECK(custom_key_test::member(j, "n").size() == 2);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "x").template get<int>() == 1);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "z").template get<int>() == 3);
|
||||
}
|
||||
|
||||
SECTION("update")
|
||||
{
|
||||
J j = J::parse(R"({"a":1,"b":2,"n":{"x":1}})");
|
||||
const J other = J::parse(R"({"b":3,"c":4,"n":{"y":2}})");
|
||||
|
||||
J replaced = j;
|
||||
replaced.update(other);
|
||||
CHECK(replaced.size() == 4);
|
||||
CHECK(custom_key_test::member(replaced, "a").template get<int>() == 1);
|
||||
CHECK(custom_key_test::member(replaced, "b").template get<int>() == 3);
|
||||
CHECK(custom_key_test::member(replaced, "c").template get<int>() == 4);
|
||||
CHECK(custom_key_test::member(replaced, "n").size() == 1);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(replaced, "n"), "y").template get<int>() == 2);
|
||||
|
||||
j.update(other, true);
|
||||
CHECK(j.size() == 4);
|
||||
CHECK(custom_key_test::member(j, "n").size() == 2);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "x").template get<int>() == 1);
|
||||
CHECK(custom_key_test::member(custom_key_test::member(j, "n"), "y").template get<int>() == 2);
|
||||
}
|
||||
|
||||
SECTION("insert")
|
||||
{
|
||||
J j = J::parse(R"({"a":1,"b":2})");
|
||||
const J other = J::parse(R"({"b":3,"c":4})");
|
||||
j.insert(other.begin(), other.end());
|
||||
|
||||
CHECK(j.size() == 3);
|
||||
CHECK(custom_key_test::member(j, "b").template get<int>() == 2);
|
||||
CHECK(custom_key_test::member(j, "c").template get<int>() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
// at() reports a missing key
|
||||
template<class J>
|
||||
void test_at()
|
||||
{
|
||||
// not for key_to_json: at() needs the key's size() or a conversion to
|
||||
// string_t for its error message, which also was the case in version 3.12.0
|
||||
J j = J::parse(R"({"a":1})");
|
||||
const J& j_const = j;
|
||||
|
||||
CHECK(j.at("a").template get<int>() == 1);
|
||||
CHECK(j_const.at("a").template get<int>() == 1);
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
CHECK_THROWS_WITH_AS(j.at("missing"), "[json.exception.out_of_range.403] (bytes 0-7) key 'missing' not found", typename J::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(j_const.at("missing"), "[json.exception.out_of_range.403] (bytes 0-7) key 'missing' not found", typename J::out_of_range&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(j.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", typename J::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(j_const.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", typename J::out_of_range&);
|
||||
#endif
|
||||
}
|
||||
|
||||
// BSON
|
||||
template<class J>
|
||||
void test_bson()
|
||||
{
|
||||
SECTION("shallow")
|
||||
{
|
||||
const J value = custom_key_test::make_shallow<J>();
|
||||
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_bson(value);
|
||||
CHECK(encoded == nlohmann::json::to_bson(expected));
|
||||
CHECK(nlohmann::json::from_bson(encoded) == expected);
|
||||
}
|
||||
|
||||
SECTION("deep")
|
||||
{
|
||||
const std::size_t depth = custom_key_test::deep_depth();
|
||||
const J value = custom_key_test::make_deep<J>(depth, false);
|
||||
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, false);
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_bson(value);
|
||||
CHECK(encoded == nlohmann::json::to_bson(expected));
|
||||
CHECK(nlohmann::json::from_bson(encoded) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// CBOR
|
||||
template<class J>
|
||||
void test_cbor()
|
||||
{
|
||||
SECTION("shallow")
|
||||
{
|
||||
const J value = custom_key_test::make_shallow<J>();
|
||||
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_cbor(value);
|
||||
CHECK(encoded == nlohmann::json::to_cbor(expected));
|
||||
CHECK(nlohmann::json::from_cbor(encoded) == expected);
|
||||
}
|
||||
|
||||
SECTION("deeper than the recursion depth limit")
|
||||
{
|
||||
const std::size_t depth = custom_key_test::deep_depth();
|
||||
const J value = custom_key_test::make_deep<J>(depth, true);
|
||||
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, true);
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_cbor(value);
|
||||
CHECK(encoded == nlohmann::json::to_cbor(expected));
|
||||
CHECK(nlohmann::json::from_cbor(encoded) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
// MessagePack
|
||||
template<class J>
|
||||
void test_msgpack()
|
||||
{
|
||||
SECTION("shallow")
|
||||
{
|
||||
const J value = custom_key_test::make_shallow<J>();
|
||||
const nlohmann::json expected = custom_key_test::make_shallow<nlohmann::json>();
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_msgpack(value);
|
||||
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
||||
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
||||
}
|
||||
|
||||
SECTION("deeper than the recursion depth limit")
|
||||
{
|
||||
const std::size_t depth = custom_key_test::deep_depth();
|
||||
const J value = custom_key_test::make_deep<J>(depth, true);
|
||||
const nlohmann::json expected = custom_key_test::make_deep<nlohmann::json>(depth, true);
|
||||
|
||||
const std::vector<std::uint8_t> encoded = J::to_msgpack(value);
|
||||
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
||||
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace custom_key_test
|
||||
@@ -71,5 +71,5 @@ class object
|
||||
using base_type::base_type;
|
||||
};
|
||||
|
||||
using json = nlohmann::basic_json<object>;
|
||||
using json = nlohmann::json::with_object_t<object>;
|
||||
} // namespace custom_object_key_test
|
||||
@@ -51,22 +51,7 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// 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
|
||||
@@ -80,23 +65,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_bjdata(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -25,23 +25,8 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -72,23 +57,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_bon8(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::from_bon8(vec1, true, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_bson(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::from_bson(vec1, true, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_cbor(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::from_cbor(vec1, true, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -22,43 +22,14 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::parse(data, data + size, nullptr, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_msgpack(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::from_msgpack(vec1, true, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -30,45 +30,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip 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;
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
static bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions; a parse error must then be
|
||||
// reported as a discarded value, never thrown
|
||||
json j_noexcept;
|
||||
// step 0: parse input without exceptions
|
||||
bool noexcept_threw = false;
|
||||
try
|
||||
{
|
||||
j_noexcept = json::from_ubjson(vec1, true, false);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
// type and out-of-range errors are not parse errors and still throw
|
||||
noexcept_threw = true;
|
||||
}
|
||||
json const j_noexcept = parse_without_exceptions([&] { return json::from_ubjson(vec1, true, false); }, noexcept_threw);
|
||||
// whether step 1 succeeded; if not, the catch blocks below check that
|
||||
// step 0 failed, too
|
||||
bool parsed = false;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// code shared by the fuzzer drivers tests/src/fuzzer-parse_*.cpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers
|
||||
|
||||
// the round-trip checks of the drivers are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
// compares dumps rather than values, because NaN != NaN; keep writes strings
|
||||
// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw
|
||||
inline bool same_value(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
|
||||
// step 0 of each driver: parse the input without exceptions; a parse error
|
||||
// must then be reported as a discarded value, never thrown. Type and
|
||||
// out-of-range errors are not parse errors and still throw; then @a threw is
|
||||
// set and null is returned.
|
||||
template<typename Parse>
|
||||
json parse_without_exceptions(Parse parse, bool& threw)
|
||||
{
|
||||
threw = false;
|
||||
try
|
||||
{
|
||||
return parse();
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
catch (const json::exception&)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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> // size_t
|
||||
#include <cstdint> // uint8_t (via json::binary_t)
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the loggers are defined in this header only, so their vtables are emitted in
|
||||
// every test that includes it
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/// a SAX event consumer that records every event it receives as a
|
||||
/// human-readable string, used by the deserialization tests to check the
|
||||
/// exact sequence of SAX events a parse run produces
|
||||
struct SaxEventLogger : public nlohmann::json_sax<nlohmann::json>
|
||||
{
|
||||
using json = nlohmann::json;
|
||||
|
||||
bool null() override
|
||||
{
|
||||
events.emplace_back("null()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolean(bool val) override
|
||||
{
|
||||
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val) override
|
||||
{
|
||||
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val) override
|
||||
{
|
||||
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& s) override
|
||||
{
|
||||
events.push_back("number_float(" + s + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool string(std::string& val) override
|
||||
{
|
||||
events.push_back("string(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val) override
|
||||
{
|
||||
std::string binary_contents = "binary(";
|
||||
std::string comma_space;
|
||||
for (const auto b : val)
|
||||
{
|
||||
binary_contents.append(comma_space);
|
||||
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||
comma_space = ", ";
|
||||
}
|
||||
binary_contents.append(")");
|
||||
events.push_back(binary_contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
events.emplace_back("end_object()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
events.emplace_back("end_array()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
errored = true;
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
bool errored = false;
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
||||
{
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterKey : public SaxEventLogger
|
||||
{
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
|
||||
{
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
} // namespace utils
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
+49
-2
@@ -82,9 +82,56 @@ TEST_CASE("BON8")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
// a discarded value cannot be serialized to BON8
|
||||
const json j = json::value_t::discarded;
|
||||
CHECK(json::to_bon8(j).empty());
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
const json discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in a small array")
|
||||
{
|
||||
const json j = {1, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BON8", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as a small object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/a) cannot serialize discarded value to BON8", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("in a large array")
|
||||
{
|
||||
const json j = {1, 2, 3, 4, 5, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/5) cannot serialize discarded value to BON8", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as a large object value")
|
||||
{
|
||||
json j = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}};
|
||||
j["f"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/f) cannot serialize discarded value to BON8", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -37,194 +37,15 @@ using nlohmann::json;
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "sax_countdown.hpp"
|
||||
#include "sax_event_loggers.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using utils::SaxCountdown;
|
||||
using utils::SaxEventLogger;
|
||||
|
||||
namespace
|
||||
{
|
||||
class SaxEventLogger
|
||||
{
|
||||
public:
|
||||
bool null()
|
||||
{
|
||||
events.emplace_back("null()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolean(bool val)
|
||||
{
|
||||
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val)
|
||||
{
|
||||
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val)
|
||||
{
|
||||
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*unused*/, const std::string& s)
|
||||
{
|
||||
events.push_back("number_float(" + s + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool string(std::string& val)
|
||||
{
|
||||
events.push_back("string(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val)
|
||||
{
|
||||
std::string binary_contents = "binary(";
|
||||
std::string comma_space;
|
||||
for (auto b : val)
|
||||
{
|
||||
binary_contents.append(comma_space);
|
||||
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||
comma_space = ", ";
|
||||
}
|
||||
binary_contents.append(")");
|
||||
events.push_back(binary_contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements)
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(std::string& val)
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
events.emplace_back("end_object()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements)
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
events.emplace_back("end_array()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/)
|
||||
{
|
||||
errored = true;
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
bool errored = false;
|
||||
};
|
||||
|
||||
class SaxCountdown : public nlohmann::json::json_sax_t
|
||||
{
|
||||
public:
|
||||
explicit SaxCountdown(const int count) : events_left(count)
|
||||
{}
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool string(std::string& /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool key(std::string& /*val*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int events_left = 0;
|
||||
};
|
||||
|
||||
json parser_helper(const std::string& s);
|
||||
bool accept_helper(const std::string& s);
|
||||
void comments_helper(const std::string& s);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "custom_key_test.hpp"
|
||||
|
||||
// object type whose key type has no conversion, but size() and c_str() (see custom_key_test.hpp)
|
||||
using custom_key_test::json_c_str;
|
||||
|
||||
TEST_CASE("custom object key types: copy (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_copy<json_c_str>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: parse (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_parse<json_c_str>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: merge_patch, update, and insert (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_patch<json_c_str>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: at() reports a missing key (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_at<json_c_str>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: CBOR (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_cbor<json_c_str>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: MessagePack (json_c_str)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_c_str>();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "custom_key_test.hpp"
|
||||
|
||||
// object type whose key type has explicit conversion to std::string, no operator== (see custom_key_test.hpp)
|
||||
using custom_key_test::json_explicit;
|
||||
|
||||
TEST_CASE("custom object key types: copy (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_copy<json_explicit>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: parse (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_parse<json_explicit>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: merge_patch, update, and insert (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_patch<json_explicit>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: at() reports a missing key (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_at<json_explicit>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: CBOR (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_cbor<json_explicit>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: MessagePack (json_explicit)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_explicit>();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "custom_key_test.hpp"
|
||||
|
||||
// object type whose key type has implicit conversion to std::string and operator== (see custom_key_test.hpp)
|
||||
using custom_key_test::json_full;
|
||||
|
||||
TEST_CASE("custom object key types: copy (json_full)")
|
||||
{
|
||||
custom_key_test::test_copy<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: parse (json_full)")
|
||||
{
|
||||
custom_key_test::test_parse<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: merge_patch, update, and insert (json_full)")
|
||||
{
|
||||
custom_key_test::test_patch<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: at() reports a missing key (json_full)")
|
||||
{
|
||||
custom_key_test::test_at<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: BSON (json_full)")
|
||||
{
|
||||
custom_key_test::test_bson<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: CBOR (json_full)")
|
||||
{
|
||||
custom_key_test::test_cbor<json_full>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: MessagePack (json_full)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_full>();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "custom_key_test.hpp"
|
||||
|
||||
// object type whose key type has implicit conversion to std::string, no operator== (see custom_key_test.hpp)
|
||||
using custom_key_test::json_no_eq;
|
||||
|
||||
TEST_CASE("custom object key types: copy (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_copy<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: parse (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_parse<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: merge_patch, update, and insert (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_patch<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: at() reports a missing key (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_at<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: BSON (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_bson<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: CBOR (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_cbor<json_no_eq>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: MessagePack (json_no_eq)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_no_eq>();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "custom_key_test.hpp"
|
||||
|
||||
// object type whose key type has no conversion, only a to_json overload (see custom_key_test.hpp)
|
||||
using custom_key_test::json_to_json;
|
||||
|
||||
TEST_CASE("custom object key types: copy (json_to_json)")
|
||||
{
|
||||
custom_key_test::test_copy<json_to_json>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: parse (json_to_json)")
|
||||
{
|
||||
custom_key_test::test_parse<json_to_json>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: merge_patch, update, and insert (json_to_json)")
|
||||
{
|
||||
custom_key_test::test_patch<json_to_json>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: CBOR (json_to_json)")
|
||||
{
|
||||
custom_key_test::test_cbor<json_to_json>();
|
||||
}
|
||||
|
||||
TEST_CASE("custom object key types: MessagePack (json_to_json)")
|
||||
{
|
||||
custom_key_test::test_msgpack<json_to_json>();
|
||||
}
|
||||
@@ -36,155 +36,15 @@ using nlohmann::json;
|
||||
#include <string>
|
||||
#include <valarray>
|
||||
|
||||
#include "sax_event_loggers.hpp"
|
||||
|
||||
using utils::SaxEventLogger;
|
||||
using utils::SaxEventLoggerExitAfterKey;
|
||||
using utils::SaxEventLoggerExitAfterStartArray;
|
||||
using utils::SaxEventLoggerExitAfterStartObject;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SaxEventLogger : public nlohmann::json_sax<json>
|
||||
{
|
||||
bool null() override
|
||||
{
|
||||
events.emplace_back("null()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool boolean(bool val) override
|
||||
{
|
||||
events.emplace_back(val ? "boolean(true)" : "boolean(false)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t val) override
|
||||
{
|
||||
events.push_back("number_integer(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t val) override
|
||||
{
|
||||
events.push_back("number_unsigned(" + std::to_string(val) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*val*/, const std::string& s) override
|
||||
{
|
||||
events.push_back("number_float(" + s + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool string(std::string& val) override
|
||||
{
|
||||
events.push_back("string(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary(json::binary_t& val) override
|
||||
{
|
||||
std::string binary_contents = "binary(";
|
||||
std::string comma_space;
|
||||
for (auto b : val)
|
||||
{
|
||||
binary_contents.append(comma_space);
|
||||
binary_contents.append(std::to_string(static_cast<int>(b)));
|
||||
comma_space = ", ";
|
||||
}
|
||||
binary_contents.append(")");
|
||||
events.push_back(binary_contents);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_object() override
|
||||
{
|
||||
events.emplace_back("end_object()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool end_array() override
|
||||
{
|
||||
events.emplace_back("end_array()");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
||||
{
|
||||
bool start_object(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_object()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_object(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterKey : public SaxEventLogger
|
||||
{
|
||||
bool key(std::string& val) override
|
||||
{
|
||||
events.push_back("key(" + val + ")");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger
|
||||
{
|
||||
bool start_array(std::size_t elements) override
|
||||
{
|
||||
if (elements == (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
events.emplace_back("start_array()");
|
||||
}
|
||||
else
|
||||
{
|
||||
events.push_back("start_array(" + std::to_string(elements) + ")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class proxy_iterator
|
||||
{
|
||||
|
||||
@@ -535,6 +535,8 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
// Test malformed index (non-numeric) throws parse_error
|
||||
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&);
|
||||
|
||||
// Test leading-zero index throws parse_error
|
||||
CHECK_THROWS_WITH_AS(j_array.value("/01"_json_pointer, 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", typename Json::parse_error&);
|
||||
|
||||
@@ -1713,6 +1713,21 @@ TEST_CASE("JSON patch - move where 'from' is a proper prefix of 'path' (regressi
|
||||
CHECK(doc.patch(patch) == R"({"b": 1})"_json);
|
||||
}
|
||||
|
||||
SECTION("array index tokens that are not a number")
|
||||
{
|
||||
json const doc = R"({"a": [1, 2]})"_json;
|
||||
|
||||
// "-" cannot be removed and is reported as unresolved
|
||||
json const patch_dash = {{{"op", "remove"}, {"path", "/a/-"}}};
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch_dash), "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&);
|
||||
|
||||
// a single character is reported like a longer token
|
||||
json const patch_x = {{{"op", "remove"}, {"path", "/a/x"}}};
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch_x), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
json const patch_xy = {{{"op", "remove"}, {"path", "/a/xy"}}};
|
||||
CHECK_THROWS_WITH_AS(doc.patch(patch_xy), "[json.exception.parse_error.109] parse error: array index 'xy' is not a number", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("the array-append token '-' is an ordinary child token")
|
||||
{
|
||||
// "-" (append-to-array) addresses a location *inside* the array,
|
||||
|
||||
@@ -421,6 +421,27 @@ TEST_CASE("JSON pointers")
|
||||
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(),
|
||||
"[json.exception.parse_error.109] parse error: array index 'three' is not a number", json::parse_error&);
|
||||
|
||||
// a single-character token that is not a digit is reported like a
|
||||
// longer one (parse_error.109), not as unresolved (out_of_range.404)
|
||||
CHECK_THROWS_WITH_AS(j["/x"_json_pointer] = 1,
|
||||
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j_const["/x"_json_pointer] == 1,
|
||||
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j.at("/x"_json_pointer) = 1,
|
||||
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j_const.at("/x"_json_pointer) == 1,
|
||||
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(j.at("/+"_json_pointer),
|
||||
"[json.exception.parse_error.109] parse error: array index '+' is not a number", json::parse_error&);
|
||||
CHECK(!j.contains("/x"_json_pointer));
|
||||
CHECK(!j_const.contains("/x"_json_pointer));
|
||||
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/x", 2}}).unflatten(),
|
||||
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
|
||||
|
||||
// "-" is a valid reference token, so it is still reported as unresolved
|
||||
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/-", 2}}).unflatten(),
|
||||
"[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&);
|
||||
|
||||
// assign to "-"
|
||||
j["/-"_json_pointer] = 99;
|
||||
CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));
|
||||
|
||||
@@ -400,26 +400,31 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_array, true, false)) == deep_array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_array, true, true)) == deep_array);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_array)) == deep_array);
|
||||
CHECK(json::from_bon8(json::to_bon8(deep_array)) == deep_array);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(deep_object)) == deep_object);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(deep_object)) == deep_object);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_object)) == deep_object);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_object, true, true)) == deep_object);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_object)) == deep_object);
|
||||
CHECK(json::from_bon8(json::to_bon8(deep_object)) == deep_object);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(empty_array)) == empty_array);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(empty_array)) == empty_array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(empty_array)) == empty_array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(empty_array, true, true)) == empty_array);
|
||||
CHECK(json::from_bon8(json::to_bon8(empty_array)) == empty_array);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(empty_object)) == empty_object);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(empty_object)) == empty_object);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(empty_object)) == empty_object);
|
||||
CHECK(json::from_bon8(json::to_bon8(empty_object)) == empty_object);
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(mixed)) == mixed);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(mixed)) == mixed);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(mixed)) == mixed);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(mixed)) == mixed);
|
||||
CHECK(json::from_bon8(json::to_bon8(mixed)) == mixed);
|
||||
}
|
||||
|
||||
SECTION("the two ways of writing a value meet at the bound")
|
||||
@@ -432,11 +437,13 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
CHECK(json::from_cbor(json::to_cbor(array)) == array);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(array)) == array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(array, true, true)) == array);
|
||||
CHECK(json::from_bon8(json::to_bon8(array)) == array);
|
||||
|
||||
const json object = nested_object(depth, json(7));
|
||||
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(object)) == object);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(object)) == object);
|
||||
CHECK(json::from_bon8(json::to_bon8(object)) == object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,6 +488,10 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
expected_ubjson.append(depth, ']');
|
||||
const auto packed_ubjson = json::to_ubjson(array);
|
||||
CHECK(std::string(packed_ubjson.begin(), packed_ubjson.end()) == expected_ubjson);
|
||||
|
||||
std::vector<std::uint8_t> expected_bon8(depth, 0x81);
|
||||
expected_bon8.push_back(0x90);
|
||||
CHECK(json::to_bon8(array) == expected_bon8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +504,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
CHECK(json::from_msgpack(json::to_msgpack(object)) == object);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(object, true, true)) == object);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(object)) == object);
|
||||
CHECK(json::from_bon8(json::to_bon8(object)) == object);
|
||||
|
||||
const json ndarray = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
const json deep_ndarray = nested_array(depth, ndarray);
|
||||
@@ -523,6 +535,27 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error);
|
||||
}
|
||||
|
||||
SECTION("BON8 past the recursion bound: discarded values and errors")
|
||||
{
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
|
||||
// BON8 rejects a discarded value (type_error.321), deep or not
|
||||
const json deep_discarded = nested_array(depth, json(json::value_t::discarded));
|
||||
CHECK_THROWS_AS(json::to_bon8(deep_discarded), json::type_error);
|
||||
|
||||
// errors are thrown from below the bound as from above it
|
||||
const json deep_invalid_string = nested_array(depth, json(std::string("\x80")));
|
||||
CHECK_THROWS_AS(json::to_bon8(deep_invalid_string), json::type_error);
|
||||
|
||||
json deep_invalid_key = json::object();
|
||||
deep_invalid_key[std::string("\x80")] = 1;
|
||||
deep_invalid_key = nested_object(depth, deep_invalid_key);
|
||||
CHECK_THROWS_AS(json::to_bon8(deep_invalid_key), json::type_error);
|
||||
|
||||
const json deep_too_large = nested_array(depth, json(9223372036854775808u));
|
||||
CHECK_THROWS_AS(json::to_bon8(deep_too_large), json::out_of_range);
|
||||
}
|
||||
|
||||
SECTION("does not overflow the C++ stack")
|
||||
{
|
||||
const std::size_t depth = 100000;
|
||||
@@ -543,6 +576,27 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_bjdata(j));
|
||||
CHECK(json::from_bjdata(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_bon8(j));
|
||||
CHECK(json::from_bon8(packed) == j);
|
||||
}
|
||||
|
||||
SECTION("BON8 containers with more than four elements past the recursion bound")
|
||||
{
|
||||
// such containers are closed with 0xFE, which the iterative writer
|
||||
// must emit when it leaves them; strings next to each other must
|
||||
// still be separated with 0xFF
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
json wide_array = json::array({1, "a", "b", json::array(), 5});
|
||||
json wide_object = json({{"k1", "v"}, {"k2", 2}, {"k3", ""}, {"k4", json::object()}, {"k5", nullptr}});
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
wide_array = json::array({"s", wide_array, "t", wide_object, true});
|
||||
wide_object = json({{"a", wide_object}, {"b", "x"}, {"c", "y"}, {"d", wide_array.size()}, {"e", "z"}});
|
||||
}
|
||||
|
||||
CHECK(json::from_bon8(json::to_bon8(wide_array)) == wide_array);
|
||||
CHECK(json::from_bon8(json::to_bon8(wide_object)) == wide_object);
|
||||
}
|
||||
|
||||
SECTION("regression test for https://issues.oss-fuzz.com/issues/566583014")
|
||||
@@ -561,5 +615,14 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
const json j_msgpack = json::from_msgpack(v_msgpack);
|
||||
CHECK(json::to_msgpack(j_msgpack) == v_msgpack);
|
||||
}
|
||||
|
||||
SECTION("regression test for https://issues.oss-fuzz.com/issues/572238015")
|
||||
{
|
||||
// the BON8 analogue: 200000 nested one-element arrays down to null
|
||||
std::vector<std::uint8_t> v(200000, 0x81);
|
||||
v.push_back(0xFA);
|
||||
const json j = json::from_bon8(v);
|
||||
CHECK(json::to_bon8(j) == v);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user