From 71f35542436e0789585d7742ee367851a2b8b8c2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 29 Sep 2026 23:14:33 +0200 Subject: [PATCH] Document that update() and merge_patch() must not alias *this update() and merge_patch() read their argument while they modify *this. When the argument is *this or a value nested inside *this (for example a subobject returned by operator[]), the modification destroys or relocates the value while it is still being iterated, so the functions read freed memory or dereference invalidated iterators (heap-use-after-free, or an uncaught invalid_iterator.214 for update()). This reproduces with plain std::map-backed json and, for update() on ordered_json, also via reallocation of the underlying vector. A fix would require copying the argument whenever it may alias *this, which cannot be checked in constant time without parent pointers (only available under JSON_DIAGNOSTICS), and would cost an unconditional deep copy per call otherwise. The maintainer decided to document the restriction instead of changing the library. Add a "Notes" section with a "!!! danger" admonition to update.md and merge_patch.md explaining that the argument must not be *this or refer into *this, and showing the workaround of passing a copy, e.g. j.update(json(j["a"])) and j.merge_patch(json(j)). Fixes #5641. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/merge_patch.md | 18 ++++++++++++++++++ docs/mkdocs/docs/api/basic_json/update.md | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/mkdocs/docs/api/basic_json/merge_patch.md b/docs/mkdocs/docs/api/basic_json/merge_patch.md index 1718c9227..712244022 100644 --- a/docs/mkdocs/docs/api/basic_json/merge_patch.md +++ b/docs/mkdocs/docs/api/basic_json/merge_patch.md @@ -37,6 +37,22 @@ Thereby, `Target` is the current object; that is, the patch is applied to the cu Linear in the lengths of `apply_patch`. +## Notes + +!!! danger "Undefined behavior" + + `merge_patch()` reads `apply_patch` while it modifies `#!cpp *this`. `apply_patch` must not be `#!cpp *this` + itself and must not refer to a value contained in `#!cpp *this` (for example, a subobject returned by + `#!cpp (*this)[key]`). Calling `merge_patch()` with such an argument reads the argument after it has been + invalidated by the modification, which is undefined behavior. If the patch may alias `#!cpp *this`, pass a copy + instead: + + ```cpp + j.merge_patch(json(j)); // instead of j.merge_patch(j) + ``` + + See [GitHub issue #5641](https://github.com/nlohmann/json/issues/5641) for more information. + ## Examples ??? example @@ -61,3 +77,5 @@ Linear in the lengths of `apply_patch`. ## Version history - Added in version 3.0.0. +- Documented that `apply_patch` must not be `#!cpp *this` or refer to a value contained in `#!cpp *this`, in version + 3.13.0. diff --git a/docs/mkdocs/docs/api/basic_json/update.md b/docs/mkdocs/docs/api/basic_json/update.md index b34140dbc..56d8aa1e4 100644 --- a/docs/mkdocs/docs/api/basic_json/update.md +++ b/docs/mkdocs/docs/api/basic_json/update.md @@ -59,6 +59,22 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value 1. O(N*log(size() + N)), where N is the number of elements to insert. 2. O(N*log(size() + N)), where N is the number of elements to insert. +## Notes + +!!! danger "Undefined behavior" + + Both overloads read the argument while they modify `#!cpp *this`. The argument `j` (or, for overload (2), the + range `[first, last)`) must not be `#!cpp *this` itself and must not refer to a value contained in + `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[key]`). Calling `update()` with such an + argument reads the argument after it has been invalidated by the modification, which is undefined behavior. If + the argument may alias `#!cpp *this`, pass a copy instead: + + ```cpp + j.update(json(j["defaults"])); // instead of j.update(j["defaults"]) + ``` + + See [GitHub issue #5641](https://github.com/nlohmann/json/issues/5641) for more information. + ## Examples ??? example @@ -155,3 +171,5 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value - Added in version 3.0.0. - Added `merge_objects` parameter in 3.10.5. +- Documented that the argument must not be `#!cpp *this` or refer to a value contained in `#!cpp *this`, in version + 3.13.0.