* Fix silent wrong results for custom number types
Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:
- Fix silent wrong results for custom number types
- Fix CI: build the out_of_range.407 message from string_t and fix a link
- Fix CI: diagnostics message, clang-tidy, and MinGW section limit
- Format the out_of_range.407 number without dump()
- Move the std::deque array test out of unit-regression3.cpp
- Fix clang-tidy and clang 3.4/3.5 failures in the custom number type changes
- Fix CI: cast the 128-bit unsigned value explicitly in the integer fast path
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Name the range checks of integer_fits_max/min
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
213 lines
7.5 KiB
C++
213 lines
7.5 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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 <cstdint>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
|
|
// std::deque has no capacity() member function, which the library only needs
|
|
// to detect a reallocation for JSON_DIAGNOSTICS
|
|
using deque_json = nlohmann::json::with_array_t<std::deque>;
|
|
|
|
// a std::vector whose at() is hidden: the library performs its own bounds
|
|
// check and must not fall back to the container's checked accessor
|
|
template<class T, class Allocator = std::allocator<T>>
|
|
class vector_without_at : public std::vector<T, Allocator>
|
|
{
|
|
public:
|
|
vector_without_at() = default;
|
|
|
|
// the array of an initializer list is built from a range
|
|
template<class InputIt>
|
|
vector_without_at(InputIt first, InputIt last) : std::vector<T, Allocator>(first, last) {}
|
|
|
|
void at() = delete;
|
|
};
|
|
|
|
using no_at_json = nlohmann::json::with_array_t<vector_without_at>;
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("array type without capacity()")
|
|
{
|
|
SECTION("the iterators take their exception specification from the container")
|
|
{
|
|
// basic_json's iterators move exactly as the container iterators do:
|
|
// their move operations are defaulted without a declared noexcept,
|
|
// because an array or object type whose iterator is not nothrow move
|
|
// constructible would otherwise have them deleted (std::deque's is not
|
|
// with libstdc++ before 11, and neither are MSVC's debug iterators)
|
|
CHECK(std::is_nothrow_move_constructible<nlohmann::json::iterator>::value ==
|
|
(std::is_nothrow_move_constructible<nlohmann::json::object_t::iterator>::value
|
|
&& std::is_nothrow_move_constructible<nlohmann::json::array_t::iterator>::value));
|
|
CHECK(std::is_nothrow_move_assignable<nlohmann::json::iterator>::value ==
|
|
(std::is_nothrow_move_assignable<nlohmann::json::object_t::iterator>::value
|
|
&& std::is_nothrow_move_assignable<nlohmann::json::array_t::iterator>::value));
|
|
CHECK(std::is_nothrow_move_constructible<nlohmann::json::const_iterator>::value ==
|
|
(std::is_nothrow_move_constructible<nlohmann::json::object_t::const_iterator>::value
|
|
&& std::is_nothrow_move_constructible<nlohmann::json::array_t::const_iterator>::value));
|
|
|
|
// and they are movable at all, which is what dropping the declared
|
|
// noexcept buys for a std::deque array
|
|
CHECK(std::is_move_constructible<deque_json::iterator>::value);
|
|
CHECK(std::is_move_assignable<deque_json::iterator>::value);
|
|
}
|
|
|
|
SECTION("adding elements")
|
|
{
|
|
deque_json j = deque_json::array();
|
|
j.push_back(1);
|
|
j.push_back("two");
|
|
j.emplace_back(3);
|
|
j += 4;
|
|
|
|
CHECK(j.size() == 4);
|
|
CHECK(j == deque_json({1, "two", 3, 4}));
|
|
CHECK(j.back() == 4);
|
|
CHECK(j.front() == 1);
|
|
}
|
|
|
|
SECTION("accessing and modifying elements")
|
|
{
|
|
auto j = deque_json::parse(R"([1,2,3])");
|
|
|
|
CHECK(j[1] == 2);
|
|
CHECK(j.at(2) == 3);
|
|
|
|
// growing through operator[] fills up with null values
|
|
j[5] = 6;
|
|
CHECK(j.size() == 6);
|
|
CHECK(j[4].is_null());
|
|
CHECK(j[5] == 6);
|
|
|
|
j.erase(0);
|
|
CHECK(j == deque_json({2, 3, nullptr, nullptr, 6}));
|
|
|
|
auto it = j.erase(j.begin());
|
|
CHECK(*it == 3);
|
|
|
|
j.insert(j.begin(), 1);
|
|
CHECK(j.front() == 1);
|
|
}
|
|
|
|
SECTION("serialization and deserialization")
|
|
{
|
|
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
|
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
|
CHECK(deque_json::parse(j.dump()) == j);
|
|
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
|
|
|
// empty containers are flattened to null and cannot be restored
|
|
const auto nested = deque_json::parse(R"({"a":[1,[2,3]]})");
|
|
CHECK(nested.flatten().unflatten() == nested);
|
|
}
|
|
|
|
SECTION("insert(pos, initializer_list) compiles and works without reserve()")
|
|
{
|
|
// std::deque has no reserve() either; insert(pos, ilist) must not
|
|
// require it (regression test for #5656, which also covers an ilist
|
|
// that refers to elements of the array being inserted into)
|
|
deque_json j = deque_json::array();
|
|
j.push_back("a");
|
|
j.push_back("b");
|
|
j.push_back("c");
|
|
|
|
const deque_json& cj = j;
|
|
auto it = j.insert(j.begin(), {cj[0], cj[1]});
|
|
CHECK(*it == deque_json("a"));
|
|
CHECK(j == deque_json({"a", "b", "a", "b", "c"}));
|
|
}
|
|
|
|
SECTION("references stay valid while the array grows")
|
|
{
|
|
deque_json j = deque_json::array();
|
|
j.push_back(1);
|
|
auto& first = j[0];
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
j.push_back(i);
|
|
}
|
|
CHECK(&first == &j[0]);
|
|
CHECK(first == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("array type without at()")
|
|
{
|
|
// built in memory rather than parsed, so that the exception message does
|
|
// not gain a byte range with JSON_DIAGNOSTIC_POSITIONS
|
|
no_at_json j = {1, 2, 3};
|
|
const auto& jc = j;
|
|
|
|
CHECK(j.at(0) == 1);
|
|
CHECK(j.at(2) == 3);
|
|
CHECK(jc.at(2) == 3);
|
|
|
|
CHECK_THROWS_WITH_AS(j.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", no_at_json::out_of_range);
|
|
CHECK_THROWS_WITH_AS(jc.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", no_at_json::out_of_range);
|
|
|
|
CHECK(j.at(no_at_json::json_pointer("/1")) == 2);
|
|
CHECK_THROWS_AS(j.at(no_at_json::json_pointer("/3")), no_at_json::out_of_range);
|
|
}
|
|
|
|
TEST_CASE("regression test #5476 - array type without reserve()")
|
|
{
|
|
// the capacity reserved for definite-length arrays must not require the
|
|
// array type to have a reserve() member function
|
|
SECTION("std::deque")
|
|
{
|
|
const auto j = deque_json::parse(R"({"a":[1,[2,3]],"b":[]})");
|
|
CHECK(j.dump() == R"({"a":[1,[2,3]],"b":[]})");
|
|
|
|
// the binary formats pass a definite length to start_array()
|
|
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
|
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
|
CHECK(deque_json::from_bon8(deque_json::to_bon8(j)) == j);
|
|
|
|
// parse() instantiates the callback parser as well, which reserves too
|
|
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
|
{
|
|
return true;
|
|
});
|
|
CHECK(with_callback == deque_json({1, 2, 3}));
|
|
}
|
|
|
|
SECTION("std::vector still reserves")
|
|
{
|
|
json array = json::array();
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
array.push_back(i);
|
|
}
|
|
|
|
const auto j = json::from_cbor(json::to_cbor(array));
|
|
CHECK(j == array);
|
|
CHECK(j.get_ref<const json::array_t&>().capacity() >= 100);
|
|
}
|
|
|
|
SECTION("the reservation stays capped")
|
|
{
|
|
// CBOR array announcing 2^32-1 elements, but truncated right after the
|
|
// header: the input must be rejected without reserving that capacity
|
|
const std::vector<std::uint8_t> truncated = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
|
}
|
|
}
|