Make the guard page test work without exceptions and under UBSan

Under JSON_NOEXCEPTION, the error paths abort instead of throwing, so the
outcomes are now computed with allow_exceptions=false there. Copying the
input with std::copy from char to unsigned char tripped Clang's implicit
conversion sanitizer; std::memcpy avoids the conversion.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-17 08:28:55 +02:00
1 parent 3d02b8f23e
commit 0cbe6a5cdc
1 file changed
+44 -19
+44 -19
View File
@@ -19,8 +19,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#include <algorithm>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -86,7 +86,7 @@ class guarded_buffer
{ {
REQUIRE(input.size() <= page); REQUIRE(input.size() <= page);
unsigned char* const first = base + (2 * page) - input.size(); unsigned char* const first = base + (2 * page) - input.size();
std::copy(input.begin(), input.end(), first); std::memcpy(first, input.data(), input.size());
return first; return first;
} }
@@ -95,7 +95,7 @@ class guarded_buffer
{ {
REQUIRE(input.size() <= page); REQUIRE(input.size() <= page);
unsigned char* const first = base + page; unsigned char* const first = base + page;
std::copy(input.begin(), input.end(), first); std::memcpy(first, input.data(), input.size());
return first; return first;
} }
@@ -115,18 +115,24 @@ class guarded_buffer
unsigned char* base = nullptr; unsigned char* base = nullptr;
}; };
// the serialization of the result of @a f, or the exception message // 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> template<typename F>
std::string outcome(F f) std::string outcome(F f)
{ {
#if defined(JSON_NOEXCEPTION)
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
#else
try try
{ {
return "value: " + f().dump(-1, ' ', false, json::error_handler_t::keep); return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
} }
catch (const json::exception& e) catch (const json::exception& e)
{ {
return std::string("error: ") + e.what(); return std::string("error: ") + e.what();
} }
#endif
} }
// a SAX handler that only counts events, so sax_parse runs without a DOM // a SAX handler that only counts events, so sax_parse runs without a DOM
@@ -263,14 +269,15 @@ json binary_document()
} }
// readers for check_binary(), callable with a container or an iterator range // readers for check_binary(), callable with a container or an iterator range
#define JSON_TEST_GUARD_PAGE_READER(name, from) \ // followed by the allow_exceptions flag
struct name \ #define JSON_TEST_GUARD_PAGE_READER(name, from) \
{ \ struct name \
template<typename... Args> \ { \
json operator()(Args&& ... args) const \ template<typename... Args> \
{ \ json operator()(bool allow_exceptions, Args&& ... args) const \
return json::from(std::forward<Args>(args)...); \ { \
} \ 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_bson, from_bson)
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8) JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
@@ -288,11 +295,17 @@ void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes,
for (const auto& input : all_prefixes({std::string(bytes.begin(), bytes.end())})) for (const auto& input : all_prefixes({std::string(bytes.begin(), bytes.end())}))
{ {
CAPTURE(input) CAPTURE(input)
const std::string expected = outcome([&] { return parse(input); }); const std::string expected = outcome([&](bool allow)
{
return parse(allow, input);
});
for (int placement = 0; placement < 2; ++placement) 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 first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
CHECK(outcome([&] { return parse(first, first + input.size()); }) == expected); CHECK(outcome([&](bool allow)
{
return parse(allow, first, first + input.size());
}) == expected);
} }
} }
} }
@@ -382,8 +395,14 @@ TEST_CASE("guard page")
for (const auto& input : json_seeds()) for (const auto& input : json_seeds())
{ {
CAPTURE(input) CAPTURE(input)
const std::string expected = outcome([&] { return json::parse(input); }); const std::string expected = outcome([&](bool allow)
const std::string expected_comments = outcome([&] { return json::parse(input, nullptr, true, true); }); {
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); const bool expected_accept = json::accept(input);
counting_sax expected_sax; counting_sax expected_sax;
const bool expected_sax_result = json::sax_parse(input, &expected_sax); const bool expected_sax_result = json::sax_parse(input, &expected_sax);
@@ -393,8 +412,14 @@ TEST_CASE("guard page")
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input); 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(); const std::uint8_t* const last = first + input.size();
CHECK(outcome([&] { return json::parse(first, last); }) == expected); CHECK(outcome([&](bool allow)
CHECK(outcome([&] { return json::parse(first, last, nullptr, true, true); }) == expected_comments); {
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); CHECK(json::accept(first, last) == expected_accept);
counting_sax sax; counting_sax sax;