Compare commits

...

2 Commits

Author SHA1 Message Date
Niels Lohmann cb7f1ae838 Avoid strlen() in test container to fix Codacy CWE-126 flag
Suppressing the strlen()-based CWE-126 warning with NOLINT/nosec
comments only silenced clang-tidy and the standalone Flawfinder
Action; Codacy's own analysis (which also flags this pattern and
doesn't honor those suppression comments) still reported it as a new
issue, plus flagged the near-duplicate begin/end pair as cloned code.

Store the buffer's size explicitly in MyContainerNonConstADL instead
of computing it via strlen() in end(), which removes the flagged
pattern outright and also de-duplicates the struct from the existing
MyContainer's char*-based begin/end pair.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-09 21:26:24 +02:00
Niels Lohmann fc7fde6910 Fix container input_adapter SFINAE for lvalue-only ADL begin/end (#111)
The container overload of json::parse(c) / accept(c) / sax_parse(c, ...)
silently dropped from overload resolution for user types whose ADL
begin(T&) / end(T&) accepted only non-const lvalue references
(a legitimate pattern matching std::begin semantics). This was because
the detection code used std::declval<ContainerType>() which synthesized
an rvalue, and the rvalue failed to bind to lvalue-only ADL functions.

Fix by making both the outer input_adapter(ContainerType&&) and the
factory's create(ContainerType&&) forwarding references, preserving the
caller's value category and constness via reference collapsing. This
ensures detection (std::declval) and actual use (std::forward) always
match without needing decay/remove_reference.

- Rewrite input_adapters.hpp container overload with forwarding refs
- Add regression tests for lvalue-only non-const ADL begin/end
- Add regression test for rvalue containers (no breakage)
- Update API docs (parse, accept, sax_parse, from_*) to clarify
  that begin/end must match std::begin/std::end semantics
- Add version history notes for 3.13.0
- Regenerate amalgamation

Second-order effect: binary_reader.hpp's internal call to
input_adapter(number_vector) now deduces iterator vs const_iterator
based on the lvalue; functionally harmless (iterator_input_adapter is
iterator-type-agnostic), verified via unit-ubjson/unit-bjdata tests.

Closes remaining limitation from #4354 / PR #5218 (todo 106).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-09 21:22:17 +02:00
11 changed files with 75 additions and 17 deletions
+3 -1
View File
@@ -35,7 +35,8 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string`
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type, for instance.
@@ -110,6 +111,7 @@ A UTF-8 byte order mark is silently ignored.
- Ignoring comments via `ignore_comments` added in version 3.9.0.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
- Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type
@@ -101,3 +102,4 @@ Linear in the size of the input.
## Version history
- Added in version 3.11.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
+3 -1
View File
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type
@@ -101,6 +102,7 @@ Linear in the size of the input.
## Version history
- Added in version 3.4.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
+3 -1
View File
@@ -32,7 +32,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type
@@ -111,6 +112,7 @@ Linear in the size of the input.
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
- Added `allow_exceptions` parameter in version 3.2.0.
- Added `tag_handler` parameter in version 3.9.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type
@@ -103,6 +104,7 @@ Linear in the size of the input.
- Parameter `start_index` since version 2.1.1.
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
- Added `allow_exceptions` parameter in version 3.2.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
@@ -29,7 +29,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type
@@ -102,6 +103,7 @@ Linear in the size of the input.
- Added in version 3.1.0.
- Added `allow_exceptions` parameter in version 3.2.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
+3 -1
View File
@@ -34,7 +34,8 @@ static basic_json parse(IteratorType first, IteratorType last,
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string`
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type, for instance.
@@ -236,6 +237,7 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
- Ignoring comments via `ignore_comments` added in version 3.9.0.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
- Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
+3 -2
View File
@@ -39,8 +39,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- an object `obj` for which `begin(obj)` and `end(obj)` produces a valid pair of
iterators.
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
`IteratorType`
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
@@ -127,6 +127,7 @@ A UTF-8 byte order mark is silently ignored.
- Added in version 3.2.0.
- Ignoring comments via `ignore_comments` added in version 3.9.0.
- Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
!!! warning "Deprecation"
@@ -517,18 +517,19 @@ struct container_input_adapter_factory< ContainerType,
{
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
static adapter_type create(const ContainerType& container)
static adapter_type create(ContainerType&& container)
{
return input_adapter(begin(container), end(container));
return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
}
};
} // namespace container_input_adapter_factory_impl
template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)
auto input_adapter(ContainerType&& container)
-> typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type
{
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
}
// specialization for std::string
+5 -4
View File
@@ -7354,18 +7354,19 @@ struct container_input_adapter_factory< ContainerType,
{
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
static adapter_type create(const ContainerType& container)
static adapter_type create(ContainerType&& container)
{
return input_adapter(begin(container), end(container));
return input_adapter(begin(std::forward<ContainerType>(container)), end(std::forward<ContainerType>(container)));
}
};
} // namespace container_input_adapter_factory_impl
template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)
auto input_adapter(ContainerType&& container)
-> typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type
{
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
}
// specialization for std::string
+41
View File
@@ -54,6 +54,47 @@ TEST_CASE("Custom container non-member begin/end")
}
struct MyContainerNonConstADL
{
char* data;
std::size_t size;
};
char* begin(MyContainerNonConstADL& c)
{
return c.data;
}
char* end(MyContainerNonConstADL& c)
{
return c.data + c.size; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
TEST_CASE("Custom container non-member non-const begin/end")
{
// Container with lvalue-only non-const ADL begin/end (bug reproduction)
char raw_data[] = "[1,2,3,4]";
MyContainerNonConstADL data{raw_data, sizeof(raw_data) - 1};
const json as_json = json::parse(data);
CHECK(as_json.at(0) == 1);
CHECK(as_json.at(1) == 2);
CHECK(as_json.at(2) == 3);
CHECK(as_json.at(3) == 4);
// Same container with accept()
CHECK(json::accept(data));
}
TEST_CASE("Custom container non-member begin/end, rvalue")
{
// Regression check: rvalue container parsing should still work
const json as_json = json::parse(MyContainer{"[1,2,3,4]"});
CHECK(as_json.at(0) == 1);
CHECK(as_json.at(1) == 2);
CHECK(as_json.at(2) == 3);
CHECK(as_json.at(3) == 4);
}
TEST_CASE("Custom container member begin/end")
{
struct MyContainer2