Compare commits

..
Author SHA1 Message Date
Niels Lohmann 404427ab72 Merge branch 'develop' into claude/noexception-value-json-pointer-5672
Conflicts:
- tests/src/unit-disabled_exceptions.cpp: kept both new sections (#5672 value(json_pointer) test and develop's ordered_json growth test)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:31:16 +02:00
Niels Lohmann 57890cebad Fix value(json_pointer, default) aborting under JSON_NOEXCEPTION
With exceptions disabled (JSON_NOEXCEPTION or -fno-exceptions),
value(const json_pointer&, default) called std::abort() for array
reference tokens that array_index() rejects with out_of_range.404/410:
indices too large to fit size_type, the empty token ("/"), and tokens
like "/1a". With exceptions enabled, the same tokens correctly yielded
the default value, because get_checked_or_null() relied on
JSON_TRY/JSON_INTERNAL_CATCH (detail::out_of_range&) to turn the
exception into nullptr; under JSON_NOEXCEPTION, JSON_THROW aborts
before that catch is ever reached.

get_checked_or_null() now detects those out-of-range tokens itself,
the same way contains(json_pointer) already does (#5495), and only
calls array_index() for tokens that must still raise parse_error.106
or parse_error.109 (e.g. "/01", "/+1"), matching the documented
behavior of value().

Added regression tests to tests/src/unit-disabled_exceptions.cpp
(built with JSON_NOEXCEPTION and -fno-exceptions) and the matching
checks to tests/src/unit-element_access2.cpp for normal exception
mode.

Fixes #5672.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:41:00 +02:00
13 changed files with 68 additions and 236 deletions
-1
View File
@@ -16,7 +16,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json', 'Class', 'api/ba
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::accept', 'Function', 'api/basic_json/accept/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array', 'Function', 'api/basic_json/array/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::array_t', 'Type', 'api/basic_json/array_t/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::as_base_class', 'Method', 'api/basic_json/as_base_class/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::at', 'Method', 'api/basic_json/at/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::back', 'Method', 'api/basic_json/back/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::basic_json', 'Constructor', 'api/basic_json/basic_json/index.html');
@@ -1,53 +0,0 @@
# <small>nlohmann::basic_json::</small>as_base_class
```cpp
json_base_class_t& as_base_class() noexcept;
const json_base_class_t& as_base_class() const noexcept;
```
Returns a reference to this object as its custom base class [`json_base_class_t`](json_base_class_t.md). No copy is
made.
Since `basic_json` derives from `json_base_class_t`, a member of `basic_json` hides any member of the custom base class
with the same name. This function makes such hidden members accessible again.
## Return value
reference to this object as [`json_base_class_t`](json_base_class_t.md)
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Constant.
## Notes
The function is equivalent to `static_cast<json_base_class_t&>(j)` (or `static_cast<const json_base_class_t&>(j)`).
## Examples
??? example
The example shows how to use `as_base_class` to access members of the custom base class that are hidden by members
of `basic_json`.
```cpp
--8<-- "examples/as_base_class.cpp"
```
Output:
```json
--8<-- "examples/as_base_class.output"
```
## See also
- [json_base_class_t](json_base_class_t.md) - type of the custom base class
## Version history
- Added in version 3.13.0.
-1
View File
@@ -200,7 +200,6 @@ Direct access to the stored value of a JSON value.
- [**get_ref**](get_ref.md) - get a reference value
- [**operator ValueType**](operator_ValueType.md) - get a value
- [**get_binary**](get_binary.md) - get a binary value
- [**as_base_class**](as_base_class.md) - access the custom base class
### Element access
@@ -27,18 +27,6 @@ A `CustomBaseClass` with non-static data members forfeits `basic_json`'s
[standard layout](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType) guarantee. See
[Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass).
#### Name conflicts
Since `basic_json` derives from `CustomBaseClass`, members of `basic_json` hide members of `CustomBaseClass` with the
same name. Hidden members remain accessible via [`as_base_class`](as_base_class.md) or by casting the value to
`json_base_class_t`.
!!! warning "Avoid generic member names"
Future versions of the library may add members to `basic_json` that hide members of `CustomBaseClass` that are
accessible today. To reduce the risk of such conflicts, avoid generic names for the members of `CustomBaseClass`,
for instance by using a distinctive prefix.
## Examples
??? example
@@ -55,11 +43,6 @@ same name. Hidden members remain accessible via [`as_base_class`](as_base_class.
--8<-- "examples/json_base_class_t.output"
```
## See also
- [as_base_class](as_base_class.md) - access the custom base class
## Version history
- Added in version 3.12.0.
- Made a public member type in version 3.13.0; it was private before, so it could not be named outside the class.
@@ -1,41 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
class base_class_with_hidden_members
{
public:
const char* type_name() const noexcept
{
return "my_type_name";
}
std::size_t size() const noexcept
{
return 42;
}
};
using json = nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
base_class_with_hidden_members
>;
int main()
{
json j = {1, 2, 3};
// the members of basic_json hide the members of the base class
std::cout << j.type_name() << ' ' << j.size() << '\n';
// access the hidden members of the base class
std::cout << j.as_base_class().type_name() << ' ' << j.as_base_class().size() << '\n';
}
@@ -1,2 +0,0 @@
array 3
my_type_name 42
-1
View File
@@ -114,7 +114,6 @@ nav:
- 'accept': api/basic_json/accept.md
- 'array': api/basic_json/array.md
- 'array_t': api/basic_json/array_t.md
- 'as_base_class': api/basic_json/as_base_class.md
- 'at': api/basic_json/at.md
- 'back': api/basic_json/back.md
- 'begin': api/basic_json/begin.md
+18 -8
View File
@@ -679,19 +679,29 @@ class json_pointer
return nullptr;
}
// may throw parse_error.106/109 for a malformed index; an
// index that is syntactically valid but cannot be
// represented (out_of_range.404/410) is treated like an
// out-of-range index below
typename BasicJsonType::size_type idx{};
JSON_TRY
// tokens that array_index() rejects with parse_error.106/109
// are passed on to it; all other tokens that it would reject
// with out_of_range.404/410 are detected here, so that this
// also works without exceptions
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9')))
{
idx = array_index<BasicJsonType>(reference_token);
static_cast<void>(array_index<BasicJsonType>(reference_token)); // throws parse_error.106/109
}
JSON_INTERNAL_CATCH (detail::out_of_range&)
if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c)
{
return c >= '0' && c <= '9';
})))
{
return nullptr;
}
errno = 0; // strtoull() does not reset errno on success
char* p_end = nullptr; // NOLINT(misc-const-correctness)
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int)
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
{
return nullptr;
}
const auto idx = static_cast<typename BasicJsonType::size_type>(magnitude);
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
+1 -17
View File
@@ -155,6 +155,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
JSON_PRIVATE_UNLESS_TESTED:
// convenience aliases for types residing in namespace detail;
@@ -214,9 +215,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using cbor_tag_handler_t = detail::cbor_tag_handler_t;
/// how to encode BJData
using bjdata_version_t = detail::bjdata_version_t;
/// base class used to inject custom functionality into each instance of basic_json
/// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
/// helper type for initializer lists of basic_json values
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
@@ -2693,20 +2691,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *get_ptr<const binary_t*>();
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
json_base_class_t& as_base_class() noexcept
{
return static_cast<json_base_class_t&>(*this);
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
const json_base_class_t& as_base_class() const noexcept
{
return static_cast<const json_base_class_t&>(*this);
}
/// @}
////////////////////
+19 -25
View File
@@ -20291,19 +20291,29 @@ class json_pointer
return nullptr;
}
// may throw parse_error.106/109 for a malformed index; an
// index that is syntactically valid but cannot be
// represented (out_of_range.404/410) is treated like an
// out-of-range index below
typename BasicJsonType::size_type idx{};
JSON_TRY
// tokens that array_index() rejects with parse_error.106/109
// are passed on to it; all other tokens that it would reject
// with out_of_range.404/410 are detected here, so that this
// also works without exceptions
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 && !(reference_token[0] >= '1' && reference_token[0] <= '9')))
{
idx = array_index<BasicJsonType>(reference_token);
static_cast<void>(array_index<BasicJsonType>(reference_token)); // throws parse_error.106/109
}
JSON_INTERNAL_CATCH (detail::out_of_range&)
if (JSON_HEDLEY_UNLIKELY(reference_token.empty() || !std::all_of(reference_token.begin(), reference_token.end(), [](const char c)
{
return c >= '0' && c <= '9';
})))
{
return nullptr;
}
errno = 0; // strtoull() does not reset errno on success
char* p_end = nullptr; // NOLINT(misc-const-correctness)
const unsigned long long magnitude = std::strtoull(reference_token.data(), &p_end, 10); // NOLINT(runtime/int)
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE || magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
{
return nullptr;
}
const auto idx = static_cast<typename BasicJsonType::size_type>(magnitude);
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
{
@@ -27082,6 +27092,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// workaround type for MSVC
using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
JSON_PRIVATE_UNLESS_TESTED:
// convenience aliases for types residing in namespace detail;
@@ -27141,9 +27152,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using cbor_tag_handler_t = detail::cbor_tag_handler_t;
/// how to encode BJData
using bjdata_version_t = detail::bjdata_version_t;
/// base class used to inject custom functionality into each instance of basic_json
/// @sa https://json.nlohmann.me/api/basic_json/json_base_class_t/
using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;
/// helper type for initializer lists of basic_json values
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
@@ -29620,20 +29628,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return *get_ptr<const binary_t*>();
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
json_base_class_t& as_base_class() noexcept
{
return static_cast<json_base_class_t&>(*this);
}
/// @brief access the custom base class
/// @sa https://json.nlohmann.me/api/basic_json/as_base_class/
const json_base_class_t& as_base_class() const noexcept
{
return static_cast<const json_base_class_t&>(*this);
}
/// @}
////////////////////
-70
View File
@@ -10,8 +10,6 @@
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "doctest_compatibility.h"
@@ -407,71 +405,3 @@ TEST_CASE("JSON Visit Node")
);
CHECK(expected.empty());
}
// Test accessing members of a custom base class that are hidden by members of nlohmann::basic_json
class base_class_with_hidden_members
{
public:
const char* type_name() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
return "custom type_name";
}
std::size_t size() const noexcept
{
return m_size;
}
std::size_t m_size = 42;
};
using json_with_hidden_base_members =
nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
base_class_with_hidden_members
>;
TEST_CASE("JSON Node as_base_class")
{
using json = json_with_hidden_base_members;
static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, "");
static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, "");
static_assert(noexcept(std::declval<json&>().as_base_class()), "");
static_assert(noexcept(std::declval<const json&>().as_base_class()), "");
SECTION("non-const")
{
json j = {1, 2, 3};
CHECK(std::string(j.type_name()) == "array");
CHECK(j.size() == 3);
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
CHECK(j.as_base_class().size() == 42);
CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j));
j.as_base_class().m_size = 7;
CHECK(j.as_base_class().size() == 7);
CHECK(j.size() == 3);
}
SECTION("const")
{
const json j = {1, 2, 3};
CHECK(std::string(j.type_name()) == "array");
CHECK(j.size() == 3);
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
CHECK(j.as_base_class().size() == 42);
CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j));
}
}
+15
View File
@@ -47,6 +47,21 @@ TEST_CASE("Tests with disabled exceptions")
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
}
SECTION("issue #5672 - value(json_pointer, default) must not abort for array tokens that are not a valid index")
{
const json j = {1, 2, 3};
// a syntactically valid index that is out of range for this array
CHECK(j.value("/7"_json_pointer, 42) == 42);
// a reference token that is not a number at all
CHECK(j.value("/1a"_json_pointer, 42) == 42);
// the empty reference token (JSON pointer "/")
CHECK(j.value("/"_json_pointer, 42) == 42);
// an index whose magnitude does not fit into size_type
CHECK(j.value("/99999999999999999999999"_json_pointer, 42) == 42);
CHECK(j.value("/18446744073709551615"_json_pointer, 42) == 42);
}
SECTION("growing an ordered_json object")
{
auto j = nlohmann::ordered_json::object();
+15
View File
@@ -516,6 +516,21 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
CHECK(j_array.value("/-"_json_pointer, 42) == 42);
CHECK(j_array_const.value("/-"_json_pointer, 42) == 42);
// Test an index with a non-digit after a valid leading digit; this is
// out_of_range (not parse_error) and must not throw (see #5672)
CHECK(j_array.value("/1a"_json_pointer, 42) == 42);
CHECK(j_array_const.value("/1a"_json_pointer, 42) == 42);
// Test the empty reference token (JSON pointer "/"); see #5672
CHECK(j_array.value("/"_json_pointer, 42) == 42);
CHECK(j_array_const.value("/"_json_pointer, 42) == 42);
// Test an index whose magnitude does not fit into size_type (see #5672)
CHECK(j_array.value("/99999999999999999999999"_json_pointer, 42) == 42);
CHECK(j_array_const.value("/99999999999999999999999"_json_pointer, 42) == 42);
CHECK(j_array.value("/18446744073709551615"_json_pointer, 42) == 42);
CHECK(j_array_const.value("/18446744073709551615"_json_pointer, 42) == 42);
#if !defined(JSON_NOEXCEPTION)
// Test malformed index (non-numeric) throws parse_error
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);