diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 67631a296..ff65e8dbd 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -73,11 +73,14 @@ The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the op 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" - 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. + 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 @@ -132,4 +135,5 @@ the patch made before the first operation is applied, so the result is the same - 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 no longer results in undefined behavior in version 3.13.0. +- 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. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 2606323fc..9339d2475 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6951,8 +6951,16 @@ public: void patch_inplace(const basic_json& json_patch) { // the operations below change *this while iterating json_patch: apply - // a copy when a value is applied as patch to itself - if (JSON_HEDLEY_UNLIKELY(&json_patch == this)) + // a copy when a value is applied as patch to itself or, with + // JSON_DIAGNOSTICS, to a value stored inside it + bool patch_aliases_this = (&json_patch == this); +#if JSON_DIAGNOSTICS + for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent) + { + patch_aliases_this = (p == this); + } +#endif + if (JSON_HEDLEY_UNLIKELY(patch_aliases_this)) { // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below const basic_json json_patch_copy = json_patch; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6bfff31d2..553f1ab1e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -43253,8 +43253,16 @@ public: void patch_inplace(const basic_json& json_patch) { // the operations below change *this while iterating json_patch: apply - // a copy when a value is applied as patch to itself - if (JSON_HEDLEY_UNLIKELY(&json_patch == this)) + // a copy when a value is applied as patch to itself or, with + // JSON_DIAGNOSTICS, to a value stored inside it + bool patch_aliases_this = (&json_patch == this); +#if JSON_DIAGNOSTICS + for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent) + { + patch_aliases_this = (p == this); + } +#endif + if (JSON_HEDLEY_UNLIKELY(patch_aliases_this)) { // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below const basic_json json_patch_copy = json_patch; diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index c9d351185..d828be56b 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -855,6 +855,19 @@ TEST_CASE("JSON patch") CHECK(doc == expected_copies); } +#if JSON_DIAGNOSTICS + SECTION("patch stored inside the patched value") + { + // the parent pointers of JSON_DIAGNOSTICS make the aliasing detectable + const json op = {{"op", "replace"}, {"path", "/patch"}, {"value", 1}}; + json doc = {{"a", 1}, {"patch", json::array({op})}}; + const json expected = doc.patch(doc["patch"]); + + doc.patch_inplace(doc["patch"]); + CHECK(doc == expected); + } +#endif + SECTION("copy operations with spare capacity") { // reserve while the array is empty: reallocating non-empty