Read iterator pairs of std::string and std::vector as pointer ranges (#6029)
* Read iterator pairs of std::string and std::vector as pointer ranges Before C++20, a pair of iterators of a contiguous container cannot be told from any other random-access iterator, so json::parse(s.begin(), s.end()) on a std::string or std::vector<char> was read one character at a time, without the bulk scans of strings, numbers and indentation that the container itself gets (the container overload of input_adapter() already maps it to a pointer range). The iterators of std::string and of a std::vector of 1-byte integral elements are now matched exactly and mapped to the pointer range they cover; an empty range is not dereferenced. The character type stays the same, so the results and error positions do not change. parse(first, last) of a std::string gets 1.7-2.0x as fast in C++17 on an Apple M1 Max, accept(first, last) 2.7-3.1x, and from_cbor(first, last) of a std::vector 8-23% faster. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: clang-tidy, unused result, Infer false positive in tests - unit-deserialization.cpp: raw string literals, and assign the result of parse() in CHECK_THROWS_AS (-Wunused-result) - unit-bson.cpp: mark Infer's NULLPTR_DEREFERENCE on from_bson() with an iterator pair as a false positive Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix implicit char conversion in iterator-pair test The test built std::vector<unsigned char> and <signed char> from a char range, which -fsanitize=integer reports as an implicit conversion. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: sweep_allocator::construct() accepts pointers to const Recent libc++ (emsdk latest) probes construct(const T*, const T&) in an unevaluated context while checking for trivial copies; the static_cast to void* in the noexcept specification then fails to compile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Tests: guard sweep_allocator::construct() against const types instead of const_cast The const_cast added in the previous commit is rejected by cppcoreguidelines-pro-type-const-cast (clang-tidy). Make construct() not viable for const U instead, which is what libc++ probes for. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI: astyle and clang-tidy for sweep_allocator::construct() astyle 3.4.13 reformats the enable_if<!...> default template argument (Check amalgamation). Delete the const overload instead, which makes the libc++ probe for const objects fail in the SFINAE context just the same and needs no <type_traits>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5NasEsrzHhSNouxSoEwqz Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Iterator ranges: astyle-safe trait members; reserve in the test helper The byte check and the container match are static constexpr members instead of integral_constant arguments and a default template argument with comparisons, which astyle spaced out. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a4aec3e69
commit
8d591bf720
4 files changed
+193
No files matched your search
@@ -1674,6 +1674,8 @@ TEST_CASE("BSON input that cannot be read is discarded by every overload")
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
// false positive: Infer takes std::integral_constant<bool, false>::value for a pointer
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
|
||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
|
||||
|
||||
@@ -32,6 +32,7 @@ using nlohmann::json;
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <valarray>
|
||||
@@ -1409,3 +1410,77 @@ TEST_CASE("indentation runs of every length around the 8-byte skip")
|
||||
CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
/// the value of parsing [first, last), or the message of the exception
|
||||
template<typename Iterator>
|
||||
std::string outcome_of(Iterator first, Iterator last)
|
||||
{
|
||||
try
|
||||
{
|
||||
return json::parse(first, last).dump();
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
}
|
||||
|
||||
/// the bytes of @a text as a vector of @a T (explicit casts: no implicit char conversion)
|
||||
template<typename T>
|
||||
std::vector<T> bytes_as(const std::string& text)
|
||||
{
|
||||
std::vector<T> result;
|
||||
result.reserve(text.size());
|
||||
for (const char c : text)
|
||||
{
|
||||
result.push_back(static_cast<T>(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// a std::string, std::vector<char>, <unsigned char> and <signed char> as
|
||||
/// iterator pairs: the same as the byte path (std::list iterators)
|
||||
void check_iterator_pairs(const std::string& text)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const std::list<char> bytes(text.begin(), text.end());
|
||||
const std::string expected = outcome_of(bytes.begin(), bytes.end());
|
||||
const bool expected_accept = json::accept(bytes.begin(), bytes.end());
|
||||
|
||||
std::string s = text;
|
||||
CHECK(outcome_of(s.begin(), s.end()) == expected);
|
||||
CHECK(outcome_of(s.cbegin(), s.cend()) == expected);
|
||||
CHECK(json::accept(s.cbegin(), s.cend()) == expected_accept);
|
||||
|
||||
const std::vector<char> vc(text.begin(), text.end());
|
||||
CHECK(outcome_of(vc.begin(), vc.end()) == expected);
|
||||
const std::vector<unsigned char> vu = bytes_as<unsigned char>(text);
|
||||
CHECK(outcome_of(vu.begin(), vu.end()) == expected);
|
||||
CHECK(json::accept(vu.begin(), vu.end()) == expected_accept);
|
||||
const std::vector<signed char> vs = bytes_as<signed char>(text);
|
||||
CHECK(outcome_of(vs.begin(), vs.end()) == expected);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("deserialization from iterator pairs of std::string and std::vector")
|
||||
{
|
||||
// these are read as the pointer range they are, also before C++20
|
||||
for (const char* text :
|
||||
{
|
||||
"", " ", R"([1, 2.5, "a", true, null, {"k": ["v"]}])", "[1, 2", R"({"a" 1})", "\"\xC3\xA4\"", "\"\xC3\"", "[1]x", "1e400"
|
||||
})
|
||||
{
|
||||
check_iterator_pairs(text);
|
||||
}
|
||||
check_iterator_pairs(std::string("[1]" "\0" "x", 5));
|
||||
|
||||
// an empty range is not dereferenced
|
||||
std::string empty;
|
||||
CHECK_FALSE(json::accept(empty.begin(), empty.end()));
|
||||
json _;
|
||||
CHECK_THROWS_AS(_ = json::parse(empty.begin(), empty.end()), json::parse_error&);
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user