diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 884ff014a..0332474b2 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -48,7 +48,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -102,7 +102,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -111,7 +111,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -121,7 +121,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -137,7 +137,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -183,7 +183,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -200,7 +200,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -305,7 +305,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -324,7 +324,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -335,7 +335,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -356,7 +356,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -377,7 +377,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -432,7 +432,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -504,7 +504,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -517,14 +517,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -591,7 +591,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 8edaee2fe..d0d9610f4 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -414,13 +414,13 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second)) { - JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key)); + JSON_THROW(type_error::create(exception_id::enum_key_duplicate, concat("duplicate object key '", key_string, "'"), &key)); } } external_constructor::construct(j, std::move(obj)); diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 155f4a4e5..f2c6651c4 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -45,6 +45,84 @@ namespace detail // exceptions // //////////////// +/*! +@brief the ids of the exceptions thrown by the library +@note The values are part of the public API: they are the `id` member of the + exceptions and appear in their `what()` messages. +@sa https://json.nlohmann.me/home/exceptions/ +*/ +enum class exception_id : int +{ + // parse_error + syntax_error = 101, ///< unexpected token or invalid literal while parsing JSON + patch_not_an_array = 104, ///< a JSON Patch document is not an array of objects + patch_invalid_operation = 105, ///< a JSON Patch operation is malformed + pointer_index_leading_zero = 106, ///< a JSON Pointer array index has a leading zero + pointer_missing_slash = 107, ///< a JSON Pointer does not start with '/' + pointer_invalid_escape = 108, ///< a JSON Pointer contains an escape other than ~0 and ~1 + pointer_index_not_a_number = 109, ///< a JSON Pointer array index is not a number + unexpected_end_of_input = 110, ///< a binary input ends early, or has bytes left at its end + unexpected_byte = 112, ///< a binary input contains an unexpected byte or invalid length + 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_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 + iterator_range_from_other_value = 203, ///< an iterator range does not belong to the value it is used with + iterator_range_out_of_range = 204, ///< an iterator range of a primitive value is not [begin, end) + iterator_out_of_range = 205, ///< an iterator of a primitive value is not begin() + iterator_range_of_null = 206, ///< an iterator range belongs to a null value + iterator_key_not_object = 207, ///< key() is called on an iterator of a non-object + iterator_subscript_on_object = 208, ///< operator[] is called on an iterator of an object + iterator_arithmetic_on_object = 209, ///< an offset operator is used on an iterator of an object + insert_range_incompatible = 210, ///< the iterators of an inserted range belong to different values + insert_range_into_itself = 211, ///< an inserted range belongs to the value it is inserted into + iterators_compare_different_values = 212, ///< iterators of different values are compared + iterator_order_on_object = 213, ///< iterators of an object are compared by order + iterator_value_unavailable = 214, ///< an iterator does not refer to a value + // type_error + object_from_non_pairs = 301, ///< an object is created from an initializer list that is not a list of pairs + type_mismatch = 302, ///< a value has the wrong type for a conversion + incompatible_reference_type = 303, ///< get_ref() is called with a reference type that does not match the value + at_wrong_type = 304, ///< at() is called on a value of the wrong type + subscript_wrong_type = 305, ///< operator[] is called on a value of the wrong type + value_wrong_type = 306, ///< value() is called on a value of the wrong type + erase_wrong_type = 307, ///< erase() is called on a value of the wrong type + push_back_wrong_type = 308, ///< push_back() or operator+= is called on a value of the wrong type + insert_wrong_type = 309, ///< insert() is called on a value of the wrong type + swap_wrong_type = 310, ///< swap() is called on a value of the wrong type + emplace_wrong_type = 311, ///< emplace() or emplace_back() is called on a value of the wrong type + update_wrong_type = 312, ///< update() is called on a value of the wrong type + unflatten_invalid_value = 313, ///< unflatten() finds conflicting paths + unflatten_not_object = 314, ///< unflatten() is called on a non-object + unflatten_value_not_primitive = 315, ///< unflatten() is called on an object with non-primitive values + 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 + 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 + pointer_past_the_end_index = 402, ///< a JSON Pointer uses the array index '-' + key_not_found = 403, ///< an object key is not found + pointer_unresolved = 404, ///< a JSON Pointer reference token cannot be resolved + patch_on_root = 405, ///< the root of a value has no parent, e.g. for a JSON Patch 'remove' or 'add' at the root + number_overflow = 406, ///< a number cannot be stored without overflowing to NaN or INF + integer_too_large = 407, ///< an integer cannot be represented in the binary format + container_too_large = 408, ///< the size of a container in a binary input exceeds the maximal capacity + bson_key_with_null = 409, ///< a BSON key contains U+0000 + value_out_of_range = 410, ///< an enum value is undefined, or a JSON Pointer array index exceeds size_type + patch_add_parent_not_container = 411, ///< the parent of a JSON Patch 'add' target is not a container + length_too_large = 412, ///< a length does not fit into the length field of a binary format + 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 + // other_error + internal_error = 500, ///< unreachable code was reached + patch_test_failed = 501, ///< a JSON Patch 'test' operation failed + size_marker_required = 502 ///< UBJSON/BJData output with type markers requires size markers +}; + /// @brief general exception of the @ref basic_json class /// @sa https://json.nlohmann.me/api/basic_json/exception/ class exception : public std::exception @@ -195,6 +273,18 @@ class parse_error : public exception return {id_, byte_, w.c_str()}; } + template::value, int> = 0> + static parse_error create(exception_id id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), pos, what_arg, context); + } + + template::value, int> = 0> + static parse_error create(exception_id id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), byte_, what_arg, context); + } + /*! @brief byte index of the parse error @@ -229,6 +319,12 @@ class invalid_iterator : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static invalid_iterator create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) @@ -247,6 +343,12 @@ class type_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static type_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -264,6 +366,12 @@ class out_of_range : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static out_of_range create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -281,6 +389,12 @@ class other_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static other_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -307,6 +421,36 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + +/*! +@brief throws because an operation is not supported for the type of @a j +@param[in] id_ the id of the type_error exception (at_wrong_type..update_wrong_type) +@param[in] operation the operation, e.g. "erase()" +@param[in] j the value the operation was called on +@throw type_error always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(operation); + static_cast(j); + JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 3dfff13b9..4fdb31609 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -12,7 +12,6 @@ #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -196,8 +195,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -319,8 +317,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -512,9 +509,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -524,10 +519,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -547,9 +539,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -639,11 +629,9 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, - parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); + parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } } } @@ -967,9 +955,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -989,9 +975,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -1065,9 +1049,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -1080,10 +1062,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + 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); } /*! @@ -1160,9 +1139,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -1292,9 +1269,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -1375,9 +1350,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -1523,7 +1496,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message(concat("excessive ", context, " size"), "size"), nullptr)); } result = conditional_static_cast(len); @@ -2011,9 +1984,7 @@ class binary_reader default: // anything else { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -2094,9 +2065,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -2188,9 +2157,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -2499,8 +2466,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "string length must not be negative", "string"); } return true; } @@ -2600,18 +2566,7 @@ class binary_reader default: break; } - auto last_token = get_token_string(); - std::string message; - - if (input_format != input_format_t::bjdata) - { - message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; - } - else - { - message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; - } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "string"), nullptr)); + return length_type_error("", "string"); } /*! @@ -2696,12 +2651,11 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char @@ -2799,7 +2753,7 @@ class binary_reader } if (!value_in_range_of(number)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); @@ -2814,7 +2768,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -2850,9 +2804,7 @@ class binary_reader const char* type_name = bjd_type_name(ndarray_dtype); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type_key = "_ArrayType_"; @@ -2880,7 +2832,7 @@ class binary_reader // or SIZE_MAX. if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits::max)() / i)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } result *= i; // the pre-check above already rules out result becoming 0 @@ -2889,7 +2841,7 @@ class binary_reader // unknown-size container (see get_ubjson_size_type()) if (result == npos) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i))) { @@ -2906,18 +2858,7 @@ class binary_reader default: break; } - auto last_token = get_token_string(); - std::string message; - - if (input_format != input_format_t::bjdata) - { - message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; - } - else - { - message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; - } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "size"), nullptr)); + return length_type_error(" after '#'", "size"); } /*! @@ -2950,9 +2891,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -2967,9 +2906,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second); @@ -2988,8 +2925,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); } return is_error; } @@ -3121,9 +3057,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -3144,8 +3078,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3208,7 +3141,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F') && size_and_type.first > max_valueless_container_size)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive array size", "size"), nullptr)); } @@ -3244,9 +3177,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -3283,7 +3214,7 @@ class binary_reader // the lexer would stop at a NUL and accept the digits before it if (JSON_HEDLEY_UNLIKELY(current == '\0')) { - return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + return sax->parse_error(chars_read, "00", parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); } number_vector.push_back(static_cast(current)); @@ -3300,7 +3231,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } @@ -3318,7 +3249,7 @@ class binary_reader return sax->parse_error( chars_read, number_string, - out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", number_string, '\''), nullptr)); } // number_string is a std::string, while the SAX interface takes a // string_t; convert explicitly, as the two are only implicitly @@ -3340,7 +3271,7 @@ class binary_reader case token_type::end_of_input: case token_type::literal_or_value: default: - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -3399,20 +3330,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -3628,7 +3545,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -3755,7 +3672,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -3878,7 +3795,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -3892,7 +3809,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -3937,7 +3854,7 @@ class binary_reader { // in case of failure, advance position by 1 to report the failing location ++chars_read; - sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + sax->parse_error(chars_read, "", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); return false; } return true; @@ -4091,7 +4008,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result))) { return sax->parse_error(chars_read, get_token_string(), - out_of_range::create(406, exception_message("number overflow", "value"), nullptr)); + out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr)); } return sax->number_float(result, ""); } @@ -4211,9 +4128,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -4309,19 +4224,53 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current == char_traits::eof())) { return sax->parse_error(chars_read, "", - parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); } return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context); + } + + /*! + @brief reports that the last read byte is not a UBJSON/BJData length type (parse_error.113) + @param[in] position where the length type was expected, e.g. " after '#'" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool length_type_error(const char* position, const char* context) const + { + const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L"; + return last_byte_error(exception_id::invalid_string_or_size, + concat("expected length type specification (", types, ")", position, "; last byte: 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 9578e275a..847890f7a 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -581,7 +581,7 @@ class wide_string_input_adapter template JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) { - JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + JSON_THROW(parse_error::create(exception_id::unexpected_byte, 1, "wide string type cannot be interpreted as binary data", nullptr)); } private: @@ -793,7 +793,7 @@ inline file_input_adapter input_adapter(std::FILE* file) { if (file == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return file_input_adapter(file); } @@ -802,7 +802,7 @@ inline input_stream_adapter input_adapter(std::istream& stream) { if (stream.rdbuf() == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return input_stream_adapter(stream); } @@ -827,7 +827,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b) { if (b == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 146944261..816359a60 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -176,6 +176,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -185,6 +206,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -349,22 +399,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -383,11 +422,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -400,17 +435,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -427,11 +458,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -611,21 +638,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -695,11 +712,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -710,13 +723,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { @@ -742,20 +749,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -779,11 +779,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -809,13 +805,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); // remove discarded value if (!ref_stack.empty() && ref_stack.back()) @@ -902,6 +892,23 @@ class json_sax_dom_callback_parser return string_t{}; } + /*! + @brief leave the object or array that end_object()/end_array() just closed + @return the key the container is stored under in its parent (see + container_key_stack) + */ + string_t pop_container() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + JSON_ASSERT(!container_key_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + string_t object_key = std::move(container_key_stack.back()); + container_key_stack.pop_back(); + return object_key; + } + /*! @brief remove the discarded value the callback rejected from its parent, unless it is a duplicate key's slot with a stashed previous value, in diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index aa2b30fd7..3a53ff6b3 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -156,9 +156,7 @@ class parser // strict mode: next byte must be EOF if (get_token() != token_type::end_of_input) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_of_input, "value")); } } else @@ -197,10 +195,7 @@ class parser // in strict mode, input must be completely read if (get_token() != token_type::end_of_input) { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); + syntax_error(sdp, exception_message(token_type::end_of_input, "value")); } } else @@ -250,9 +245,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { @@ -262,9 +255,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // remember we are now inside an object @@ -307,7 +298,7 @@ class parser { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), - out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) @@ -375,23 +366,16 @@ class parser case token_type::parse_error: { // using "uninitialized" to avoid an "expected" message - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::uninitialized, "value")); } case token_type::end_of_input: { if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON"); } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } case token_type::uninitialized: case token_type::end_array: @@ -401,9 +385,7 @@ class parser case token_type::literal_or_value: default: // the last token was unexpected { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } } } @@ -454,9 +436,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_array, "array")); } // states.back() is false -> object @@ -473,9 +453,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) @@ -486,9 +464,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // parse values @@ -516,9 +492,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_object, "object")); } } @@ -535,6 +509,21 @@ class parser return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type; } + /*! + @brief reports a syntax error at the current token (parse_error.101) + @param[in] sax the SAX parser to report the error to + @param[in] message the error message + @return the result of the SAX parser's parse_error() + */ + template + bool syntax_error(SAX& sax, const std::string& message) + { + // MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static + static_cast(sax); + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr)); + } + std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index 84fdd07f8..52b459ad2 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -279,6 +279,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } } + /// @throw invalid_iterator.214 always + JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const + { + JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); + } + public: /*! @brief return a reference to the value pointed to by the iterator @@ -303,7 +309,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -319,7 +325,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -361,7 +367,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -478,7 +484,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -527,7 +533,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -540,7 +546,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + JSON_THROW(invalid_iterator::create(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); @@ -596,7 +602,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: { @@ -675,7 +681,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(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; @@ -704,13 +710,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -726,7 +732,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -744,7 +750,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); } /*! diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 2ec4e3b58..cff8420be 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -166,7 +166,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -178,7 +178,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -204,7 +204,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -216,7 +216,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -244,6 +244,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + 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 @@ -329,13 +344,13 @@ class json_pointer // 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(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: default: break; @@ -349,7 +364,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -486,7 +501,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); } id = tree.find_child(id, reference_token); @@ -569,7 +584,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -601,7 +616,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( + 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)); } @@ -610,7 +625,7 @@ class json_pointer // 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(401, detail::concat( + 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->operator[](idx); @@ -626,7 +641,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -670,7 +685,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + 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[] @@ -688,7 +703,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -743,9 +758,9 @@ class json_pointer // 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(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + 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(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + 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; @@ -872,7 +887,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(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + 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)); } // extract the reference tokens: @@ -908,7 +923,7 @@ class json_pointer (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } } @@ -1072,7 +1087,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value)); } BasicJsonType result; @@ -1096,7 +1111,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } // Assign the value to the reference pointed to by JSON pointer. Note diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 1a847e1a9..f5f6473b9 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -312,7 +312,7 @@ return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -327,7 +327,7 @@ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 212d2658f..cbe771ee8 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -157,7 +157,7 @@ class binary_writer case value_t::discarded: default: { - JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_not_serializable, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); } } } @@ -348,7 +348,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(length))) { - JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); } static_cast(j); @@ -575,7 +575,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); } write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -730,7 +730,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } oa.write_character(to_char_type('$')); oa.write_character(bjdata_draft3 ? 'B' : 'U'); @@ -831,7 +831,7 @@ class binary_writer { static_cast(j); // unused when JSON_NOEXCEPTION is defined static_cast(format_name); - JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j)); } void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) @@ -1091,7 +1091,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), @@ -1141,7 +1141,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), @@ -1358,7 +1358,7 @@ class binary_writer const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { - JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } string_t storage; @@ -1376,7 +1376,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) { - JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); } return static_cast(size); @@ -1562,7 +1562,7 @@ class binary_writer if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); } return sizeof(std::int32_t) + value.size() + 1ul; @@ -2591,7 +2591,7 @@ class binary_writer { if (j.m_data.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); } write_bon8_integer(static_cast(j.m_data.m_value.number_unsigned)); string_open = false; @@ -2744,7 +2744,7 @@ class binary_writer const std::size_t valid = valid_utf8_prefix(data, s.size()); if (JSON_HEDLEY_UNLIKELY(valid != s.size())) { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); } } diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 8306440ef..64c4fcf03 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -888,7 +888,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); } case error_handler_t::ignore: @@ -1021,7 +1021,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); } case error_handler_t::ignore: diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9c89ab0bc..b841e9fa3 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -639,7 +639,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -2287,7 +2287,7 @@ public: // if an object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { - JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + JSON_THROW(type_error::create(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); } } @@ -2407,7 +2407,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); } // copy type from the first iterator @@ -2426,7 +2426,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); } break; } @@ -2494,7 +2494,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); } set_parents(); @@ -2882,7 +2882,7 @@ public: return *ptr; } - JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } public: @@ -3266,7 +3266,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -3278,7 +3278,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -3320,7 +3320,7 @@ public: // at only works for objects if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -3330,7 +3330,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); } return it->second; } @@ -3345,12 +3345,12 @@ public: // at only works for arrays if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } return (*j.m_data.m_value.array)[idx]; @@ -3383,6 +3383,15 @@ public: m_data.m_type = value_t::object; } + /// @brief throws because operator[] is not supported for the type of this value + /// @param[in] argument the kind of the operator's argument, "numeric" or "string" + /// @throw type_error.305 always + JSON_HEDLEY_NO_RETURN void throw_subscript_wrong_type(const char* argument) const + { + detail::throw_cannot_use_with(detail::exception_id::subscript_wrong_type, + detail::concat("operator[] with a ", argument, " argument").c_str(), *this); + } + public: //////////////////// // element access // @@ -3487,7 +3496,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified array element @@ -3501,7 +3510,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified object element @@ -3521,7 +3530,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -3536,7 +3545,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -3572,7 +3581,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -3589,7 +3598,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } private: @@ -3613,7 +3622,7 @@ public: // value only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } const auto it = find(std::forward(key)); @@ -3628,7 +3637,7 @@ public: // value only works for arrays and objects if (JSON_HEDLEY_UNLIKELY(!is_structured())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } return ptr.get_checked_or_null(this); @@ -3795,7 +3804,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } IteratorType result = end(); @@ -3811,7 +3820,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -3837,7 +3846,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -3853,7 +3862,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); } IteratorType result = end(); @@ -3870,7 +3879,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -3898,7 +3907,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -3912,7 +3921,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -3927,7 +3936,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -3969,14 +3978,14 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } } @@ -4465,7 +4474,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -4496,7 +4505,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -4526,7 +4535,7 @@ public: // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an object @@ -4580,7 +4589,7 @@ public: // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace_back()", *this); } // transform a null object into an array @@ -4603,7 +4612,7 @@ public: // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace()", *this); } // transform a null object into an object @@ -4655,14 +4664,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts element into array @@ -4675,7 +4684,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // moving into a local first keeps this safe even if val aliases @@ -4684,7 +4693,7 @@ public: return insert_iterator(pos, std::move(tmp)); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts copies of element into array @@ -4697,14 +4706,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts range of elements into array @@ -4714,30 +4723,30 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); } // passed iterators must belong to arrays if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); } // insert to array and return iterator @@ -4751,13 +4760,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // copy the values first: ilist may refer to elements of this array @@ -4779,19 +4788,19 @@ public: // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); @@ -4808,7 +4817,7 @@ public: // j, not the copy made below) if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -4826,13 +4835,13 @@ public: // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -4868,7 +4877,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *this); } } @@ -5031,7 +5040,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this); } } @@ -5048,7 +5057,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this); } } @@ -5064,7 +5073,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this); } } @@ -5080,7 +5089,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this); } } @@ -5096,7 +5105,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this); } } @@ -6558,7 +6567,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); } // default case: insert add offset @@ -6577,7 +6586,7 @@ public: case value_t::binary: case value_t::discarded: default: - JSON_THROW(out_of_range::create(411, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_add_parent_not_container, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -6600,7 +6609,7 @@ public: } else { - JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", last_path, "' not found"), this)); } } else if (parent.is_array()) @@ -6612,7 +6621,7 @@ public: { // the parent of a "remove" target must be an object or array // (see #5396) - JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_remove_parent_not_container, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -6643,7 +6652,7 @@ public: // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &json_patch)); } // iterate and apply the operations @@ -6664,14 +6673,14 @@ public: if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); } // check if the result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); } // no error: return value @@ -6681,7 +6690,7 @@ public: // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &val)); } // collect mandatory members @@ -6717,7 +6726,7 @@ public: if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) { - JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_move_into_child, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); } // the "from" location must exist - use at() @@ -6764,7 +6773,7 @@ public: // throw an exception if the test fails if (JSON_HEDLEY_UNLIKELY(!success)) { - JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + JSON_THROW(other_error::create(detail::exception_id::patch_test_failed, detail::concat("unsuccessful: ", val.dump()), &val)); } break; @@ -6775,7 +6784,7 @@ public: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat("operation value '", op, "' is invalid"), &val)); } } } diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 74539d376..5f0be7d28 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -90,6 +90,19 @@ private: return self.end(); } + /// @brief shared implementation of the const and non-const at() overloads + /// @throw std::out_of_range if @a key is not found + template + static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second)) + { + const auto it = find_impl(self, key); + if (it == self.end()) + { + JSON_THROW(std::out_of_range("key not found")); + } + return it->second; + } + /// @brief remove the entry @a it points to, preserving order /// @note keys are not movable, so the tail is destroyed and re-constructed in place void erase_at(iterator it) @@ -156,46 +169,26 @@ public: T& at(const key_type& key) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } const T& at(const key_type& key) const { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } size_type erase(const key_type& key) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 09f67bdc3..a8a9bab3e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2649,7 +2649,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.first == e; \ }); \ if (it != std::end(m)) j = it->second; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, nullptr)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ @@ -2664,7 +2664,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(410, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5237,6 +5237,84 @@ namespace detail // exceptions // //////////////// +/*! +@brief the ids of the exceptions thrown by the library +@note The values are part of the public API: they are the `id` member of the + exceptions and appear in their `what()` messages. +@sa https://json.nlohmann.me/home/exceptions/ +*/ +enum class exception_id : int +{ + // parse_error + syntax_error = 101, ///< unexpected token or invalid literal while parsing JSON + patch_not_an_array = 104, ///< a JSON Patch document is not an array of objects + patch_invalid_operation = 105, ///< a JSON Patch operation is malformed + pointer_index_leading_zero = 106, ///< a JSON Pointer array index has a leading zero + pointer_missing_slash = 107, ///< a JSON Pointer does not start with '/' + pointer_invalid_escape = 108, ///< a JSON Pointer contains an escape other than ~0 and ~1 + pointer_index_not_a_number = 109, ///< a JSON Pointer array index is not a number + unexpected_end_of_input = 110, ///< a binary input ends early, or has bytes left at its end + unexpected_byte = 112, ///< a binary input contains an unexpected byte or invalid length + 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_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 + iterator_range_from_other_value = 203, ///< an iterator range does not belong to the value it is used with + iterator_range_out_of_range = 204, ///< an iterator range of a primitive value is not [begin, end) + iterator_out_of_range = 205, ///< an iterator of a primitive value is not begin() + iterator_range_of_null = 206, ///< an iterator range belongs to a null value + iterator_key_not_object = 207, ///< key() is called on an iterator of a non-object + iterator_subscript_on_object = 208, ///< operator[] is called on an iterator of an object + iterator_arithmetic_on_object = 209, ///< an offset operator is used on an iterator of an object + insert_range_incompatible = 210, ///< the iterators of an inserted range belong to different values + insert_range_into_itself = 211, ///< an inserted range belongs to the value it is inserted into + iterators_compare_different_values = 212, ///< iterators of different values are compared + iterator_order_on_object = 213, ///< iterators of an object are compared by order + iterator_value_unavailable = 214, ///< an iterator does not refer to a value + // type_error + object_from_non_pairs = 301, ///< an object is created from an initializer list that is not a list of pairs + type_mismatch = 302, ///< a value has the wrong type for a conversion + incompatible_reference_type = 303, ///< get_ref() is called with a reference type that does not match the value + at_wrong_type = 304, ///< at() is called on a value of the wrong type + subscript_wrong_type = 305, ///< operator[] is called on a value of the wrong type + value_wrong_type = 306, ///< value() is called on a value of the wrong type + erase_wrong_type = 307, ///< erase() is called on a value of the wrong type + push_back_wrong_type = 308, ///< push_back() or operator+= is called on a value of the wrong type + insert_wrong_type = 309, ///< insert() is called on a value of the wrong type + swap_wrong_type = 310, ///< swap() is called on a value of the wrong type + emplace_wrong_type = 311, ///< emplace() or emplace_back() is called on a value of the wrong type + update_wrong_type = 312, ///< update() is called on a value of the wrong type + unflatten_invalid_value = 313, ///< unflatten() finds conflicting paths + unflatten_not_object = 314, ///< unflatten() is called on a non-object + unflatten_value_not_primitive = 315, ///< unflatten() is called on an object with non-primitive values + 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 + 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 + pointer_past_the_end_index = 402, ///< a JSON Pointer uses the array index '-' + key_not_found = 403, ///< an object key is not found + pointer_unresolved = 404, ///< a JSON Pointer reference token cannot be resolved + patch_on_root = 405, ///< the root of a value has no parent, e.g. for a JSON Patch 'remove' or 'add' at the root + number_overflow = 406, ///< a number cannot be stored without overflowing to NaN or INF + integer_too_large = 407, ///< an integer cannot be represented in the binary format + container_too_large = 408, ///< the size of a container in a binary input exceeds the maximal capacity + bson_key_with_null = 409, ///< a BSON key contains U+0000 + value_out_of_range = 410, ///< an enum value is undefined, or a JSON Pointer array index exceeds size_type + patch_add_parent_not_container = 411, ///< the parent of a JSON Patch 'add' target is not a container + length_too_large = 412, ///< a length does not fit into the length field of a binary format + 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 + // other_error + internal_error = 500, ///< unreachable code was reached + patch_test_failed = 501, ///< a JSON Patch 'test' operation failed + size_marker_required = 502 ///< UBJSON/BJData output with type markers requires size markers +}; + /// @brief general exception of the @ref basic_json class /// @sa https://json.nlohmann.me/api/basic_json/exception/ class exception : public std::exception @@ -5387,6 +5465,18 @@ class parse_error : public exception return {id_, byte_, w.c_str()}; } + template::value, int> = 0> + static parse_error create(exception_id id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), pos, what_arg, context); + } + + template::value, int> = 0> + static parse_error create(exception_id id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), byte_, what_arg, context); + } + /*! @brief byte index of the parse error @@ -5421,6 +5511,12 @@ class invalid_iterator : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static invalid_iterator create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) @@ -5439,6 +5535,12 @@ class type_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static type_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5456,6 +5558,12 @@ class out_of_range : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static out_of_range create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5473,6 +5581,12 @@ class other_error : public exception return {id_, w.c_str()}; } + template::value, int> = 0> + static other_error create(exception_id id_, const std::string& what_arg, BasicJsonContext context) + { + return create(static_cast(id_), what_arg, context); + } + private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} @@ -5499,6 +5613,36 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +/*! +@brief throws because @a j does not have the type a conversion expects +@param[in] expected the expected type(s), e.g. "array" or "binary or array" +@param[in] j the value with the wrong type +@throw type_error.302 always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +{ + static_cast(expected); // unused when JSON_NOEXCEPTION is defined + static_cast(j); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); +} + +/*! +@brief throws because an operation is not supported for the type of @a j +@param[in] id_ the id of the type_error exception (at_wrong_type..update_wrong_type) +@param[in] operation the operation, e.g. "erase()" +@param[in] j the value the operation was called on +@throw type_error always +*/ +template +JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(operation); + static_cast(j); + JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -5591,7 +5735,7 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { - JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + throw_type_must_be("null", j); } n = nullptr; } @@ -5645,7 +5789,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -5654,7 +5798,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { - JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + throw_type_must_be("boolean", j); } b = *j.template get_ptr(); } @@ -5664,7 +5808,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); } @@ -5680,7 +5824,7 @@ inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } s = *j.template get_ptr(); @@ -5726,7 +5870,7 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.clear(); std::transform(j.rbegin(), j.rend(), @@ -5743,7 +5887,7 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } l.resize(j.size()); std::transform(j.begin(), j.end(), std::begin(l), @@ -5848,7 +5992,7 @@ void()) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } from_json_array_impl(j, arr, priority_tag<3> {}); @@ -5867,7 +6011,7 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_inplace_array_impl(j, tag, make_index_sequence {}); @@ -5878,7 +6022,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { - JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + throw_type_must_be("binary", j); } bin = *j.template get_ptr(); @@ -5899,7 +6043,7 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) } else { - JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j)); + throw_type_must_be("binary or array", j); } } @@ -5920,7 +6064,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + throw_type_must_be("object", j); } ConstructibleObjectType ret; @@ -5975,7 +6119,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) case value_t::binary: case value_t::discarded: default: - JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + throw_type_must_be("number", j); } } @@ -6047,7 +6191,7 @@ auto from_json(const BasicJsonType& j, TupleRelated&& t) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } return from_json_tuple_impl(j, std::forward(t), priority_tag<3> {}); @@ -6060,14 +6204,14 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + throw_type_must_be("array", j); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(!p.is_array())) { - JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p)); + throw_type_must_be("array", p); } m.emplace(p.at(0).template get(), p.at(1).template get()); } @@ -6134,7 +6278,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + throw_type_must_be("string", j); } const auto& s = *j.template get_ptr(); // Checking for C++20 standard or later can be insufficient in case the @@ -7208,13 +7352,13 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) BasicJsonType key = p.first; if (JSON_HEDLEY_UNLIKELY(!key.is_string())) { - JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key)); + throw_type_must_be("string", key); } auto& key_string = *key.template get_ptr(); if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second)) { - JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key)); + JSON_THROW(type_error::create(exception_id::enum_key_duplicate, concat("duplicate object key '", key_string, "'"), &key)); } } external_constructor::construct(j, std::move(obj)); @@ -7792,7 +7936,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t -#include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits @@ -8394,7 +8537,7 @@ class wide_string_input_adapter template JSON_HEDLEY_NO_RETURN std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) { - JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + JSON_THROW(parse_error::create(exception_id::unexpected_byte, 1, "wide string type cannot be interpreted as binary data", nullptr)); } private: @@ -8606,7 +8749,7 @@ inline file_input_adapter input_adapter(std::FILE* file) { if (file == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return file_input_adapter(file); } @@ -8615,7 +8758,7 @@ inline input_stream_adapter input_adapter(std::istream& stream) { if (stream.rdbuf() == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } return input_stream_adapter(stream); } @@ -8640,7 +8783,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b) { if (b == nullptr) { - JSON_THROW(parse_error::create(101, 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, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); @@ -12603,6 +12746,27 @@ template inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/) {} +/*! +@brief reports an object or array whose announced size exceeds max_size() + +Shared by json_sax_dom_parser and json_sax_dom_callback_parser. + +@param[in] sax the SAX parser to report the error to +@param[in] len the number of elements announced by the input, or unknown_size() +@param[in] kind "object" or "array" +@param[in] ref the object or array that was just created +@return whether parsing should continue (false after reporting out_of_range.408) +*/ +template +bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref) +{ + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size())) + { + return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref)); + } + return true; +} + #if JSON_DIAGNOSTIC_POSITIONS /*! @brief set the diagnostic positions of a value the DOM SAX parsers just stored @@ -12612,6 +12776,35 @@ befriends this struct, as the position members are private. */ struct diagnostic_positions { + /*! + @param[in,out] v the object or array whose opening brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_start(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer has read the first character of the container, so + // subtract 1 from the position to get the correct start position. + v.start_position = lexer->get_position() - 1; + } + } + + /*! + @param[in,out] v the object or array whose closing brace or bracket was just read + @param[in] lexer the lexer that read it, or nullptr to leave @a v alone + */ + template + static void set_container_end(BasicJsonType& v, LexerType* lexer) + { + if (lexer) + { + // Lexer's position is past the closing brace or bracket, so set that as the end position. + v.end_position = lexer->get_position(); + } + } + /*! @param[in,out] v the value that was just parsed @param[in] lexer the lexer that read it, or nullptr to leave @a v alone @@ -12776,22 +12969,11 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; + return check_container_size(*this, len, "object", ref_stack.back()); } bool key(string_t& val) @@ -12810,11 +12992,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_object()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -12827,17 +13005,13 @@ class json_sax_dom_parser ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -12854,11 +13028,7 @@ class json_sax_dom_parser JSON_ASSERT(ref_stack.back()->is_array()); #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13038,21 +13208,11 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the object here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the object, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check object limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) - { - return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + return check_container_size(*this, len, "object", ref_stack.back()); } return true; } @@ -13122,11 +13282,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing brace, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13137,13 +13293,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { @@ -13169,20 +13319,13 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - // Manually set the start position of the array here. // Ensure this is after the call to handle_value to ensure correct start position. - if (m_lexer_ref) - { - // Lexer has read the first character of the array, so - // subtract 1 from the position to get the correct start position. - ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; - } + diagnostic_positions::set_container_start(*ref_stack.back(), m_lexer_ref); #endif - // check array limit - if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + if (JSON_HEDLEY_UNLIKELY(!check_container_size(*this, len, "array", ref_stack.back()))) { - return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + return false; } if (len != detail::unknown_size()) @@ -13206,11 +13349,7 @@ class json_sax_dom_callback_parser { #if JSON_DIAGNOSTIC_POSITIONS - if (m_lexer_ref) - { - // Lexer's position is past the closing bracket, so set that as the end position. - ref_stack.back()->end_position = m_lexer_ref->get_position(); - } + diagnostic_positions::set_container_end(*ref_stack.back(), m_lexer_ref); #endif ref_stack.back()->set_parents(); @@ -13236,13 +13375,7 @@ class json_sax_dom_callback_parser } } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - JSON_ASSERT(!container_key_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - const string_t object_key = std::move(container_key_stack.back()); - container_key_stack.pop_back(); + const string_t object_key = pop_container(); // remove discarded value if (!ref_stack.empty() && ref_stack.back()) @@ -13329,6 +13462,23 @@ class json_sax_dom_callback_parser return string_t{}; } + /*! + @brief leave the object or array that end_object()/end_array() just closed + @return the key the container is stored under in its parent (see + container_key_stack) + */ + string_t pop_container() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + JSON_ASSERT(!container_key_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + string_t object_key = std::move(container_key_stack.back()); + container_key_stack.pop_back(); + return object_key; + } + /*! @brief remove the discarded value the callback rejected from its parent, unless it is a duplicate key's slot with a stashed previous value, in @@ -13904,8 +14054,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current != char_traits::eof())) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, - exception_message(concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value"); } } @@ -14027,8 +14176,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast(document_size) != chars_read - document_start)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message(concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"); } return true; } @@ -14220,9 +14368,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 1)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string"); } if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast(1), result))) @@ -14232,10 +14378,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(get() != 0x00)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BSON string is not null-terminated", - "string"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BSON string is not null-terminated", "string"); } return check_string_utf8(result, "string"); @@ -14255,9 +14398,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"); } // All BSON binary values have a subtype @@ -14347,11 +14488,9 @@ class binary_reader default: // anything else is not supported (yet) { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const std::string cr_str{cr.data()}; + const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(element_type_parse_position, cr_str, - parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); + parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); } } } @@ -14675,9 +14814,7 @@ class binary_reader { if (tag_handler == cbor_tag_handler_t::error) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } // ignore and store: the tag value is already in the head, so @@ -14697,9 +14834,7 @@ class binary_reader { case cbor_tag_handler_t::error: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } case cbor_tag_handler_t::ignore: @@ -14773,9 +14908,7 @@ class binary_reader default: // anything else (0xFF is handled inside the other types) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -14788,10 +14921,7 @@ class binary_reader */ bool cbor_indefinite_string_error(const char* type_name, const char* context) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("indefinite-length ", type_name, - " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr)); + 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); } /*! @@ -14868,9 +14998,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string"); } } } @@ -15000,9 +15128,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -15083,9 +15209,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary"); } } } @@ -15231,7 +15355,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message(concat("excessive ", context, " size"), "size"), nullptr)); } result = conditional_static_cast(len); @@ -15719,9 +15843,7 @@ class binary_reader default: // anything else { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("invalid byte: 0x", last_token), "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } } } @@ -15802,9 +15924,7 @@ class binary_reader default: { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string"); } } } @@ -15896,9 +16016,7 @@ class binary_reader break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key"); } /*! @@ -16207,8 +16325,7 @@ class binary_reader { if (JSON_HEDLEY_UNLIKELY(len < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("string length must not be negative", "string"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "string length must not be negative", "string"); } return true; } @@ -16308,18 +16425,7 @@ class binary_reader default: break; } - auto last_token = get_token_string(); - std::string message; - - if (input_format != input_format_t::bjdata) - { - message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; - } - else - { - message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; - } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "string"), nullptr)); + return length_type_error("", "string"); } /*! @@ -16404,12 +16510,11 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(number < 0)) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, - exception_message("count in an optimized container must be positive", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "count in an optimized container must be positive", "size"); } if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(number))) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char @@ -16507,7 +16612,7 @@ class binary_reader } if (!value_in_range_of(number)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("integer value overflow", "size"), nullptr)); } result = detail::conditional_static_cast(number); @@ -16522,7 +16627,7 @@ class binary_reader } if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message("ndarray dimensional vector is not allowed", "size"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "ndarray dimensional vector is not allowed", "size"); } std::vector dim; if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) @@ -16558,9 +16663,7 @@ class binary_reader const char* type_name = bjd_type_name(ndarray_dtype); if (JSON_HEDLEY_UNLIKELY(type_name == nullptr)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("invalid byte: 0x" + last_token, "type"), nullptr)); + return unexpected_byte("invalid byte", "type"); } string_t type_key = "_ArrayType_"; @@ -16588,7 +16691,7 @@ class binary_reader // or SIZE_MAX. if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits::max)() / i)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } result *= i; // the pre-check above already rules out result becoming 0 @@ -16597,7 +16700,7 @@ class binary_reader // unknown-size container (see get_ubjson_size_type()) if (result == npos) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i))) { @@ -16614,18 +16717,7 @@ class binary_reader default: break; } - auto last_token = get_token_string(); - std::string message; - - if (input_format != input_format_t::bjdata) - { - message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; - } - else - { - message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; - } - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(message, "size"), nullptr)); + return length_type_error(" after '#'", "size"); } /*! @@ -16658,9 +16750,7 @@ class binary_reader if (input_format == input_format_t::bjdata && JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second))) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type"); } if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type"))) @@ -16675,9 +16765,7 @@ class binary_reader { return false; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size"); } const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second); @@ -16696,8 +16784,7 @@ class binary_reader const bool is_error = get_ubjson_size_value(result.first, is_ndarray); if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray) { - return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, - exception_message("ndarray requires both type and size", "size"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size"); } return is_error; } @@ -16829,9 +16916,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(current > 127)) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message(concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char"); } string_t s(1, static_cast(current)); return sax->string(s); @@ -16852,8 +16937,7 @@ class binary_reader default: // anything else break; } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message("invalid byte: 0x" + last_token, "value"), nullptr)); + return unexpected_byte("invalid byte", "value"); } /*! @@ -16916,7 +17000,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F') && size_and_type.first > max_valueless_container_size)) { - return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive array size", "size"), nullptr)); } @@ -16952,9 +17036,7 @@ class binary_reader // do not accept ND-array size in objects in BJData if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message("BJData object does not support ND-array size in optimized format", "object"), nullptr)); + return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object"); } if (size_and_type.first != npos) @@ -16991,7 +17073,7 @@ class binary_reader // the lexer would stop at a NUL and accept the digits before it if (JSON_HEDLEY_UNLIKELY(current == '\0')) { - return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + return sax->parse_error(chars_read, "00", parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); } number_vector.push_back(static_cast(current)); @@ -17008,7 +17090,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) { - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } @@ -17026,7 +17108,7 @@ class binary_reader return sax->parse_error( chars_read, number_string, - out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", number_string, '\''), nullptr)); } // number_string is a std::string, while the SAX interface takes a // string_t; convert explicitly, as the two are only implicitly @@ -17048,7 +17130,7 @@ class binary_reader case token_type::end_of_input: case token_type::literal_or_value: default: - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + return sax->parse_error(chars_read, number_string, parse_error::create(exception_id::invalid_high_precision_number, chars_read, exception_message(concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); } } @@ -17107,20 +17189,6 @@ class binary_reader return 0x80 <= c && c <= 0xBF; } - /*! - @brief report a parse error at the last read byte - - @param[in] detail a detailed error message - @param[in] context further context information - @return false - */ - bool bon8_error(const std::string& detail, const char* context) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, - exception_message(concat(detail, ": 0x", last_token), context), nullptr)); - } - /*! @brief read a BON8 value and everything nested inside it @@ -17336,7 +17404,7 @@ class binary_reader } // 0xFE: end of container where a value is expected - return bon8_error("invalid byte", "value"); + return unexpected_byte("invalid byte", "value"); } /*! @@ -17463,7 +17531,7 @@ class binary_reader current = byte; } - return bon8_error("expected a string; last byte", "key"); + return unexpected_byte("expected a string; last byte", "key"); } /*! @@ -17586,7 +17654,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(!valid_second)) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(byte)); @@ -17600,7 +17668,7 @@ class binary_reader } if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current))) { - return bon8_error("invalid UTF-8 byte", "string"); + return unexpected_byte("invalid UTF-8 byte", "string"); } result.push_back(static_cast(current)); } @@ -17645,7 +17713,7 @@ class binary_reader { // in case of failure, advance position by 1 to report the failing location ++chars_read; - sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + sax->parse_error(chars_read, "", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); return false; } return true; @@ -17799,7 +17867,7 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result))) { return sax->parse_error(chars_read, get_token_string(), - out_of_range::create(406, exception_message("number overflow", "value"), nullptr)); + out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr)); } return sax->number_float(result, ""); } @@ -17919,9 +17987,7 @@ class binary_reader if (error_handler == error_handler_t::strict) { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, - exception_message("invalid string: ill-formed UTF-8 byte", context), nullptr)); + return last_byte_error(exception_id::invalid_string_or_size, "invalid string: ill-formed UTF-8 byte", context); } result = sanitize_utf8(result, error_handler); @@ -18017,19 +18083,53 @@ class binary_reader if (JSON_HEDLEY_UNLIKELY(current == char_traits::eof())) { return sax->parse_error(chars_read, "", - parse_error::create(110, chars_read, exception_message("unexpected end of input", context), nullptr)); + parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr)); } return true; } + /*! + @brief reports a parse error at the last read byte + @param[in] id_ the id of the parse_error exception + @param[in] detail a detailed error message + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr)); + } + + /*! + @brief reports the last read byte as unexpected (parse_error.112) + @param[in] detail what is wrong with the byte, e.g. "invalid byte" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool unexpected_byte(const char* detail, const std::string& context) const + { + return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context); + } + + /*! + @brief reports that the last read byte is not a UBJSON/BJData length type (parse_error.113) + @param[in] position where the length type was expected, e.g. " after '#'" + @param[in] context further context information + @return the result of the SAX parser's parse_error() + */ + bool length_type_error(const char* position, const char* context) const + { + const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L"; + return last_byte_error(exception_id::invalid_string_or_size, + concat("expected length type specification (", types, ")", position, "; last byte: 0x", get_token_string()), context); + } + /*! @return a string representation of the last read byte */ std::string get_token_string() const { - std::array cr{{}}; - static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - return std::string{cr.data()}; + return hex_byte(static_cast(current)); } /*! @@ -18346,9 +18446,7 @@ class parser // strict mode: next byte must be EOF if (get_token() != token_type::end_of_input) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_of_input, "value")); } } else @@ -18387,10 +18485,7 @@ class parser // in strict mode, input must be completely read if (get_token() != token_type::end_of_input) { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"), nullptr)); + syntax_error(sdp, exception_message(token_type::end_of_input, "value")); } } else @@ -18440,9 +18535,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) { @@ -18452,9 +18545,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // remember we are now inside an object @@ -18497,7 +18588,7 @@ class parser { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), - out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); } if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) @@ -18565,23 +18656,16 @@ class parser case token_type::parse_error: { // using "uninitialized" to avoid an "expected" message - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::uninitialized, "value")); } case token_type::end_of_input: { if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON"); } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } case token_type::uninitialized: case token_type::end_array: @@ -18591,9 +18675,7 @@ class parser case token_type::literal_or_value: default: // the last token was unexpected { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + return syntax_error(*sax, exception_message(token_type::literal_or_value, "value")); } } } @@ -18644,9 +18726,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_array, "array")); } // states.back() is false -> object @@ -18663,9 +18743,7 @@ class parser // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + return syntax_error(*sax, exception_message(token_type::value_string, "object key")); } if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) @@ -18676,9 +18754,7 @@ class parser // parse separator (:) if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator))) { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + return syntax_error(*sax, exception_message(token_type::name_separator, "object separator")); } // parse values @@ -18706,9 +18782,7 @@ class parser continue; } - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + return syntax_error(*sax, exception_message(token_type::end_object, "object")); } } @@ -18725,6 +18799,21 @@ class parser return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type; } + /*! + @brief reports a syntax error at the current token (parse_error.101) + @param[in] sax the SAX parser to report the error to + @param[in] message the error message + @return the result of the SAX parser's parse_error() + */ + template + bool syntax_error(SAX& sax, const std::string& message) + { + // MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static + static_cast(sax); + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr)); + } + std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; @@ -19231,6 +19320,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } } + /// @throw invalid_iterator.214 always + JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const + { + JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); + } + public: /*! @brief return a reference to the value pointed to by the iterator @@ -19255,7 +19350,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci } case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -19271,7 +19366,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19313,7 +19408,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19430,7 +19525,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -19479,7 +19574,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); } // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 @@ -19492,7 +19587,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + JSON_THROW(invalid_iterator::create(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); @@ -19548,7 +19643,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); case value_t::array: { @@ -19627,7 +19722,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + JSON_THROW(invalid_iterator::create(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; @@ -19656,13 +19751,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci switch (m_object->m_data.m_type) { case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); case value_t::string: case value_t::boolean: @@ -19678,7 +19773,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return *m_object; } - JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + throw_cannot_get_value(); } } } @@ -19696,7 +19791,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); } /*! @@ -20084,7 +20179,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.erase(reference_tokens.begin()); @@ -20096,7 +20191,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.front(); @@ -20122,7 +20217,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } reference_tokens.pop_back(); @@ -20134,7 +20229,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } return reference_tokens.back(); @@ -20162,6 +20257,21 @@ class json_pointer } private: + /// @throw out_of_range.405 always + JSON_HEDLEY_NO_RETURN static void throw_no_parent() + { + JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr)); + } + + /// @throw out_of_range.404 always + template + JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context) + { + static_cast(reference_token); // unused when JSON_NOEXCEPTION is defined + static_cast(context); + 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 @@ -20247,13 +20357,13 @@ class json_pointer // 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(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); case array_index_status::not_a_number: - JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); case array_index_status::unresolved: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + throw_unresolved(s, nullptr); case array_index_status::exceeds_size_type: - JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); + JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr)); case array_index_status::ok: default: break; @@ -20267,7 +20377,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(empty())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + throw_no_parent(); } json_pointer result = *this; @@ -20404,7 +20514,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); } id = tree.find_child(id, reference_token); @@ -20487,7 +20597,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20519,7 +20629,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, detail::concat( + 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)); } @@ -20528,7 +20638,7 @@ class json_pointer // 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(401, detail::concat( + 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->operator[](idx); @@ -20544,7 +20654,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20588,7 +20698,7 @@ class json_pointer if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + 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[] @@ -20606,7 +20716,7 @@ class json_pointer case detail::value_t::binary: case detail::value_t::discarded: default: - JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + throw_unresolved(reference_token, ptr); } } @@ -20661,9 +20771,9 @@ class json_pointer // 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(106, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr)); + 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(109, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr)); + 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; @@ -20790,7 +20900,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(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + 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)); } // extract the reference tokens: @@ -20826,7 +20936,7 @@ class json_pointer (reference_token[pos + 1] != '0' && reference_token[pos + 1] != '1'))) { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); } } @@ -20990,7 +21100,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!value.is_object())) { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_not_object, "only objects can be unflattened", &value)); } BasicJsonType result; @@ -21014,7 +21124,7 @@ class json_pointer { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } // Assign the value to the reference pointed to by JSON pointer. Note @@ -21774,7 +21884,7 @@ class binary_writer case value_t::discarded: default: { - JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_not_serializable, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); } } } @@ -21965,7 +22075,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(length))) { - JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), &j)); } static_cast(j); @@ -22192,7 +22302,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j)); } write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -22347,7 +22457,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } oa.write_character(to_char_type('$')); oa.write_character(bjdata_draft3 ? 'B' : 'U'); @@ -22448,7 +22558,7 @@ class binary_writer { static_cast(j); // unused when JSON_NOEXCEPTION is defined static_cast(format_name); - JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j)); } void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) @@ -22708,7 +22818,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin() + 1, j.end(), @@ -22758,7 +22868,7 @@ class binary_writer { if (!use_count) { - JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j)); + JSON_THROW(other_error::create(exception_id::size_marker_required, "use_type requires use_size = true", &j)); } const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); const bool same_prefix = std::all_of(j.begin(), j.end(), @@ -22975,7 +23085,7 @@ class binary_writer const auto it = name.find(static_cast(0)); if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) { - JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + JSON_THROW(out_of_range::create(exception_id::bson_key_with_null, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); } string_t storage; @@ -22993,7 +23103,7 @@ class binary_writer { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) { - JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); + JSON_THROW(out_of_range::create(exception_id::length_too_large, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits::max)())), nullptr)); } return static_cast(size); @@ -23179,7 +23289,7 @@ class binary_writer if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); + JSON_THROW(out_of_range::create(exception_id::subtype_out_of_range, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), &j)); } return sizeof(std::int32_t) + value.size() + 1ul; @@ -24208,7 +24318,7 @@ class binary_writer { if (j.m_data.m_value.number_unsigned > static_cast((std::numeric_limits::max)())) { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); + JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); } write_bon8_integer(static_cast(j.m_data.m_value.number_unsigned)); string_open = false; @@ -24361,7 +24471,7 @@ class binary_writer const std::size_t valid = valid_utf8_prefix(data, s.size()); if (JSON_HEDLEY_UNLIKELY(valid != s.size())) { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context)); } } @@ -26785,7 +26895,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr)); } case error_handler_t::ignore: @@ -26918,7 +27028,7 @@ class serializer { case error_handler_t::strict: { - JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); + JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast(s[s.size() - 1]))), nullptr)); } case error_handler_t::ignore: @@ -27623,6 +27733,19 @@ private: return self.end(); } + /// @brief shared implementation of the const and non-const at() overloads + /// @throw std::out_of_range if @a key is not found + template + static auto at_impl(Self& self, const KeyType& key) -> decltype((self.begin()->second)) + { + const auto it = find_impl(self, key); + if (it == self.end()) + { + JSON_THROW(std::out_of_range("key not found")); + } + return it->second; + } + /// @brief remove the entry @a it points to, preserving order /// @note keys are not movable, so the tail is destroyed and re-constructed in place void erase_at(iterator it) @@ -27689,46 +27812,26 @@ public: T& at(const key_type& key) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } const T& at(const key_type& key) const { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } template::value, int> = 0> const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) { - const auto it = find_impl(*this, key); - if (it == this->end()) - { - JSON_THROW(std::out_of_range("key not found")); - } - return it->second; + return at_impl(*this, key); } size_type erase(const key_type& key) @@ -28509,7 +28612,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -30157,7 +30260,7 @@ public: // if an object is wanted but impossible, throw an exception if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) { - JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + JSON_THROW(type_error::create(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); } } @@ -30277,7 +30380,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); } // copy type from the first iterator @@ -30296,7 +30399,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); } break; } @@ -30364,7 +30467,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); } set_parents(); @@ -30752,7 +30855,7 @@ public: return *ptr; } - JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } public: @@ -31136,7 +31239,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -31148,7 +31251,7 @@ public: { if (!is_binary()) { - JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + detail::throw_type_must_be("binary", *this); } return *get_ptr(); @@ -31190,7 +31293,7 @@ public: // at only works for objects if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -31200,7 +31303,7 @@ public: // std::map or ordered_map) never moves from its argument, so key is still // valid here regardless of whether KeyType was deduced as an rvalue reference // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) - JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(key), "' not found"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", string_t(key), "' not found"), &j)); } return it->second; } @@ -31215,12 +31318,12 @@ public: // at only works for arrays if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { - JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::at_wrong_type, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } return (*j.m_data.m_value.array)[idx]; @@ -31253,6 +31356,15 @@ public: m_data.m_type = value_t::object; } + /// @brief throws because operator[] is not supported for the type of this value + /// @param[in] argument the kind of the operator's argument, "numeric" or "string" + /// @throw type_error.305 always + JSON_HEDLEY_NO_RETURN void throw_subscript_wrong_type(const char* argument) const + { + detail::throw_cannot_use_with(detail::exception_id::subscript_wrong_type, + detail::concat("operator[] with a ", argument, " argument").c_str(), *this); + } + public: //////////////////// // element access // @@ -31357,7 +31469,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified array element @@ -31371,7 +31483,7 @@ public: return m_data.m_value.array->operator[](idx); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + throw_subscript_wrong_type("numeric"); } /// @brief access specified object element @@ -31391,7 +31503,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -31406,7 +31518,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -31442,7 +31554,7 @@ public: return set_parent(result.first->second); } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } /// @brief access specified object element @@ -31459,7 +31571,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + throw_subscript_wrong_type("string"); } private: @@ -31483,7 +31595,7 @@ public: // value only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } const auto it = find(std::forward(key)); @@ -31498,7 +31610,7 @@ public: // value only works for arrays and objects if (JSON_HEDLEY_UNLIKELY(!is_structured())) { - JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::value_wrong_type, "value()", *this); } return ptr.get_checked_or_null(this); @@ -31665,7 +31777,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } IteratorType result = end(); @@ -31681,7 +31793,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -31707,7 +31819,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -31723,7 +31835,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); } IteratorType result = end(); @@ -31740,7 +31852,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); } m_data.m_value.destroy(m_data.m_type); @@ -31768,7 +31880,7 @@ public: case value_t::null: case value_t::discarded: default: - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } return result; @@ -31782,7 +31894,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -31797,7 +31909,7 @@ public: // this erase only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -31839,14 +31951,14 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::erase_wrong_type, "erase()", *this); } } @@ -32335,7 +32447,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -32366,7 +32478,7 @@ public: // push_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an array @@ -32396,7 +32508,7 @@ public: // push_back only works for null objects or objects if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::push_back_wrong_type, "push_back()", *this); } // transform a null object into an object @@ -32450,7 +32562,7 @@ public: // emplace_back only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace_back()", *this); } // transform a null object into an array @@ -32473,7 +32585,7 @@ public: // emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { - JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::emplace_wrong_type, "emplace()", *this); } // transform a null object into an object @@ -32525,14 +32637,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts element into array @@ -32545,7 +32657,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // moving into a local first keeps this safe even if val aliases @@ -32554,7 +32666,7 @@ public: return insert_iterator(pos, std::move(tmp)); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts copies of element into array @@ -32567,14 +32679,14 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // insert to array and return iterator return insert_iterator(pos, cnt, val); } - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } /// @brief inserts range of elements into array @@ -32584,30 +32696,30 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); } // passed iterators must belong to arrays if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); } // insert to array and return iterator @@ -32621,13 +32733,13 @@ public: // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); } // copy the values first: ilist may refer to elements of this array @@ -32649,19 +32761,19 @@ public: // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); } // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); @@ -32678,7 +32790,7 @@ public: // j, not the copy made below) if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -32696,13 +32808,13 @@ public: // check if range iterators belong to the same JSON object if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); } // passed iterators must belong to objects if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -32738,7 +32850,7 @@ public: if (JSON_HEDLEY_UNLIKELY(!is_object())) { - JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::update_wrong_type, "update()", *this); } } @@ -32901,7 +33013,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(array_t&)", *this); } } @@ -32918,7 +33030,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(object_t&)", *this); } } @@ -32934,7 +33046,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(string_t&)", *this); } } @@ -32950,7 +33062,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t&)", *this); } } @@ -32966,7 +33078,7 @@ public: } else { - JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + detail::throw_cannot_use_with(detail::exception_id::swap_wrong_type, "swap(binary_t::container_type&)", *this); } } @@ -34428,7 +34540,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) { // avoid undefined behavior - JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); } // default case: insert add offset @@ -34447,7 +34559,7 @@ public: case value_t::binary: case value_t::discarded: default: - JSON_THROW(out_of_range::create(411, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_add_parent_not_container, detail::concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -34470,7 +34582,7 @@ public: } else { - JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", last_path, "' not found"), this)); } } else if (parent.is_array()) @@ -34482,7 +34594,7 @@ public: { // the parent of a "remove" target must be an object or array // (see #5396) - JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_remove_parent_not_container, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent)); } }; @@ -34513,7 +34625,7 @@ public: // type check: top level value must be an array if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &json_patch)); } // iterate and apply the operations @@ -34534,14 +34646,14 @@ public: if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); } // check if the result is of type string if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) { // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); } // no error: return value @@ -34551,7 +34663,7 @@ public: // type check: every element of the array must be an object if (JSON_HEDLEY_UNLIKELY(!val.is_object())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &val)); } // collect mandatory members @@ -34587,7 +34699,7 @@ public: if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr))) { - JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); + JSON_THROW(out_of_range::create(detail::exception_id::patch_move_into_child, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result)); } // the "from" location must exist - use at() @@ -34634,7 +34746,7 @@ public: // throw an exception if the test fails if (JSON_HEDLEY_UNLIKELY(!success)) { - JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + JSON_THROW(other_error::create(detail::exception_id::patch_test_failed, detail::concat("unsuccessful: ", val.dump()), &val)); } break; @@ -34645,7 +34757,7 @@ public: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + JSON_THROW(parse_error::create(detail::exception_id::patch_invalid_operation, 0, detail::concat("operation value '", op, "' is invalid"), &val)); } } } diff --git a/tests/src/fuzzer-parse_bjdata.cpp b/tests/src/fuzzer-parse_bjdata.cpp index 6302db104..92e1bed4c 100644 --- a/tests/src/fuzzer-parse_bjdata.cpp +++ b/tests/src/fuzzer-parse_bjdata.cpp @@ -51,22 +51,7 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // value-stable comparison for the round-trip checks below; see the note // above on why this compares dump()s rather than the json values directly @@ -80,23 +65,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bjdata(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bjdata(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_bon8.cpp b/tests/src/fuzzer-parse_bon8.cpp index 8d9a119fa..792606d05 100644 --- a/tests/src/fuzzer-parse_bon8.cpp +++ b/tests/src/fuzzer-parse_bon8.cpp @@ -25,23 +25,8 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include #include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" namespace { @@ -72,23 +57,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bon8(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bon8(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_bson.cpp b/tests/src/fuzzer-parse_bson.cpp index 357bc7eb2..548c30f38 100644 --- a/tests/src/fuzzer-parse_bson.cpp +++ b/tests/src/fuzzer-parse_bson.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_bson(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_bson(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_cbor.cpp b/tests/src/fuzzer-parse_cbor.cpp index 1574e8aee..6d995dc6e 100644 --- a/tests/src/fuzzer-parse_cbor.cpp +++ b/tests/src/fuzzer-parse_cbor.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_cbor(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_cbor(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_json.cpp b/tests/src/fuzzer-parse_json.cpp index c0dd969f3..31a811a6c 100644 --- a/tests/src/fuzzer-parse_json.cpp +++ b/tests/src/fuzzer-parse_json.cpp @@ -22,43 +22,14 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::parse(data, data + size, nullptr, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_msgpack.cpp b/tests/src/fuzzer-parse_msgpack.cpp index 9ab2aadd8..a5e2e346f 100644 --- a/tests/src/fuzzer-parse_msgpack.cpp +++ b/tests/src/fuzzer-parse_msgpack.cpp @@ -21,45 +21,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_msgpack(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_msgpack(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer-parse_ubjson.cpp b/tests/src/fuzzer-parse_ubjson.cpp index 9750286ae..641526c90 100644 --- a/tests/src/fuzzer-parse_ubjson.cpp +++ b/tests/src/fuzzer-parse_ubjson.cpp @@ -30,45 +30,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer drivers. */ -#include -#include - -// the round-trip checks below are assertions; NDEBUG would compile them away -#ifdef NDEBUG - #error "the fuzzer drivers must be built without NDEBUG" -#endif - -using json = nlohmann::json; - -// compares dumps rather than values, because NaN != NaN; keep writes strings -// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw -static bool same_value(const json& lhs, const json& rhs) -{ - return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); -} +#include "fuzzer_common.hpp" // see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector const vec1(data, data + size); - // step 0: parse input without exceptions; a parse error must then be - // reported as a discarded value, never thrown - json j_noexcept; + // step 0: parse input without exceptions bool noexcept_threw = false; - try - { - j_noexcept = json::from_ubjson(vec1, true, false); - } - catch (const json::parse_error&) - { - assert(false); - } - catch (const json::exception&) - { - // type and out-of-range errors are not parse errors and still throw - noexcept_threw = true; - } + json const j_noexcept = parse_without_exceptions([&] { return json::from_ubjson(vec1, true, false); }, noexcept_threw); // whether step 1 succeeded; if not, the catch blocks below check that // step 0 failed, too bool parsed = false; diff --git a/tests/src/fuzzer_common.hpp b/tests/src/fuzzer_common.hpp new file mode 100644 index 000000000..4b6c9289c --- /dev/null +++ b/tests/src/fuzzer_common.hpp @@ -0,0 +1,51 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// code shared by the fuzzer drivers tests/src/fuzzer-parse_*.cpp + +#pragma once + +#include // assert +#include + +using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers + +// the round-trip checks of the drivers are assertions; NDEBUG would compile them away +#ifdef NDEBUG + #error "the fuzzer drivers must be built without NDEBUG" +#endif + +// compares dumps rather than values, because NaN != NaN; keep writes strings +// byte for byte, so ill-formed UTF-8 that a binary reader accepts cannot throw +inline bool same_value(const json& lhs, const json& rhs) +{ + return lhs.dump(-1, ' ', false, json::error_handler_t::keep) == rhs.dump(-1, ' ', false, json::error_handler_t::keep); +} + +// step 0 of each driver: parse the input without exceptions; a parse error +// must then be reported as a discarded value, never thrown. Type and +// out-of-range errors are not parse errors and still throw; then @a threw is +// set and null is returned. +template +json parse_without_exceptions(Parse parse, bool& threw) +{ + threw = false; + try + { + return parse(); + } + catch (const json::parse_error&) + { + assert(false); + } + catch (const json::exception&) + { + threw = true; + } + return {}; +} diff --git a/tests/src/sax_event_loggers.hpp b/tests/src/sax_event_loggers.hpp new file mode 100644 index 000000000..e39ad9e56 --- /dev/null +++ b/tests/src/sax_event_loggers.hpp @@ -0,0 +1,185 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // uint8_t (via json::binary_t) +#include // numeric_limits +#include // string, to_string +#include // vector + +#include + +// the loggers are defined in this header only, so their vtables are emitted in +// every test that includes it +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +namespace utils +{ +/// a SAX event consumer that records every event it receives as a +/// human-readable string, used by the deserialization tests to check the +/// exact sequence of SAX events a parse run produces +struct SaxEventLogger : public nlohmann::json_sax +{ + using json = nlohmann::json; + + bool null() override + { + events.emplace_back("null()"); + return true; + } + + bool boolean(bool val) override + { + events.emplace_back(val ? "boolean(true)" : "boolean(false)"); + return true; + } + + bool number_integer(json::number_integer_t val) override + { + events.push_back("number_integer(" + std::to_string(val) + ")"); + return true; + } + + bool number_unsigned(json::number_unsigned_t val) override + { + events.push_back("number_unsigned(" + std::to_string(val) + ")"); + return true; + } + + bool number_float(json::number_float_t /*val*/, const std::string& s) override + { + events.push_back("number_float(" + s + ")"); + return true; + } + + bool string(std::string& val) override + { + events.push_back("string(" + val + ")"); + return true; + } + + bool binary(json::binary_t& val) override + { + std::string binary_contents = "binary("; + std::string comma_space; + for (const auto b : val) + { + binary_contents.append(comma_space); + binary_contents.append(std::to_string(static_cast(b))); + comma_space = ", "; + } + binary_contents.append(")"); + events.push_back(binary_contents); + return true; + } + + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return true; + } + + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return true; + } + + bool end_object() override + { + events.emplace_back("end_object()"); + return true; + } + + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return true; + } + + bool end_array() override + { + events.emplace_back("end_array()"); + return true; + } + + bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override + { + errored = true; + events.push_back("parse_error(" + std::to_string(position) + ")"); + return false; + } + + std::vector events {}; // NOLINT(readability-redundant-member-init) + bool errored = false; +}; + +struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger +{ + bool start_object(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_object()"); + } + else + { + events.push_back("start_object(" + std::to_string(elements) + ")"); + } + return false; + } +}; + +struct SaxEventLoggerExitAfterKey : public SaxEventLogger +{ + bool key(std::string& val) override + { + events.push_back("key(" + val + ")"); + return false; + } +}; + +struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger +{ + bool start_array(std::size_t elements) override + { + if (elements == (std::numeric_limits::max)()) + { + events.emplace_back("start_array()"); + } + else + { + events.push_back("start_array(" + std::to_string(elements) + ")"); + } + return false; + } +}; +} // namespace utils + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 978d94f92..3245d76b9 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -37,194 +37,15 @@ using nlohmann::json; #include #include +#include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" #include "test_utils.hpp" +using utils::SaxCountdown; +using utils::SaxEventLogger; + namespace { -class SaxEventLogger -{ - public: - bool null() - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*unused*/, const std::string& s) - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*unused*/, const json::exception& /*unused*/) - { - errored = true; - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) - bool errored = false; -}; - -class SaxCountdown : public nlohmann::json::json_sax_t -{ - public: - explicit SaxCountdown(const int count) : events_left(count) - {} - - bool null() override - { - return events_left-- > 0; - } - - bool boolean(bool /*val*/) override - { - return events_left-- > 0; - } - - bool number_integer(json::number_integer_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_unsigned(json::number_unsigned_t /*val*/) override - { - return events_left-- > 0; - } - - bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override - { - return events_left-- > 0; - } - - bool string(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool binary(json::binary_t& /*val*/) override - { - return events_left-- > 0; - } - - bool start_object(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool key(std::string& /*val*/) override - { - return events_left-- > 0; - } - - bool end_object() override - { - return events_left-- > 0; - } - - bool start_array(std::size_t /*elements*/) override - { - return events_left-- > 0; - } - - bool end_array() override - { - return events_left-- > 0; - } - - bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - return false; - } - - private: - int events_left = 0; -}; - json parser_helper(const std::string& s); bool accept_helper(const std::string& s); void comments_helper(const std::string& s); diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index ca9b63112..6222def34 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -36,155 +36,15 @@ using nlohmann::json; #include #include +#include "sax_event_loggers.hpp" + +using utils::SaxEventLogger; +using utils::SaxEventLoggerExitAfterKey; +using utils::SaxEventLoggerExitAfterStartArray; +using utils::SaxEventLoggerExitAfterStartObject; + namespace { -struct SaxEventLogger : public nlohmann::json_sax -{ - bool null() override - { - events.emplace_back("null()"); - return true; - } - - bool boolean(bool val) override - { - events.emplace_back(val ? "boolean(true)" : "boolean(false)"); - return true; - } - - bool number_integer(json::number_integer_t val) override - { - events.push_back("number_integer(" + std::to_string(val) + ")"); - return true; - } - - bool number_unsigned(json::number_unsigned_t val) override - { - events.push_back("number_unsigned(" + std::to_string(val) + ")"); - return true; - } - - bool number_float(json::number_float_t /*val*/, const std::string& s) override - { - events.push_back("number_float(" + s + ")"); - return true; - } - - bool string(std::string& val) override - { - events.push_back("string(" + val + ")"); - return true; - } - - bool binary(json::binary_t& val) override - { - std::string binary_contents = "binary("; - std::string comma_space; - for (auto b : val) - { - binary_contents.append(comma_space); - binary_contents.append(std::to_string(static_cast(b))); - comma_space = ", "; - } - binary_contents.append(")"); - events.push_back(binary_contents); - return true; - } - - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return true; - } - - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return true; - } - - bool end_object() override - { - events.emplace_back("end_object()"); - return true; - } - - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return true; - } - - bool end_array() override - { - events.emplace_back("end_array()"); - return true; - } - - bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& /*ex*/) override - { - events.push_back("parse_error(" + std::to_string(position) + ")"); - return false; - } - - std::vector events {}; // NOLINT(readability-redundant-member-init) -}; - -struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger -{ - bool start_object(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_object()"); - } - else - { - events.push_back("start_object(" + std::to_string(elements) + ")"); - } - return false; - } -}; - -struct SaxEventLoggerExitAfterKey : public SaxEventLogger -{ - bool key(std::string& val) override - { - events.push_back("key(" + val + ")"); - return false; - } -}; - -struct SaxEventLoggerExitAfterStartArray : public SaxEventLogger -{ - bool start_array(std::size_t elements) override - { - if (elements == (std::numeric_limits::max)()) - { - events.emplace_back("start_array()"); - } - else - { - events.push_back("start_array(" + std::to_string(elements) + ")"); - } - return false; - } -}; - template class proxy_iterator {