From c1945cb8d50b26a3887f1bc56ab2eebf1951bb2f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 06:37:09 +0200 Subject: [PATCH] Do not throw in contains() for an empty array reference token json_pointer::contains() rejected malformed array indices, but an empty reference token (e.g. "/a/" where "a" is an array, or "/" on an array) passed every check and reached array_index(), which throws out_of_range.404. contains() must not throw (cf. #5395), so it now returns false for an empty token. at() still throws out_of_range.404. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 6 ++++++ single_include/nlohmann/json.hpp | 6 ++++++ tests/src/unit-json_pointer.cpp | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 1540a8d6f..0b9f9651a 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -746,6 +746,12 @@ class json_pointer // "-" always fails the range check return false; } + if (JSON_HEDLEY_UNLIKELY(reference_token.empty())) + { + // an empty reference token is not an array index; array_index() + // would throw out_of_range.404 -- contains() must not throw (see #5395) + return false; + } if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) { // invalid char diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 576498738..fa94da9e4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -19588,6 +19588,12 @@ class json_pointer // "-" always fails the range check return false; } + if (JSON_HEDLEY_UNLIKELY(reference_token.empty())) + { + // an empty reference token is not an array index; array_index() + // would throw out_of_range.404 -- contains() must not throw (see #5395) + return false; + } if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) { // invalid char diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index e7d6df530..cb3a2f75d 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -380,6 +380,26 @@ TEST_CASE("JSON pointers") DOCTEST_MSVC_SUPPRESS_WARNING_POP + { + // contains() must not throw for an empty reference token if the current + // value is an array (cf. #5395) -- at() still reports out_of_range.404 + json j_nested = {{"a", {1, 2}}}; + const json j_nested_const = j_nested; + json::json_pointer const jp("/a/"); + std::string const throw_msg = "[json.exception.out_of_range.404] unresolved reference token ''"; + + CHECK_THROWS_WITH_AS(j_nested.at(jp), throw_msg.c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(j_nested_const.at(jp), throw_msg.c_str(), json::out_of_range&); + + CHECK(j_nested.contains(json::json_pointer("/a/1"))); + CHECK(!j_nested.contains(jp)); + CHECK(!j_nested_const.contains(jp)); + + // same for an empty reference token on a top-level array + CHECK(!j.contains(json::json_pointer("/"))); + CHECK(!j_const.contains(json::json_pointer("/"))); + } + CHECK_THROWS_WITH_AS(j.at("/one"_json_pointer) = 1, "[json.exception.parse_error.109] parse error: array index 'one' is not a number", json::parse_error&); CHECK_THROWS_WITH_AS(j_const.at("/one"_json_pointer) == 1,