diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index f2c6651c4..1d5ff0db7 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -428,7 +428,7 @@ void templated_json_throw(ExceptionType exception) @throw type_error.302 always */ template -JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const char* expected, const BasicJsonType& j) { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); @@ -443,7 +443,7 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const @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) +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_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); @@ -451,6 +451,18 @@ JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); } +/*! +@brief throws ExceptionType with a fixed message; kept out of line so that callers do not carry the exception construction +*/ +template +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_literal(const exception_id id_, const char* what_arg, BasicJsonContext context) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(what_arg); + static_cast(context); + JSON_THROW(ExceptionType::create(id_, what_arg, context)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index 52b459ad2..81eae570d 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -280,7 +280,7 @@ 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_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_get_value() const { JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); } @@ -484,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(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); + throw_literal(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 @@ -533,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(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); + throw_literal(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 @@ -546,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(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object)); + throw_literal(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); @@ -602,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(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::array: { @@ -681,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(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; @@ -710,7 +710,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(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); + throw_literal(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); @@ -750,7 +750,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); + throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); } /*! diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3dc609038..f51b0642b 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -630,7 +630,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(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + detail::throw_literal(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr); // LCOV_EXCL_LINE } break; } @@ -2348,7 +2348,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(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); + detail::throw_literal(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr); } } @@ -2468,7 +2468,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(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); + detail::throw_literal(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr); } // copy type from the first iterator @@ -2487,7 +2487,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(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); + detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object); } break; } @@ -2555,7 +2555,7 @@ public: case value_t::null: case value_t::discarded: default: - 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)); + throw_iterator_range_of_null(first.m_object); } set_parents(); @@ -2935,6 +2935,40 @@ public: return is_binary() ? m_data.m_value.binary : nullptr; } + /// out-of-line throw helpers (message construction kept out of the hot paths) + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_iterator_range_of_null(const basic_json* obj) + { + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); + } + + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_incompatible_reference_type(const basic_json& 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)); + } + + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_exceeds_size_type(size_type idx) + { + JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + } + + /// @throw out_of_range.401 + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_out_of_range(size_type idx, const basic_json& 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)); + } + + /// @throw out_of_range.403 + template + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found(const KeyType& key, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); + } + /*! @brief helper function to implement get_ref() @@ -2957,7 +2991,7 @@ public: return *ptr; } - 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)); + throw_incompatible_reference_type(obj); } public: @@ -3405,7 +3439,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(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); + throw_key_not_found(key_for_message(key), j); } return it->second; } @@ -3442,7 +3476,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - 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)); + throw_array_index_out_of_range(idx, j); } return (*j.m_data.m_value.array)[idx]; @@ -3560,7 +3594,7 @@ public: // resize() rejects other indices that are too large to represent if (JSON_HEDLEY_UNLIKELY(idx == (std::numeric_limits::max)())) { - JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + throw_array_index_exceeds_size_type(idx); } #if JSON_DIAGNOSTICS // remember array size & capacity before resizing @@ -3898,7 +3932,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } IteratorType result = end(); @@ -3914,7 +3948,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); + detail::throw_literal(detail::exception_id::iterator_out_of_range, "iterator out of range", this); } m_data.m_value.destroy(m_data.m_type); @@ -3956,7 +3990,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(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this); } IteratorType result = end(); @@ -3973,7 +4007,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(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); + detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this); } m_data.m_value.destroy(m_data.m_type); @@ -4072,7 +4106,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - 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)); + throw_array_index_out_of_range(idx, *this); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); @@ -4760,7 +4794,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } // insert to array and return iterator @@ -4780,7 +4814,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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 @@ -4802,7 +4836,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } // insert to array and return iterator @@ -4825,24 +4859,24 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(detail::exception_id::insert_range_incompatible, "iterators do not fit", this); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); + detail::throw_literal(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(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this); } // insert to array and return iterator @@ -4862,7 +4896,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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 @@ -4890,13 +4924,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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(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(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); + detail::throw_literal(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); @@ -4931,7 +4965,7 @@ 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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(detail::exception_id::insert_range_incompatible, "iterators do not fit", this); } // passed iterators must belong to objects diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index ef27bbe45..10fc764ea 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -90,6 +90,15 @@ private: return self.end(); } + /// @brief throw the exception of a failed at() lookup + /// @note kept out of line so that at_impl() stays small enough to inline + /// @throw std::out_of_range always + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found() + { + JSON_THROW(std::out_of_range("key not found")); + } + /// @brief shared implementation of the const and non-const at() overloads /// @throw std::out_of_range if @a key is not found template @@ -98,7 +107,7 @@ private: const auto it = find_impl(self, key); if (it == self.end()) { - JSON_THROW(std::out_of_range("key not found")); + throw_key_not_found(); } return it->second; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8c36c353f..2b0a0f705 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5692,7 +5692,7 @@ void templated_json_throw(ExceptionType exception) @throw type_error.302 always */ template -JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j) +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const char* expected, const BasicJsonType& j) { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); @@ -5707,7 +5707,7 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const @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) +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_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); @@ -5715,6 +5715,18 @@ JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j)); } +/*! +@brief throws ExceptionType with a fixed message; kept out of line so that callers do not carry the exception construction +*/ +template +JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_literal(const exception_id id_, const char* what_arg, BasicJsonContext context) +{ + static_cast(id_); // unused when JSON_NOEXCEPTION is defined + static_cast(what_arg); + static_cast(context); + JSON_THROW(ExceptionType::create(id_, what_arg, context)); +} + } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -20121,7 +20133,7 @@ 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_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_get_value() const { JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object)); } @@ -20325,7 +20337,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(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); + throw_literal(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 @@ -20374,7 +20386,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(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object)); + throw_literal(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 @@ -20387,7 +20399,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(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object)); + throw_literal(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); @@ -20443,7 +20455,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(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::array: { @@ -20522,7 +20534,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(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object)); + throw_literal(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; @@ -20551,7 +20563,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(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object)); + throw_literal(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); @@ -20591,7 +20603,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci return m_it.object_iterator->first; } - JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object)); + throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); } /*! @@ -29555,6 +29567,15 @@ private: return self.end(); } + /// @brief throw the exception of a failed at() lookup + /// @note kept out of line so that at_impl() stays small enough to inline + /// @throw std::out_of_range always + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found() + { + JSON_THROW(std::out_of_range("key not found")); + } + /// @brief shared implementation of the const and non-const at() overloads /// @throw std::out_of_range if @a key is not found template @@ -29563,7 +29584,7 @@ private: const auto it = find_impl(self, key); if (it == self.end()) { - JSON_THROW(std::out_of_range("key not found")); + throw_key_not_found(); } return it->second; } @@ -30439,7 +30460,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(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + detail::throw_literal(detail::exception_id::internal_error, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr); // LCOV_EXCL_LINE } break; } @@ -32157,7 +32178,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(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr)); + detail::throw_literal(detail::exception_id::object_from_non_pairs, "cannot create object from initializer list", nullptr); } } @@ -32277,7 +32298,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(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr)); + detail::throw_literal(detail::exception_id::iterators_incompatible, "iterators are not compatible", nullptr); } // copy type from the first iterator @@ -32296,7 +32317,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(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object)); + detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", first.m_object); } break; } @@ -32364,7 +32385,7 @@ public: case value_t::null: case value_t::discarded: default: - 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)); + throw_iterator_range_of_null(first.m_object); } set_parents(); @@ -32744,6 +32765,40 @@ public: return is_binary() ? m_data.m_value.binary : nullptr; } + /// out-of-line throw helpers (message construction kept out of the hot paths) + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_iterator_range_of_null(const basic_json* obj) + { + JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); + } + + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_incompatible_reference_type(const basic_json& 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)); + } + + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_exceeds_size_type(size_type idx) + { + JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + } + + /// @throw out_of_range.401 + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_array_index_out_of_range(size_type idx, const basic_json& 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)); + } + + /// @throw out_of_range.403 + template + JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE + static void throw_key_not_found(const KeyType& key, const basic_json& j) + { + JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); + } + /*! @brief helper function to implement get_ref() @@ -32766,7 +32821,7 @@ public: return *ptr; } - 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)); + throw_incompatible_reference_type(obj); } public: @@ -33214,7 +33269,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(detail::exception_id::key_not_found, detail::concat("key '", key_for_message(key), "' not found"), &j)); + throw_key_not_found(key_for_message(key), j); } return it->second; } @@ -33251,7 +33306,7 @@ public: if (JSON_HEDLEY_UNLIKELY(idx >= j.m_data.m_value.array->size())) { - 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)); + throw_array_index_out_of_range(idx, j); } return (*j.m_data.m_value.array)[idx]; @@ -33369,7 +33424,7 @@ public: // resize() rejects other indices that are too large to represent if (JSON_HEDLEY_UNLIKELY(idx == (std::numeric_limits::max)())) { - JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); + throw_array_index_exceeds_size_type(idx); } #if JSON_DIAGNOSTICS // remember array size & capacity before resizing @@ -33707,7 +33762,7 @@ public: // make sure the iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } IteratorType result = end(); @@ -33723,7 +33778,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_out_of_range, "iterator out of range", this)); + detail::throw_literal(detail::exception_id::iterator_out_of_range, "iterator out of range", this); } m_data.m_value.destroy(m_data.m_type); @@ -33765,7 +33820,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(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_range_from_other_value, "iterators do not fit current value", this); } IteratorType result = end(); @@ -33782,7 +33837,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(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this)); + detail::throw_literal(detail::exception_id::iterator_range_out_of_range, "iterators out of range", this); } m_data.m_value.destroy(m_data.m_type); @@ -33881,7 +33936,7 @@ public: { if (JSON_HEDLEY_UNLIKELY(idx >= size())) { - 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)); + throw_array_index_out_of_range(idx, *this); } m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); @@ -34569,7 +34624,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } // insert to array and return iterator @@ -34589,7 +34644,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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 @@ -34611,7 +34666,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } // insert to array and return iterator @@ -34634,24 +34689,24 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(detail::exception_id::insert_range_incompatible, "iterators do not fit", this); } if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::insert_range_into_itself, "passed iterators may not belong to container", this)); + detail::throw_literal(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(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this)); + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterators first and last must point to arrays", this); } // insert to array and return iterator @@ -34671,7 +34726,7 @@ public: // check if iterator pos fits to this JSON value if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this)); + detail::throw_literal(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 @@ -34699,13 +34754,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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(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(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this)); + detail::throw_literal(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); @@ -34740,7 +34795,7 @@ 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(detail::exception_id::insert_range_incompatible, "iterators do not fit", this)); + detail::throw_literal(detail::exception_id::insert_range_incompatible, "iterators do not fit", this); } // passed iterators must belong to objects diff --git a/tests/src/unit-throw_messages.cpp b/tests/src/unit-throw_messages.cpp new file mode 100644 index 000000000..bf19d45f2 --- /dev/null +++ b/tests/src/unit-throw_messages.cpp @@ -0,0 +1,94 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +// The exceptions in hot core functions are constructed out of line. These tests pin the +// exact what() texts of the sites that moved, so the refactoring cannot change them. + +#define CHECK_MSG(expr, msg) CHECK_THROWS_WITH(expr, msg) + +TEST_CASE("exception messages of out-of-line throw sites") +{ + json arr = {1, 2, 3}; + json arr2 = {4, 5}; + json obj = {{"a", 1}, {"b", 2}}; + json obj2 = {{"c", 3}}; + json num = 1; + + SECTION("iterators") + { + CHECK_MSG((void)(arr.begin() == arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers"); + CHECK_MSG((void)(arr.begin() < arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers"); + CHECK_MSG((void)(obj.begin() < obj.end()), "[json.exception.invalid_iterator.213] cannot compare order of object iterators"); + CHECK_MSG(obj.begin() += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators"); + CHECK_MSG((void)(obj.begin() - 1), "[json.exception.invalid_iterator.209] cannot use offsets with object iterators"); + CHECK_MSG((void)obj.begin()[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators"); + CHECK_MSG((void)arr.begin().key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators"); + json null; + CHECK_MSG((void)*null.end(), "[json.exception.invalid_iterator.214] cannot get value"); + } + + SECTION("constructors") + { + CHECK_MSG(json(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible"); + CHECK_MSG(json(num.begin() + 1, num.end() + 1), "[json.exception.invalid_iterator.204] iterators out of range"); + CHECK_MSG(json(json::initializer_list_t{1, 2}, false, json::value_t::object), "[json.exception.type_error.301] cannot create object from initializer list"); + } + + SECTION("get_ref and operator[]") + { + CHECK_MSG((void)num.get_ref(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number"); + CHECK_MSG((void)num[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number"); + } + + SECTION("at") + { + const json carr = arr; + const json cobj = obj; + CHECK_MSG((void)arr.at(static_cast(10)), "[json.exception.out_of_range.401] array index 10 is out of range"); + CHECK_MSG((void)carr.at(static_cast(3)), "[json.exception.out_of_range.401] array index 3 is out of range"); + CHECK_MSG((void)num.at(static_cast(0)), "[json.exception.type_error.304] cannot use at() with number"); + CHECK_MSG((void)obj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); + CHECK_MSG((void)cobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); + CHECK_MSG((void)obj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found"); + CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number"); + + nlohmann::ordered_json oobj = {{"a", 1}}; + const nlohmann::ordered_json coobj = oobj; + CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); + CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); + CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found"); + } + + SECTION("erase") + { + CHECK_MSG(arr.erase(arr2.begin()), "[json.exception.invalid_iterator.202] iterator does not fit current value"); + CHECK_MSG(num.erase(num.end()), "[json.exception.invalid_iterator.205] iterator out of range"); + CHECK_MSG(arr.erase(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.203] iterators do not fit current value"); + CHECK_MSG(num.erase(num.begin() + 1, num.end()), "[json.exception.invalid_iterator.204] iterators out of range"); + CHECK_MSG(arr.erase(static_cast(10)), "[json.exception.out_of_range.401] array index 10 is out of range"); + } + + SECTION("insert") + { + CHECK_MSG(arr.insert(arr2.begin(), 1), "[json.exception.invalid_iterator.202] iterator does not fit current value"); + CHECK_MSG(arr.insert(arr2.begin(), 2, 1), "[json.exception.invalid_iterator.202] iterator does not fit current value"); + CHECK_MSG(arr.insert(arr2.begin(), arr2.begin(), arr2.end()), "[json.exception.invalid_iterator.202] iterator does not fit current value"); + CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); + CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr.end()), "[json.exception.invalid_iterator.211] passed iterators may not belong to container"); + CHECK_MSG(arr.insert(arr.begin(), obj.begin(), obj.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays"); + CHECK_MSG(arr.insert(arr2.begin(), {1, 2}), "[json.exception.invalid_iterator.202] iterator does not fit current value"); + CHECK_MSG(obj.insert(obj2.begin(), arr.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); + CHECK_MSG(obj.insert(arr.begin(), arr.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to objects"); + CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); + } +}