diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index f5b7ae101..43094ead5 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -321,6 +321,22 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const JSON_THROW(type_error::create(302, 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 (304..312) +@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 int 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 154dd39fb..65f2c2123 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2566,18 +2566,10 @@ 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 last_byte_error(113, message, "string"); + const char* expected = input_format != input_format_t::bjdata + ? "expected length type specification (U, i, I, l, L)" + : "expected length type specification (U, i, u, I, m, l, M, L)"; + return last_byte_error(113, concat(expected, "; last byte: 0x", get_token_string()), "string"); } /*! @@ -2840,18 +2832,10 @@ 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 last_byte_error(113, message, "size"); + const char* expected = input_format != input_format_t::bjdata + ? "expected length type specification (U, i, I, l, L) after '#'" + : "expected length type specification (U, i, u, I, m, l, M, L) after '#'"; + return last_byte_error(113, concat(expected, "; last byte: 0x", get_token_string()), "size"); } /*! diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index d56d88739..2c09ffdbd 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -723,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()) { @@ -811,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()) @@ -904,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..8563d5e7f 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 @@ -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,19 @@ 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) + { + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(101, 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..b6462686a 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(214, "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(); } } } @@ -710,7 +716,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci 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(); } } } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 41656d133..c81587a1c 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -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(304, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -3345,7 +3345,7 @@ 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(304, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) @@ -3487,7 +3487,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)); + detail::throw_cannot_use_with(305, "operator[] with a numeric argument", *this); } /// @brief access specified array element @@ -3501,7 +3501,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)); + detail::throw_cannot_use_with(305, "operator[] with a numeric argument", *this); } /// @brief access specified object element @@ -3521,7 +3521,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)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } /// @brief access specified object element @@ -3536,7 +3536,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -3572,7 +3572,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)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } /// @brief access specified object element @@ -3589,7 +3589,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } private: @@ -3613,7 +3613,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(306, "value()", *this); } const auto it = find(std::forward(key)); @@ -3628,7 +3628,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(306, "value()", *this); } return ptr.get_checked_or_null(this); @@ -3837,7 +3837,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(307, "erase()", *this); } return result; @@ -3898,7 +3898,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(307, "erase()", *this); } return result; @@ -3912,7 +3912,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(307, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -3927,7 +3927,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(307, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -3976,7 +3976,7 @@ public: } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(307, "erase()", *this); } } @@ -4465,7 +4465,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(308, "push_back()", *this); } // transform a null object into an array @@ -4496,7 +4496,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(308, "push_back()", *this); } // transform a null object into an array @@ -4526,7 +4526,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(308, "push_back()", *this); } // transform a null object into an object @@ -4580,7 +4580,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(311, "emplace_back()", *this); } // transform a null object into an array @@ -4603,7 +4603,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(311, "emplace()", *this); } // transform a null object into an object @@ -4662,7 +4662,7 @@ public: 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(309, "insert()", *this); } /// @brief inserts element into array @@ -4684,7 +4684,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(309, "insert()", *this); } /// @brief inserts copies of element into array @@ -4704,7 +4704,7 @@ public: 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(309, "insert()", *this); } /// @brief inserts range of elements into array @@ -4714,7 +4714,7 @@ 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(309, "insert()", *this); } // check if iterator pos fits to this JSON value @@ -4751,7 +4751,7 @@ 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(309, "insert()", *this); } // check if iterator pos fits to this JSON value @@ -4779,7 +4779,7 @@ 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(309, "insert()", *this); } // check if range iterators belong to the same JSON object @@ -4808,7 +4808,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(312, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -4832,7 +4832,7 @@ public: // 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(312, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -4868,7 +4868,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(312, "update()", *this); } } @@ -5031,7 +5031,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(310, "swap(array_t&)", *this); } } @@ -5048,7 +5048,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(310, "swap(object_t&)", *this); } } @@ -5064,7 +5064,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(310, "swap(string_t&)", *this); } } @@ -5080,7 +5080,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(310, "swap(binary_t&)", *this); } } @@ -5096,7 +5096,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(310, "swap(binary_t::container_type&)", *this); } } 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 7229587cb..a274253da 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5513,6 +5513,22 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const JSON_THROW(type_error::create(302, 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 (304..312) +@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 int 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 @@ -13151,13 +13167,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()) { @@ -13239,13 +13249,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()) @@ -13332,6 +13336,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 @@ -16278,18 +16299,10 @@ 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 last_byte_error(113, message, "string"); + const char* expected = input_format != input_format_t::bjdata + ? "expected length type specification (U, i, I, l, L)" + : "expected length type specification (U, i, u, I, m, l, M, L)"; + return last_byte_error(113, concat(expected, "; last byte: 0x", get_token_string()), "string"); } /*! @@ -16552,18 +16565,10 @@ 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 last_byte_error(113, message, "size"); + const char* expected = input_format != input_format_t::bjdata + ? "expected length type specification (U, i, I, l, L) after '#'" + : "expected length type specification (U, i, u, I, m, l, M, L) after '#'"; + return last_byte_error(113, concat(expected, "; last byte: 0x", get_token_string()), "size"); } /*! @@ -18287,9 +18292,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 @@ -18328,10 +18331,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 @@ -18381,9 +18381,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()))) { @@ -18393,9 +18391,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 @@ -18506,23 +18502,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: @@ -18532,9 +18521,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")); } } } @@ -18585,9 +18572,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 @@ -18604,9 +18589,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()))) @@ -18617,9 +18600,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 @@ -18647,9 +18628,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")); } } @@ -18666,6 +18645,19 @@ 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) + { + return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), message, nullptr)); + } + std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; @@ -19172,6 +19164,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(214, "cannot get value", m_object)); + } + public: /*! @brief return a reference to the value pointed to by the iterator @@ -19196,7 +19194,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: @@ -19212,7 +19210,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(); } } } @@ -19254,7 +19252,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(); } } } @@ -19603,7 +19601,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci 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: @@ -19619,7 +19617,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(); } } } @@ -26958,6 +26956,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) @@ -27024,46 +27035,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) @@ -30525,7 +30516,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(304, "at()", j); } auto it = object_lookup(j, std::forward(key)); @@ -30550,7 +30541,7 @@ 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(304, "at()", j); } if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) @@ -30692,7 +30683,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)); + detail::throw_cannot_use_with(305, "operator[] with a numeric argument", *this); } /// @brief access specified array element @@ -30706,7 +30697,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)); + detail::throw_cannot_use_with(305, "operator[] with a numeric argument", *this); } /// @brief access specified object element @@ -30726,7 +30717,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)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } /// @brief access specified object element @@ -30741,7 +30732,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC @@ -30777,7 +30768,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)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } /// @brief access specified object element @@ -30794,7 +30785,7 @@ public: return it->second; } - JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + detail::throw_cannot_use_with(305, "operator[] with a string argument", *this); } private: @@ -30818,7 +30809,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(306, "value()", *this); } const auto it = find(std::forward(key)); @@ -30833,7 +30824,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(306, "value()", *this); } return ptr.get_checked_or_null(this); @@ -31042,7 +31033,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(307, "erase()", *this); } return result; @@ -31103,7 +31094,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(307, "erase()", *this); } return result; @@ -31117,7 +31108,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(307, "erase()", *this); } const auto erased = m_data.m_value.object->erase(std::forward(key)); @@ -31132,7 +31123,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(307, "erase()", *this); } const auto it = object_lookup(*this, std::forward(key)); @@ -31181,7 +31172,7 @@ public: } else { - JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + detail::throw_cannot_use_with(307, "erase()", *this); } } @@ -31670,7 +31661,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(308, "push_back()", *this); } // transform a null object into an array @@ -31701,7 +31692,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(308, "push_back()", *this); } // transform a null object into an array @@ -31731,7 +31722,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(308, "push_back()", *this); } // transform a null object into an object @@ -31785,7 +31776,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(311, "emplace_back()", *this); } // transform a null object into an array @@ -31808,7 +31799,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(311, "emplace()", *this); } // transform a null object into an object @@ -31867,7 +31858,7 @@ public: 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(309, "insert()", *this); } /// @brief inserts element into array @@ -31889,7 +31880,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(309, "insert()", *this); } /// @brief inserts copies of element into array @@ -31909,7 +31900,7 @@ public: 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(309, "insert()", *this); } /// @brief inserts range of elements into array @@ -31919,7 +31910,7 @@ 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(309, "insert()", *this); } // check if iterator pos fits to this JSON value @@ -31956,7 +31947,7 @@ 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(309, "insert()", *this); } // check if iterator pos fits to this JSON value @@ -31984,7 +31975,7 @@ 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(309, "insert()", *this); } // check if range iterators belong to the same JSON object @@ -32013,7 +32004,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(312, "update()", j); } // copy first: j may be *this or one of its descendants, and is @@ -32037,7 +32028,7 @@ public: // 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(312, "update()", *first.m_object); } // copy first: the range may belong to *this or one of its @@ -32073,7 +32064,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(312, "update()", *this); } } @@ -32236,7 +32227,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(310, "swap(array_t&)", *this); } } @@ -32253,7 +32244,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(310, "swap(object_t&)", *this); } } @@ -32269,7 +32260,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(310, "swap(string_t&)", *this); } } @@ -32285,7 +32276,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(310, "swap(binary_t&)", *this); } } @@ -32301,7 +32292,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(310, "swap(binary_t::container_type&)", *this); } } diff --git a/tests/src/fuzzer-parse_bjdata.cpp b/tests/src/fuzzer-parse_bjdata.cpp index 6302db104..a56e2af37 100644 --- a/tests/src/fuzzer-parse_bjdata.cpp +++ b/tests/src/fuzzer-parse_bjdata.cpp @@ -53,20 +53,9 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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 +69,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..98c1a0f25 100644 --- a/tests/src/fuzzer-parse_bon8.cpp +++ b/tests/src/fuzzer-parse_bon8.cpp @@ -28,20 +28,9 @@ drivers. #include #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; namespace { @@ -72,23 +61,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..b7abc6413 100644 --- a/tests/src/fuzzer-parse_bson.cpp +++ b/tests/src/fuzzer-parse_bson.cpp @@ -23,43 +23,18 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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..3232996ce 100644 --- a/tests/src/fuzzer-parse_cbor.cpp +++ b/tests/src/fuzzer-parse_cbor.cpp @@ -23,43 +23,18 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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..c1259d152 100644 --- a/tests/src/fuzzer-parse_json.cpp +++ b/tests/src/fuzzer-parse_json.cpp @@ -24,41 +24,16 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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..cb92c9b39 100644 --- a/tests/src/fuzzer-parse_msgpack.cpp +++ b/tests/src/fuzzer-parse_msgpack.cpp @@ -23,43 +23,18 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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..d8cbced93 100644 --- a/tests/src/fuzzer-parse_ubjson.cpp +++ b/tests/src/fuzzer-parse_ubjson.cpp @@ -32,43 +32,18 @@ drivers. #include #include +#include "fuzzer_common.hpp" -// 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); -} +using nlohmann::json; // 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..ed6e4d9cd --- /dev/null +++ b/tests/src/fuzzer_common.hpp @@ -0,0 +1,49 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 + +// 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 nlohmann::json& lhs, const nlohmann::json& rhs) +{ + return lhs.dump(-1, ' ', false, nlohmann::json::error_handler_t::keep) == rhs.dump(-1, ' ', false, nlohmann::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 +nlohmann::json parse_without_exceptions(Parse parse, bool& threw) +{ + threw = false; + try + { + return parse(); + } + catch (const nlohmann::json::parse_error&) + { + assert(false); + } + catch (const nlohmann::json::exception&) + { + threw = true; + } + return {}; +}