mirror of
https://github.com/nlohmann/json.git
synced 2026-10-17 19:48:12 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6c60dcde2 | ||
|
|
ef29747c0a | ||
|
|
1e761f2a14 | ||
|
|
0cbe6a5cdc | ||
|
|
3d02b8f23e | ||
|
|
1011179589 | ||
|
|
0e95fe8200 | ||
|
|
cd6b428ef0 |
No files matched your search
@@ -166,19 +166,21 @@ jobs:
|
||||
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
||||
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
||||
# Keep the objects small by splitting the test files instead.
|
||||
# Objects with more than 65535 sections fail to link with "relocation
|
||||
# truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW
|
||||
# linker stores the section an associative COMDAT section belongs to in
|
||||
# 16 bits (x_associated in binutils' include/coff/internal.h), so it
|
||||
# discards the jump tables of inline functions together with the wrong
|
||||
# function. Each basic_json specialization a test file instantiates adds
|
||||
# many sections, so test a second one in a file of its own.
|
||||
# The tests link with LLD: GNU ld fails on objects with more than 32767
|
||||
# sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32
|
||||
# against `.rdata'", as it stores the section an associative COMDAT
|
||||
# section belongs to in 16 bits (x_associated in binutils'
|
||||
# include/coff/internal.h) and so discards the jump tables of inline
|
||||
# functions together with the wrong function. Each basic_json
|
||||
# specialization and input type a test file instantiates adds many
|
||||
# sections, so unit-msgpack.cpp and unit-regression2.cpp kept crossing
|
||||
# that limit as the library grew.
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build ^
|
||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^
|
||||
-G"MinGW Makefiles" ^
|
||||
-DCMAKE_BUILD_TYPE=Debug ^
|
||||
-DJSON_BuildTests=On
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <optional> // optional
|
||||
#endif
|
||||
|
||||
#include <iterator> // begin, end
|
||||
#include <iterator> // begin, end, make_move_iterator, move_iterator
|
||||
#include <memory> // allocator_traits
|
||||
#include <string> // basic_string, char_traits
|
||||
#include <tuple> // tuple, get
|
||||
@@ -48,6 +48,32 @@ namespace detail
|
||||
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
||||
*/
|
||||
|
||||
// a move iterator over the elements of an rvalue container, unless they are
|
||||
// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a
|
||||
// std::move_iterator as value_type&&, which cannot bind to a const element
|
||||
template<typename Iterator>
|
||||
std::move_iterator<Iterator> make_move_iterator_if_mutable_impl(const Iterator& it, std::true_type /*mutable*/)
|
||||
{
|
||||
return std::make_move_iterator(it);
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/)
|
||||
{
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
using is_mutable_iterator = std::integral_constant < bool,
|
||||
!std::is_const<typename std::remove_reference<decltype(*std::declval<Iterator&>())>::type>::value >;
|
||||
|
||||
template<typename Iterator>
|
||||
auto make_move_iterator_if_mutable(const Iterator& it)
|
||||
-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {}))
|
||||
{
|
||||
return make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {});
|
||||
}
|
||||
|
||||
template<value_t> struct external_constructor;
|
||||
|
||||
template<>
|
||||
@@ -174,7 +200,7 @@ struct external_constructor<value_t::array>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr))));
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
@@ -235,7 +261,7 @@ struct external_constructor<value_t::object>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj))));
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
@@ -413,25 +439,27 @@ using is_movable_container_rvalue = negation<disjunction<
|
||||
std::is_const<T>,
|
||||
has_dedicated_to_json<T>,
|
||||
is_basic_json<T>,
|
||||
is_range_view_type<T>,
|
||||
is_enum_keyed_map_type<T>,
|
||||
std::is_same<T, std::vector<bool>>>>;
|
||||
|
||||
// rvalue array-like containers
|
||||
// rvalue array-like containers; object types are ruled out before views are
|
||||
// checked, as std::ranges::view<T> for the object_t of an ordered_json would
|
||||
// need that ordered_json to be complete (GCC 11, see #3312)
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
negation<is_compatible_object_type<BasicJsonType, T>>,
|
||||
negation<is_range_view_type<T>>,
|
||||
is_compatible_array_type<BasicJsonType, T>,
|
||||
negation<disjunction<
|
||||
std::is_same<T, typename BasicJsonType::array_t>,
|
||||
std::is_same<T, typename BasicJsonType::binary_t>,
|
||||
is_compatible_object_type<BasicJsonType, T>,
|
||||
is_compatible_string_type<BasicJsonType, T>,
|
||||
is_compatible_binary_type<BasicJsonType, T>>>>;
|
||||
|
||||
// rvalue containers: their elements are moved instead of copied
|
||||
template<typename BasicJsonType, typename CompatibleArrayType,
|
||||
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||
}
|
||||
@@ -509,7 +537,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
||||
}
|
||||
#endif
|
||||
|
||||
// rvalue object-like containers
|
||||
// rvalue object-like containers (views have no key_type and mapped_type, so
|
||||
// they are no compatible object type)
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
||||
@@ -517,7 +546,7 @@ using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
external_constructor<value_t::object>::construct_moving(j, obj);
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
|
||||
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
||||
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
||||
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
|
||||
constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||
constexpr auto fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
|
||||
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
||||
std::uint64_t digit = 0;
|
||||
|
||||
@@ -494,8 +494,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c
|
||||
static_cast<void>(string_type);
|
||||
static_cast<void>(val);
|
||||
// context-sensitive error message
|
||||
const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\'');
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
|
||||
@@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->is_object()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!m_object->is_object()))
|
||||
{
|
||||
return m_it.object_iterator->first;
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||
}
|
||||
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||
return m_it.object_iterator->first;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <algorithm> // all_of
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdlib> // strtoull
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
@@ -334,7 +335,7 @@ class json_pointer
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
@@ -529,10 +530,10 @@ class json_pointer
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
{
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL & (std::numeric_limits<std::size_t>::max)());
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
{
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL & (std::numeric_limits<std::size_t>::max)());
|
||||
}
|
||||
return h ^ (h >> 29u);
|
||||
}
|
||||
|
||||
+51
-49
@@ -29,6 +29,7 @@
|
||||
#endif // JSON_NO_IO
|
||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <set> // swap, operator!=
|
||||
#include <stdexcept> // length_error
|
||||
@@ -754,26 +755,20 @@ private:
|
||||
// one level deep); stops at the first structured child it meets
|
||||
static bool all_children_leaves(const basic_json& v) noexcept
|
||||
{
|
||||
// array_t only needs forward iterators (see custom_array_type)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
const auto& a = *v.m_data.m_value.array;
|
||||
for (auto it = a.rbegin(); it != a.rend(); ++it)
|
||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||
{
|
||||
if (!has_no_children(*it))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el);
|
||||
});
|
||||
}
|
||||
for (const auto& el : *v.m_data.m_value.object)
|
||||
const auto& o = *v.m_data.m_value.object;
|
||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||
{
|
||||
if (!has_no_children(el.second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el.second);
|
||||
});
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
@@ -1230,7 +1225,7 @@ public:
|
||||
}
|
||||
|
||||
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1242,7 +1237,7 @@ public:
|
||||
}
|
||||
|
||||
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
@@ -3043,18 +3038,21 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_iterator_range_of_null(const basic_json* obj)
|
||||
{
|
||||
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_incompatible_reference_type(const basic_json& obj)
|
||||
{
|
||||
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_array_index_exceeds_size_type(size_type idx)
|
||||
{
|
||||
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
|
||||
}
|
||||
|
||||
@@ -3062,6 +3060,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
|
||||
{
|
||||
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
|
||||
}
|
||||
|
||||
@@ -3070,6 +3070,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_key_not_found(const KeyType& key, const basic_json& j)
|
||||
{
|
||||
static_cast<void>(key); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
|
||||
}
|
||||
|
||||
@@ -4893,19 +4895,19 @@ public:
|
||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
/// @brief inserts element into array
|
||||
@@ -4913,21 +4915,21 @@ public:
|
||||
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// moving into a local first keeps this safe even if val aliases
|
||||
// an element of this array
|
||||
basic_json tmp(std::move(val));
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// moving into a local first keeps this safe even if val aliases
|
||||
// an element of this array
|
||||
basic_json tmp(std::move(val));
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
}
|
||||
|
||||
/// @brief inserts copies of element into array
|
||||
@@ -4935,19 +4937,19 @@ public:
|
||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
/// @brief inserts range of elements into array
|
||||
@@ -5453,7 +5455,7 @@ public:
|
||||
// inline fast path for two values of the same non-owning scalar type; anything
|
||||
// else falls through to the out-of-line general comparison
|
||||
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \
|
||||
{ \
|
||||
switch (lhs.m_data.m_type) \
|
||||
{ \
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
Generated
+100
-67
@@ -29,6 +29,7 @@
|
||||
#endif // JSON_NO_IO
|
||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <set> // swap, operator!=
|
||||
#include <stdexcept> // length_error
|
||||
@@ -5794,8 +5795,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c
|
||||
static_cast<void>(string_type);
|
||||
static_cast<void>(val);
|
||||
// context-sensitive error message
|
||||
const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\'');
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
@@ -6555,7 +6555,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <optional> // optional
|
||||
#endif
|
||||
|
||||
#include <iterator> // begin, end
|
||||
#include <iterator> // begin, end, make_move_iterator, move_iterator
|
||||
#include <memory> // allocator_traits
|
||||
#include <string> // basic_string, char_traits
|
||||
#include <tuple> // tuple, get
|
||||
@@ -7204,6 +7204,32 @@ namespace detail
|
||||
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
||||
*/
|
||||
|
||||
// a move iterator over the elements of an rvalue container, unless they are
|
||||
// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a
|
||||
// std::move_iterator as value_type&&, which cannot bind to a const element
|
||||
template<typename Iterator>
|
||||
std::move_iterator<Iterator> make_move_iterator_if_mutable_impl(const Iterator& it, std::true_type /*mutable*/)
|
||||
{
|
||||
return std::make_move_iterator(it);
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/)
|
||||
{
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
using is_mutable_iterator = std::integral_constant < bool,
|
||||
!std::is_const<typename std::remove_reference<decltype(*std::declval<Iterator&>())>::type>::value >;
|
||||
|
||||
template<typename Iterator>
|
||||
auto make_move_iterator_if_mutable(const Iterator& it)
|
||||
-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {}))
|
||||
{
|
||||
return make_move_iterator_if_mutable_impl(it, is_mutable_iterator<Iterator> {});
|
||||
}
|
||||
|
||||
template<value_t> struct external_constructor;
|
||||
|
||||
template<>
|
||||
@@ -7330,7 +7356,7 @@ struct external_constructor<value_t::array>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr))));
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
@@ -7391,7 +7417,7 @@ struct external_constructor<value_t::object>
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj))));
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
@@ -7569,25 +7595,27 @@ using is_movable_container_rvalue = negation<disjunction<
|
||||
std::is_const<T>,
|
||||
has_dedicated_to_json<T>,
|
||||
is_basic_json<T>,
|
||||
is_range_view_type<T>,
|
||||
is_enum_keyed_map_type<T>,
|
||||
std::is_same<T, std::vector<bool>>>>;
|
||||
|
||||
// rvalue array-like containers
|
||||
// rvalue array-like containers; object types are ruled out before views are
|
||||
// checked, as std::ranges::view<T> for the object_t of an ordered_json would
|
||||
// need that ordered_json to be complete (GCC 11, see #3312)
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
negation<is_compatible_object_type<BasicJsonType, T>>,
|
||||
negation<is_range_view_type<T>>,
|
||||
is_compatible_array_type<BasicJsonType, T>,
|
||||
negation<disjunction<
|
||||
std::is_same<T, typename BasicJsonType::array_t>,
|
||||
std::is_same<T, typename BasicJsonType::binary_t>,
|
||||
is_compatible_object_type<BasicJsonType, T>,
|
||||
is_compatible_string_type<BasicJsonType, T>,
|
||||
is_compatible_binary_type<BasicJsonType, T>>>>;
|
||||
|
||||
// rvalue containers: their elements are moved instead of copied
|
||||
template<typename BasicJsonType, typename CompatibleArrayType,
|
||||
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||
}
|
||||
@@ -7665,7 +7693,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
||||
}
|
||||
#endif
|
||||
|
||||
// rvalue object-like containers
|
||||
// rvalue object-like containers (views have no key_type and mapped_type, so
|
||||
// they are no compatible object type)
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
||||
@@ -7673,7 +7702,7 @@ using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||
{
|
||||
external_constructor<value_t::object>::construct_moving(j, obj);
|
||||
}
|
||||
@@ -20715,12 +20744,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->is_object()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!m_object->is_object()))
|
||||
{
|
||||
return m_it.object_iterator->first;
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||
}
|
||||
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||
return m_it.object_iterator->first;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -20947,6 +20976,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
#include <algorithm> // all_of
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdlib> // strtoull
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
@@ -21277,7 +21307,7 @@ class json_pointer
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||
acc = static_cast<size_type>((acc * 10) + static_cast<size_type>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
@@ -21472,10 +21502,10 @@ class json_pointer
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
{
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL & (std::numeric_limits<std::size_t>::max)());
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
{
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL & (std::numeric_limits<std::size_t>::max)());
|
||||
}
|
||||
return h ^ (h >> 29u);
|
||||
}
|
||||
@@ -26454,7 +26484,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
|
||||
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
||||
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
||||
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
|
||||
constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||
constexpr auto fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
|
||||
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
||||
std::uint64_t digit = 0;
|
||||
@@ -29704,6 +29734,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/meta/type_traits.hpp>
|
||||
|
||||
// #include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
@@ -30788,26 +30820,20 @@ private:
|
||||
// one level deep); stops at the first structured child it meets
|
||||
static bool all_children_leaves(const basic_json& v) noexcept
|
||||
{
|
||||
// array_t only needs forward iterators (see custom_array_type)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
const auto& a = *v.m_data.m_value.array;
|
||||
for (auto it = a.rbegin(); it != a.rend(); ++it)
|
||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||
{
|
||||
if (!has_no_children(*it))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el);
|
||||
});
|
||||
}
|
||||
for (const auto& el : *v.m_data.m_value.object)
|
||||
const auto& o = *v.m_data.m_value.object;
|
||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||
{
|
||||
if (!has_no_children(el.second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return has_no_children(el.second);
|
||||
});
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
@@ -31264,7 +31290,7 @@ public:
|
||||
}
|
||||
|
||||
template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements<O>::value, int > = 0 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -31276,7 +31302,7 @@ public:
|
||||
}
|
||||
|
||||
template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements<O>::value && !detail::is_nlohmann_ordered_map<O>::value, int > = 0 >
|
||||
static constexpr std::size_t object_capacity() noexcept
|
||||
std::size_t object_capacity() const noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
@@ -33077,18 +33103,21 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_iterator_range_of_null(const basic_json* obj)
|
||||
{
|
||||
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_incompatible_reference_type(const basic_json& obj)
|
||||
{
|
||||
static_cast<void>(obj); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_array_index_exceeds_size_type(size_type idx)
|
||||
{
|
||||
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type")));
|
||||
}
|
||||
|
||||
@@ -33096,6 +33125,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_array_index_out_of_range(size_type idx, const basic_json& j)
|
||||
{
|
||||
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j));
|
||||
}
|
||||
|
||||
@@ -33104,6 +33135,8 @@ public:
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_key_not_found(const KeyType& key, const basic_json& j)
|
||||
{
|
||||
static_cast<void>(key); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j));
|
||||
}
|
||||
|
||||
@@ -34927,19 +34960,19 @@ public:
|
||||
iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
/// @brief inserts element into array
|
||||
@@ -34947,21 +34980,21 @@ public:
|
||||
iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// moving into a local first keeps this safe even if val aliases
|
||||
// an element of this array
|
||||
basic_json tmp(std::move(val));
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// moving into a local first keeps this safe even if val aliases
|
||||
// an element of this array
|
||||
basic_json tmp(std::move(val));
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
}
|
||||
|
||||
/// @brief inserts copies of element into array
|
||||
@@ -34969,19 +35002,19 @@ public:
|
||||
iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// insert only works for arrays
|
||||
if (JSON_HEDLEY_LIKELY(is_array()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_array()))
|
||||
{
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
}
|
||||
|
||||
detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this);
|
||||
// check if iterator pos fits to this JSON value
|
||||
if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))
|
||||
{
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this);
|
||||
}
|
||||
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, cnt, val);
|
||||
}
|
||||
|
||||
/// @brief inserts range of elements into array
|
||||
@@ -35487,7 +35520,7 @@ public:
|
||||
// inline fast path for two values of the same non-owning scalar type; anything
|
||||
// else falls through to the out-of-line general comparison
|
||||
#define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) \
|
||||
if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \
|
||||
{ \
|
||||
switch (lhs.m_data.m_type) \
|
||||
{ \
|
||||
|
||||
@@ -118,7 +118,7 @@ endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
# avoid stack overflow, see https://github.com/nlohmann/json/issues/2955
|
||||
json_test_set_test_options("test-bon8;test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
json_test_set_test_options("test-bon8;test-cbor;test-msgpack;test-ubjson;test-bjdata;test-bjdata_round_trip_draft2;test-bjdata_round_trip_draft3;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
endif()
|
||||
|
||||
# disable exceptions for test-disabled_exceptions
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 <cstdint> // uint8_t
|
||||
#include <vector> // vector
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#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<options> 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<std::uint8_t> 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<std::uint8_t> 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
|
||||
@@ -28,7 +28,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "round_trip_corpus.hpp"
|
||||
#include "test_utils.hpp"
|
||||
#include "sax_countdown.hpp"
|
||||
using utils::SaxCountdown;
|
||||
@@ -4441,70 +4440,6 @@ TEST_CASE("BJData use_type requires use_size")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData round-trip invariants")
|
||||
{
|
||||
// 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.
|
||||
struct options
|
||||
{
|
||||
bool use_size;
|
||||
bool use_type;
|
||||
json::bjdata_version_t version;
|
||||
};
|
||||
const std::vector<options> 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)
|
||||
{
|
||||
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<std::uint8_t> 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<std::uint8_t> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData round trip of a binary value is value-stable, not byte-stable")
|
||||
{
|
||||
// OSS-Fuzz issue 474480402: a Draft 3 optimized binary array is read as a
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
using nlohmann::json;
|
||||
|
||||
#include "bjdata_round_trip.hpp"
|
||||
|
||||
// split from unit-bjdata.cpp, see bjdata_round_trip.hpp; the other half is in
|
||||
// unit-bjdata_round_trip_draft3.cpp
|
||||
|
||||
TEST_CASE("BJData round-trip invariants (j1 written with Draft 2)")
|
||||
{
|
||||
utils::check_bjdata_round_trip(json::bjdata_version_t::draft2);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
using nlohmann::json;
|
||||
|
||||
#include "bjdata_round_trip.hpp"
|
||||
|
||||
// split from unit-bjdata.cpp, see bjdata_round_trip.hpp; the other half is in
|
||||
// unit-bjdata_round_trip_draft2.cpp
|
||||
|
||||
TEST_CASE("BJData round-trip invariants (j1 written with Draft 3)")
|
||||
{
|
||||
utils::check_bjdata_round_trip(json::bjdata_version_t::draft3);
|
||||
}
|
||||
@@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types")
|
||||
const json src_uint = 7u;
|
||||
const json src_float = 2.5;
|
||||
|
||||
json c_null(src_null);
|
||||
json c_bool(src_bool);
|
||||
json c_int(src_int);
|
||||
json c_uint(src_uint);
|
||||
json c_float(src_float);
|
||||
json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor
|
||||
|
||||
CHECK(c_null.is_null());
|
||||
CHECK(c_bool == true);
|
||||
@@ -300,12 +300,12 @@ TEST_CASE("copy constructor and destructor across value types")
|
||||
REQUIRE(v.size() == 70);
|
||||
|
||||
std::vector<json> copy(v);
|
||||
CHECK(copy == v);
|
||||
CHECK((copy == v));
|
||||
|
||||
// mutating the copy leaves the original alone
|
||||
copy[5] = "changed";
|
||||
copy[6].push_back(1);
|
||||
CHECK(copy != v);
|
||||
CHECK((copy != v));
|
||||
CHECK(v[5] == std::string(5, 'x'));
|
||||
CHECK(v[6].size() == 3);
|
||||
|
||||
|
||||
@@ -817,8 +817,8 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
|
||||
{
|
||||
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
|
||||
using typename base_type::iterator;
|
||||
using typename base_type::Container;
|
||||
using iterator = typename base_type::iterator;
|
||||
using Container = typename base_type::Container;
|
||||
using base_type::base_type;
|
||||
|
||||
template<typename... Args>
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
|
||||
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
|
||||
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
|
||||
// test places every input directly in front of (or directly behind) an
|
||||
// inaccessible page, so reading even one byte outside the input crashes, with
|
||||
// or without sanitizers. ASan alone misses over-reads into the slack of the
|
||||
// same heap allocation.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define JSON_TEST_GUARD_PAGE_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
#define JSON_TEST_GUARD_PAGE_POSIX
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
|
||||
// input is copied either to the end of the middle page (any read past its last
|
||||
// byte faults) or to its start (any read before its first byte faults).
|
||||
class guarded_buffer
|
||||
{
|
||||
public:
|
||||
guarded_buffer()
|
||||
: page(page_size())
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
||||
REQUIRE(p != nullptr);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
DWORD old_protect = 0;
|
||||
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
|
||||
#else
|
||||
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
REQUIRE(p != MAP_FAILED);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
~guarded_buffer()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
VirtualFree(base, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(base, 3 * page);
|
||||
#endif
|
||||
}
|
||||
|
||||
guarded_buffer(const guarded_buffer&) = delete;
|
||||
guarded_buffer& operator=(const guarded_buffer&) = delete;
|
||||
guarded_buffer(guarded_buffer&&) = delete;
|
||||
guarded_buffer& operator=(guarded_buffer&&) = delete;
|
||||
|
||||
// copies @a input so its last byte is the last readable byte
|
||||
const std::uint8_t* at_end(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + (2 * page) - input.size();
|
||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c);
|
||||
});
|
||||
return first;
|
||||
}
|
||||
|
||||
// copies @a input so its first byte is the first readable byte
|
||||
const std::uint8_t* at_start(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + page;
|
||||
std::transform(input.begin(), input.end(), first, [](const char c)
|
||||
{
|
||||
return static_cast<unsigned char>(c);
|
||||
});
|
||||
return first;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t page_size()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return static_cast<std::size_t>(info.dwPageSize);
|
||||
#else
|
||||
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t page = 0;
|
||||
unsigned char* base = nullptr;
|
||||
};
|
||||
|
||||
// the serialization of the result of @a f, or the exception message; @a f
|
||||
// takes the allow_exceptions flag, which is false without exceptions, where
|
||||
// an error yields a discarded value instead
|
||||
template<typename F>
|
||||
std::string outcome(F f)
|
||||
{
|
||||
#if defined(JSON_NOEXCEPTION)
|
||||
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
#else
|
||||
try
|
||||
{
|
||||
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return std::string("error: ") + e.what();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// a SAX handler that only counts events, so sax_parse runs without a DOM
|
||||
struct counting_sax : nlohmann::json_sax<json>
|
||||
{
|
||||
std::size_t events = 0;
|
||||
std::string error{}; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const nlohmann::detail::exception& ex) override
|
||||
{
|
||||
error = std::to_string(position) + " " + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool count()
|
||||
{
|
||||
++events;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// all prefixes of each seed, so the input ends at every byte of it
|
||||
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (const auto& seed : seeds)
|
||||
{
|
||||
for (std::size_t n = 0; n <= seed.size(); ++n)
|
||||
{
|
||||
result.push_back(seed.substr(0, n));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> json_seeds()
|
||||
{
|
||||
std::vector<std::string> seeds =
|
||||
{
|
||||
// string runs: ASCII, escapes, \u escapes, surrogate pairs
|
||||
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
|
||||
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
|
||||
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
|
||||
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
|
||||
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
|
||||
std::string("\"abcdefg\x01") + "abcdefgh\"",
|
||||
// numbers, so the 8-digit loop runs up to the end
|
||||
"12345678",
|
||||
"1234567890123456",
|
||||
"12345678901234567890123",
|
||||
"-0.000000001234567890123456789",
|
||||
"3.14159265358979323846264338327950288e+100",
|
||||
"-1.2345678901234567e-12",
|
||||
"1E-5",
|
||||
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
|
||||
// literals
|
||||
"[true,false,null]",
|
||||
// comments (only parsed with ignore_comments)
|
||||
"/* comment */ [1, // line comment\n 2] /**/",
|
||||
// a pretty-printed document
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
|
||||
};
|
||||
|
||||
// indentation runs of every length, with and without a closing bracket
|
||||
for (std::size_t n = 0; n <= 20; ++n)
|
||||
{
|
||||
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
|
||||
}
|
||||
|
||||
return all_prefixes(seeds);
|
||||
}
|
||||
|
||||
// a value whose binary serializations contain long strings and keys, numbers
|
||||
// of every width, and nested containers
|
||||
json binary_document()
|
||||
{
|
||||
json j;
|
||||
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
|
||||
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
|
||||
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
|
||||
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
|
||||
j["s"] = "";
|
||||
return j;
|
||||
}
|
||||
|
||||
// readers for check_binary(), callable with a container or an iterator range
|
||||
// followed by the allow_exceptions flag
|
||||
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
|
||||
struct name \
|
||||
{ \
|
||||
template<typename... Args> \
|
||||
json operator()(bool allow_exceptions, Args&& ... args) const \
|
||||
{ \
|
||||
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
|
||||
} \
|
||||
};
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
|
||||
#undef JSON_TEST_GUARD_PAGE_READER
|
||||
|
||||
// @a bytes as characters (explicitly converted: the iterator-range constructor
|
||||
// would convert implicitly, which -fsanitize=integer reports)
|
||||
std::string as_chars(const std::vector<std::uint8_t>& bytes)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(bytes.size());
|
||||
for (const std::uint8_t b : bytes)
|
||||
{
|
||||
result.push_back(static_cast<char>(b));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// reads every prefix of @a bytes at both ends of the guarded page and from a
|
||||
// std::string, which must give the same outcome
|
||||
template<typename Reader>
|
||||
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
|
||||
{
|
||||
for (const auto& input : all_prefixes({as_chars(bytes)}))
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, input);
|
||||
});
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, first, first + input.size());
|
||||
}) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse_float_native() at both ends of the guarded page, compared bit for bit
|
||||
// with converting the same token from a std::string
|
||||
template<typename FloatType>
|
||||
std::uint64_t float_bits(FloatType value)
|
||||
{
|
||||
static_assert(sizeof(FloatType) <= sizeof(std::uint64_t), "unsupported floating-point width");
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(value));
|
||||
return bits;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
void check_float_native(guarded_buffer& guard, const std::string& token)
|
||||
{
|
||||
const auto decimal_point = token.find('.');
|
||||
const auto exponent = token.find_first_of("eE");
|
||||
const auto mantissa_end = exponent == std::string::npos ? token.size() : exponent;
|
||||
const auto expected = nlohmann::detail::parse_float_native<FloatType>(token.data(), token.data() + token.size(), decimal_point, mantissa_end);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const char* const first = reinterpret_cast<const char*>(placement == 0 ? guard.at_end(token) : guard.at_start(token));
|
||||
const auto value = nlohmann::detail::parse_float_native<FloatType>(first, first + token.size(), decimal_point, mantissa_end);
|
||||
CHECK(float_bits(value) == float_bits(expected));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("guard page")
|
||||
{
|
||||
guarded_buffer guard;
|
||||
|
||||
SECTION("string scanning")
|
||||
{
|
||||
// fills that drive each function to a different stop
|
||||
const std::vector<std::string> fills =
|
||||
{
|
||||
std::string(64, 'a'),
|
||||
std::string(64, '"'),
|
||||
std::string(64, '\\'),
|
||||
std::string(64, '\x01'),
|
||||
std::string(64, '\x7F'),
|
||||
std::string(64, '\xFF'),
|
||||
};
|
||||
std::vector<std::string> patterns = fills;
|
||||
{
|
||||
std::string utf8;
|
||||
while (utf8.size() < 64)
|
||||
{
|
||||
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
|
||||
}
|
||||
patterns.push_back(utf8);
|
||||
}
|
||||
{
|
||||
std::string bytes;
|
||||
for (unsigned int i = 0; i < 64; ++i)
|
||||
{
|
||||
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
|
||||
}
|
||||
patterns.push_back(bytes);
|
||||
}
|
||||
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
for (std::size_t n = 0; n <= 64; ++n)
|
||||
{
|
||||
const std::string input = pattern.substr(0, n);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
|
||||
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
||||
#endif
|
||||
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("float conversion")
|
||||
{
|
||||
// the lexer converts floats from its token buffer, so the JSON section
|
||||
// below never places a float token at the page boundary; this calls the
|
||||
// conversion directly: the 8-digit loops of parse_float_significand()
|
||||
// and digit_comparison() read up to the end of the token
|
||||
const std::vector<std::string> tokens = all_prefixes(
|
||||
{
|
||||
"1234567812345678123456781234567812345678",
|
||||
"-1.2345678901234567890123456789e-123",
|
||||
"0.00000000000000000000000000000000000001",
|
||||
"98765432.10987654",
|
||||
"123456789012345678901234567890e-30",
|
||||
// halfway between 1 and the next double, then just above it
|
||||
"1.00000000000000011102230246251565404236316680908203125",
|
||||
"1.000000000000000111022302462515654042363166809082031250000000001",
|
||||
});
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
// only complete number tokens: the conversion expects validated input
|
||||
if (!token.empty() && json::accept(token))
|
||||
{
|
||||
CAPTURE(token)
|
||||
check_float_native<double>(guard, token);
|
||||
check_float_native<float>(guard, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("JSON")
|
||||
{
|
||||
for (const auto& input : json_seeds())
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow);
|
||||
});
|
||||
const std::string expected_comments = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow, true);
|
||||
});
|
||||
const bool expected_accept = json::accept(input);
|
||||
counting_sax expected_sax;
|
||||
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
|
||||
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
const std::uint8_t* const last = first + input.size();
|
||||
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow);
|
||||
}) == expected);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow, true);
|
||||
}) == expected_comments);
|
||||
CHECK(json::accept(first, last) == expected_accept);
|
||||
|
||||
counting_sax sax;
|
||||
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
|
||||
CHECK(sax.events == expected_sax.events);
|
||||
CHECK(sax.error == expected_sax.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
// every prefix ends inside a value, e.g., inside a BSON key or BON8
|
||||
// string without its terminator
|
||||
const json j = binary_document();
|
||||
check_binary(guard, json::to_bson(j), read_bson());
|
||||
check_binary(guard, json::to_bon8(j), read_bon8());
|
||||
check_binary(guard, json::to_cbor(j), read_cbor());
|
||||
check_binary(guard, json::to_msgpack(j), read_msgpack());
|
||||
check_binary(guard, json::to_ubjson(j), read_ubjson());
|
||||
check_binary(guard, json::to_bjdata(j), read_bjdata());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1637,6 +1637,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
||||
{
|
||||
Json j = {1, 2, 3, 4};
|
||||
|
||||
const std::vector<int> reversed = {4, 3, 2, 1};
|
||||
auto it = j.end();
|
||||
std::vector<int> seen;
|
||||
while (it != j.begin())
|
||||
@@ -1644,7 +1645,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
||||
--it;
|
||||
seen.push_back(it->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
CHECK(seen == reversed);
|
||||
|
||||
auto it2 = j.end();
|
||||
auto old = it2--;
|
||||
@@ -1656,14 +1657,14 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json,
|
||||
{
|
||||
seen.push_back(r->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
CHECK(seen == reversed);
|
||||
|
||||
seen.clear();
|
||||
for (auto r = j.crbegin(); r != j.crend(); ++r)
|
||||
{
|
||||
seen.push_back(*r);
|
||||
seen.push_back(r->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
CHECK(seen == reversed);
|
||||
}
|
||||
|
||||
SECTION("object: -- and rbegin/rend")
|
||||
|
||||
@@ -1230,11 +1230,11 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
||||
|
||||
SECTION("random tokens")
|
||||
{
|
||||
std::mt19937 gen(42);
|
||||
std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
std::string token(static_cast<std::size_t>(gen() % 23), '0');
|
||||
std::string token(gen() % 23, '0');
|
||||
for (auto& c : token)
|
||||
{
|
||||
c = alphabet[gen() % alphabet.size()];
|
||||
|
||||
@@ -13,8 +13,6 @@ using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
@@ -55,24 +53,6 @@ const json* innermost_value(const json& j, std::size_t& depth)
|
||||
return current;
|
||||
}
|
||||
|
||||
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||
// the single member "a", which every object type enumerates in the same order.
|
||||
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||
{
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
const bool array = pattern[i % pattern.size()] == '[';
|
||||
text += array ? "[" : "{\"a\":";
|
||||
closing += array ? ']' : '}';
|
||||
}
|
||||
text += '0';
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("tests on deeply nested JSONs")
|
||||
@@ -244,385 +224,4 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
CHECK(*innermost_value(j, unused) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #5650 - stack overflow converting between specializations")
|
||||
{
|
||||
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||
|
||||
SECTION("json to ordered_json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json to json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||
|
||||
const json converted = o;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get<ordered_json>()")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("depths around the bound of the recursive descent")
|
||||
{
|
||||
for (std::size_t d = 1; d <= 300; ++d)
|
||||
{
|
||||
CAPTURE(d)
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(d, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
const json back = converted;
|
||||
CHECK(back.dump() == text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values below the bound are converted as values above it")
|
||||
{
|
||||
// Bury a value below the bound, where it is converted without the
|
||||
// call stack, and compare it with the same value converted on its
|
||||
// own by the containers' range constructors. Its objects have
|
||||
// members that the two object types enumerate in different orders.
|
||||
const auto bury = [](nlohmann::ordered_json value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
value = nlohmann::ordered_json::array({std::move(value)});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const auto dig = [](const json & value)
|
||||
{
|
||||
const json* current = &value;
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||
};
|
||||
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||
|
||||
const json converted_above = value;
|
||||
const json buried = bury(value);
|
||||
const json& converted_below = *dig(buried);
|
||||
|
||||
CHECK(converted_below.dump() == converted_above.dump());
|
||||
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||
|
||||
// a discarded value is never equal to anything, so compare the rest
|
||||
value.erase("r");
|
||||
const json without_discarded_above = value;
|
||||
const json without_discarded_buried = bury(value);
|
||||
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
json nested_array(const std::size_t depth, json leaf)
|
||||
{
|
||||
json j = std::move(leaf);
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
json a = json::array();
|
||||
a.push_back(std::move(j));
|
||||
j = std::move(a);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
json nested_object(const std::size_t depth, json leaf)
|
||||
{
|
||||
json j = std::move(leaf);
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
json o = json::object();
|
||||
o["k"] = std::move(j);
|
||||
j = std::move(o);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
{
|
||||
// 200 is past the point where the writers stop recursing, and still
|
||||
// shallow enough that from_* and operator== (which still recurse) are fine.
|
||||
const json deep_array = nested_array(200, json(0));
|
||||
const json deep_object = nested_object(200, json("x"));
|
||||
const json empty_array = nested_array(200, json::array());
|
||||
const json empty_object = nested_object(200, json::object());
|
||||
const json mixed = nested_object(80, nested_array(80, json(true)));
|
||||
|
||||
SECTION("roundtrip past the recursion bound")
|
||||
{
|
||||
CHECK(json::from_cbor(json::to_cbor(deep_array)) == deep_array);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(deep_array)) == deep_array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_array)) == deep_array);
|
||||
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")
|
||||
{
|
||||
for (std::size_t depth = 120; depth <= 140; ++depth)
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
const json array = nested_array(depth, json(7));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a BJData ndarray below the bound is still an ndarray")
|
||||
{
|
||||
const json ndarray = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
const json invalid = json({{"_ArrayType_", "nope"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1}}});
|
||||
|
||||
const json deep_ndarray = nested_array(140, ndarray);
|
||||
const json deep_invalid = nested_array(140, invalid);
|
||||
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_ndarray)) == deep_ndarray);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_invalid)) == deep_invalid);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(ndarray)) == ndarray);
|
||||
}
|
||||
|
||||
SECTION("byte-exact across the switch-over")
|
||||
{
|
||||
// nested one-element arrays around the recursion bound: the exact
|
||||
// bytes a writer produces do not depend on whether it stayed on the
|
||||
// call stack or moved to the heap one partway through
|
||||
for (const std::size_t depth :
|
||||
{
|
||||
nlohmann::detail::recursion_depth_limit() - 1, nlohmann::detail::recursion_depth_limit(),
|
||||
nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2
|
||||
})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json array = nested_array(depth, json(0));
|
||||
|
||||
std::vector<std::uint8_t> expected_cbor(depth, 0x81);
|
||||
expected_cbor.push_back(0x00);
|
||||
CHECK(json::to_cbor(array) == expected_cbor);
|
||||
|
||||
std::vector<std::uint8_t> expected_msgpack(depth, 0x91);
|
||||
expected_msgpack.push_back(0x00);
|
||||
CHECK(json::to_msgpack(array) == expected_msgpack);
|
||||
|
||||
std::string expected_ubjson(depth, '[');
|
||||
expected_ubjson += "i";
|
||||
expected_ubjson += '\0';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a deep object, and a BJData ndarray, past the recursion bound")
|
||||
{
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
|
||||
const json object = nested_object(depth, json(42));
|
||||
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||
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);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_ndarray)) == deep_ndarray);
|
||||
}
|
||||
|
||||
SECTION("a discarded value past the recursion bound still throws type_error.321")
|
||||
{
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
const json discarded_leaf(json::value_t::discarded);
|
||||
const json deep_discarded = nested_array(depth, discarded_leaf);
|
||||
|
||||
// with diagnostics, the message names the path to the discarded leaf
|
||||
#if JSON_DIAGNOSTICS
|
||||
std::string path;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
path += "/0";
|
||||
}
|
||||
const std::string prefix = "[json.exception.type_error.321] (" + path + ") ";
|
||||
#else
|
||||
const std::string prefix = "[json.exception.type_error.321] ";
|
||||
#endif
|
||||
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error);
|
||||
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;
|
||||
const json j = json::parse(std::string(depth, '[') + "0" + std::string(depth, ']'));
|
||||
|
||||
std::vector<std::uint8_t> packed;
|
||||
CHECK_NOTHROW(packed = json::to_cbor(j));
|
||||
CHECK(json::from_cbor(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_msgpack(j));
|
||||
CHECK(json::from_msgpack(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_ubjson(j));
|
||||
CHECK(json::from_ubjson(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_ubjson(j, true, false));
|
||||
CHECK(json::from_ubjson(packed) == j);
|
||||
|
||||
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")
|
||||
{
|
||||
// 200000 nested one-element CBOR arrays, the innermost holding null;
|
||||
// round-tripping this used to recurse once per level on the way back
|
||||
// out through to_cbor(), deep enough to overflow the stack
|
||||
std::vector<std::uint8_t> v(200000, 0x81);
|
||||
v.push_back(0xf6);
|
||||
const json j = json::from_cbor(v);
|
||||
CHECK(json::to_cbor(j) == v);
|
||||
|
||||
// the MessagePack analogue: fixarray of 1 nesting down to nil
|
||||
std::vector<std::uint8_t> v_msgpack(200000, 0x91);
|
||||
v_msgpack.push_back(0xc0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// split from unit-large_json.cpp so that both run in parallel and stay below
|
||||
// the ctest timeout under Valgrind
|
||||
|
||||
namespace
|
||||
{
|
||||
json nested_array(const std::size_t depth, json leaf)
|
||||
{
|
||||
json j = std::move(leaf);
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
json a = json::array();
|
||||
a.push_back(std::move(j));
|
||||
j = std::move(a);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
json nested_object(const std::size_t depth, json leaf)
|
||||
{
|
||||
json j = std::move(leaf);
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
json o = json::object();
|
||||
o["k"] = std::move(j);
|
||||
j = std::move(o);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("issue #5392 - binary writers on deeply nested values")
|
||||
{
|
||||
// 200 is past the point where the writers stop recursing, and still
|
||||
// shallow enough that from_* and operator== (which still recurse) are fine.
|
||||
const json deep_array = nested_array(200, json(0));
|
||||
const json deep_object = nested_object(200, json("x"));
|
||||
const json empty_array = nested_array(200, json::array());
|
||||
const json empty_object = nested_object(200, json::object());
|
||||
const json mixed = nested_object(80, nested_array(80, json(true)));
|
||||
|
||||
SECTION("roundtrip past the recursion bound")
|
||||
{
|
||||
CHECK(json::from_cbor(json::to_cbor(deep_array)) == deep_array);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(deep_array)) == deep_array);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(deep_array)) == deep_array);
|
||||
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")
|
||||
{
|
||||
for (std::size_t depth = 120; depth <= 140; ++depth)
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
const json array = nested_array(depth, json(7));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a BJData ndarray below the bound is still an ndarray")
|
||||
{
|
||||
const json ndarray = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
const json invalid = json({{"_ArrayType_", "nope"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1}}});
|
||||
|
||||
const json deep_ndarray = nested_array(140, ndarray);
|
||||
const json deep_invalid = nested_array(140, invalid);
|
||||
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_ndarray)) == deep_ndarray);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_invalid)) == deep_invalid);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(ndarray)) == ndarray);
|
||||
}
|
||||
|
||||
SECTION("byte-exact across the switch-over")
|
||||
{
|
||||
// nested one-element arrays around the recursion bound: the exact
|
||||
// bytes a writer produces do not depend on whether it stayed on the
|
||||
// call stack or moved to the heap one partway through
|
||||
for (const std::size_t depth :
|
||||
{
|
||||
nlohmann::detail::recursion_depth_limit() - 1, nlohmann::detail::recursion_depth_limit(),
|
||||
nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2
|
||||
})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
const json array = nested_array(depth, json(0));
|
||||
|
||||
std::vector<std::uint8_t> expected_cbor(depth, 0x81);
|
||||
expected_cbor.push_back(0x00);
|
||||
CHECK(json::to_cbor(array) == expected_cbor);
|
||||
|
||||
std::vector<std::uint8_t> expected_msgpack(depth, 0x91);
|
||||
expected_msgpack.push_back(0x00);
|
||||
CHECK(json::to_msgpack(array) == expected_msgpack);
|
||||
|
||||
std::string expected_ubjson(depth, '[');
|
||||
expected_ubjson += "i";
|
||||
expected_ubjson += '\0';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a deep object, and a BJData ndarray, past the recursion bound")
|
||||
{
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
|
||||
const json object = nested_object(depth, json(42));
|
||||
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||
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);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(deep_ndarray)) == deep_ndarray);
|
||||
}
|
||||
|
||||
SECTION("a discarded value past the recursion bound still throws type_error.321")
|
||||
{
|
||||
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 50;
|
||||
const json discarded_leaf(json::value_t::discarded);
|
||||
const json deep_discarded = nested_array(depth, discarded_leaf);
|
||||
|
||||
// with diagnostics, the message names the path to the discarded leaf
|
||||
#if JSON_DIAGNOSTICS
|
||||
std::string path;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
path += "/0";
|
||||
}
|
||||
const std::string prefix = "[json.exception.type_error.321] (" + path + ") ";
|
||||
#else
|
||||
const std::string prefix = "[json.exception.type_error.321] ";
|
||||
#endif
|
||||
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error);
|
||||
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;
|
||||
const json j = json::parse(std::string(depth, '[') + "0" + std::string(depth, ']'));
|
||||
|
||||
std::vector<std::uint8_t> packed;
|
||||
CHECK_NOTHROW(packed = json::to_cbor(j));
|
||||
CHECK(json::from_cbor(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_msgpack(j));
|
||||
CHECK(json::from_msgpack(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_ubjson(j));
|
||||
CHECK(json::from_ubjson(packed) == j);
|
||||
|
||||
CHECK_NOTHROW(packed = json::to_ubjson(j, true, false));
|
||||
CHECK(json::from_ubjson(packed) == j);
|
||||
|
||||
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")
|
||||
{
|
||||
// 200000 nested one-element CBOR arrays, the innermost holding null;
|
||||
// round-tripping this used to recurse once per level on the way back
|
||||
// out through to_cbor(), deep enough to overflow the stack
|
||||
std::vector<std::uint8_t> v(200000, 0x81);
|
||||
v.push_back(0xf6);
|
||||
const json j = json::from_cbor(v);
|
||||
CHECK(json::to_cbor(j) == v);
|
||||
|
||||
// the MessagePack analogue: fixarray of 1 nesting down to nil
|
||||
std::vector<std::uint8_t> v_msgpack(200000, 0x91);
|
||||
v_msgpack.push_back(0xc0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// split from unit-large_json.cpp so that both run in parallel and stay below
|
||||
// the ctest timeout under Valgrind
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||
// the single member "a", which every object type enumerates in the same order.
|
||||
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||
{
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
const bool array = pattern[i % pattern.size()] == '[';
|
||||
text += array ? "[" : "{\"a\":";
|
||||
closing += array ? ']' : '}';
|
||||
}
|
||||
text += '0';
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("issue #5650 - stack overflow converting between specializations")
|
||||
{
|
||||
// deep enough to exhaust the call stack, but small enough to stay cheap:
|
||||
// parsing is iterative, so building the values below costs little
|
||||
const std::size_t depth = 100000;
|
||||
|
||||
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||
|
||||
SECTION("json to ordered_json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json to json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||
|
||||
const json converted = o;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get<ordered_json>()")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("depths around the bound of the recursive descent")
|
||||
{
|
||||
for (std::size_t d = 1; d <= 300; ++d)
|
||||
{
|
||||
CAPTURE(d)
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern)
|
||||
const std::string text = nested_text(d, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
const json back = converted;
|
||||
CHECK(back.dump() == text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values below the bound are converted as values above it")
|
||||
{
|
||||
// Bury a value below the bound, where it is converted without the
|
||||
// call stack, and compare it with the same value converted on its
|
||||
// own by the containers' range constructors. Its objects have
|
||||
// members that the two object types enumerate in different orders.
|
||||
const auto bury = [](nlohmann::ordered_json value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
value = nlohmann::ordered_json::array({std::move(value)});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const auto dig = [](const json & value)
|
||||
{
|
||||
const json* current = &value;
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||
};
|
||||
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||
|
||||
const json converted_above = value;
|
||||
const json buried = bury(value);
|
||||
const json& converted_below = *dig(buried);
|
||||
|
||||
CHECK(converted_below.dump() == converted_above.dump());
|
||||
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||
|
||||
// a discarded value is never equal to anything, so compare the rest
|
||||
value.erase("r");
|
||||
const json without_discarded_above = value;
|
||||
const json without_discarded_buried = bury(value);
|
||||
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||
}
|
||||
}
|
||||
@@ -1374,7 +1374,13 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
|
||||
};
|
||||
for (const auto error_handler : error_handlers)
|
||||
{
|
||||
#if defined(JSON_NOEXCEPTION)
|
||||
// a strict dump of invalid UTF-8 calls std::abort() instead of throwing
|
||||
static_cast<void>(with_strict);
|
||||
if (error_handler == json::error_handler_t::strict)
|
||||
#else
|
||||
if (!with_strict && error_handler == json::error_handler_t::strict)
|
||||
#endif
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1429,7 +1435,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
"\xF4\x90\x80\x80",
|
||||
// sequences cut short by ASCII, a control character, a quote, a
|
||||
// backslash, DEL, or the lead byte of a well-formed character
|
||||
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80",
|
||||
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", // NOLINT(bugprone-suspicious-missing-comma): "\xC3" "a" ends the hex escape
|
||||
// truncated sequences
|
||||
"\xC3", "\xE2\x82", "\xF0\x9F\x98"
|
||||
};
|
||||
@@ -1489,7 +1495,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8));
|
||||
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17u : 8u));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,11 @@ TEST_CASE("compliance tests from nativejson-benchmark")
|
||||
n1e308[0] = '[';
|
||||
n1e308[1] = '1';
|
||||
n1e308[310] = ']';
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
n1e308.pop_back(); // a NUL byte after the value is a syntax error in strict mode
|
||||
#else
|
||||
n1e308[311] = '\0';
|
||||
#endif
|
||||
TEST_DOUBLE(n1e308, 1E308);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
|
||||
SECTION("at")
|
||||
{
|
||||
const json carr = arr;
|
||||
const json cobj = obj;
|
||||
const json& carr = arr;
|
||||
const json& cobj = obj;
|
||||
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
|
||||
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
|
||||
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
|
||||
@@ -65,7 +65,7 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
|
||||
|
||||
nlohmann::ordered_json oobj = {{"a", 1}};
|
||||
const nlohmann::ordered_json coobj = oobj;
|
||||
const nlohmann::ordered_json& coobj = oobj;
|
||||
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
|
||||
@@ -94,9 +94,10 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort()
|
||||
SECTION("patch")
|
||||
{
|
||||
const auto patch_msg = [](const char* doc, const char* patch)
|
||||
const auto patch_msg = [](const char* doc, const char* patch) -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -105,16 +106,23 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it
|
||||
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) ",
|
||||
// and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both
|
||||
std::string msg = e.what();
|
||||
const auto pos = msg.find(" (/");
|
||||
if (pos != std::string::npos)
|
||||
for (const char* marker :
|
||||
{
|
||||
" (/", " (bytes "
|
||||
})
|
||||
{
|
||||
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
|
||||
const auto pos = msg.find(marker);
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return std::string();
|
||||
return {};
|
||||
};
|
||||
|
||||
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
|
||||
@@ -132,4 +140,5 @@ TEST_CASE("exception messages of out-of-line throw sites")
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user