mirror of
https://github.com/nlohmann/json.git
synced 2026-07-11 21:15:10 +00:00
855f511db4
* Fix stale Clang -Weverything suppression comments; eliminate -Wno-missing-noreturn cmake/clang_flags.cmake claimed -Wno-unsafe-buffer-usage was needed only for Doctest and that -Wno-missing-noreturn had "no way to silence... otherwise" (PR #4871, which never actually attempted a source fix). Neither held up under investigation (todo 130): - -Wno-unsafe-buffer-usage is pervasive (208 distinct sites across 19 files measured with clang trunk in silkeh/clang:dev), spanning the library's own low-level numeric/buffer code (to_chars, serializer, lexer, binary reader/writer, input adapters, json_pointer) as well as vendored Doctest itself (96 of the 208 sites). A source-level fix is not feasible at this scale; the comment now says so instead of blaming Doctest alone. - -Wno-missing-noreturn had exactly two real trigger sites, both genuinely and unconditionally non-returning: a test-only throwing allocator (tests/src/unit-allocator.cpp) and, previously undiscovered, wide_string_input_adapter::get_elements<T>() in include/nlohmann/detail/input/input_adapters.hpp. Verified this isn't a wider pattern by checking all 160 JSON_THROW call sites in the library for functions whose entire body is an unconditional throw. Annotated both ([[noreturn]] in the test file, since JSON_HEDLEY_NO_RETURN is #undef'd by the time test code runs; JSON_HEDLEY_NO_RETURN in the library file, its first real use anywhere in the codebase) and dropped the suppression entirely. single_include/nlohmann/json.hpp regenerated via `make amalgamate`; `make check-amalgamation` passes. Verified in Docker (silkeh/clang:dev, matching the ci_static_analysis_clang CI job): baseline builds clean, and the full 194-target test suite builds with zero warnings under the corrected CLANG_CXXFLAGS (-Wno-missing-noreturn no longer in the list). Also sanity-compiled and ran unit-allocator.cpp and unit-wstring.cpp on host Apple Clang to confirm behavior is unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix MSVC C4702 warning caused by JSON_HEDLEY_NO_RETURN on get_elements() PR #5250 annotated wide_string_input_adapter::get_elements<T>() with JSON_HEDLEY_NO_RETURN (it unconditionally throws). On MSVC this expands to __declspec(noreturn), and MSVC correctly determined that the code following its call in binary_reader.hpp is unreachable for that instantiation, firing C4702 under /W4 /WX in the msvc, msvc-vs2026, and msvc-arm64 Debug jobs. Clang doesn't flag this case, so the Docker verification for #5250 (which only checked Clang -Weverything) didn't catch it. This is the same warning class already tolerated for Release builds since PR #5216, where MSVC's optimizer independently found the same dead code after /Od was removed. Extend that existing /wd4702 suppression to Debug builds too, instead of reverting the noreturn annotation. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
264 lines
7.2 KiB
C++
264 lines
7.2 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"
|
|
|
|
#define JSON_TESTS_PRIVATE
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
namespace
|
|
{
|
|
// special test case to check if memory is leaked if constructor throws
|
|
template<class T>
|
|
struct bad_allocator : std::allocator<T>
|
|
{
|
|
using std::allocator<T>::allocator;
|
|
|
|
bad_allocator() = default;
|
|
template<class U> bad_allocator(const bad_allocator<U>& /*unused*/) { }
|
|
|
|
template<class... Args>
|
|
[[noreturn]] void construct(T* /*unused*/, Args&& ... /*unused*/) // NOLINT(cppcoreguidelines-missing-std-forward)
|
|
{
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
template <class U>
|
|
struct rebind
|
|
{
|
|
using other = bad_allocator<U>;
|
|
};
|
|
};
|
|
} // namespace
|
|
|
|
TEST_CASE("bad_alloc")
|
|
{
|
|
SECTION("bad_alloc")
|
|
{
|
|
// create JSON type using the throwing allocator
|
|
using bad_json = nlohmann::basic_json<std::map,
|
|
std::vector,
|
|
std::string,
|
|
bool,
|
|
std::int64_t,
|
|
std::uint64_t,
|
|
double,
|
|
bad_allocator>;
|
|
|
|
// creating an object should throw
|
|
CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
bool next_construct_fails = false;
|
|
bool next_destroy_fails = false;
|
|
bool next_deallocate_fails = false;
|
|
|
|
template<class T>
|
|
struct my_allocator : std::allocator<T>
|
|
{
|
|
using std::allocator<T>::allocator;
|
|
|
|
template<class... Args>
|
|
void construct(T* p, Args&& ... args)
|
|
{
|
|
if (next_construct_fails)
|
|
{
|
|
next_construct_fails = false;
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
|
}
|
|
|
|
void deallocate(T* p, std::size_t n)
|
|
{
|
|
if (next_deallocate_fails)
|
|
{
|
|
next_deallocate_fails = false;
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
std::allocator<T>::deallocate(p, n);
|
|
}
|
|
|
|
void destroy(T* p)
|
|
{
|
|
if (next_destroy_fails)
|
|
{
|
|
next_destroy_fails = false;
|
|
throw std::bad_alloc();
|
|
}
|
|
|
|
static_cast<void>(p); // fix MSVC's C4100 warning
|
|
p->~T();
|
|
}
|
|
|
|
template <class U>
|
|
struct rebind
|
|
{
|
|
using other = my_allocator<U>;
|
|
};
|
|
};
|
|
|
|
// allows deletion of raw pointer, usually hold by json_value
|
|
template<class T>
|
|
void my_allocator_clean_up(T* p)
|
|
{
|
|
assert(p != nullptr);
|
|
my_allocator<T> alloc;
|
|
alloc.destroy(p);
|
|
alloc.deallocate(p, 1);
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("controlled bad_alloc")
|
|
{
|
|
// 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>;
|
|
|
|
SECTION("class json_value")
|
|
{
|
|
SECTION("json_value(value_t)")
|
|
{
|
|
SECTION("object")
|
|
{
|
|
next_construct_fails = false;
|
|
auto t = my_json::value_t::object;
|
|
CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).object));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
SECTION("array")
|
|
{
|
|
next_construct_fails = false;
|
|
auto t = my_json::value_t::array;
|
|
CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).array));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
SECTION("string")
|
|
{
|
|
next_construct_fails = false;
|
|
auto t = my_json::value_t::string;
|
|
CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(t).string));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json::json_value(t), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
}
|
|
|
|
SECTION("json_value(const string_t&)")
|
|
{
|
|
next_construct_fails = false;
|
|
const my_json::string_t v("foo");
|
|
CHECK_NOTHROW(my_allocator_clean_up(my_json::json_value(v).string));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json::json_value(v), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
}
|
|
|
|
SECTION("class basic_json")
|
|
{
|
|
SECTION("basic_json(const CompatibleObjectType&)")
|
|
{
|
|
next_construct_fails = false;
|
|
const std::map<std::string, std::string> v {{"foo", "bar"}};
|
|
CHECK_NOTHROW(my_json(v));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json(v), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
|
|
SECTION("basic_json(const CompatibleArrayType&)")
|
|
{
|
|
next_construct_fails = false;
|
|
const std::vector<std::string> v {"foo", "bar", "baz"};
|
|
CHECK_NOTHROW(my_json(v));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json(v), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
|
|
SECTION("basic_json(const typename string_t::value_type*)")
|
|
{
|
|
next_construct_fails = false;
|
|
CHECK_NOTHROW(my_json("foo"));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json("foo"), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
|
|
SECTION("basic_json(const typename string_t::value_type*)")
|
|
{
|
|
next_construct_fails = false;
|
|
const std::string s("foo");
|
|
CHECK_NOTHROW(my_json(s));
|
|
next_construct_fails = true;
|
|
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
|
|
next_construct_fails = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template<class T>
|
|
struct allocator_no_forward : std::allocator<T>
|
|
{
|
|
allocator_no_forward() = default;
|
|
template <class U>
|
|
allocator_no_forward(allocator_no_forward<U> /*unused*/) {}
|
|
|
|
template <class U>
|
|
struct rebind
|
|
{
|
|
using other = allocator_no_forward<U>;
|
|
};
|
|
|
|
template <class... Args>
|
|
void construct(T* p, const Args& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) T(args...)))
|
|
{
|
|
// force copy even if move is available
|
|
::new (static_cast<void*>(p)) T(args...);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
TEST_CASE("bad my_allocator::construct")
|
|
{
|
|
SECTION("my_allocator::construct doesn't forward")
|
|
{
|
|
using bad_alloc_json = nlohmann::basic_json<std::map,
|
|
std::vector,
|
|
std::string,
|
|
bool,
|
|
std::int64_t,
|
|
std::uint64_t,
|
|
double,
|
|
allocator_no_forward>;
|
|
|
|
bad_alloc_json j;
|
|
j["test"] = bad_alloc_json::array_t();
|
|
j["test"].push_back("should not leak");
|
|
}
|
|
}
|