From 03d408aafa4260f82b2340c88684c25698d36aac Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 2 Nov 2026 17:47:48 +0100 Subject: [PATCH] JSON_DIAGNOSTIC_POSITIONS: comparing a value-initialized iterator throws instead of crashing (#6003) * JSON_DIAGNOSTIC_POSITIONS: comparing a value-initialized iterator throws instead of crashing With JSON_DIAGNOSTIC_POSITIONS, comparing a value-initialized iterator (which has no container) with another iterator built the invalid_iterator.212 message from the byte positions of the iterator's value, which is a null pointer: UBSan reports "member call on null pointer" in exception::get_byte_positions(), then the process crashes. Every other configuration throws invalid_iterator.212. get_byte_positions() now returns no positions for a null value, as the JSON_DIAGNOSTICS path already does. Signed-off-by: Niels Lohmann * Declare the value-initialized iterator in the test const clang-tidy (misc-const-correctness) flags it, as it is only compared. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp Signed-off-by: Claude --------- Signed-off-by: Niels Lohmann Signed-off-by: Claude Co-authored-by: Claude --- include/nlohmann/detail/exceptions.hpp | 3 ++- single_include/nlohmann/json.hpp | 3 ++- tests/src/unit-diagnostic-positions.cpp | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index 144bb770c..098d70495 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -222,7 +222,8 @@ class exception : public std::exception template static std::string get_byte_positions(const BasicJsonType* leaf_element) { - if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + // a value-initialized iterator has no value (invalid_iterator.212) + if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos) { return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2a7289203..958cc4ae4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5719,7 +5719,8 @@ class exception : public std::exception template static std::string get_byte_positions(const BasicJsonType* leaf_element) { - if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + // a value-initialized iterator has no value (invalid_iterator.212) + if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos) { return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); } diff --git a/tests/src/unit-diagnostic-positions.cpp b/tests/src/unit-diagnostic-positions.cpp index 9e0625115..11f63fb59 100644 --- a/tests/src/unit-diagnostic-positions.cpp +++ b/tests/src/unit-diagnostic-positions.cpp @@ -243,3 +243,14 @@ TEST_CASE("values read from a binary format have no positions") check_no_positions(j); } } + +TEST_CASE("comparing a value-initialized iterator throws invalid_iterator.212") +{ + // such an iterator has no value to take the byte positions from; the + // comparison used to dereference the null pointer instead of throwing + json j = {1, 2}; + const json::iterator it{}; + CHECK_THROWS_WITH_AS(it == j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator); + CHECK_THROWS_WITH_AS(it < j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator); + CHECK_THROWS_WITH_AS(j.begin() == it, "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator); +}