From bf27f0ba995acd2f5af7466cc5e19bf54bc47159 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 23 Oct 2026 10:34:03 +0200 Subject: [PATCH] Amalgamate (#5891) Signed-off-by: Niels Lohmann --- single_include/nlohmann/json.hpp | 3165 ++++++++++++------------- single_include/nlohmann/json_view.hpp | 230 +- tests/src/unit-class_lexer.cpp | 5 +- 3 files changed, 1571 insertions(+), 1829 deletions(-) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f53ebbbf9..2814782ad 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3459,6 +3459,35 @@ inline bool owns_memory(const value_t t) noexcept } } +/*! +@brief whether a value of the given type is neither an object nor an array + +Unlike basic_json::is_primitive(), this includes value_t::discarded: it names +the types for which iterators, JSON Pointer resolution, and the serializer take +the "single value" path. Every enumerator is listed so that -Wswitch-enum flags +this function when a new type is added. +*/ +inline bool is_primitive(const value_t t) noexcept +{ + switch (t) + { + case value_t::object: + case value_t::array: + return false; + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return true; + } +} + /*! @brief compare an integer with a floating point number without precision loss @@ -5499,6 +5528,7 @@ enum class exception_id : int invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification bson_unsupported_type = 114, ///< a BSON record type is not supported invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed + invalid_document_image = 116, ///< json_document::load() rejects an image // invalid_iterator iterators_incompatible = 201, ///< the iterators of a range belong to different values iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with @@ -5533,6 +5563,8 @@ enum class exception_id : int invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8 type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string + document_binary_value = 319, ///< a binary value is stored in a json_document + document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) discarded_value_used = 321, ///< a discarded value is used // out_of_range array_index_out_of_range = 401, ///< an array index is out of range @@ -5550,6 +5582,7 @@ enum class exception_id : int patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte + document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit // other_error internal_error = 500, ///< unreachable code was reached patch_test_failed = 501, ///< a JSON Patch 'test' operation failed @@ -5579,6 +5612,13 @@ class exception : public std::exception return concat("[json.exception.", ename, '.', std::to_string(id_), "] "); } + /// build the message "[json.exception..] " + template + static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context) + { + return concat(name(ename, id_), diagnostics(context), what_arg); + } + static std::string diagnostics(std::nullptr_t /*leaf_element*/) { return ""; @@ -5591,48 +5631,33 @@ class exception : public std::exception std::vector tokens; for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent) { - switch (current->m_parent->type()) + const auto* parent = current->m_parent; + if (parent->is_array()) { - case value_t::array: + for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i) { - for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i) + if (&parent->m_data.m_value.array->operator[](i) == current) { - if (¤t->m_parent->m_data.m_value.array->operator[](i) == current) - { - tokens.emplace_back(std::to_string(i)); - break; - } + tokens.emplace_back(std::to_string(i)); + break; } - break; } - - case value_t::object: - { - for (const auto& element : *current->m_parent->m_data.m_value.object) - { - if (&element.second == current) - { - // data() is null-terminated, so a key containing - // a null byte is cut short here rather than - // truncating the whole message at what() - tokens.emplace_back(element.first.data()); - break; - } - } - break; - } - - case value_t::null: // LCOV_EXCL_LINE - case value_t::string: // LCOV_EXCL_LINE - case value_t::boolean: // LCOV_EXCL_LINE - case value_t::number_integer: // LCOV_EXCL_LINE - case value_t::number_unsigned: // LCOV_EXCL_LINE - case value_t::number_float: // LCOV_EXCL_LINE - case value_t::binary: // LCOV_EXCL_LINE - case value_t::discarded: // LCOV_EXCL_LINE - default: // LCOV_EXCL_LINE - break; // LCOV_EXCL_LINE } + else if (parent->is_object()) + { + for (const auto& element : *parent->m_data.m_value.object) + { + if (&element.second == current) + { + // data() is null-terminated, so a key containing + // a null byte is cut short here rather than + // truncating the whole message at what() + tokens.emplace_back(element.first.data()); + break; + } + } + } + // all other parent types (primitives) contribute no token } if (tokens.empty()) @@ -5748,7 +5773,7 @@ class invalid_iterator : public exception template::value, int> = 0> static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context) { - const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg); + const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context); return {id_, w.c_str()}; } @@ -5772,7 +5797,7 @@ class type_error : public exception template::value, int> = 0> static type_error create(int id_, const std::string& what_arg, BasicJsonContext context) { - const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg); + const std::string w = exception::what_impl("type_error", id_, what_arg, context); return {id_, w.c_str()}; } @@ -5795,7 +5820,7 @@ class out_of_range : public exception template::value, int> = 0> static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context) { - const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg); + const std::string w = exception::what_impl("out_of_range", id_, what_arg, context); return {id_, w.c_str()}; } @@ -5818,7 +5843,7 @@ class other_error : public exception template::value, int> = 0> static other_error create(int id_, const std::string& what_arg, BasicJsonContext context) { - const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg); + const std::string w = exception::what_impl("other_error", id_, what_arg, context); return {id_, w.c_str()}; } @@ -6067,12 +6092,10 @@ void from_json(const BasicJsonType& j, std::optional& opt) } #endif // JSON_HAS_CPP_17 -// overloads for basic_json template parameters -template < typename BasicJsonType, typename ArithmeticType, - enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, - int > = 0 > -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +// read a number value of any of the three number types into val; +// returns false (leaving val untouched) if j is not a number +template +bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { @@ -6083,17 +6106,17 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) const auto* ptr = j.template get_ptr(); JSON_ASSERT(ptr != nullptr); val = static_cast(*ptr); - break; + return true; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); - break; + return true; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); - break; + return true; } case value_t::null: @@ -6104,7 +6127,20 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - throw_type_must_be("number", j); + return false; + } +} + +// overloads for basic_json template parameters +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < std::is_arithmetic::value&& + !std::is_same::value, + int > = 0 > +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val))) + { + throw_type_must_be("number", j); } } @@ -6254,18 +6290,18 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, } } -// reserve() is called through this pair (modeled on from_json_object_reserve) -// so from_json_array_impl below has a single body for both ConstructibleArrayType -// that support reserve() and those that don't. -template -auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/) --> decltype(arr.reserve(size), void()) +// reserve() is called through this pair (used for arrays and objects alike) +// so from_json_array_impl and the object from_json have a single body for both +// containers that support reserve() and those that don't. +template +auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/) +-> decltype(c.reserve(size), void()) { - arr.reserve(size); + c.reserve(size); } -template -void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/) +template +void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/) {} // append through push_back() where available; containers without it (such as @@ -6295,7 +6331,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p { ConstructibleArrayType ret; const auto* inner_array = j.template get_ptr(); - from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {}); + reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); for (const auto& elem : *inner_array) { // get() returns *this, this won't call a from_json @@ -6376,17 +6412,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } } -template -auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/) --> decltype(obj.reserve(size), void()) -{ - obj.reserve(size); -} - -template -inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/) -{} - // the keys of the source are sorted for the usual object types, so hinting the // end makes every insertion constant-time (for containers that order their keys // the same way); containers without emplace_hint() (such as ordered_map) fall @@ -6415,7 +6440,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) ConstructibleObjectType ret; const auto* inner_object = j.template get_ptr(); - from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {}); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); for (const auto& p : *inner_object) { from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); @@ -6435,42 +6460,17 @@ template < typename BasicJsonType, typename ArithmeticType, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { - switch (static_cast(j)) + if (get_number_value(j, val)) { - case value_t::number_unsigned: - { - // never null: a basic_json whose number_unsigned_t is number_integer_t - // stores no number_unsigned values (#2573) - const auto* ptr = j.template get_ptr(); - JSON_ASSERT(ptr != nullptr); - val = static_cast(*ptr); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::boolean: - { - val = static_cast(*j.template get_ptr()); - break; - } - - case value_t::null: - case value_t::object: - case value_t::array: - case value_t::string: - case value_t::binary: - case value_t::discarded: - default: - throw_type_must_be("number", j); + return; } + + // unlike get_arithmetic_value(), a boolean is accepted here + if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) + { + throw_type_must_be("number", j); + } + val = static_cast(*j.template get_ptr()); } template @@ -6829,6 +6829,24 @@ inline std::string hex_byte(const std::uint8_t byte) return result; } +/*! +@brief write a UTF-16 code unit as four lowercase hexadecimal digits + +The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the +serializers, whose output is lowercase. Writes exactly four characters to +@a out and returns the pointer past them. +*/ +JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept +{ + constexpr const char* nibble_to_hex = "0123456789abcdef"; + const unsigned int unit = codeunit; + out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu]; + out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu]; + out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu]; + out[3] = nibble_to_hex[unit & 0x0Fu]; + return out + 4; +} + /////////////////// // UTF-8 encoding // /////////////////// @@ -6884,6 +6902,66 @@ void encode_utf8(std::uint32_t cp, const Out& out) } } +/*! +@brief the shape of a well-formed UTF-8 sequence, by its lead byte + +@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that +cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5 +to 0xFF). For a multibyte sequence, the second byte must lie in +[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF] +(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges +exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points +beyond U+10FFFF (0xF4). +*/ +struct utf8_sequence_shape +{ + std::uint8_t length; + std::uint8_t second_lo; + std::uint8_t second_hi; +}; + +/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape +inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept +{ + if (lead < 0x80u) + { + return {1, 0, 0}; + } + if (lead < 0xC2u) + { + return {0, 0, 0}; + } + if (lead <= 0xDFu) + { + return {2, 0x80u, 0xBFu}; + } + if (lead == 0xE0u) + { + return {3, 0xA0u, 0xBFu}; + } + if (lead == 0xEDu) + { + return {3, 0x80u, 0x9Fu}; + } + if (lead <= 0xEFu) + { + return {3, 0x80u, 0xBFu}; + } + if (lead == 0xF0u) + { + return {4, 0x90u, 0xBFu}; + } + if (lead <= 0xF3u) + { + return {4, 0x80u, 0xBFu}; + } + if (lead == 0xF4u) + { + return {4, 0x80u, 0x8Fu}; + } + return {0, 0, 0}; +} + /////////////////// // UTF-8 decoding // /////////////////// @@ -6899,7 +6977,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally written by Björn Hoehrmann. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -The library checks UTF-8 well-formedness (RFC 3629, section 4) in three +The library checks UTF-8 well-formedness (RFC 3629, section 4) in five places, which differ in speed, diagnostics, and how they read the input: - decode() below: the serializer, to escape and, in strict mode, reject @@ -6913,10 +6991,15 @@ places, which differ in speed, diagnostics, and how they read the input: bulk string scan, the bulk path of the BON8 reader, and the BON8 writer. They must accept exactly what the lexer's switch accepts. - the byte path of binary_reader::get_bon8_string(): BON8 input without bulk - access, and the bytes the bulk path leaves to it. + access, and the bytes the bulk path leaves to it. It takes the valid range + of the second byte from utf8_shape(). +- check_utf8() in detail/view/edit.hpp: strings stored in an editable + json_document, with the index of the offending byte. It also uses + utf8_shape(). -All four must accept the same set of sequences, so a change to one needs a -matching change to the others. +All five must accept the same set of sequences, which the test "UTF-8 +validators accept the same sequences" in unit-class_lexer.cpp checks +exhaustively against decode() and utf8_shape(). @param[in,out] state the current decoder state @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) @@ -7182,35 +7265,25 @@ template class iteration_proxy_value { JSON_ASSERT(anchor.m_object != nullptr); - switch (anchor.m_object->type()) + if (anchor.m_object->is_array()) { // use integer array index as key - case value_t::array: + if (array_index != array_index_last) { - if (array_index != array_index_last) - { - int_to_string( array_index_str, array_index ); - array_index_last = array_index; - } - return array_index_str; + int_to_string( array_index_str, array_index ); + array_index_last = array_index; } - - // use key from the object - case value_t::object: - return anchor.key(); - - // use an empty key for all primitive types - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - return empty_str; + return array_index_str; } + + if (anchor.m_object->is_object()) + { + // use key from the object + return anchor.key(); + } + + // use an empty key for all primitive types + return empty_str; } /// return value of the iterator @@ -8533,6 +8606,36 @@ namespace detail /// the supported input formats enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 }; +/// @return the name of a binary input format, as used in exception messages +inline const char* input_format_name(const input_format_t format) noexcept +{ + switch (format) + { + case input_format_t::cbor: + return "CBOR"; + case input_format_t::msgpack: + return "MessagePack"; + case input_format_t::ubjson: + return "UBJSON"; + case input_format_t::bson: + return "BSON"; + case input_format_t::bjdata: + return "BJData"; + case input_format_t::bon8: + return "BON8"; + case input_format_t::json: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return ""; // LCOV_EXCL_LINE + } +} + +/// @return the message of the parse_error thrown or reported for an empty input +inline const char* empty_input_message() noexcept +{ + return "attempting to parse an empty input; check that your input string or stream contains the expected JSON"; +} + //////////////////// // input adapters // //////////////////// @@ -9734,7 +9837,7 @@ inline file_input_adapter input_adapter(std::FILE* file) { if (file == nullptr) { - JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); } return file_input_adapter(file); } @@ -9743,7 +9846,7 @@ inline input_stream_adapter input_adapter(std::istream& stream) { if (stream.rdbuf() == nullptr) { - JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); } return input_stream_adapter(stream); } @@ -9768,7 +9871,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b) { if (b == nullptr) { - JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr)); } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); @@ -12019,7 +12122,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d // printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs // from is_string_special() only in that 0x7F is also a stop (it is escaped as // \u007f under ensure_ascii). -inline bool is_ascii_copyable(unsigned char c) noexcept +constexpr bool is_ascii_copyable(unsigned char c) noexcept { return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\'; } @@ -13068,194 +13171,74 @@ class lexer : public lexer_base // invalid control characters case 0x00: - { - error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; - return token_type::parse_error; - } - case 0x01: - { - error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; - return token_type::parse_error; - } - case 0x02: - { - error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; - return token_type::parse_error; - } - case 0x03: - { - error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; - return token_type::parse_error; - } - case 0x04: - { - error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; - return token_type::parse_error; - } - case 0x05: - { - error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; - return token_type::parse_error; - } - case 0x06: - { - error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; - return token_type::parse_error; - } - case 0x07: - { - error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; - return token_type::parse_error; - } - case 0x08: - { - error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; - return token_type::parse_error; - } - case 0x09: - { - error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; - return token_type::parse_error; - } - case 0x0A: - { - error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; - return token_type::parse_error; - } - case 0x0B: - { - error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; - return token_type::parse_error; - } - case 0x0C: - { - error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; - return token_type::parse_error; - } - case 0x0D: - { - error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; - return token_type::parse_error; - } - case 0x0E: - { - error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; - return token_type::parse_error; - } - case 0x0F: - { - error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; - return token_type::parse_error; - } - case 0x10: - { - error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; - return token_type::parse_error; - } - case 0x11: - { - error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; - return token_type::parse_error; - } - case 0x12: - { - error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; - return token_type::parse_error; - } - case 0x13: - { - error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; - return token_type::parse_error; - } - case 0x14: - { - error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; - return token_type::parse_error; - } - case 0x15: - { - error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; - return token_type::parse_error; - } - case 0x16: - { - error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; - return token_type::parse_error; - } - case 0x17: - { - error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; - return token_type::parse_error; - } - case 0x18: - { - error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; - return token_type::parse_error; - } - case 0x19: - { - error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; - return token_type::parse_error; - } - case 0x1A: - { - error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; - return token_type::parse_error; - } - case 0x1B: - { - error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; - return token_type::parse_error; - } - case 0x1C: - { - error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; - return token_type::parse_error; - } - case 0x1D: - { - error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; - return token_type::parse_error; - } - case 0x1E: - { - error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; - return token_type::parse_error; - } - case 0x1F: { - error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + static const char* const control_character_messages[32] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + { + "invalid string: control character U+0000 (NUL) must be escaped to \\u0000", + "invalid string: control character U+0001 (SOH) must be escaped to \\u0001", + "invalid string: control character U+0002 (STX) must be escaped to \\u0002", + "invalid string: control character U+0003 (ETX) must be escaped to \\u0003", + "invalid string: control character U+0004 (EOT) must be escaped to \\u0004", + "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005", + "invalid string: control character U+0006 (ACK) must be escaped to \\u0006", + "invalid string: control character U+0007 (BEL) must be escaped to \\u0007", + "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b", + "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t", + "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n", + "invalid string: control character U+000B (VT) must be escaped to \\u000B", + "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f", + "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r", + "invalid string: control character U+000E (SO) must be escaped to \\u000E", + "invalid string: control character U+000F (SI) must be escaped to \\u000F", + "invalid string: control character U+0010 (DLE) must be escaped to \\u0010", + "invalid string: control character U+0011 (DC1) must be escaped to \\u0011", + "invalid string: control character U+0012 (DC2) must be escaped to \\u0012", + "invalid string: control character U+0013 (DC3) must be escaped to \\u0013", + "invalid string: control character U+0014 (DC4) must be escaped to \\u0014", + "invalid string: control character U+0015 (NAK) must be escaped to \\u0015", + "invalid string: control character U+0016 (SYN) must be escaped to \\u0016", + "invalid string: control character U+0017 (ETB) must be escaped to \\u0017", + "invalid string: control character U+0018 (CAN) must be escaped to \\u0018", + "invalid string: control character U+0019 (EM) must be escaped to \\u0019", + "invalid string: control character U+001A (SUB) must be escaped to \\u001A", + "invalid string: control character U+001B (ESC) must be escaped to \\u001B", + "invalid string: control character U+001C (FS) must be escaped to \\u001C", + "invalid string: control character U+001D (GS) must be escaped to \\u001D", + "invalid string: control character U+001E (RS) must be escaped to \\u001E", + "invalid string: control character U+001F (US) must be escaped to \\u001F" + }; + error_message = control_character_messages[static_cast(current)]; return token_type::parse_error; } @@ -15344,6 +15327,52 @@ struct diagnostic_positions }; #endif +/*! +@brief error state shared by the DOM-building SAX parsers + +Holds whether a parse error occurred and whether errors are thrown, and +implements the parse_error() and is_errored() members of the SAX interface for +json_sax_dom_parser and json_sax_dom_callback_parser. +*/ +class json_sax_dom_error_state +{ + public: + explicit json_sax_dom_error_state(const bool allow_exceptions_) noexcept + : allow_exceptions(allow_exceptions_) + {} + + // make class move-only + json_sax_dom_error_state(const json_sax_dom_error_state&) = delete; + json_sax_dom_error_state(json_sax_dom_error_state&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_error_state& operator=(const json_sax_dom_error_state&) = delete; + json_sax_dom_error_state& operator=(json_sax_dom_error_state&&) = delete; // the const member prevents assignment + ~json_sax_dom_error_state() = default; + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + /*! @brief SAX implementation to create a JSON value from SAX events @@ -15358,7 +15387,7 @@ constructor contains the parsed value. @tparam BasicJsonType the JSON type */ template -class json_sax_dom_parser +class json_sax_dom_parser : public json_sax_dom_error_state { public: using number_integer_t = typename BasicJsonType::number_integer_t; @@ -15374,7 +15403,7 @@ class json_sax_dom_parser @param[in] allow_exceptions_ whether parse errors yield exceptions */ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) - : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + : json_sax_dom_error_state(allow_exceptions_), root(r), m_lexer_ref(lexer_) {} // make class move-only @@ -15503,24 +15532,6 @@ class json_sax_dom_parser return true; } - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - private: /*! @@ -15574,16 +15585,12 @@ class json_sax_dom_parser std::vector ref_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; /// the lexer reference to obtain the current position lexer_t* m_lexer_ref = nullptr; }; template -class json_sax_dom_callback_parser +class json_sax_dom_callback_parser : public json_sax_dom_error_state { public: using number_integer_t = typename BasicJsonType::number_integer_t; @@ -15599,7 +15606,7 @@ class json_sax_dom_callback_parser parser_callback_t cb, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) - : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + : json_sax_dom_error_state(allow_exceptions_), root(r), callback(std::move(cb)), m_lexer_ref(lexer_) { keep_stack.push_back(true); } @@ -15866,24 +15873,6 @@ class json_sax_dom_callback_parser return true; } - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - private: /// if there is a pending duplicate-key stash entry for this exact slot, @@ -16125,12 +16114,8 @@ class json_sax_dom_callback_parser /// value is later rejected by the callback, instead of erasing the /// member entirely std::vector> duplicate_key_stash {}; - /// whether a syntax error occurred - bool errored = false; /// callback function const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; /// a discarded value for the callback BasicJsonType discarded = BasicJsonType::value_t::discarded; /// the lexer reference to obtain the current position @@ -16622,6 +16607,40 @@ class binary_reader return is_object ? sax->end_object() : sax->end_array(); } + /*! + @brief close the innermost open container and report whether that + completed the value being read + + The value begun by the driver is complete once its container is not + inside another one. + + @param[out] value_complete whether no container is left open + @return whether the SAX parser accepted the end event + */ + bool leave_container_and_check(bool& value_complete) + { + if (JSON_HEDLEY_UNLIKELY(!leave_container())) + { + return false; + } + value_complete = container_stack.empty(); + return true; + } + + /*! + @brief read an object key and pass it to the SAX parser + + @tparam GetKey member function that reads the key into a string + @param[in,out] key string the key is read into; cleared first + @return whether reading the key and the SAX event succeeded + */ + template + bool read_object_key(string_t& key) + { + key.clear(); + return (this->*GetKey)(key) && sax->key(key); + } + ////////// // BSON // ////////// @@ -17393,6 +17412,34 @@ class binary_reader return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context); } + /*! + @brief report an object key that is not a string + + @param[in] found description of what was found instead of a string key + @return false + */ + bool unsupported_key_type(const char* found) const + { + return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found); + } + + /*! + @brief read the length of a CBOR string or byte array and then its bytes + + @tparam NumberType the type of the length field + @tparam ContainerType the destination container (string_t or binary_t) + @param[in] context further context information (for diagnostics) + @param[out] result container the bytes are appended to + + @return whether reading completed + */ + template + bool get_cbor_sized_bytes(const char* context, ContainerType& result) + { + NumberType len{}; + return get_number(len) && get_bytes(len, context, result); + } + /*! @brief reads a definite-length CBOR string @@ -17443,26 +17490,22 @@ class binary_reader case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { - std::uint8_t len{}; - return get_number(len) && get_string(len, result); + return get_cbor_sized_bytes("string", result); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { - std::uint16_t len{}; - return get_number(len) && get_string(len, result); + return get_cbor_sized_bytes("string", result); } case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { - std::uint32_t len{}; - return get_number(len) && get_string(len, result); + return get_cbor_sized_bytes("string", result); } case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { - std::uint64_t len{}; - return get_number(len) && get_string(len, result); + return get_cbor_sized_bytes("string", result); } default: @@ -17597,7 +17640,7 @@ class binary_reader break; } - return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found); + return unsupported_key_type(found); } /*! @@ -17650,30 +17693,22 @@ class binary_reader case 0x58: // Binary data (one-byte uint8_t for n follows) { - std::uint8_t len{}; - return get_number(len) && - get_binary(len, result); + return get_cbor_sized_bytes("binary", result); } case 0x59: // Binary data (two-byte uint16_t for n follow) { - std::uint16_t len{}; - return get_number(len) && - get_binary(len, result); + return get_cbor_sized_bytes("binary", result); } case 0x5A: // Binary data (four-byte uint32_t for n follow) { - std::uint32_t len{}; - return get_number(len) && - get_binary(len, result); + return get_cbor_sized_bytes("binary", result); } case 0x5B: // Binary data (eight-byte uint64_t for n follow) { - std::uint64_t len{}; - return get_number(len) && - get_binary(len, result); + return get_cbor_sized_bytes("binary", result); } default: @@ -17888,12 +17923,12 @@ class binary_reader if (at_end) { - if (JSON_HEDLEY_UNLIKELY(!leave_container())) + bool value_complete = false; + if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete))) { return false; } - // the value begun here is complete once its container is - if (container_stack.empty()) + if (value_complete) { return true; } @@ -17902,8 +17937,7 @@ class binary_reader if (top.is_object) { - key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) { return false; } @@ -18374,20 +18408,17 @@ class binary_reader case 0xD9: // str 8 { - std::uint8_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } case 0xDA: // str 16 { - std::uint16_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } case 0xDB: // str 32 { - std::uint32_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } default: @@ -18484,7 +18515,7 @@ class binary_reader break; } - return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found); + return unsupported_key_type(found); } /*! @@ -18629,12 +18660,12 @@ class binary_reader if (container_stack.back().remaining == 0) { - if (JSON_HEDLEY_UNLIKELY(!leave_container())) + bool value_complete = false; + if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete))) { return false; } - // the value begun here is complete once its container is - if (container_stack.empty()) + if (value_complete) { return true; } @@ -18647,8 +18678,7 @@ class binary_reader if (is_object) { get(); - key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key))) { return false; } @@ -18832,8 +18862,7 @@ class binary_reader { case 'U': { - std::uint8_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } case 'i': @@ -18866,8 +18895,7 @@ class binary_reader { break; } - std::uint16_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } case 'm': @@ -18876,8 +18904,7 @@ class binary_reader { break; } - std::uint32_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } case 'M': @@ -18886,8 +18913,7 @@ class binary_reader { break; } - std::uint64_t len{}; - return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + return get_string_with_length(result, context); } default: @@ -19703,12 +19729,12 @@ class binary_reader if (at_end) { - if (JSON_HEDLEY_UNLIKELY(!leave_container())) + bool value_complete = false; + if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete))) { return false; } - // the value begun here is complete once its container is - if (container_stack.empty()) + if (value_complete) { return true; } @@ -19717,8 +19743,7 @@ class binary_reader if (top.is_object) { - key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key))) + if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key))) { return false; } @@ -20099,23 +20124,11 @@ class binary_reader // the valid range of the second byte excludes overlong forms, // surrogates, and code points above U+10FFFF - // (RFC 3629, section 4) - int continuation_bytes = 0; - bool valid_second = true; - if (byte <= 0xDF) - { - continuation_bytes = 1; - } - else if (byte <= 0xEF) - { - continuation_bytes = 2; - valid_second = (byte != 0xE0 || second >= 0xA0) && (byte != 0xED || second <= 0x9F); - } - else - { - continuation_bytes = 3; - valid_second = byte <= 0xF4 && (byte != 0xF0 || second >= 0x90) && (byte != 0xF4 || second <= 0x8F); - } + // (RFC 3629, section 4); length 0 flags the lead bytes + // 0xF5..0xF7, which BON8 can read but UTF-8 cannot encode + const auto shape = utf8_shape(static_cast(byte)); + const int continuation_bytes = (byte <= 0xDF) ? 1 : ((byte <= 0xEF) ? 2 : 3); + const bool valid_second = shape.length != 0 && second >= shape.second_lo && second <= shape.second_hi; if (JSON_HEDLEY_UNLIKELY(!valid_second)) { @@ -20458,6 +20471,22 @@ class binary_reader return true; } + /*! + @brief read a length of type @a NumberType, then a string of that many bytes + + @tparam NumberType the type of the length field + @param[out] result string the bytes are appended to + @param[in] context further context information (for diagnostics) + + @return whether reading and validating the string succeeded + */ + template + bool get_string_with_length(string_t& result, const char* context) + { + NumberType len{}; + return get_number(len) && get_string(len, result) && check_string_utf8(result, context); + } + /*! @brief create a byte array by reading bytes from the input @@ -20667,37 +20696,7 @@ class binary_reader const std::string& context) const { std::string error_msg = "syntax error while parsing "; - - switch (input_format) - { - case input_format_t::cbor: - error_msg += "CBOR"; - break; - - case input_format_t::msgpack: - error_msg += "MessagePack"; - break; - - case input_format_t::ubjson: - error_msg += "UBJSON"; - break; - - case input_format_t::bson: - error_msg += "BSON"; - break; - - case input_format_t::bjdata: - error_msg += "BJData"; - break; - - case input_format_t::bon8: - error_msg += "BON8"; - break; - - case input_format_t::json: // LCOV_EXCL_LINE - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE - } + error_msg += input_format_name(input_format); return concat(error_msg, ' ', context, ": ", detail); } @@ -20981,20 +20980,7 @@ class parser if (result) { - if (strict) - { - // strict mode: next byte must be EOF - if (get_token() != token_type::end_of_input) - { - return syntax_error(*sax, exception_message(token_type::end_of_input, "value")); - } - } - else - { - // the caller keeps using the input: position it right after - // the value by leaving the character that terminated it - m_lexer.release_lookahead(); - } + return finish_input(*sax, strict); } return result; @@ -21018,6 +21004,39 @@ class parser return m_lexer.get_string(); } + /*! + @brief apply the end-of-input handling after a value was parsed + + In strict mode, the next token must be the end of the input; otherwise + parse_error.101 is reported through @a sax. In non-strict mode, the + lookahead is released so the caller can keep reading the input right after + the parsed value. + + @param[in,out] sax the SAX parser to report an error to + @param[in] strict whether to expect the last token to be EOF + @return true, or what @a sax returned for the error + */ + template + bool finish_input(SAX& sax, const bool strict) + { + if (strict) + { + // strict mode: next byte must be EOF + if (get_token() != token_type::end_of_input) + { + return syntax_error(sax, exception_message(token_type::end_of_input, "value")); + } + } + else + { + // the caller keeps using the input: position it right after + // the value by leaving the character that terminated it + m_lexer.release_lookahead(); + } + + return true; + } + /*! @brief run a DOM SAX parser to completion and position the lexer @@ -21037,20 +21056,7 @@ class parser { sax_parse_internal(&sdp); - if (strict) - { - // in strict mode, input must be completely read - if (get_token() != token_type::end_of_input) - { - syntax_error(sdp, exception_message(token_type::end_of_input, "value")); - } - } - else - { - // the caller keeps using the input: position it right after - // the value by leaving the character that terminated it - m_lexer.release_lookahead(); - } + static_cast(finish_input(sdp, strict)); return !sdp.is_errored(); } @@ -21220,7 +21226,7 @@ class parser { if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1)) { - return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON"); + return syntax_error(*sax, empty_input_message()); } return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); @@ -21709,33 +21715,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - switch (m_object->m_data.m_type) + if (m_object->is_object()) { - case value_t::object: - { - m_it.object_iterator = typename object_t::iterator(); - break; - } - - case value_t::array: - { - m_it.array_iterator = typename array_t::iterator(); - break; - } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } + m_it.object_iterator = typename object_t::iterator(); + } + else if (m_object->is_array()) + { + m_it.array_iterator = typename array_t::iterator(); + } + else + { + // primitive (is_primitive(), including discarded) + m_it.primitive_iterator = primitive_iterator_t(); } } @@ -21850,33 +21841,28 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - switch (m_object->m_data.m_type) + if (m_object->is_object()) { - case value_t::object: - { - m_it.object_iterator = m_object->m_data.m_value.object->end(); - break; - } + m_it.object_iterator = m_object->m_data.m_value.object->end(); + } + else if (m_object->is_array()) + { + m_it.array_iterator = m_object->m_data.m_value.array->end(); + } + else + { + // primitive (is_primitive(), including discarded) + m_it.primitive_iterator.set_end(); + } + } - case value_t::array: - { - m_it.array_iterator = m_object->m_data.m_value.array->end(); - break; - } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - m_it.primitive_iterator.set_end(); - break; - } + /// @throw invalid_iterator.212 if @a other points to another container + template + JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const + { + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + throw_literal(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object); } } @@ -22025,10 +22011,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci bool operator==(const IterImpl& other) const { // if objects are not the same, the comparison is undefined - if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) - { - throw_literal(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object); - } + check_same_container(other); // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 if (m_object == nullptr) @@ -22067,10 +22050,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined - if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) - { - throw_literal(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object); - } + check_same_container(other); // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 if (m_object == nullptr) @@ -22079,25 +22059,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return false; } - switch (m_object->m_data.m_type) + if (m_object->is_object()) { - case value_t::object: - throw_literal(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); - - case value_t::array: - return (m_it.array_iterator < other.m_it.array_iterator); - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - return (m_it.primitive_iterator < other.m_it.primitive_iterator); + throw_literal(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); } + + if (m_object->is_array()) + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + // primitive (is_primitive(), including discarded) + return (m_it.primitive_iterator < other.m_it.primitive_iterator); } /*! @@ -22135,30 +22108,19 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - switch (m_object->m_data.m_type) + if (m_object->is_object()) { - case value_t::object: - throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); + } - case value_t::array: - { - std::advance(m_it.array_iterator, i); - break; - } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - m_it.primitive_iterator += i; - break; - } + if (m_object->is_array()) + { + std::advance(m_it.array_iterator, i); + } + else + { + // primitive (is_primitive(), including discarded) + m_it.primitive_iterator += i; } return *this; @@ -22214,25 +22176,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - switch (m_object->m_data.m_type) + if (m_object->is_object()) { - case value_t::object: - throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); - - case value_t::array: - return m_it.array_iterator - other.m_it.array_iterator; - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - return m_it.primitive_iterator - other.m_it.primitive_iterator; + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); } + + if (m_object->is_array()) + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + // primitive (is_primitive(), including discarded) + return m_it.primitive_iterator - other.m_it.primitive_iterator; } /*! @@ -22548,6 +22503,100 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { struct json_pointer_access; + +/*! +@brief result of parse_array_index() + +json_pointer::array_index() maps each value to the corresponding +parse_error/out_of_range exception; json_pointer::contains() and +get_checked_or_null(), which must not throw for an out-of-range or +unrepresentable index, and the json_view pointer resolution switch on it +directly instead. +*/ +enum class array_index_status +{ + ok, ///< @a s is a valid, representable array index + leading_zero, ///< @a s begins with '0' but has more than one character + not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit + unresolved, ///< @a s could not be converted to an integer + exceeds_size_type ///< @a s converts to an integer that exceeds SizeType +}; + +/*! +@brief convert a JSON Pointer reference token into an array index (RFC 6901, Sect. 4) + +@param[in] s reference token to be converted into an array index; its data() + must be NUL-terminated +@param[out] idx the integer representation of @a s if array_index_status::ok + is returned; left unchanged otherwise + +@return whether @a s is a valid array index, and if not, why + +@note this function never throws; json_pointer::array_index(), the callers that + must not throw, and the json_view pointer resolution build on it instead + of each re-implementing the RFC 6901 digit rules and the range check +*/ +template +array_index_status parse_array_index(const StringType& s, SizeType& idx) noexcept +{ + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + return array_index_status::leading_zero; + } + + // error condition (cf. RFC 6901, Sect. 4); this also covers single- + // character tokens, so "/x" and "/xy" fail alike; "-" and the empty + // token are left to the conversion below and are reported as unresolved + if (JSON_HEDLEY_UNLIKELY(!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9'))) + { + return array_index_status::not_a_number; + } + + // fast path: up to 18 decimal digits cannot overflow unsigned long long or + // SizeType (9 digits for a 32-bit SizeType), so strtoull and errno are not needed + constexpr std::size_t max_fast_digits = sizeof(SizeType) >= 8 ? 18 : 9; + if (!s.empty() && s.size() <= max_fast_digits) + { + SizeType acc = 0; + for (std::size_t i = 0; i < s.size(); ++i) + { + const char c = s[i]; + if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9')) + { + break; // not a plain number: let strtoull decide + } + acc = static_cast((acc * 10) + static_cast(c - '0')); + if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) + { + idx = acc; + return array_index_status::ok; + } + } + } + + const char* p = s.data(); + char* p_end = nullptr; // NOLINT(misc-const-correctness) + errno = 0; // strtoull doesn't reset errno + const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) + if (p == p_end // invalid input or empty string + || errno == ERANGE // out of range + || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read + { + return array_index_status::unresolved; + } + + // the index does not fit into SizeType; on 64-bit platforms this is + // only SIZE_MAX itself (see #2203 and #5395) + if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) + { + return array_index_status::exceeds_size_type; + } + + idx = static_cast(res); + return array_index_status::ok; +} + } // namespace detail /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document @@ -22812,6 +22861,28 @@ class json_pointer JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); } + /// whether the '~' at @a pos of @a s is not followed by '0' or '1' + JSON_HEDLEY_ALWAYS_INLINE static bool is_invalid_escape(const string_t& s, std::size_t pos) + { + return pos == s.size() - 1 || (s[pos + 1] != '0' && s[pos + 1] != '1'); + } + + /// @throw parse_error.107 always + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_missing_slash(const string_t& pointer) + { + static_cast(pointer); // unused when JSON_NOEXCEPTION is defined + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", pointer, "'"), nullptr)); + } + + /// @throw parse_error.107 if a nonempty @a pointer does not begin with '/' + JSON_HEDLEY_ALWAYS_INLINE static void check_begins_with_slash(const string_t& pointer) + { + if (JSON_HEDLEY_UNLIKELY(pointer[0] != '/')) + { + throw_missing_slash(pointer); + } + } + /// @throw out_of_range.404 always template JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) @@ -22821,21 +22892,7 @@ class json_pointer JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context)); } - /*! - @brief result of @ref parse_array_index - - @ref array_index maps each value to the corresponding parse_error/out_of_range - exception; @ref contains and @ref get_checked_or_null, which must not throw for - an out-of-range or unrepresentable index, switch on it directly instead. - */ - enum class array_index_status - { - ok, ///< @a s is a valid, representable array index - leading_zero, ///< @a s begins with '0' but has more than one character - not_a_number, ///< @a s is neither empty nor "-" and does not begin with a digit - unresolved, ///< @a s could not be converted to an integer - exceeds_size_type ///< @a s converts to an integer that exceeds size_type - }; + using array_index_status = detail::array_index_status; /// whether a reference token is "-" (a plain comparison with a string literal is not always inlined) static bool is_dash(const string_t& s) noexcept @@ -22843,78 +22900,11 @@ class json_pointer return s.size() == 1 && s[0] == '-'; } - /*! - @param[in] s reference token to be converted into an array index - @param[out] idx the integer representation of @a s if @ref array_index_status::ok - is returned; left unchanged otherwise - - @return whether @a s is a valid array index, and if not, why - - @note this function never throws; @ref array_index and the callers that must not - throw (@ref contains, @ref get_checked_or_null) build on it instead of each - re-implementing the RFC 6901 digit rules and the @a size_type range check - */ + /// detail::parse_array_index() for the size_type of @a BasicJsonType template static array_index_status parse_array_index(const string_t& s, typename BasicJsonType::size_type& idx) noexcept { - using size_type = typename BasicJsonType::size_type; - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) - { - return array_index_status::leading_zero; - } - - // error condition (cf. RFC 6901, Sect. 4); this also covers single- - // character tokens, so "/x" and "/xy" fail alike; "-" and the empty - // token are left to the conversion below and are reported as unresolved - if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9'))) - { - return array_index_status::not_a_number; - } - - // fast path: up to 18 decimal digits cannot overflow unsigned long long or - // size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed - constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9; - if (!s.empty() && s.size() <= max_fast_digits) - { - size_type acc = 0; - for (std::size_t i = 0; i < s.size(); ++i) - { - const char c = s[i]; - if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9')) - { - break; // not a plain number: let strtoull decide - } - acc = static_cast((acc * 10) + static_cast(c - '0')); - if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) - { - idx = acc; - return array_index_status::ok; - } - } - } - - const char* p = s.data(); - char* p_end = nullptr; // NOLINT(misc-const-correctness) - errno = 0; // strtoull doesn't reset errno - const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) - if (p == p_end // invalid input or empty string - || errno == ERANGE // out of range - || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read - { - return array_index_status::unresolved; - } - - // the index does not fit into size_type; on 64-bit platforms this is - // only SIZE_MAX itself (see #2203 and #5395) - if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) - { - return array_index_status::exceeds_size_type; - } - - idx = static_cast(res); - return array_index_status::ok; + return detail::parse_array_index(s, idx); } /*! @@ -22964,10 +22954,7 @@ class json_pointer return reference_tokens.empty(); } - if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) - { - JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); - } + check_begins_with_slash(reference_string); // the whole string is validated, whether or not it matches const std::size_t size = reference_string.size(); @@ -22975,7 +22962,7 @@ class json_pointer pos != string_t::npos; pos = reference_string.find_first_of('~', pos + 1)) { - if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1'))) + if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_string, pos))) { JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } @@ -23253,10 +23240,7 @@ class json_pointer } const char* const data = key.data(); - if (JSON_HEDLEY_UNLIKELY(data[0] != '/')) - { - JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr)); - } + check_begins_with_slash(key); // The beginning of the key that it has in common with the previous // one, which was checked already, is skipped: the scan starts with @@ -23289,7 +23273,7 @@ class json_pointer else if (data[i] == '~') { // ~ must be followed by 0 or 1 - if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 1] != '1'))) + if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(key, i))) { JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } @@ -23388,58 +23372,46 @@ class json_pointer ++end; } - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + token.clear(); + token.append(begin, static_cast(end - begin)); + const auto it = ptr->m_data.m_value.object->find(token); + if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) { - token.clear(); - token.append(begin, static_cast(end - begin)); - const auto it = ptr->m_data.m_value.object->find(token); - if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) - { - return nullptr; - } - ptr = &it->second; - break; - } - - case detail::value_t::array: - { - // an index of up to 18 digits without a leading zero fits into size_type - // (9 digits for a 32-bit size_type); everything else is left to json_pointer - constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9; - const auto digits = static_cast(end - begin); - if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0'))) - { - return nullptr; - } - typename BasicJsonType::size_type idx = 0; - for (const char* c = begin; c != end; ++c) - { - if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9')) - { - return nullptr; - } - idx = static_cast((idx * 10) + static_cast(*c - '0')); - } - if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) - { - return nullptr; - } - ptr = &(*ptr->m_data.m_value.array)[idx]; - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: return nullptr; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + // an index of up to 18 digits without a leading zero fits into size_type + // (9 digits for a 32-bit size_type); everything else is left to json_pointer + constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9; + const auto digits = static_cast(end - begin); + if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0'))) + { + return nullptr; + } + typename BasicJsonType::size_type idx = 0; + for (const char* c = begin; c != end; ++c) + { + if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9')) + { + return nullptr; + } + idx = static_cast((idx * 10) + static_cast(*c - '0')); + } + if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) + { + return nullptr; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else + { + // primitive value with a reference token left + return nullptr; } pos = static_cast(end - path.data()); steps.emplace_back(pos, ptr); @@ -23489,40 +23461,28 @@ class json_pointer : detail::value_t::object; } - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + // use unchecked object access + ptr = &object_member(*ptr, reference_token); + } + else if (ptr->is_array()) + { + if (reference_token == "-") { - // use unchecked object access - ptr = &object_member(*ptr, reference_token); - break; + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); } - - case detail::value_t::array: + else { - if (reference_token == "-") - { - // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - ptr = &ptr->operator[](array_index(reference_token)); - } - break; + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index(reference_token)); } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - throw_unresolved(reference_token, ptr); + } + else + { + // primitive value with a reference token left + throw_unresolved(reference_token, ptr); } } @@ -23540,51 +23500,39 @@ class json_pointer { for (const auto& reference_token : reference_tokens) { - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + const auto it = ptr->m_data.m_value.object->find(reference_token); + if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) { - const auto it = ptr->m_data.m_value.object->find(reference_token); - if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) - { - // at() throws out_of_range.403 with the usual message - ptr->at(reference_token); - } - ptr = &it->second; - break; + // at() throws out_of_range.403 with the usual message + ptr->at(reference_token); + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( + "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), + ") is out of range"), ptr)); } - case detail::value_t::array: + const auto idx = array_index(reference_token); + // Bounds check before access to avoid exception with JSON_NOEXCEPTION + if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) { - if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat( - "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), - ") is out of range"), ptr)); - } - - const auto idx = array_index(reference_token); - // Bounds check before access to avoid exception with JSON_NOEXCEPTION - if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) - { - JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( - "array index ", std::to_string(idx), " is out of range"), ptr)); - } - ptr = &(*ptr->m_data.m_value.array)[idx]; - break; + JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat( + "array index ", std::to_string(idx), " is out of range"), ptr)); } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - throw_unresolved(reference_token, ptr); + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else + { + // primitive value with a reference token left + throw_unresolved(reference_token, ptr); } } @@ -23613,44 +23561,32 @@ class json_pointer { for (const auto& reference_token : reference_tokens) { - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + // use unchecked object access; the const operator[] + // asserts that the key exists + const auto it = ptr->m_data.m_value.object->find(reference_token); + JSON_ASSERT(it != ptr->m_data.m_value.object->end()); + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) { - // use unchecked object access; the const operator[] - // asserts that the key exists - const auto it = ptr->m_data.m_value.object->find(reference_token); - JSON_ASSERT(it != ptr->m_data.m_value.object->end()); - ptr = &it->second; - break; + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); } - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) - { - // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); - } - - // use unchecked array access; the const operator[] - // asserts that the index exists - const auto idx = array_index(reference_token); - JSON_ASSERT(idx < ptr->m_data.m_value.array->size()); - ptr = &(*ptr->m_data.m_value.array)[idx]; - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - throw_unresolved(reference_token, ptr); + // use unchecked array access; the const operator[] + // asserts that the index exists + const auto idx = array_index(reference_token); + JSON_ASSERT(idx < ptr->m_data.m_value.array->size()); + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else + { + // primitive value with a reference token left + throw_unresolved(reference_token, ptr); } } @@ -23674,66 +23610,54 @@ class json_pointer { for (const auto& reference_token : reference_tokens) { - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + const auto it = ptr->m_data.m_value.object->find(reference_token); + if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) { - const auto it = ptr->m_data.m_value.object->find(reference_token); - if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end())) - { - return nullptr; - } - ptr = &it->second; - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) - { - // "-" always fails the range check - return nullptr; - } - - // a malformed index throws parse_error.106/109; an - // index that is syntactically valid but cannot be - // represented (out_of_range.404/410) is treated like an - // out-of-range index below - typename BasicJsonType::size_type idx{}; - switch (parse_array_index(reference_token, idx)) - { - // the branches differ in their messages, not after JSON_THROW's expansion - // NOLINTNEXTLINE(bugprone-branch-clone) - case array_index_status::leading_zero: - JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); - case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); - case array_index_status::unresolved: - case array_index_status::exceeds_size_type: - return nullptr; - case array_index_status::ok: - default: - break; - } - - if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) - { - return nullptr; - } - ptr = &(*ptr->m_data.m_value.array)[idx]; - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: return nullptr; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) + { + // "-" always fails the range check + return nullptr; + } + + // a malformed index throws parse_error.106/109; an + // index that is syntactically valid but cannot be + // represented (out_of_range.404/410) is treated like an + // out-of-range index below + typename BasicJsonType::size_type idx{}; + switch (parse_array_index(reference_token, idx)) + { + // the branches differ in their messages, not after JSON_THROW's expansion + // NOLINTNEXTLINE(bugprone-branch-clone) + case array_index_status::leading_zero: + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + case array_index_status::not_a_number: + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + case array_index_status::unresolved: + case array_index_status::exceeds_size_type: + return nullptr; + case array_index_status::ok: + default: + break; + } + + if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size())) + { + return nullptr; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else + { + // primitive value with a reference token left + return nullptr; } } @@ -23749,63 +23673,48 @@ class json_pointer { for (const auto& reference_token : reference_tokens) { - switch (ptr->type()) + if (ptr->is_object()) { - case detail::value_t::object: + const auto it = ptr->m_data.m_value.object->find(reference_token); + if (it == ptr->m_data.m_value.object->end()) { - const auto it = ptr->m_data.m_value.object->find(reference_token); - if (it == ptr->m_data.m_value.object->end()) - { - // we did not find the key in the object - return false; - } - - ptr = &it->second; - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) - { - // "-" always fails the range check - return false; - } - - // any parse failure (malformed index, or one that is syntactically - // valid but not representable as size_type) means the reference - // token cannot denote an existing array element -- contains() must - // not throw (see #5395), so it is treated as "not found" - typename BasicJsonType::size_type idx{}; - if (JSON_HEDLEY_UNLIKELY(parse_array_index(reference_token, idx) != array_index_status::ok)) - { - return false; - } - - if (idx >= ptr->m_data.m_value.array->size()) - { - // index out of range - return false; - } - - ptr = &(*ptr->m_data.m_value.array)[idx]; - break; - } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - { - // we do not expect primitive values if there is still a - // reference token to process + // we did not find the key in the object return false; } + + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) + { + // "-" always fails the range check + return false; + } + + // any parse failure (malformed index, or one that is syntactically + // valid but not representable as size_type) means the reference + // token cannot denote an existing array element -- contains() must + // not throw (see #5395), so it is treated as "not found" + typename BasicJsonType::size_type idx{}; + if (JSON_HEDLEY_UNLIKELY(parse_array_index(reference_token, idx) != array_index_status::ok)) + { + return false; + } + + if (idx >= ptr->m_data.m_value.array->size()) + { + // index out of range + return false; + } + + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else + { + // we do not expect primitive values if there is still a + // reference token to process + return false; } } @@ -23833,10 +23742,7 @@ class json_pointer } // check if a nonempty reference string begins with slash - if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) - { - JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); - } + check_begins_with_slash(reference_string); // every slash starts a reference token const std::size_t size = reference_string.size(); @@ -23877,9 +23783,7 @@ class json_pointer JSON_ASSERT(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 - if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || - (reference_token[pos + 1] != '0' && - reference_token[pos + 1] != '1'))) + if (JSON_HEDLEY_UNLIKELY(is_invalid_escape(reference_token, pos))) { JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } @@ -24036,50 +23940,34 @@ class json_pointer auto hint = object.end(); const auto enter = [&stack, &path, &object, &hint](const BasicJsonType & v) { - switch (v.type()) + if (v.is_array()) { - case detail::value_t::array: + if (v.m_data.m_value.array->empty()) { - if (v.m_data.m_value.array->empty()) - { - // flatten empty array as null - add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); - } - else - { - stack.emplace_back(&v, object_const_iterator(), path.size()); - } - return; + // flatten empty array as null + add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); } - - case detail::value_t::object: + else { - if (v.m_data.m_value.object->empty()) - { - // flatten empty object as null - add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); - } - else - { - stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size()); - } - return; + stack.emplace_back(&v, object_const_iterator(), path.size()); } - - case detail::value_t::null: - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: + } + else if (v.is_object()) + { + if (v.m_data.m_value.object->empty()) { - // add a primitive value with its reference string - add_flattened(object, hint, path, v, detail::priority_tag<2> {}); - return; + // flatten empty object as null + add_flattened(object, hint, path, nullptr, detail::priority_tag<2> {}); } + else + { + stack.emplace_back(&v, v.m_data.m_value.object->begin(), path.size()); + } + } + else + { + // add a primitive value with its reference string + add_flattened(object, hint, path, v, detail::priority_tag<2> {}); } }; @@ -24642,6 +24530,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -25547,9 +25437,7 @@ class binary_writer write_cbor_head(0x40, N); // step 2: write each element - oa.write_characters( - reinterpret_cast(j.m_data.m_value.binary->data()), - N); + write_bytes(j.m_data.m_value.binary->data(), N); break; } @@ -25562,7 +25450,7 @@ class binary_writer case value_t::discarded: default: - throw_on_discarded(j, "CBOR"); + throw_on_discarded(j, input_format_name(input_format_t::cbor)); } } @@ -25762,9 +25650,7 @@ class binary_writer } // step 2: write the byte string - oa.write_characters( - reinterpret_cast(j.m_data.m_value.binary->data()), - N); + write_bytes(j.m_data.m_value.binary->data(), N); break; } @@ -25778,7 +25664,7 @@ class binary_writer case value_t::discarded: default: - throw_on_discarded(j, "MessagePack"); + throw_on_discarded(j, input_format_name(input_format_t::msgpack)); } } @@ -25862,7 +25748,7 @@ class binary_writer case value_t::array: { bool prefix_required = true; - const bool write_closer = write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); + const bool write_closer = write_ubjson_start_container(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); { const auto& array = *j.m_data.m_value.array; @@ -25912,9 +25798,7 @@ class binary_writer if (use_type) { - oa.write_characters( - reinterpret_cast(j.m_data.m_value.binary->data()), - j.m_data.m_value.binary->size()); + write_bytes(j.m_data.m_value.binary->data(), j.m_data.m_value.binary->size()); } else { @@ -25940,7 +25824,7 @@ class binary_writer } bool prefix_required = true; - const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); + const bool write_closer = write_ubjson_start_container(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth); @@ -25954,7 +25838,7 @@ class binary_writer case value_t::discarded: default: - throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON"); + throw_on_discarded(j, input_format_name(use_bjdata ? input_format_t::bjdata : input_format_t::ubjson)); } } @@ -26164,53 +26048,56 @@ class binary_writer JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } + /// @brief write @a size bytes starting at @a data to the output adapter + JSON_HEDLEY_ALWAYS_INLINE + void write_bytes(const void* data, const std::size_t size) + { + oa.write_characters(static_cast(data), size); + } + + /// @brief write the MessagePack prefix of an array or object with @a N + /// elements: the fix marker @a fix_marker | n, or @a marker_16 or + /// @a marker_32 followed by the length + JSON_HEDLEY_ALWAYS_INLINE + void write_msgpack_container_prefix(const std::size_t N, const BasicJsonType& j, + const std::uint8_t fix_marker, const std::uint8_t marker_16, const std::uint8_t marker_32) + { + const auto n = to_msgpack_length(N, j); + if (n <= 15) + { + // fixarray, fixmap + oa.write_character(to_char_type(static_cast(fix_marker | n))); + } + else if (n <= (std::numeric_limits::max)()) + { + // array 16, map 16 + oa.write_character(to_char_type(marker_16)); + write_number(static_cast(n)); + } + else + { + // array 32, map 32 + oa.write_character(to_char_type(marker_32)); + write_number(static_cast(n)); + } + } + + JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) { - const auto n = to_msgpack_length(N, j); - if (n <= 15) - { - // fixarray - oa.write_character(to_char_type(static_cast(0x90 | n))); - } - else if (n <= (std::numeric_limits::max)()) - { - // array 16 - oa.write_character(to_char_type(0xDC)); - write_number(static_cast(n)); - } - else - { - // array 32 - oa.write_character(to_char_type(0xDD)); - write_number(static_cast(n)); - } + write_msgpack_container_prefix(N, j, 0x90, 0xDC, 0xDD); } + JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j) { - const auto n = to_msgpack_length(N, j); - if (n <= 15) - { - // fixmap - oa.write_character(to_char_type(static_cast(0x80 | (n & 0xF)))); - } - else if (n <= (std::numeric_limits::max)()) - { - // map 16 - oa.write_character(to_char_type(0xDE)); - write_number(static_cast(n)); - } - else - { - // map 32 - oa.write_character(to_char_type(0xDF)); - write_number(static_cast(n)); - } + write_msgpack_container_prefix(N, j, 0x80, 0xDE, 0xDF); } - /// @brief a CBOR, MessagePack, or BON8 array or object whose elements - /// @ref write_cbor_iterative, @ref write_msgpack_iterative, or - /// @ref write_bon8_iterative is still writing + /// @brief a CBOR, MessagePack, BON8, or (as the base of @ref ubjson_frame) + /// UBJSON array or object whose elements @ref write_container_iterative, + /// @ref write_bon8_iterative, or @ref write_ubjson_iterative is still + /// writing struct binary_container_frame { explicit binary_container_frame(const BasicJsonType* value_) noexcept @@ -26246,19 +26133,82 @@ class binary_writer typename BasicJsonType::array_t::const_iterator array_it{}; }; + /// @brief the CBOR specifics of @ref write_container_iterative + struct cbor_walk + { + static void array_head(binary_writer& w, const BasicJsonType& j) + { + w.write_cbor_head(0x80, j.m_data.m_value.array->size()); + } + + static void object_head(binary_writer& w, const BasicJsonType& j) + { + w.write_cbor_head(0xA0, j.m_data.m_value.object->size()); + } + + static void scalar(binary_writer& w, const BasicJsonType& j) + { + w.write_cbor(j); + } + + static void string(binary_writer& w, const string_t& key, const BasicJsonType& context) + { + w.write_cbor_string(key, context); + } + + template + static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object) + { + w.write_cbor_key(k, object); + } + }; + + /// @brief the MessagePack specifics of @ref write_container_iterative + struct msgpack_walk + { + static void array_head(binary_writer& w, const BasicJsonType& j) + { + w.write_msgpack_array_prefix(j.m_data.m_value.array->size(), j); + } + + static void object_head(binary_writer& w, const BasicJsonType& j) + { + w.write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); + } + + static void scalar(binary_writer& w, const BasicJsonType& j) + { + w.write_msgpack(j); + } + + static void string(binary_writer& w, const string_t& key, const BasicJsonType& context) + { + w.write_msgpack_string(key, context); + } + + template + static void key(binary_writer& w, const KeyType& k, const BasicJsonType& object) + { + w.write_msgpack_key(k, object); + } + }; + /*! - @brief write @a j with @ref write_cbor, or write its header and push a - frame for @ref write_cbor_iterative to continue with its elements + @brief write @a j with @a Walk::scalar, or write its header and push a + frame for @ref write_container_iterative to continue with its + elements A scalar, and an empty array or object, are written out in full: there is - nothing below them for @ref write_cbor_iterative to come back to, so + nothing below them for @ref write_container_iterative to come back to, so nothing is pushed for them. */ - void write_cbor_value_or_push(const BasicJsonType& j, std::vector& stack) + template + JSON_HEDLEY_ALWAYS_INLINE + void write_container_value_or_push(const BasicJsonType& j, std::vector& stack) { if (j.is_array()) { - write_cbor_head(0x80, j.m_data.m_value.array->size()); + Walk::array_head(*this, j); if (!j.m_data.m_value.array->empty()) { stack.emplace_back(&j); @@ -26268,7 +26218,7 @@ class binary_writer if (j.is_object()) { - write_cbor_head(0xA0, j.m_data.m_value.object->size()); + Walk::object_head(*this, j); if (!j.m_data.m_value.object->empty()) { stack.emplace_back(&j); @@ -26276,22 +26226,26 @@ class binary_writer return; } - write_cbor(j); + Walk::scalar(*this, j); } /*! @brief write out @a root and everything below it without the call stack - Emits the same bytes as @ref write_cbor, keeping the containers it has + Emits the same bytes as the recursive writer, keeping the containers it has entered on an explicit stack instead of descending into them. Only reached for values nested deeper than @ref recursion_depth_limit, which is why it - is not written for speed. + is not written for speed. Shared by CBOR and MessagePack, which differ only + in the @a Walk policy (@ref cbor_walk, @ref msgpack_walk); BON8 has its own + walker, as it tracks an open string run and closes a container with a + marker. */ - void write_cbor_iterative(const BasicJsonType& root) + template + void write_container_iterative(const BasicJsonType& root) { - // only a container with elements is ever pushed; see write_cbor_value_or_push + // only a container with elements is ever pushed; see write_container_value_or_push std::vector stack; - write_cbor_value_or_push(root, stack); + write_container_value_or_push(root, stack); while (!stack.empty()) { @@ -26309,7 +26263,7 @@ class binary_writer // read the child before pushing: entering it can move every frame const BasicJsonType* child = &(*current.array_it); ++stack.back().array_it; - write_cbor_value_or_push(*child, stack); + write_container_value_or_push(*child, stack); } else { @@ -26321,110 +26275,53 @@ class binary_writer } // the key is written directly (not via a temporary basic_json), - // with the object as diagnostics context, as in write_cbor - write_cbor_key(current.object_it->first, *current.value); + // with the object as diagnostics context, as in the recursive writer + Walk::key(*this, current.object_it->first, *current.value); const BasicJsonType* child = &(current.object_it->second); ++stack.back().object_it; - write_cbor_value_or_push(*child, stack); + write_container_value_or_push(*child, stack); } } } - /*! - @brief write @a j with @ref write_msgpack, or write its header and push a - frame for @ref write_msgpack_iterative to continue with its elements - - @sa @ref write_cbor_value_or_push - */ - void write_msgpack_value_or_push(const BasicJsonType& j, std::vector& stack) + void write_cbor_iterative(const BasicJsonType& root) { - if (j.is_array()) - { - write_msgpack_array_prefix(j.m_data.m_value.array->size(), j); - if (!j.m_data.m_value.array->empty()) - { - stack.emplace_back(&j); - } - return; - } - - if (j.is_object()) - { - write_msgpack_object_prefix(j.m_data.m_value.object->size(), j); - if (!j.m_data.m_value.object->empty()) - { - stack.emplace_back(&j); - } - return; - } - - write_msgpack(j); + write_container_iterative(root); } - /*! - @brief write out @a root and everything below it without the call stack - - @sa @ref write_cbor_iterative - */ void write_msgpack_iterative(const BasicJsonType& root) { - std::vector stack; - write_msgpack_value_or_push(root, stack); - - while (!stack.empty()) - { - const binary_container_frame current = stack.back(); - - if (current.value->is_array()) - { - const auto& array = *current.value->m_data.m_value.array; - if (current.array_it == array.cend()) - { - stack.pop_back(); - continue; - } - - const BasicJsonType* child = &(*current.array_it); - ++stack.back().array_it; - write_msgpack_value_or_push(*child, stack); - } - else - { - const auto& object = *current.value->m_data.m_value.object; - if (current.object_it == object.cend()) - { - stack.pop_back(); - continue; - } - - // as in write_cbor_iterative, the key is written directly with - // the object as diagnostics context - write_msgpack_key(current.object_it->first, *current.value); - const BasicJsonType* child = &(current.object_it->second); - ++stack.back().object_it; - write_msgpack_value_or_push(*child, stack); - } - } + write_container_iterative(root); } - /// @return true when a closing ']' still has to be written after the elements - bool write_ubjson_start_array(const BasicJsonType& j, const bool use_count, const bool use_type, - const bool add_prefix, const bool use_bjdata, bool& prefix_required) + /*! + @brief write the start of an array (@a IsArray) or object: its opening + marker, the optimized type, and the count + + @return true when a closing ']' or '}' still has to be written after the + elements + */ + template + bool write_ubjson_start_container(const BasicJsonType& j, const bool use_count, const bool use_type, + const bool add_prefix, const bool use_bjdata, bool& prefix_required) { prefix_required = true; if (add_prefix) { - oa.write_character(to_char_type('[')); + oa.write_character(to_char_type(IsArray ? '[' : '{')); } - if (use_type && !j.m_data.m_value.array->empty()) + const std::size_t size = IsArray ? j.m_data.m_value.array->size() : j.m_data.m_value.object->size(); + + if (use_type && size != 0) { if (!use_count) { throw_size_marker_required(j); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); - const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + // the first element of an array has been compared with itself + const bool same_prefix = std::all_of(IsArray ? j.begin() + 1 : j.begin(), j.end(), [this, first_prefix, use_bjdata](const BasicJsonType & v) { return ubjson_prefix(v, use_bjdata) == first_prefix; @@ -26436,8 +26333,9 @@ class binary_writer // those, at one byte per element, so the result can be read back. // Objects are not affected: every element is preceded by its key. const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F'); - const bool excessive_valueless = valueless_type - && j.m_data.m_value.array->size() > detail::max_valueless_container_size; + const bool excessive_valueless = IsArray + && valueless_type + && size > detail::max_valueless_container_size; if (same_prefix && !excessive_valueless && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix))) @@ -26450,46 +26348,7 @@ class binary_writer if (use_count) { - write_ubjson_count(j.m_data.m_value.array->size(), use_bjdata); - } - - return !use_count; - } - - /// @return true when a closing '}' still has to be written after the elements - bool write_ubjson_start_object(const BasicJsonType& j, const bool use_count, const bool use_type, - const bool add_prefix, const bool use_bjdata, bool& prefix_required) - { - prefix_required = true; - if (add_prefix) - { - oa.write_character(to_char_type('{')); - } - - if (use_type && !j.m_data.m_value.object->empty()) - { - if (!use_count) - { - throw_size_marker_required(j); - } - const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); - const bool same_prefix = std::all_of(j.begin(), j.end(), - [this, first_prefix, use_bjdata](const BasicJsonType & v) - { - return ubjson_prefix(v, use_bjdata) == first_prefix; - }); - - if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix))) - { - prefix_required = false; - oa.write_character(to_char_type('$')); - oa.write_character(first_prefix); - } - } - - if (use_count) - { - write_ubjson_count(j.m_data.m_value.object->size(), use_bjdata); + write_ubjson_count(size, use_bjdata); } return !use_count; @@ -26517,21 +26376,12 @@ class binary_writer /// @brief an object or array @ref write_ubjson_iterative is still writing /// the elements of - struct ubjson_frame + struct ubjson_frame : binary_container_frame { ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept - : value(value_) + : binary_container_frame(value_) , prefix_required(prefix_required_) - { - if (value->is_object()) - { - object_it = value->m_data.m_value.object->cbegin(); - } - else - { - array_it = value->m_data.m_value.array->cbegin(); - } - } + {} // declared for GCC's -Weffc++, which asks for them in a class with // pointer members and a non-trivial destructor; the exception @@ -26543,13 +26393,9 @@ class binary_writer ubjson_frame& operator=(ubjson_frame&&) = default; ~ubjson_frame() = default; - /// the array or object being written - const BasicJsonType* value; /// whether value's elements each carry their own type marker; an /// optimized ($type) container writes it once for all of them instead bool prefix_required; - typename BasicJsonType::object_t::const_iterator object_it{}; - typename BasicJsonType::array_t::const_iterator array_it{}; }; /*! @@ -26560,7 +26406,7 @@ class binary_writer elements of an optimized container, and everything below the top level, never repeat it) - @sa @ref write_cbor_value_or_push + @sa @ref write_container_value_or_push */ void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count, const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version, @@ -26583,11 +26429,11 @@ class binary_writer bool prefix_required = true; if (is_array) { - write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); + write_ubjson_start_container(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); } else { - write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); + write_ubjson_start_container(j, use_count, use_type, add_prefix, use_bjdata, prefix_required); } const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty(); @@ -26597,7 +26443,7 @@ class binary_writer return; } - // write_ubjson_start_array/_object return !use_count, i.e. whether a + // write_ubjson_start_container returns !use_count, i.e. whether a // closer still has to be written; use_count is constant for the whole // document, so that is recomputed here instead of being carried along if (!use_count) @@ -26786,14 +26632,15 @@ class binary_writer { oa.write_character(to_char_type(element_type)); - if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(name)) + if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)) { - oa.write_characters(reinterpret_cast(name.data()), name.size()); + write_bytes(name.data(), name.size()); } else { - const string_t sanitized = sanitize_utf8(name, error_handler); - oa.write_characters(reinterpret_cast(sanitized.data()), sanitized.size()); + string_t storage; + const string_t& written = sanitize_utf8_for_bson_write(name, storage); + write_bytes(written.data(), written.size()); } // the terminating null byte is written explicitly rather than taken @@ -26869,16 +26716,11 @@ class binary_writer { write_bson_entry_header(name, 0x02); - const bool sanitize = error_handler != error_handler_t::keep - && error_handler != error_handler_t::strict - && !is_valid_utf8(value); - const string_t sanitized = sanitize ? sanitize_utf8(value, error_handler) : string_t{}; - const string_t& written = sanitize ? sanitized : value; + string_t storage; + const string_t& written = sanitize_utf8_for_bson_write(value, storage); write_number(to_bson_length(written.size() + 1ul), true); - oa.write_characters( - reinterpret_cast(written.data()), - written.size()); + write_bytes(written.data(), written.size()); // the terminating null byte is written explicitly rather than taken // from the buffer, so that string_t::data() need not be null-terminated oa.write_character(to_char_type(0x00)); @@ -26984,7 +26826,7 @@ class binary_writer write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); - oa.write_characters(reinterpret_cast(value.data()), value.size()); + write_bytes(value.data(), value.size()); } /*! @@ -27014,14 +26856,14 @@ class binary_writer case value_t::number_integer: if (JSON_HEDLEY_UNLIKELY(!integer_fits(j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "BSON", "int64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::bson), "int64"); } return calc_bson_integer_size(static_cast(j.m_data.m_value.number_integer)); case value_t::number_unsigned: if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_unsigned))) { - throw_integer_out_of_range(j, "BSON", "uint64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::bson), "uint64"); } return calc_bson_unsigned_size(conditional_static_cast(j.m_data.m_value.number_unsigned)); @@ -27032,7 +26874,7 @@ class binary_writer return 0ul; case value_t::discarded: - throw_on_discarded(j, "BSON"); + throw_on_discarded(j, input_format_name(input_format_t::bson)); // LCOV_EXCL_START case value_t::object: @@ -27196,7 +27038,7 @@ class binary_writer // the terminating null byte is written explicitly rather than // taken from the buffer, so that string_t::data() need not be // null-terminated - oa.write_characters(reinterpret_cast(value.data()), value.size()); + write_bytes(value.data(), value.size()); oa.write_character(to_char_type(0x00)); return true; } @@ -27213,7 +27055,7 @@ class binary_writer p += sizeof(std::int32_t); *p++ = value.has_subtype() ? to_char_type(static_cast(value.subtype())) : to_char_type(0x00); oa.commit(static_cast(p - out)); - oa.write_characters(reinterpret_cast(value.data()), value.size()); + write_bytes(value.data(), value.size()); return true; } @@ -27261,7 +27103,7 @@ class binary_writer return write_bson_null(name); case value_t::discarded: - throw_on_discarded(j, "BSON"); + throw_on_discarded(j, input_format_name(input_format_t::bson)); // LCOV_EXCL_START case value_t::object: @@ -28042,7 +27884,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_unsigned))) { - throw_integer_out_of_range(j, "CBOR", "uint64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "uint64"); } write_cbor_head(0x00, conditional_static_cast(j.m_data.m_value.number_unsigned)); return; @@ -28052,7 +27894,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]"); + throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "[-2^64, 2^64-1]"); } // CBOR does not differentiate between positive signed // integers and unsigned integers @@ -28063,7 +27905,7 @@ class binary_writer // a negative integer n is encoded as -1 - n if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(-1 - j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]"); + throw_integer_out_of_range(j, input_format_name(input_format_t::cbor), "[-2^64, 2^64-1]"); } write_cbor_head(0x20, static_cast(-1 - j.m_data.m_value.number_integer)); } @@ -28139,7 +27981,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_unsigned))) { - throw_integer_out_of_range(j, "MessagePack", "uint64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "uint64"); } write_msgpack_unsigned(static_cast(j.m_data.m_value.number_unsigned)); return; @@ -28149,7 +27991,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]"); + throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "[-2^63, 2^64-1]"); } // MessagePack does not differentiate between positive // signed integers and unsigned integers. @@ -28159,7 +28001,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_min(j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]"); + throw_integer_out_of_range(j, input_format_name(input_format_t::msgpack), "[-2^63, 2^64-1]"); } if (j.m_data.m_value.number_integer >= -32) { @@ -28293,40 +28135,37 @@ class binary_writer } /*! - @brief write an object key as a CBOR text string + @brief write an object key as a CBOR or MessagePack string (@a Walk) A key convertible to string_t is written directly. Other key types (only an explicit conversion, or only a to_json overload) go through a temporary basic_json, as in version 3.12.0; the temporary is then the diagnostics context for strict UTF-8 checks. */ - template::value, int> = 0> + void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) + { + Walk::string(*this, key, context); + } + + template < typename Walk, typename Key = typename BasicJsonType::object_t::key_type, + enable_if_t < !std::is_convertible::value, int > = 0 > + void write_object_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) + { + Walk::scalar(*this, BasicJsonType(key)); + } + + /// @brief write an object key as a CBOR text string void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) { - write_cbor_string(key, context); + write_object_key(key, context); } - template < typename Key = typename BasicJsonType::object_t::key_type, - enable_if_t < !std::is_convertible::value, int > = 0 > - void write_cbor_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) - { - write_cbor(BasicJsonType(key)); - } - - /// @brief write an object key as a MessagePack str, as in @ref write_cbor_key - template::value, int> = 0> + /// @brief write an object key as a MessagePack str void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& context) { - write_msgpack_string(key, context); - } - - template < typename Key = typename BasicJsonType::object_t::key_type, - enable_if_t < !std::is_convertible::value, int > = 0 > - void write_msgpack_key(const typename BasicJsonType::object_t::key_type& key, const BasicJsonType& /*context*/) - { - write_msgpack(BasicJsonType(key)); + write_object_key(key, context); } /// the longest string that is written, with its head of one or two @@ -28401,9 +28240,7 @@ class binary_writer write_cbor_head(0x60, sanitized.size()); // step 2: write the string - oa.write_characters( - reinterpret_cast(sanitized.data()), - sanitized.size()); + write_bytes(sanitized.data(), sanitized.size()); } ///////////// @@ -28484,9 +28321,7 @@ class binary_writer } // step 2: write the string - oa.write_characters( - reinterpret_cast(sanitized.data()), - sanitized.size()); + write_bytes(sanitized.data(), sanitized.size()); } //////////// @@ -28769,9 +28604,7 @@ class binary_writer oa.write_character(to_char_type('S')); } write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata); - oa.write_characters( - reinterpret_cast(sanitized.data()), - sanitized.size()); + write_bytes(sanitized.data(), sanitized.size()); } /*! @@ -29571,7 +29404,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits_max(j.m_data.m_value.number_unsigned))) { - throw_integer_out_of_range(j, "BON8", "int64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::bon8), "int64"); } write_bon8_integer(static_cast(j.m_data.m_value.number_unsigned)); string_open = false; @@ -29582,7 +29415,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!integer_fits(j.m_data.m_value.number_integer))) { - throw_integer_out_of_range(j, "BON8", "int64"); + throw_integer_out_of_range(j, input_format_name(input_format_t::bon8), "int64"); } write_bon8_integer(static_cast(j.m_data.m_value.number_integer)); string_open = false; @@ -29650,7 +29483,7 @@ class binary_writer case value_t::discarded: default: - throw_on_discarded(j, "BON8"); + throw_on_discarded(j, input_format_name(input_format_t::bon8)); } } @@ -29661,7 +29494,7 @@ class binary_writer A scalar, and an empty array or object, are written out in full and not pushed. - @sa @ref write_cbor_value_or_push + @sa @ref write_container_value_or_push */ void write_bon8_value_or_push(const BasicJsonType& j, bool& string_open, std::vector& stack) { @@ -30047,7 +29880,7 @@ class binary_writer } else { - oa.write_characters(reinterpret_cast(s.data()), s.size()); + write_bytes(s.data(), s.size()); string_open = true; } } @@ -30138,6 +29971,31 @@ class binary_writer } } + /*! + @brief the write-pass counterpart of @ref sanitize_utf8_for_write for BSON + + The size pass (@ref calc_bson_entry_header_size, @ref + calc_bson_string_size) has already validated @a s, and thrown for @ref + error_handler_t::strict if it was ill-formed. Here, @ref + error_handler_t::strict therefore behaves like @ref error_handler_t::keep, + and @ref error_handler_t::replace / @ref error_handler_t::ignore produce + exactly the bytes the size pass measured. Hence no context is needed. + + @param[in] s the string (value or object key) to write + @param[out] storage backing storage for a sanitized copy + + @return a reference to @a s, or to @a storage once it holds a sanitized copy + */ + const string_t& sanitize_utf8_for_bson_write(const string_t& s, string_t& storage) const + { + if (error_handler == error_handler_t::keep || error_handler == error_handler_t::strict || is_valid_utf8(s)) + { + return s; // NOLINT(bugprone-return-const-ref-from-parameter): callers pass lvalues that outlive the call + } + storage = sanitize_utf8(s, error_handler); + return storage; + } + /// deleted: anything but a string_t would bind a temporary that dies before the returned reference is used template < typename T, enable_if_t < !std::is_same::value, int > = 0 > const string_t& sanitize_utf8_for_write(const T& /*s*/, const BasicJsonType& /*context*/, string_t& /*storage*/) const = delete; // NOLINT(hicpp-use-equals-delete,modernize-use-equals-delete): a private helper's guard, not part of the interface @@ -33227,6 +33085,28 @@ class serializer } } + /// write out the bytes pending in @ref string_buffer and reset @a bytes + JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes) + { + if (bytes != 0) + { + put_buffer(string_buffer, bytes); + bytes = 0; + } + } + + /// write buffer and reset index if fewer than 13 bytes are left, as this + /// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one + /// code point + JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes) + { + if (string_buffer.size() - bytes < 13) + { + put_buffer(string_buffer, bytes); + bytes = 0; + } + } + /*! @brief worker for @ref dump_escaped @@ -33285,11 +33165,7 @@ class serializer { // emit any bytes still pending in string_buffer first to // preserve output order, then write the run directly - if (bytes != 0) - { - put_buffer(string_buffer, bytes); - bytes = 0; - } + emit_pending(bytes); put_run(s, i, run); bytes_after_last_accept = 0; undumped_chars = 0; @@ -33310,11 +33186,10 @@ class serializer { // emit any bytes still pending in string_buffer first to // preserve output order - if (bytes != 0) - { - put_buffer(string_buffer, bytes); - bytes = 0; - } + emit_pending(bytes); + // deliberately not put_buffer(): a fixed 8-byte memcpy + // has no asserts and no write-through branch, which + // matters on the hot path const auto& escape = ascii_escapes()[data[i]]; if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size())) { @@ -33389,14 +33264,7 @@ class serializer string_buffer[bytes++] = s[i]; } - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - put_buffer(string_buffer, bytes); - bytes = 0; - } + reserve_string_buffer(bytes); // remember the byte position of this accept bytes_after_last_accept = bytes; @@ -33448,14 +33316,7 @@ class serializer string_buffer[bytes++] = '\xBD'; } - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - put_buffer(string_buffer, bytes); - bytes = 0; - } + reserve_string_buffer(bytes); bytes_after_last_accept = bytes; } @@ -33488,14 +33349,7 @@ class serializer string_buffer[bytes++] = s[i]; } - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - put_buffer(string_buffer, bytes); - bytes = 0; - } + reserve_string_buffer(bytes); bytes_after_last_accept = bytes; undumped_chars = 0; @@ -33861,30 +33715,17 @@ class serializer */ static const std::array& ascii_copyable() noexcept { - static const std::array copyable = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0 - } - }; + // constant-initialized from the predicate: no runtime loop, no guard + static constexpr std::array copyable = make_ascii_copyable(make_index_sequence<256> {}); return copyable; } + template + static constexpr std::array make_ascii_copyable(index_sequence /*unused*/) noexcept + { + return {{ static_cast(is_ascii_copyable(static_cast(Is)) ? 1 : 0)... }}; + } + /*! @brief the JSON form of each character below 0x60 @@ -34013,7 +33854,6 @@ class serializer */ JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n) { - constexpr const char* nibble_to_hex = "0123456789abcdef"; std::size_t i = 0; while (i < n && data[i] >= 0x80) { @@ -34050,26 +33890,21 @@ class serializer char* d = write_buffer.data() + write_buffer_pos; if (codepoint > 0xFFFFu) { - d = put_u_escape(d, static_cast(0xD7C0u + (codepoint >> 10u)), nibble_to_hex); + d = put_u_escape(d, static_cast(0xD7C0u + (codepoint >> 10u))); codepoint = 0xDC00u + (codepoint & 0x3FFu); } - d = put_u_escape(d, static_cast(codepoint), nibble_to_hex); + d = put_u_escape(d, static_cast(codepoint)); write_buffer_pos = static_cast(d - write_buffer.data()); i += length; } return i; } - static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept + static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept { - const auto unit = static_cast(codeunit); d[0] = '\\'; d[1] = 'u'; - d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu]; - d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu]; - d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu]; - d[5] = nibble_to_hex[unit & 0x0Fu]; - return d + 6; + return hex4_lower(codeunit, d + 2); } /*! @@ -34078,7 +33913,7 @@ class serializer * Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the * string escaping hot path. It writes exactly six characters ('\\', 'u' and * four hex digits) at position @a pos of @a string_buffer via a nibble - * lookup table, avoiding the format-string parsing and locale machinery of + * lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of * `snprintf`. Advances @a pos by the number of bytes written (6). * * @param[in] pos position in @a string_buffer to write at; there must @@ -34088,13 +33923,9 @@ class serializer void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept { JSON_ASSERT(string_buffer.size() - pos >= 6); - constexpr const char* nibble_to_hex = "0123456789abcdef"; string_buffer[pos + 0] = '\\'; string_buffer[pos + 1] = 'u'; - string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu]; - string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu]; - string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu]; - string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu]; + hex4_lower(codeunit, &string_buffer[pos + 2]); pos += 6; } @@ -34610,6 +34441,19 @@ private: Container::pop_back(); } + /// @brief remove the entry with the given key; returns the number of removed entries + template + size_type erase_impl(const K& key) + { + const auto it = find_impl(*this, key); + if (it != this->end()) + { + erase_at(it); + return 1; + } + return 0; + } + public: template::value, int> = 0> @@ -34699,26 +34543,14 @@ public: size_type erase(const key_type& key) { - const auto it = find_impl(*this, key); - if (it != this->end()) - { - erase_at(it); - return 1; - } - return 0; + return erase_impl(key); } template::value, int> = 0> size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it != this->end()) - { - erase_at(it); - return 1; - } - return 0; + return erase_impl(key); } iterator erase(iterator pos) @@ -35210,9 +35042,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = - detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.', - std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.', - std::to_string(NLOHMANN_JSON_VERSION_PATCH)); + detail::concat(detail::to_string(static_cast(NLOHMANN_JSON_VERSION_MAJOR)), '.', + detail::to_string(static_cast(NLOHMANN_JSON_VERSION_MINOR)), '.', + detail::to_string(static_cast(NLOHMANN_JSON_VERSION_PATCH))); result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; @@ -35234,10 +35066,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #elif defined(__clang__) result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) - result["compiler"] = {{"family", "gcc"}, {"version", detail::concat( - std::to_string(__GNUC__), '.', - std::to_string(__GNUC_MINOR__), '.', - std::to_string(__GNUC_PATCHLEVEL__)) + result["compiler"] = {{"family", "gcc"}, {"version", detail::concat( + detail::to_string(static_cast(__GNUC__)), '.', + detail::to_string(static_cast(__GNUC_MINOR__)), '.', + detail::to_string(static_cast(__GNUC_PATCHLEVEL__))) } }; #elif defined(__HP_aCC) @@ -35255,9 +35087,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif #if defined(_MSVC_LANG) - result["compiler"]["c++"] = std::to_string(_MSVC_LANG); + result["compiler"]["c++"] = detail::to_string(static_cast(_MSVC_LANG)); #elif defined(__cplusplus) - result["compiler"]["c++"] = std::to_string(__cplusplus); + result["compiler"]["c++"] = detail::to_string(static_cast(__cplusplus)); #else result["compiler"]["c++"] = "unknown"; #endif @@ -35568,23 +35400,15 @@ private: // true if v is not an array/object, or is an already-empty one static bool has_no_children(const basic_json& v) noexcept { - switch (v.m_data.m_type) + if (detail::is_primitive(v.m_data.m_type)) { - case value_t::array: - return v.m_data.m_value.array->empty(); - case value_t::object: - return v.m_data.m_value.object->empty(); - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - return true; + return true; } + if (v.m_data.m_type == value_t::array) + { + return v.m_data.m_value.array->empty(); + } + return v.m_data.m_value.object->empty(); } // true if no child of the non-empty array/object v has children of @@ -35689,28 +35513,17 @@ private: } public: - void destroy_string() noexcept + template + static void destroy_pointer(T*& ptr) noexcept { - if (string == nullptr) + if (ptr == nullptr) { // not initialized (e.g., due to exception in the ctor) return; } - AllocatorType alloc; - std::allocator_traits::destroy(alloc, string); - std::allocator_traits::deallocate(alloc, string, 1); - } - - void destroy_binary() noexcept - { - if (binary == nullptr) - { - // not initialized (e.g., due to exception in the ctor) - return; - } - AllocatorType alloc; - std::allocator_traits::destroy(alloc, binary); - std::allocator_traits::deallocate(alloc, binary, 1); + AllocatorType alloc; + std::allocator_traits::destroy(alloc, ptr); + std::allocator_traits::deallocate(alloc, ptr, 1); } // t must be value_t::array or value_t::object @@ -35876,11 +35689,11 @@ public: switch (t) { case value_t::string: - destroy_string(); + destroy_pointer(string); break; case value_t::binary: - destroy_binary(); + destroy_pointer(binary); break; case value_t::object: @@ -35942,36 +35755,24 @@ public: void set_parents() { #if JSON_DIAGNOSTICS - switch (m_data.m_type) + if (detail::is_primitive(m_data.m_type)) { - case value_t::array: - { - for (auto& element : *m_data.m_value.array) - { - element.m_parent = this; - } - break; - } + return; + } - case value_t::object: + if (m_data.m_type == value_t::array) + { + for (auto& element : *m_data.m_value.array) { - for (auto& element : *m_data.m_value.object) - { - element.second.m_parent = this; - } - break; + element.m_parent = this; + } + } + else + { + for (auto& element : *m_data.m_value.object) + { + element.second.m_parent = this; } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - break; } #endif } @@ -37419,8 +37220,7 @@ public: case value_t::string: case value_t::binary: { - if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) + if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last))) { detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object); } @@ -37894,6 +37694,129 @@ public: JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); } + /// @throw type_error.309 always, as insert() is not defined for the type of @a j + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_insert_wrong_type(const basic_json& j) + { + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", j); + } + + /// @throw invalid_iterator.202 always, as an iterator belongs to another value than @a obj + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_iterator_from_other_value(const basic_json* obj) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", obj); + } + + /// @throw type_error.310 always, as swap() with the given operand is not defined for the type of @a j + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_swap_wrong_type(const char* operation, const basic_json& j) + { + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, operation, j); + } + + /// exchange the stored @a Stored value (if the value has that type) with @a other + template + void swap_impl(Other& other, const char* operation) + { + Stored* const stored = get_impl_ptr(static_cast(nullptr)); + if (JSON_HEDLEY_UNLIKELY(stored == nullptr)) + { + throw_swap_wrong_type(operation, *this); + } + + using std::swap; + swap(*stored, other); + } + + /// whether [first, last) spans the whole of a primitive value + template + JSON_HEDLEY_ALWAYS_INLINE + static bool primitive_range_is_complete(const IteratorType& first, const IteratorType& last) noexcept + { + return first.m_it.primitive_iterator.is_begin() && last.m_it.primitive_iterator.is_end(); + } + + /// destroy the current (primitive) value and become null + void reset_to_null_impl() + { + m_data.m_value.destroy(m_data.m_type); + m_data.m_value = {}; + m_data.m_type = value_t::null; + assert_invariant(); + } + + /// append @a val (moved or copied) to the array; a null value becomes an array + template + JSON_HEDLEY_ALWAYS_INLINE + void push_back_array_impl(ValueType&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); + } + + // transform a null object into an array + if (is_null()) + { + convert_null_to(); + } + + // add the element to the array + const auto old_capacity = container_capacity(); + m_data.m_value.array->push_back(std::forward(val)); + set_parent_after_insert(m_data.m_value.array->back(), old_capacity); + } + + /// parse the input behind an already created adapter + template + static basic_json parse_impl(InputAdapterType&& adapter, + parser_callback_t cb, + const bool allow_exceptions, + const bool ignore_comments, + const bool ignore_trailing_commas) + { + basic_json result; + auto p = parser(std::forward(adapter), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + p.parse(true, result); + return result; + } + + /// check the input behind an already created adapter + template + static bool accept_impl(InputAdapterType&& adapter, + const bool ignore_comments, + const bool ignore_trailing_commas) + { + return parser(std::forward(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + } + + /// generate SAX events from the input behind an already created adapter + template + static bool sax_parse_impl(InputAdapterType ia, SAX* sax, + const input_format_t format, + const bool strict, + const bool ignore_comments, + const bool ignore_trailing_commas, + const cbor_tag_handler_t tag_handler) + { + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); + } + + /// serialize @a j into a new vector; @a write receives the vector-backed writer + template + static std::vector to_binary_vector(const basic_json& j, const error_handler_t error_handler, WriteFunction write) + { + std::vector result; + result.reserve(detail::binary_initial_capacity(j)); + auto writer = vector_writer(result, error_handler); + write(writer); + return result; + } + /// @throw out_of_range.401 always, as @a idx is out of range of @a j JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_out_of_range(size_type idx, const basic_json& j) @@ -38876,7 +38799,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } IteratorType result = end(); @@ -38895,10 +38818,7 @@ public: detail::throw_literal(detail::exception_id::iterator_out_of_range, "iterator out of range", this); } - m_data.m_value.destroy(m_data.m_type); - m_data.m_value = {}; - m_data.m_type = value_t::null; - assert_invariant(); + reset_to_null_impl(); break; } @@ -38948,16 +38868,12 @@ public: case value_t::string: case value_t::binary: { - if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) + if (JSON_HEDLEY_UNLIKELY(!primitive_range_is_complete(first, last))) { detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this); } - m_data.m_value.destroy(m_data.m_type); - m_data.m_value = {}; - m_data.m_type = value_t::null; - assert_invariant(); + reset_to_null_impl(); break; } @@ -39438,34 +39354,20 @@ public: JSON_HEDLEY_WARN_UNUSED_RESULT size_type max_size() const noexcept { - switch (m_data.m_type) + if (detail::is_primitive(m_data.m_type)) { - case value_t::array: - { - // delegate call to array_t::max_size() - return m_data.m_value.array->max_size(); - } - - case value_t::object: - { - // delegate call to object_t::max_size() - return m_data.m_value.object->max_size(); - } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - // all other types have max_size() == size() - return size(); - } + // all other types have max_size() == size() + return size(); } + + if (m_data.m_type == value_t::array) + { + // delegate call to array_t::max_size() + return m_data.m_value.array->max_size(); + } + + // delegate call to object_t::max_size() + return m_data.m_value.object->max_size(); } /// @} @@ -39543,22 +39445,7 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(basic_json&& val) { - // push_back only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) - { - detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); - } - - // transform a null object into an array - if (is_null()) - { - convert_null_to(); - } - - // add the element to the array (move semantics) - const auto old_capacity = container_capacity(); - m_data.m_value.array->push_back(std::move(val)); - set_parent_after_insert(m_data.m_value.array->back(), old_capacity); + push_back_array_impl(std::move(val)); // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor } @@ -39574,22 +39461,7 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/push_back/ void push_back(const basic_json& val) { - // push_back only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) - { - detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); - } - - // transform a null object into an array - if (is_null()) - { - convert_null_to(); - } - - // add the element to the array - const auto old_capacity = container_capacity(); - m_data.m_value.array->push_back(val); - set_parent_after_insert(m_data.m_value.array->back(), old_capacity); + push_back_array_impl(val); } /// @brief add an object to an array @@ -39735,13 +39607,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } // insert to array and return iterator @@ -39755,13 +39627,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } // moving into a local first keeps this safe even if val aliases @@ -39777,13 +39649,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } // insert to array and return iterator @@ -39797,13 +39669,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } // check if range iterators belong to the same JSON object @@ -39834,13 +39706,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); + throw_iterator_from_other_value(this); } // copy the values first: ilist may refer to elements of this array @@ -39862,7 +39734,7 @@ public: // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + throw_insert_wrong_type(*this); } // check if range iterators belong to the same JSON object @@ -40104,82 +39976,37 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - using std::swap; - swap(*(m_data.m_value.array), other); - set_parents(); - } - else - { - detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this); - } + swap_impl(other, "swap(array_t&)"); + set_parents(); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - using std::swap; - swap(*(m_data.m_value.object), other); - set_parents(); - } - else - { - detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this); - } + swap_impl(other, "swap(object_t&)"); + set_parents(); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for strings - if (JSON_HEDLEY_LIKELY(is_string())) - { - using std::swap; - swap(*(m_data.m_value.string), other); - } - else - { - detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this); - } + swap_impl(other, "swap(string_t&)"); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) { - // swap only works for binary values - if (JSON_HEDLEY_LIKELY(is_binary())) - { - using std::swap; - swap(*(m_data.m_value.binary), other); - } - else - { - detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this); - } + swap_impl(other, "swap(binary_t&)"); } /// @brief exchanges the values /// @sa https://json.nlohmann.me/api/basic_json/swap/ void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) { - // swap only works for binary values - if (JSON_HEDLEY_LIKELY(is_binary())) - { - using std::swap; - swap(*(m_data.m_value.binary), other); - } - else - { - detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this); - } + swap_impl(other, "swap(binary_t::container_type&)"); } /// @} @@ -40860,10 +40687,7 @@ public: const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - basic_json result; - auto p = parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); - p.parse(true, result); - return result; + return parse_impl(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } /// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) @@ -40878,10 +40702,7 @@ public: const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - basic_json result; - auto p = parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); - p.parse(true, result); - return result; + return parse_impl(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } JSON_HEDLEY_WARN_UNUSED_RESULT @@ -40895,10 +40716,7 @@ public: = delete; #else { - basic_json result; - auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); - p.parse(true, result); - return result; + return parse_impl(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } #endif @@ -40910,7 +40728,7 @@ public: const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + return accept_impl(detail::input_adapter(std::forward(i)), ignore_comments, ignore_trailing_commas); } /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -40922,7 +40740,7 @@ public: const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + return accept_impl(detail::input_adapter(std::move(first), std::move(last)), ignore_comments, ignore_trailing_commas); } JSON_HEDLEY_WARN_UNUSED_RESULT @@ -40934,7 +40752,7 @@ public: = delete; #else { - return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + return accept_impl(i.get(), ignore_comments, ignore_trailing_commas); } #endif @@ -40949,10 +40767,7 @@ public: const bool ignore_trailing_commas = false, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - auto ia = detail::input_adapter(std::forward(i)); - return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); + return sax_parse_impl(detail::input_adapter(std::forward(i)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); } /// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) @@ -40967,10 +40782,7 @@ public: const bool ignore_trailing_commas = false, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { - auto ia = detail::input_adapter(std::move(first), std::move(last)); - return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); + return sax_parse_impl(detail::input_adapter(std::move(first), std::move(last)), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); } /// @brief generate SAX events @@ -41004,12 +40816,7 @@ public: = delete; #else { - auto ia = i.get(); - return format == input_format_t::json - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) - // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict, tag_handler); + return sax_parse_impl(i.get(), sax, format, strict, ignore_comments, ignore_trailing_commas, tag_handler); } #endif #if defined(__clang__) @@ -41157,10 +40964,10 @@ public: static std::vector to_cbor(const basic_json& j, const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result, error_handler).write_cbor(j); - return result; + return to_binary_vector(j, error_handler, [&](vector_binary_writer& w) + { + w.write_cbor(j); + }); } /// @brief create a CBOR serialization of a given JSON value @@ -41188,10 +40995,10 @@ public: static std::vector to_msgpack(const basic_json& j, const error_handler_t error_handler = error_handler_t::keep) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result, error_handler).write_msgpack(j); - return result; + return to_binary_vector(j, error_handler, [&](vector_binary_writer& w) + { + w.write_msgpack(j); + }); } /// @brief create a MessagePack serialization of a given JSON value @@ -41221,10 +41028,10 @@ public: const bool use_type = false, const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result, error_handler).write_ubjson(j, use_size, use_type); - return result; + return to_binary_vector(j, error_handler, [&](vector_binary_writer& w) + { + w.write_ubjson(j, use_size, use_type); + }); } /// @brief create a UBJSON serialization of a given JSON value @@ -41257,10 +41064,10 @@ public: const bjdata_version_t version = bjdata_version_t::draft2, const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version); - return result; + return to_binary_vector(j, error_handler, [&](vector_binary_writer& w) + { + w.write_ubjson(j, use_size, use_type, true, true, version); + }); } /// @brief create a BJData serialization of a given JSON value @@ -41292,10 +41099,10 @@ public: static std::vector to_bson(const basic_json& j, const error_handler_t error_handler = detail::binary_writer_default_error_handler()) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result, error_handler).write_bson(j); - return result; + return to_binary_vector(j, error_handler, [&](vector_binary_writer& w) + { + w.write_bson(j); + }); } /// @brief create a BSON serialization of a given JSON value @@ -41322,10 +41129,10 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/to_bon8/ static std::vector to_bon8(const basic_json& j) { - std::vector result; - result.reserve(detail::binary_initial_capacity(j)); - vector_writer(result).write_bon8(j); - return result; + return to_binary_vector(j, detail::binary_writer_default_error_handler(), [&](vector_binary_writer& w) + { + w.write_bon8(j); + }); } /// @brief create a BON8 serialization of a given JSON value @@ -42661,82 +42468,68 @@ public: return; } - switch (source.type()) + if (detail::is_primitive(source.type())) { - case value_t::array: + // both primitive types: replace value + diff_replace(result, path, target); + return; + } + + if (source.is_array()) + { + const std::size_t path_length = path.size(); + + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) { + // recursive call to compare array values at index i + if (!(source[i] == target[i])) + { + diff_append_index(path, i); + diff_unequal(result, source[i], target[i], path, depth + 1); + path.resize(path_length); + } + ++i; + } + + // We now reached the end of at least one array + // in a second pass, traverse the remaining elements + diff_array_tails(result, source, target, path, i); + } + else + { + diff_target_values target_values; + std::vector added; + if (diff_object_keys(result, source, target, path, target_values, added)) + { + // fast path: target_values holds, for every member of + // source in order, its value in target -- so no further + // lookup is needed. Deleted keys are interleaved here + // too, in source's original order, to match the + // historical (pre-reordering-aware) output order. const std::size_t path_length = path.size(); - - // first pass: traverse common elements std::size_t i = 0; - while (i < source.size() && i < target.size()) + for (auto it = source.cbegin(); it != source.cend(); ++it, ++i) { - // recursive call to compare array values at index i - if (!(source[i] == target[i])) + if (target_values[i] != nullptr) { - diff_append_index(path, i); - diff_unequal(result, source[i], target[i], path, depth + 1); - path.resize(path_length); - } - ++i; - } - - // We now reached the end of at least one array - // in a second pass, traverse the remaining elements - diff_array_tails(result, source, target, path, i); - break; - } - - case value_t::object: - { - diff_target_values target_values; - std::vector added; - if (diff_object_keys(result, source, target, path, target_values, added)) - { - // fast path: target_values holds, for every member of - // source in order, its value in target -- so no further - // lookup is needed. Deleted keys are interleaved here - // too, in source's original order, to match the - // historical (pre-reordering-aware) output order. - const std::size_t path_length = path.size(); - std::size_t i = 0; - for (auto it = source.cbegin(); it != source.cend(); ++it, ++i) - { - if (target_values[i] != nullptr) + if (!(it.value() == *target_values[i])) { - if (!(it.value() == *target_values[i])) - { - diff_append_key(path, it.key()); - diff_unequal(result, it.value(), *target_values[i], path, depth + 1); - path.resize(path_length); - } - } - else - { - // found a key that is not in target -> remove it - diff_remove(result, diff_key_path(path, it.key())); + diff_append_key(path, it.key()); + diff_unequal(result, it.value(), *target_values[i], path, depth + 1); + path.resize(path_length); } } - - // append the "add" operations for brand-new keys - diff_added(result, added, path); + else + { + // found a key that is not in target -> remove it + diff_remove(result, diff_key_path(path, it.key())); + } } - break; - } - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - // both primitive types: replace value - diff_replace(result, path, target); - break; + // append the "add" operations for brand-new keys + diff_added(result, added, path); } } } @@ -42782,45 +42575,33 @@ public: return; } - switch (s.type()) + if (detail::is_primitive(s.type())) { - case value_t::array: + // both primitive types: replace value + diff_replace(result, current_path, t); + return; + } + + if (s.is_array()) + { + stack.emplace_back(&s, &t, current_path.size()); + return; + } + else + { + std::vector target_values; + std::vector added; + if (diff_object_keys(result, s, t, current_path, target_values, added)) { + // fast path: the frame walks source in lockstep with + // common_keys, as diff_recursively does, and appends + // added_ops once all members are done stack.emplace_back(&s, &t, current_path.size()); - return; - } - - case value_t::object: - { - std::vector target_values; - std::vector added; - if (diff_object_keys(result, s, t, current_path, target_values, added)) - { - // fast path: the frame walks source in lockstep with - // common_keys, as diff_recursively does, and appends - // added_ops once all members are done - stack.emplace_back(&s, &t, current_path.size()); - stack.back().member = s.cbegin(); - stack.back().target_values = std::move(target_values); - stack.back().added = std::move(added); - } - return; - } - - case value_t::null: - case value_t::string: - case value_t::boolean: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - case value_t::binary: - case value_t::discarded: - default: - { - // both primitive types: replace value - diff_replace(result, current_path, t); - return; + stack.back().member = s.cbegin(); + stack.back().target_values = std::move(target_values); + stack.back().added = std::move(added); } + return; } }; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 6220270c7..2a970a681 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -2674,32 +2674,52 @@ namespace view // Exceptions are thrown out of line, so that the accessors that may throw stay // small enough to be inlined. -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) { NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr)); } -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg) { NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr)); } -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg) { NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr)); } -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg) { NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); } /// a parse error without a position (as those of json_pointer) -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg) +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) { NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr)); } +/// out_of_range.401: the array index @a idx is not below the size +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx) +{ + throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range")); +} + +/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements +[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size) +{ + throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range")); +} + +/// out_of_range.403: the object has no member @a key +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key) +{ + throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found")); +} + /// SAX handler of the error replay: accepts every event without storing it, /// and throws the exception the parser reports template @@ -2798,7 +2818,7 @@ template if (f.code == error_code::input_too_large) { // (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes) - NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); + NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr)); } throwing_sax sax; const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas); @@ -2818,7 +2838,7 @@ template } } pos.chars_read_current_line = off + 1 - line_start; - NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr)); + NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr)); // LCOV_EXCL_STOP } @@ -2893,7 +2913,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t constexpr std::size_t limit = 0xFFFFFFFFu; if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used)) { - throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document"); + throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document"); } const std::size_t needed = used + n; const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256}); @@ -2944,7 +2964,7 @@ inline void reserve_moved(document_data& d) document_data::edit_state& e = edit_state_of(d); if (e.moved.size() >= 0xFFFFFFFFu) { - throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE + throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE } if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) { @@ -3476,11 +3496,6 @@ struct first_true : std::integral_constant < int, 1 + firs inline void check_utf8(const char* s, std::size_t n) { const auto* const p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - const auto hex = [](unsigned char c) - { - constexpr const char* digits = "0123456789ABCDEF"; - return std::string{digits[c >> 4u], digits[c & 0xFu]}; - }; for (std::size_t i = 0; i < n;) { const unsigned char c = p[i]; @@ -3489,39 +3504,22 @@ inline void check_utf8(const char* s, std::size_t n) ++i; continue; } - std::size_t len = 0; - unsigned char lo = 0x80; - unsigned char hi = 0xBF; - if (c >= 0xC2 && c <= 0xDF) + const utf8_sequence_shape shape = utf8_shape(c); + const std::size_t len = shape.length; + if (len == 0) { - len = 2; - } - else if (c >= 0xE0 && c <= 0xEF) - { - len = 3; - lo = c == 0xE0 ? 0xA0 : 0x80; - hi = c == 0xED ? 0x9F : 0xBF; - } - else if (c >= 0xF0 && c <= 0xF4) - { - len = 4; - lo = c == 0xF0 ? 0x90 : 0x80; - hi = c == 0xF4 ? 0x8F : 0xBF; - } - else - { - throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c))); + throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c))); } for (std::size_t k = 1; k < len; ++k) { if (i + k == n) { - throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1]))); + throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1]))); } const unsigned char b = p[i + k]; - if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF)) + if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF)) { - throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b))); + throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b))); } } i += len; @@ -3569,7 +3567,7 @@ class editor node* const o = own(object); if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) { - throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name()); + throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); @@ -3620,7 +3618,7 @@ class editor node* const a = own(array); if (a->kind != static_cast(value_t::array)) { - throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name()); + throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } check_index(idx, a->len); const encoded e = encode(std::forward(value)); @@ -3637,7 +3635,7 @@ class editor node* const a = own(array); if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) { - throw_type_error(308, "cannot use push_back() with ", array.type_name()); + throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); if (a->kind == static_cast(value_t::null)) @@ -3661,7 +3659,7 @@ class editor node* const a = own(array); if (a->kind != static_cast(value_t::array)) { - throw_type_error(309, "cannot use insert() with ", array.type_name()); + throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } check_index(idx, a->len + 1); const encoded e = encode(std::forward(value)); @@ -3681,7 +3679,7 @@ class editor node* const o = own(object); if (o->kind != static_cast(value_t::object)) { - throw_type_error(307, "cannot use erase() with ", object.type_name()); + throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) { @@ -3699,7 +3697,7 @@ class editor node* const a = own(array); if (a->kind != static_cast(value_t::array)) { - throw_type_error(307, "cannot use erase() with ", array.type_name()); + throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } check_index(idx, a->len); node* const h = block_of(m_doc, a, 0); @@ -3722,7 +3720,7 @@ class editor { if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr)) { - throw_invalid_iterator(202, "view does not belong to this document"); + throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document"); } edit_state_of(m_doc); return const_cast(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document @@ -3732,7 +3730,7 @@ class editor { if (idx >= limit) { - throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range")); + throw_array_index_out_of_range(idx); } } @@ -3873,7 +3871,7 @@ class editor { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { - throw_type_error(302, "type must be a value, but is ", "discarded"); + throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); } encoded r; if (!is_container(*v.m_node)) @@ -4023,7 +4021,7 @@ class editor { if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu)) { - throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE + throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } node n = plain_node(value_t::string); n.flags = static_cast(n.flags | node_flags::edited); @@ -4098,12 +4096,12 @@ class editor return string_node(s.data(), s.size()); } case value_t::binary: - throw_type_error(319, "cannot store a binary value in a json_document", ""); + throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", ""); case value_t::discarded: case value_t::object: case value_t::array: default: - throw_type_error(302, "type must be a value, but is ", "discarded"); + throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded"); } } @@ -4731,7 +4729,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v inline std::vector save_image(const document_data& d) { #if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE + throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE #endif const std::size_t arena_size = d.arena_size; std::vector compacted; @@ -4748,7 +4746,7 @@ inline std::vector save_image(const document_data& d) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) { // LCOV_EXCL_START (4 GiB) - throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document"); + throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_STOP } image_header h{}; @@ -5205,7 +5203,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) { - throw_parse_error(116, concat("invalid json_document image: ", what)); + throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); } /// Read an image into d. The text and the decoded strings stay in the image; @@ -5213,7 +5211,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) { #if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE + throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE #endif if (image == nullptr || size < sizeof(image_header)) { @@ -5567,7 +5565,7 @@ class view_iterator { if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) { - throw_invalid_iterator(207, "cannot use key() for non-object iterators"); + throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators"); } return string_view_t(m_doc->str(*m_pos), m_pos->len); } @@ -5919,51 +5917,15 @@ enum class pointer_mode contains, ///< as basic_json::contains(): no exceptions at all }; -/// the outcome of reading an array index from a reference token -enum class index_status -{ - ok, - leading_zero, ///< parse_error.106 - not_number, ///< parse_error.109 - unresolved, ///< out_of_range.404 - too_large, ///< out_of_range.410 -}; +/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer +using index_status = detail::array_index_status; /// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4), /// but reports errors instead of throwing them template index_status array_index(const StringType& s, std::size_t& idx) noexcept { - // as basic_json: the empty token and "-" (past the last element) are - // unresolved rather than not a number - if (s.empty() || (s.size() == 1 && s[0] == '-')) - { - return index_status::unresolved; - } - if (s[0] < '0' || s[0] > '9') - { - return index_status::not_number; - } - if (s.size() > 1 && s[0] == '0') - { - return index_status::leading_zero; - } - std::uint64_t v = 0; - for (const auto c : s) - { - const auto d = static_cast(static_cast(c)) - '0'; - if (d > 9 || v > ((std::numeric_limits::max)() - d) / 10) - { - return index_status::unresolved; // not a number, or beyond unsigned long long - } - v = (v * 10) + d; - } - if (v >= (std::numeric_limits::max)()) // (std::size_t converts to std::uint64_t implicitly) - { - return index_status::too_large; - } - idx = static_cast(v); - return index_status::ok; + return detail::parse_array_index(s, idx); } /// throws the exception json_pointer::array_index throws for this status @@ -5973,15 +5935,15 @@ template switch (status) { case index_status::leading_zero: - throw_parse_error(106, concat("array index '", s, "' must not begin with '0'")); - case index_status::not_number: - throw_parse_error(109, concat("array index '", s, "' is not a number")); - case index_status::too_large: - throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE + throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'")); + case index_status::not_a_number: + throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number")); + case index_status::exceeds_size_type: + throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE case index_status::unresolved: case index_status::ok: default: - throw_out_of_range(404, concat("unresolved reference token '", s, "'")); + throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'")); } } @@ -6006,7 +5968,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_out_of_range(403, concat("key '", token, "' not found")); + throw_key_not_found(token); } return View(); } @@ -6018,7 +5980,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range")); + throw_array_index_past_end(cur.size()); } return View(); } @@ -6026,7 +5988,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) const index_status status = array_index(token, idx); if (status != index_status::ok) { - const bool parse_error = status == index_status::leading_zero || status == index_status::not_number; + const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number; if (throwing || (mode == pointer_mode::value && parse_error)) { throw_array_index_error(status, token); @@ -6037,7 +5999,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range")); + throw_array_index_out_of_range(idx); } return View(); } @@ -6047,7 +6009,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_out_of_range(404, concat("unresolved reference token '", token, "'")); + throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); } return View(); } @@ -6956,21 +6918,17 @@ class view_serializer m_out.put("null"); return; } - static constexpr bool is_ieee_single_or_double - = std::numeric_limits::is_iec559 - && ((std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) - || (std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024)); - write_float(x, std::integral_constant()); + write_float(x, std::integral_constant::value>()); } - void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + void write_float(number_float_t x, std::true_type /*has_native_float_format*/) { std::array buf{}; const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x); m_out.put(buf.data(), static_cast(end - buf.data())); } - void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + void write_float(number_float_t x, std::false_type /*has_native_float_format*/) { // other types (e.g. long double) are rare: the library writes them const string_t s = BasicJsonType(x).dump(); @@ -7097,8 +7055,8 @@ class view_serializer void write_u_escape(std::uint32_t u) { - static constexpr const char* hex = "0123456789abcdef"; - const std::array e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}}; + std::array e = {{'\\', 'u', 0, 0, 0, 0}}; + hex4_lower(static_cast(u), &e[2]); m_out.put(e.data(), e.size()); } @@ -7295,7 +7253,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod case value_t::binary: case value_t::discarded: default: - throw_type_error(302, "type must be number, but is ", value_type_name(static_cast(n.kind))); + throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast(n.kind))); } } @@ -7305,7 +7263,7 @@ std::vector vector_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) { - throw_type_error(302, "type must be array, but is ", v.type_name()); + throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); } std::vector r; r.reserve(v.size()); @@ -7323,7 +7281,7 @@ Map map_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) { - throw_type_error(302, "type must be object, but is ", v.type_name()); + throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); } Map r; for (auto it = v.begin(); it != v.end(); ++it) @@ -7562,7 +7520,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); + detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); } return lookup(key); } @@ -7588,7 +7546,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); + detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); } return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))) : basic_json_view(); } @@ -7621,12 +7579,12 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } const basic_json_view r = lookup(key); if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) { - detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); + detail::view::throw_key_not_found(std::string(key.data(), key.size())); } return r; } @@ -7647,11 +7605,11 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_array())) { - detail::view::throw_type_error(304, "cannot use at() with ", type_name()); + detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) { - detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + detail::view::throw_array_index_out_of_range(idx); } return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } @@ -7679,7 +7637,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = lookup(key); return r.is_discarded() ? default_value : r.template get(); @@ -7698,7 +7656,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) { - detail::view::throw_type_error(306, "cannot use value() with ", type_name()); + detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); return r.is_discarded() ? default_value : r.template get(); @@ -7716,7 +7674,7 @@ class basic_json_view const iterator it = begin(); if (NLOHMANN_VIEW_UNLIKELY(it == end())) { - detail::view::throw_invalid_iterator(214, "cannot get value"); + detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); } return *it; } @@ -7868,7 +7826,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_string())) { - detail::view::throw_type_error(302, "type must be string, but is ", type_name()); + detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); } return {m_doc->str(*m_node), m_node->len}; } @@ -7880,7 +7838,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_number())) { - detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; } @@ -8090,7 +8048,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) { - detail::view::throw_type_error(302, "type must be boolean, but is ", type_name()); + detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); } return (m_node->flags & detail::view::node_flags::is_true) != 0; } @@ -8100,7 +8058,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) { - detail::view::throw_type_error(302, "type must be number, but is ", type_name()); + detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return detail::view::arithmetic_value(*m_doc, *m_node); } @@ -8109,7 +8067,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_null())) { - detail::view::throw_type_error(302, "type must be null, but is ", type_name()); + detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); } return nullptr; } @@ -8418,7 +8376,7 @@ class basic_json_document { if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { - detail::view::throw_type_error(320, "cannot save a discarded json_document"); + detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document"); } return detail::view::save_image(*m_data); } @@ -8519,7 +8477,7 @@ class basic_json_document const std::size_t idx = pointer_index(token); if (idx >= 0xFFFFFFFFu) { - detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + detail::view::throw_array_index_out_of_range(idx); } for (std::size_t i = 0; i < idx; ++i) { @@ -8575,7 +8533,7 @@ class basic_json_document { if (ptr.empty()) { - detail::view::throw_out_of_range(405, "JSON pointer has no parent"); + detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent"); } const view_type parent = root().at(ptr.parent_pointer()); const auto& token = ptr.back(); @@ -8595,7 +8553,7 @@ class basic_json_document static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { - detail::view::throw_invalid_iterator(202, "view does not belong to this document"); + detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); } return detail::view::editor(*m_data); } @@ -8612,7 +8570,7 @@ class basic_json_document { if (idx < 0) { - detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); + detail::view::throw_array_index_out_of_range(idx); } return static_cast(idx); } diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 0f9d4cc6d..2926ef88b 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -2056,7 +2056,10 @@ TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte" buffer.fill(static_cast('a')); buffer[position] = static_cast(value); const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast(value)); - for (const std::size_t n : {position, position + 1, std::size_t{24}}) + for (const std::size_t n : + { + position, position + 1, std::size_t{24} + }) { if (n > 24) {