Fix self-aliasing in json_pointer::operator/= and patch_inplace (#5936)

* Fix self-aliasing in json_pointer::operator/= and patch_inplace

json_pointer::operator/=(const json_pointer&) passed iterators into its
own token vector to insert() when a pointer was appended to itself
(p /= p), which is undefined behavior; libstdc++ produced "/a/b//" for
"/a/b". The pointer now appends a copy in that case.

basic_json::patch_inplace iterated the patch while changing *this, so
j.patch_inplace(j) read freed memory. It now applies a copy of the
patch when the patch is the value itself. A patch stored inside the
value remains undefined behavior and is documented as such; copying the
patch unconditionally would double the time of a large patch.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix JSON_DIAGNOSTICS test crash and clang-tidy hit in patch_inplace self-aliasing

The spare-capacity test reallocated non-empty elements through get_ref(), which leaves stale parent pointers under JSON_DIAGNOSTICS; reserve while the array is empty instead. Suppress performance-unnecessary-copy-initialization for the copy that is needed because *this is modified.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Detect a patch stored inside the patched value with JSON_DIAGNOSTICS

patch_inplace applied a copy only when the patch was the value itself. With JSON_DIAGNOSTICS the parent pointers make it cheap to also detect a patch nested inside the value, so that case is handled the same way. Without diagnostics it stays documented undefined behavior.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Silence misc-const-correctness for patch_aliases_this

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in the operator/= self-append test

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-10-28 20:23:55 +01:00
1 parent 2120927a7a
commit 091c10d4a5
7 files changed
+234 -2

No files matched your search

@@ -69,6 +69,19 @@ Duplicate member names inside an operation object are resolved by the parser bef
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
`remove` operation.
The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the operations are then read from a copy of
the patch made before the first operation is applied, so the result is the same as that of
`#!cpp j = j.patch(j)`, and the exception safety is the same as above.
If [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) is enabled, the same holds for a JSON patch stored inside the
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the parent pointers.
!!! danger "Undefined behavior"
Without [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md), the JSON patch must not be a value stored inside the
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`), as the operations may change or destroy it while it is
read. Pass a copy (`#!cpp j.patch_inplace(json(j["patch"]))`) or use [`patch`](patch.md) instead.
## Examples
??? example "Example: apply a JSON patch in place"
@@ -122,3 +135,5 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op":
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is
not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
- Applying a JSON value as patch to itself (or, with `JSON_DIAGNOSTICS`, to a value stored inside it) no longer results
in undefined behavior in version 3.13.0.
@@ -18,7 +18,8 @@ json_pointer& operator/=(std::size_t array_idx)
## Parameters
`ptr` (in)
: JSON pointer to append
: JSON pointer to append; may be the JSON pointer itself (`#!cpp p /= p`), which then appends a copy of its
reference tokens
`token` (in)
: reference token to append
@@ -69,6 +70,7 @@ json_pointer& operator/=(std::size_t array_idx)
## Version history
1. Added in version 3.6.0.
1. Added in version 3.6.0. Appending a JSON pointer to itself no longer results in undefined behavior in version
3.13.0.
2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0.
3. Added in version 3.6.0.