diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 7b7222f06..96d4691ef 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -1348,6 +1348,64 @@ struct iterator_input_adapter_factory +struct is_byte_char_type +{ + // a member, not an integral_constant argument, which astyle misformats + static constexpr bool value = sizeof(CharType) == 1 && std::is_integral::value && !std::is_same::value; +}; + +template::value> +struct is_std_contiguous_byte_iterator_impl : std::false_type {}; + +// (the containers are only named for byte types) +template +struct is_std_contiguous_byte_iterator_impl +{ + static constexpr bool value = !std::is_pointer::value + && (std::is_same::iterator>::value + || std::is_same::const_iterator>::value + || std::is_same::value + || std::is_same::value); +}; + +template +struct is_std_contiguous_byte_iterator + : is_std_contiguous_byte_iterator_impl::value_type>::type> {}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using sentinel_type = IteratorType; + using char_type = typename std::remove_cv::value_type>::type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + if (first == last) + { + // (an empty range must not be dereferenced) + return adapter_type(nullptr, nullptr); + } + const char_type* const begin = std::addressof(*first); + return adapter_type(begin, begin + (last - first)); + } +}; + // General purpose iterator-based input (iterator+sentinel pair; SentinelType // defaults to IteratorType for the common same-type case, but may differ for // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5104551ea..0db1793ac 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10457,6 +10457,64 @@ struct iterator_input_adapter_factory +struct is_byte_char_type +{ + // a member, not an integral_constant argument, which astyle misformats + static constexpr bool value = sizeof(CharType) == 1 && std::is_integral::value && !std::is_same::value; +}; + +template::value> +struct is_std_contiguous_byte_iterator_impl : std::false_type {}; + +// (the containers are only named for byte types) +template +struct is_std_contiguous_byte_iterator_impl +{ + static constexpr bool value = !std::is_pointer::value + && (std::is_same::iterator>::value + || std::is_same::const_iterator>::value + || std::is_same::value + || std::is_same::value); +}; + +template +struct is_std_contiguous_byte_iterator + : is_std_contiguous_byte_iterator_impl::value_type>::type> {}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using sentinel_type = IteratorType; + using char_type = typename std::remove_cv::value_type>::type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + if (first == last) + { + // (an empty range must not be dereferenced) + return adapter_type(nullptr, nullptr); + } + const char_type* const begin = std::addressof(*first); + return adapter_type(begin, begin + (last - first)); + } +}; + // General purpose iterator-based input (iterator+sentinel pair; SentinelType // defaults to IteratorType for the common same-type case, but may differ for // C++20 ranges-style iterator+sentinel pairs). Only enable for types that can diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index f7957d566..cf3630b8e 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -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::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()); diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index 5b8f086fd..858597cdc 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -32,6 +32,7 @@ using nlohmann::json; #include #include +#include #include #include #include @@ -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 +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 +std::vector bytes_as(const std::string& text) +{ + std::vector result; + result.reserve(text.size()); + for (const char c : text) + { + result.push_back(static_cast(c)); + } + return result; +} + +/// a std::string, std::vector, and 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 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 vc(text.begin(), text.end()); + CHECK(outcome_of(vc.begin(), vc.end()) == expected); + const std::vector vu = bytes_as(text); + CHECK(outcome_of(vu.begin(), vu.end()) == expected); + CHECK(json::accept(vu.begin(), vu.end()) == expected_accept); + const std::vector vs = bytes_as(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