mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 06:57:14 +00:00
Create a value before giving it its type
Squashed onto develop from: - Create a value before giving it its type - Skip the failed-allocation test when exceptions are disabled - Keep the created pointer rather than an uninitialized json_value - Skip the vector<bool> failed-allocation check for VS 2015 with iterator debugging - Test the remaining to_json overloads with a failing allocation - Skip the to_json allocation-failure section on VS 2015 Debug - Fix false GCC -Warray-bounds error with JSON_DIAGNOSTICS at -O3 (#5744) Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
8a142bc436
commit
6887cf7e53
6 files changed
+360
-60
No files matched your search
@@ -141,6 +141,11 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
||||
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
||||
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
||||
|
||||
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742)
|
||||
json_test_set_test_options(test-diagnostics-optimized
|
||||
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds>
|
||||
)
|
||||
|
||||
#############################################################################
|
||||
# add unit tests
|
||||
#############################################################################
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <valarray>
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// special test case to check if memory is leaked if constructor throws
|
||||
@@ -607,3 +612,173 @@ TEST_CASE("bad my_allocator::construct")
|
||||
j["test"].push_back("should not leak");
|
||||
}
|
||||
}
|
||||
|
||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||
// next_construct_fails set for the next allocation outside a check
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
{
|
||||
// create JSON type using the throwing allocator
|
||||
using my_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
my_allocator>;
|
||||
|
||||
// Each of these creates a string, array, object, or binary value. The
|
||||
// value must be created before the type is changed: otherwise, a failed
|
||||
// creation left a value of the new type without anything behind it (an
|
||||
// assertion in its destructor, a null pointer everywhere else) or, when
|
||||
// an old value was destroyed first, with a pointer to that destroyed one.
|
||||
|
||||
SECTION("creating a binary value")
|
||||
{
|
||||
const std::vector<std::uint8_t> bytes = {1, 2, 3};
|
||||
my_json _;
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&);
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&);
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes)), std::bad_alloc&);
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes), 42), std::bad_alloc&);
|
||||
next_construct_fails = false;
|
||||
}
|
||||
|
||||
SECTION("turning a null value into an array or object")
|
||||
{
|
||||
my_json j;
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j[0], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
#endif
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
const my_json one = 1;
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
const my_json object = {{"key", 1}};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(j.update(object), std::bad_alloc&);
|
||||
CHECK(j.is_null());
|
||||
|
||||
next_construct_fails = false;
|
||||
}
|
||||
|
||||
// With iterator debugging, VS 2015's containers construct a proxy with the
|
||||
// allocator in constructors that cannot report its failure, so a failing
|
||||
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
SECTION("converting into an existing value")
|
||||
{
|
||||
// to_json replaces the value it is given; the old one must survive a
|
||||
// failed creation of the new one
|
||||
my_json j = "old";
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<int> {1, 2}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::map<std::string, int> {{"a", 1}}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
// the overloads for lvalues of the value types, for the value types
|
||||
// themselves, and for the remaining compatible types
|
||||
const std::string string = "new";
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
// to_json only moves a binary value that it converted from another
|
||||
// container type, which my_json's std::vector<std::uint8_t> is not
|
||||
using binary_constructor = nlohmann::detail::external_constructor<nlohmann::detail::value_t::binary>;
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
my_json::array_t array = {1, 2};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
my_json::object_t object = {{"a", 1}};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
CHECK(j == "old");
|
||||
#endif
|
||||
|
||||
next_construct_fails = false;
|
||||
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||
CHECK(j == my_json({1, 2}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// Regression test for https://github.com/nlohmann/json/issues/5742: with
|
||||
// JSON_DIAGNOSTICS, GCC (12 to at least 16) reported a false -Warray-bounds
|
||||
// error in the inlined set_parents() at -O3. The type of a new string was set
|
||||
// before the string was allocated, so GCC had to assume that operator new
|
||||
// could change it again and checked the object branch of set_parents()
|
||||
// against the string's allocation. Setting the type after creating the value
|
||||
// avoids this. The warning depends on GCC's inlining decisions, so the
|
||||
// sections cover two patterns that trigger it on different GCC versions
|
||||
// (#4819 and #5742).
|
||||
// On GCC, this file is compiled with -O3 -Werror=array-bounds (see
|
||||
// tests/CMakeLists.txt), so the test fails to build if the warning returns.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#ifdef JSON_DIAGNOSTICS
|
||||
#undef JSON_DIAGNOSTICS
|
||||
#endif
|
||||
|
||||
#define JSON_DIAGNOSTICS 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class diag_color
|
||||
{
|
||||
red,
|
||||
green,
|
||||
blue
|
||||
};
|
||||
|
||||
void to_json(json& j, const diag_color& c)
|
||||
{
|
||||
static const std::pair<diag_color, json> m[] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
{diag_color::red, "r"},
|
||||
{diag_color::green, "g"},
|
||||
{diag_color::blue, "b"},
|
||||
};
|
||||
const auto* it = std::find_if(std::begin(m), std::end(m), [c](const std::pair<diag_color, json>& p)
|
||||
{
|
||||
return p.first == c;
|
||||
});
|
||||
j = it->second;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("diagnostics with optimization")
|
||||
{
|
||||
SECTION("issue #4819 - object in vector")
|
||||
{
|
||||
std::vector<json> jsons{};
|
||||
jsons.emplace_back(json({{"key", "value"}}));
|
||||
CHECK(jsons.back()["key"] == "value");
|
||||
}
|
||||
|
||||
SECTION("issue #5742 - string values from a static table")
|
||||
{
|
||||
json j = json::array();
|
||||
j.push_back(diag_color::red);
|
||||
j.push_back(diag_color::green);
|
||||
j.push_back(diag_color::blue);
|
||||
CHECK(j.dump() == R"(["r","g","b"])");
|
||||
CHECK_THROWS_WITH_AS(j[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user