You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pe...@apache.org on 2026/08/10 04:38:54 UTC

(celix) 24/35: Enhance JSON pointer initialization and error handling by removing pre-zeroing requirement, adding automatic cleanup support, and improving memory management in patch operations.

This is an automated email from the ASF dual-hosted git repository.

PengZheng pushed a commit to branch feature/json-schema
in repository https://gitbox.apache.org/repos/asf/celix.git

commit 00f17bb8299fdad2996e251028250da0c3367216
Author: PengZheng <ho...@gmail.com>
AuthorDate: Sun Aug 9 13:20:19 2026 +0800

    Enhance JSON pointer initialization and error handling by removing pre-zeroing requirement, adding automatic cleanup support, and improving memory management in patch operations.
---
 libs/error_injector/jansson/CMakeLists.txt         |   2 +
 libs/error_injector/jansson/include/jansson_ei.h   |   2 +
 libs/error_injector/jansson/src/jansson_ei.cc      |  16 +
 .../gtest/src/ErrorInjectionTestSuite.cc           | 399 ++++++++++++++++++++-
 libs/jansson_ext/gtest/src/test_pointer.cpp        |  93 ++---
 libs/jansson_ext/include/celix_jansson_pointer.h   |  20 +-
 libs/jansson_ext/include/celix_json_patch.h        |  30 +-
 libs/jansson_ext/src/celix_jansson_pointer.c       |   3 +-
 libs/jansson_ext/src/celix_jansson_schema.c        |   1 -
 libs/jansson_ext/src/celix_json_patch.c            | 162 ++++++---
 10 files changed, 620 insertions(+), 108 deletions(-)

diff --git a/libs/error_injector/jansson/CMakeLists.txt b/libs/error_injector/jansson/CMakeLists.txt
index fec7164e5..f99b662eb 100644
--- a/libs/error_injector/jansson/CMakeLists.txt
+++ b/libs/error_injector/jansson/CMakeLists.txt
@@ -32,6 +32,8 @@ target_link_options(jansson_ei INTERFACE
         LINKER:--wrap,json_object_set_new
         LINKER:--wrap,json_array
         LINKER:--wrap,json_array_append_new
+        LINKER:--wrap,json_array_set_new
+        LINKER:--wrap,json_array_insert_new
         LINKER:--wrap,json_integer
         LINKER:--wrap,json_string
         LINKER:--wrap,json_real
diff --git a/libs/error_injector/jansson/include/jansson_ei.h b/libs/error_injector/jansson/include/jansson_ei.h
index f8a8456b8..d86b4aac3 100644
--- a/libs/error_injector/jansson/include/jansson_ei.h
+++ b/libs/error_injector/jansson/include/jansson_ei.h
@@ -31,6 +31,8 @@ CELIX_EI_DECLARE(json_object, json_t*);
 CELIX_EI_DECLARE(json_object_set_new, int);
 CELIX_EI_DECLARE(json_array, json_t*);
 CELIX_EI_DECLARE(json_array_append_new, int);
+CELIX_EI_DECLARE(json_array_set_new, int);
+CELIX_EI_DECLARE(json_array_insert_new, int);
 CELIX_EI_DECLARE(json_integer, json_t*);
 CELIX_EI_DECLARE(json_string, json_t*);
 CELIX_EI_DECLARE(json_real, json_t*);
diff --git a/libs/error_injector/jansson/src/jansson_ei.cc b/libs/error_injector/jansson/src/jansson_ei.cc
index 6daeb35bf..e9e95e98f 100644
--- a/libs/error_injector/jansson/src/jansson_ei.cc
+++ b/libs/error_injector/jansson/src/jansson_ei.cc
@@ -67,6 +67,22 @@ int __wrap_json_array_append_new(json_t* array, json_t* value) {
     return __real_json_array_append_new(array, celix_steal_ptr(val));
 }
 
+int __real_json_array_set_new(json_t* array, size_t index, json_t* value);
+CELIX_EI_DEFINE(json_array_set_new, int)
+int __wrap_json_array_set_new(json_t* array, size_t index, json_t* value) {
+    json_auto_t* val = value;
+    CELIX_EI_IMPL(json_array_set_new);
+    return __real_json_array_set_new(array, index, celix_steal_ptr(val));
+}
+
+int __real_json_array_insert_new(json_t* array, size_t index, json_t* value);
+CELIX_EI_DEFINE(json_array_insert_new, int)
+int __wrap_json_array_insert_new(json_t* array, size_t index, json_t* value) {
+    json_auto_t* val = value;
+    CELIX_EI_IMPL(json_array_insert_new);
+    return __real_json_array_insert_new(array, index, celix_steal_ptr(val));
+}
+
 json_t* __real_json_integer(json_int_t value);
 CELIX_EI_DEFINE(json_integer, json_t*)
 json_t* __wrap_json_integer(json_int_t value) {
diff --git a/libs/jansson_ext/gtest/src/ErrorInjectionTestSuite.cc b/libs/jansson_ext/gtest/src/ErrorInjectionTestSuite.cc
index b2715d6c5..3d0666a07 100644
--- a/libs/jansson_ext/gtest/src/ErrorInjectionTestSuite.cc
+++ b/libs/jansson_ext/gtest/src/ErrorInjectionTestSuite.cc
@@ -28,8 +28,6 @@
 #include "malloc_ei.h"
 #include "string_ei.h"
 
-CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_json_pointer_t, celix_json_pointer_destroy)
-
 /**
  * Error-injection tests for the OOM (out-of-memory) handling paths of
  * celix_util, celix_jansson_pointer, celix_jansson_uri and celix_json_patch.
@@ -50,6 +48,9 @@ public:
         celix_ei_expect_json_deep_copy(nullptr, 0, nullptr);
         celix_ei_expect_json_object_set_new(nullptr, 0, 0);
         celix_ei_expect_json_array_append_new(nullptr, 0, 0);
+        celix_ei_expect_json_array_set_new(nullptr, 0, 0);
+        celix_ei_expect_json_array_insert_new(nullptr, 0, 0);
+        celix_ei_expect_json_string(nullptr, 0, nullptr);
         celix_ei_expect_json_null(nullptr, 0, nullptr);
     }
 };
@@ -919,16 +920,19 @@ TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddJsonObjectFail) {
     //Given json_object is injected to fail in patch_add
     json_auto_t* patch = json_array();
     celix_ei_expect_json_object((void*)celix_json_patch_add, 0, nullptr);
-    //Then adding an operation should fail; the value is not consumed on failure
-    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_true()));
+    //Then adding an operation should fail; the value is consumed on failure
+    //(json_integer(42) is a fresh reference, so LSAN proves the consumption)
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
 }
 
 TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplaceJsonObjectFail) {
     //Given json_object is injected to fail in patch_replace
     json_auto_t* patch = json_array();
     celix_ei_expect_json_object((void*)celix_json_patch_replace, 0, nullptr);
-    //Then replacing should fail; the value is not consumed on failure
-    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_true()));
+    //Then replacing should fail; the value is consumed on failure
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
 }
 
 TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemoveJsonObjectFail) {
@@ -937,6 +941,7 @@ TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemoveJsonObjectFail) {
     celix_ei_expect_json_object((void*)celix_json_patch_remove, 0, nullptr);
     //Then removing should fail
     EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
 }
 
 TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyDeepCopyFail) {
@@ -947,3 +952,385 @@ TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyDeepCopyFail) {
     //Then applying the patch should fail
     EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
 }
+
+/* ── builder failures beyond json_object ───────────────────────────────── */
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddOpStringFail) {
+    //Given the first json_string (the op name) is injected to fail in patch_add.
+    //json_string is called twice per add: ordinal 1 = op name, ordinal 2 = path.
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_add, 0, nullptr);
+    //Then adding fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddPathStringFail) {
+    //Given the second json_string (the path) is injected to fail in patch_add
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_add, 0, nullptr, 2);
+    //Then adding fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddOpSetNewFail) {
+    //Given the first json_object_set_new (the "op" key) is injected to fail
+    //in patch_add; the wrapper releases the op-name string
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_add, 0, -1);
+    //Then adding fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddPathSetNewFail) {
+    //Given the second json_object_set_new (the "path" key) is injected to
+    //fail in patch_add; the wrapper releases the path string
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_add, 0, -1, 2);
+    //Then adding fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddValueSetNewFail) {
+    //Given the third json_object_set_new (the "value" key) is injected to
+    //fail in patch_add; the failed set_new consumes the value, so only the
+    //op object is released here
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_add, 0, -1, 3);
+    //Then adding fails without appending the op
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchAddAppendFail) {
+    //Given json_array_append_new is injected to fail in patch_add; the
+    //failed append_new consumes the op, which owns the value
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_add, 0, -1);
+    //Then adding fails and the patch stays empty
+    EXPECT_EQ(-1, celix_json_patch_add(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplaceOpStringFail) {
+    //Given the first json_string (the op name) is injected to fail in patch_replace
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_replace, 0, nullptr);
+    //Then replacing fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplacePathStringFail) {
+    //Given the second json_string (the path) is injected to fail in patch_replace
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_replace, 0, nullptr, 2);
+    //Then replacing fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplaceOpSetNewFail) {
+    //Given the first json_object_set_new (the "op" key) is injected to fail
+    //in patch_replace
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_replace, 0, -1);
+    //Then replacing fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplacePathSetNewFail) {
+    //Given the second json_object_set_new (the "path" key) is injected to
+    //fail in patch_replace
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_replace, 0, -1, 2);
+    //Then replacing fails and releases both the op object and the value
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplaceValueSetNewFail) {
+    //Given the third json_object_set_new (the "value" key) is injected to
+    //fail in patch_replace; the failed set_new consumes the value
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_replace, 0, -1, 3);
+    //Then replacing fails without appending the op
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchReplaceAppendFail) {
+    //Given json_array_append_new is injected to fail in patch_replace; the
+    //failed append_new consumes the op, which owns the value
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_replace, 0, -1);
+    //Then replacing fails and the patch stays empty
+    EXPECT_EQ(-1, celix_json_patch_replace(patch, "/a", json_integer(42)));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemoveOpStringFail) {
+    //Given the first json_string (the op name) is injected to fail in patch_remove
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_remove, 0, nullptr);
+    //Then removing fails and releases the op object
+    EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemovePathStringFail) {
+    //Given the second json_string (the path) is injected to fail in patch_remove
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_string((void*)celix_json_patch_remove, 0, nullptr, 2);
+    //Then removing fails and releases the op object
+    EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemoveOpSetNewFail) {
+    //Given the first json_object_set_new (the "op" key) is injected to fail
+    //in patch_remove
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_remove, 0, -1);
+    //Then removing fails and releases the op object
+    EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemovePathSetNewFail) {
+    //Given the second json_object_set_new (the "path" key) is injected to
+    //fail in patch_remove
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_remove, 0, -1, 2);
+    //Then removing fails and releases the op object
+    EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchRemoveAppendFail) {
+    //Given json_array_append_new is injected to fail in patch_remove; the
+    //failed append_new consumes the op
+    json_auto_t* patch = json_array();
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_remove, 0, -1);
+    //Then removing fails and the patch stays empty
+    EXPECT_EQ(-1, celix_json_patch_remove(patch, "/a"));
+    EXPECT_EQ(0u, json_array_size(patch));
+}
+
+/* ── apply failures ────────────────────────────────────────────────────── */
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyRootReplaceDeepCopyFail) {
+    //Given deep-copy #1 (the initial copy of the original) succeeds and #2
+    //(the root-replacement copy) is injected to fail in patch_apply
+    json_auto_t* original = json_loads("{}", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_deep_copy((void*)celix_json_patch_apply, 0, nullptr, 2);
+    //Then applying fails; the error path decrefs the already-NULL result (no-op)
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyRemoveRootNullFail) {
+    //Given json_null is injected to fail for the root-removal null in patch_apply;
+    //the first json_null call of the apply
+    json_auto_t* original = json_loads("{}", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"remove","path":""}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_null((void*)celix_json_patch_apply, 0, nullptr);
+    //Then applying fails; the error path decrefs the already-NULL result (no-op)
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkObjectCreateFail) {
+    //Given json_object is injected to fail while creating the intermediate
+    //object "a" for path "/a/b" — the first json_object call of the apply
+    json_auto_t* original = json_loads("{}", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_object((void*)celix_json_patch_apply, 0, nullptr);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkObjectSetNewFail) {
+    //Given json_object_set_new is injected to fail while inserting the
+    //intermediate object "a"; this is the first set_new of the apply call
+    //(the walk insert precedes the final write)
+    json_auto_t* original = json_loads("{}", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkArrayChildCreateFail) {
+    //Given json_object is injected to fail while creating the child container
+    //at array index 0 (path "/a/0/b", original {"a":[]}) — the first
+    //json_object call of the apply
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/0/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_object((void*)celix_json_patch_apply, 0, nullptr);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkArrayChildAppendFail) {
+    //Given json_array_append_new is injected to fail while appending the
+    //child container at array index 0; no padding precedes it (idx == size),
+    //so this is the first append_new of the apply call
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/0/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkArrayPadNullFail) {
+    //Given json_null is injected to fail while padding index 2 of the walk
+    //array (path "/a/2/b") — the first json_null call of the apply
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/2/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_null((void*)celix_json_patch_apply, 0, nullptr);
+    //Then applying fails instead of looping forever on the failed padding
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyWalkArrayPadAppendFail) {
+    //Given json_array_append_new is injected to fail while appending the
+    //padding null at index 2 of the walk array — the first append_new call
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/2/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails instead of looping forever on the failed padding
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalObjectAddWriteFail) {
+    //Given json_object_set_new is injected to fail in the final "add" write.
+    //The walk finds "a" already present, so no intermediate allocation
+    //happens and the final write is the first set_new of the apply call
+    json_auto_t* original = json_loads(R"({"a":{}})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalObjectReplaceWriteFail) {
+    //Given json_object_set_new is injected to fail in the final "replace"
+    //write; key "b" exists, so the replace branch takes the set_new and it
+    //is the first set_new of the apply call
+    json_auto_t* original = json_loads(R"({"a":{"b":0}})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"replace","path":"/a/b","value":1}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_object_set_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayReplaceCopyFail) {
+    //Given deep-copy #1 (the initial copy of the original) succeeds and #2
+    //(the array-replace value copy for "/a/1" on [1,2]) is injected to fail
+    json_auto_t* original = json_loads(R"({"a":[1,2]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"replace","path":"/a/1","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_deep_copy((void*)celix_json_patch_apply, 0, nullptr, 2);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayReplaceSetNewFail) {
+    //Given json_array_set_new is injected to fail for the array-replace write
+    //("/a/1" on [1,2]); the first json_array_set_new of the apply call
+    json_auto_t* original = json_loads(R"({"a":[1,2]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"replace","path":"/a/1","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_set_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayInsertCopyFail) {
+    //Given deep-copy #1 (the initial copy of the original) succeeds and #2
+    //(the mid-array insert copy for "/a/1" on [1,2] — idx != size, so insert)
+    //is injected to fail
+    json_auto_t* original = json_loads(R"({"a":[1,2]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/1","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_deep_copy((void*)celix_json_patch_apply, 0, nullptr, 2);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayInsertNewFail) {
+    //Given json_array_insert_new is injected to fail for the mid-array "add"
+    //("/a/1" on [1,2] — idx != size, so insert); the first json_array_insert_new
+    //of the apply call
+    json_auto_t* original = json_loads(R"({"a":[1,2]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/1","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_insert_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayAppendFail) {
+    //Given json_array_append_new is injected to fail for the end-of-array
+    //"add" ("/a/2" on [1,2] — idx == size, so append); no padding precedes
+    //it, so this is the first append_new of the apply call
+    json_auto_t* original = json_loads(R"({"a":[1,2]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/2","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails and the partially built result is released
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayPadNullFail) {
+    //Given json_null is injected to fail while padding index 2 of the
+    //final "add" (path "/a/2", original {"a":[]}) — the first json_null call
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/2","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_null((void*)celix_json_patch_apply, 0, nullptr);
+    //Then applying fails instead of looping forever on the failed padding
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
+
+TEST_F(JanssonExtErrorInjectionTestSuite, PatchApplyFinalArrayPadAppendFail) {
+    //Given json_array_append_new is injected to fail while appending the
+    //padding null at index 2 of the final "add" — the first append_new call
+    json_auto_t* original = json_loads(R"({"a":[]})", 0, nullptr);
+    json_auto_t* patch = json_loads(R"([{"op":"add","path":"/a/2","value":3}])", 0, nullptr);
+    ASSERT_NE(nullptr, original);
+    ASSERT_NE(nullptr, patch);
+    celix_ei_expect_json_array_append_new((void*)celix_json_patch_apply, 0, -1);
+    //Then applying fails instead of looping forever on the failed padding
+    EXPECT_EQ(nullptr, celix_json_patch_apply(original, patch));
+}
diff --git a/libs/jansson_ext/gtest/src/test_pointer.cpp b/libs/jansson_ext/gtest/src/test_pointer.cpp
index 55a01ef3b..ca65e63f7 100644
--- a/libs/jansson_ext/gtest/src/test_pointer.cpp
+++ b/libs/jansson_ext/gtest/src/test_pointer.cpp
@@ -390,7 +390,6 @@ TEST(PointerTest, Copy) {
 
 TEST(PointerTest, StackAllocated) {
     celix_json_pointer_t ptr;
-    memset(&ptr, 0, sizeof(ptr));
 
     EXPECT_EQ(0, celix_json_pointer_init(&ptr, "/a/b"));
     EXPECT_EQ(2u, celix_json_pointer_depth(&ptr));
@@ -447,7 +446,6 @@ TEST(PointerTest, Rfc6901Section5) {
     /* Test all keys from the RFC example */
     auto check = [&](const char* ptr_str, json_t* expected, bool owned) {
         celix_json_pointer_t p;
-        memset(&p, 0, sizeof(p));
         ASSERT_EQ(0, celix_json_pointer_init(&p, ptr_str)) << "Failed: " << ptr_str;
         json_t* v = celix_json_pointer_get(doc, &p);
         ASSERT_NE(nullptr, v) << "Missing: " << ptr_str;
@@ -476,7 +474,6 @@ TEST(PointerTest, Rfc6901Section5) {
 
 TEST(PointerTest, LeadingZeroInvalid) {
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     /* Leading zero in array index is invalid per RFC 6901 */
     EXPECT_NE(0, celix_json_pointer_init(&p, "/foo/01"));
 }
@@ -487,7 +484,6 @@ TEST(PointerTest, NonNumericArrayIndex) {
     json_t* doc = json_loads("[1,2,3]", JSON_DECODE_ANY, nullptr);
     ASSERT_NE(nullptr, doc);
 
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/one"));
     EXPECT_EQ(nullptr, celix_json_pointer_get(doc, &p));
 
@@ -500,7 +496,6 @@ TEST(PointerTest, ArithmeticInIndex) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/1+1"));
     EXPECT_EQ(nullptr, celix_json_pointer_get(doc, &p));
 
@@ -512,17 +507,14 @@ TEST(PointerTest, ArithmeticInIndex) {
 
 TEST(PointerTest, InvalidEscapeSequence) {
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     /* "~~" — stray tilde not followed by valid escape */
     EXPECT_NE(0, celix_json_pointer_init(&p, "/foo/~~"));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     /* "~" at end — tilde with nothing after */
     EXPECT_NE(0, celix_json_pointer_init(&p, "/foo/~"));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     /* "~2" — invalid escape character */
     EXPECT_NE(0, celix_json_pointer_init(&p, "/foo/~2"));
     celix_json_pointer_clear(&p);
@@ -535,27 +527,23 @@ TEST(PointerTest, ArrayAccessByIndex) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/0"));
     json_t* v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
     EXPECT_EQ(10, json_integer_value(v));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/1"));
     v = celix_json_pointer_get(doc, &p);
     EXPECT_EQ(20, json_integer_value(v));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/2"));
     v = celix_json_pointer_get(doc, &p);
     EXPECT_EQ(30, json_integer_value(v));
     celix_json_pointer_clear(&p);
 
     /* Out of bounds */
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/3"));
     EXPECT_EQ(nullptr, celix_json_pointer_get(doc, &p));
     celix_json_pointer_clear(&p);
@@ -570,14 +558,12 @@ TEST(PointerTest, NestedArrayAccess) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/0/1"));
     json_t* v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
     EXPECT_EQ(2, json_integer_value(v));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/1/0"));
     v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
@@ -594,7 +580,6 @@ TEST(PointerTest, SetOverwritesExisting) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/b"));
 
     EXPECT_EQ(0, celix_json_pointer_set(doc, &p, json_integer(99)));
@@ -606,7 +591,6 @@ TEST(PointerTest, SetOverwritesExisting) {
 
     /* 'a' should still be 1 */
     celix_json_pointer_t pa;
-    memset(&pa, 0, sizeof(pa));
     ASSERT_EQ(0, celix_json_pointer_init(&pa, "/a"));
     v = celix_json_pointer_get(doc, &pa);
     EXPECT_EQ(1, json_integer_value(v));
@@ -623,14 +607,12 @@ TEST(PointerTest, SetArrayWithGap) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/arr/4"));
 
     EXPECT_EQ(0, celix_json_pointer_set(doc, &p, json_integer(99)));
 
     /* Verify array was extended with nulls */
     celix_json_pointer_t pa;
-    memset(&pa, 0, sizeof(pa));
     ASSERT_EQ(0, celix_json_pointer_init(&pa, "/arr"));
     json_t* arr = celix_json_pointer_get(doc, &pa);
     ASSERT_NE(nullptr, arr);
@@ -652,7 +634,6 @@ TEST(PointerTest, DashIndexGetReturnsNull) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/-"));
     EXPECT_EQ(nullptr, celix_json_pointer_get(doc, &p));
 
@@ -667,7 +648,6 @@ TEST(PointerTest, ResolveIntoScalar) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
 
     /* "/a" is 42 (scalar) — can't descend further */
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/a/foo"));
@@ -689,7 +669,6 @@ TEST(PointerTest, RemoveNonExistent) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/x"));
     EXPECT_NE(0, celix_json_pointer_remove(doc, &p)); /* should fail */
 
@@ -717,7 +696,6 @@ TEST(PointerTest, RemoveRootReturnsError) {
 
 TEST(PointerTest, TokenFrontBack) {
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/a/b/c"));
 
     /* token(0) is front, token(depth-1) is back */
@@ -741,7 +719,6 @@ TEST(PointerTest, PointerOnScalarRoot) {
     celix_json_pointer_clear(&p);
 
     /* Non-root pointer on scalar returns NULL */
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/foo"));
     EXPECT_EQ(nullptr, celix_json_pointer_get(doc, &p));
     celix_json_pointer_clear(&p);
@@ -755,7 +732,6 @@ TEST(PointerTest, SetCreatesArrayIntermediate) {
     /* set handles multi-level paths with array creation better */
     json_t* doc = json_object();
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/list/0/item"));
 
     EXPECT_EQ(0, celix_json_pointer_set(doc, &p, json_string("hello")));
@@ -771,7 +747,6 @@ TEST(PointerTest, SetCreatesArrayIntermediate) {
 TEST(PointerTest, GetOrCreateSimpleArray) {
     json_t* doc = json_object();
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     /* "/arr" creates an object, then push "2" — but get_or_create uses token heuristic:
      * "arr" → object (non-numeric), "2" → object key in that object.
      * For true array creation, use set which has the "look ahead" heuristic. */
@@ -789,7 +764,6 @@ TEST(PointerTest, GetOrCreateSimpleArray) {
 
     /* "/arr/2" → extend and return new null */
     celix_json_pointer_clear(&p);
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/arr/3"));
     node = celix_json_pointer_get_or_create(doc, &p);
     ASSERT_NE(nullptr, node);
@@ -808,7 +782,6 @@ TEST(PointerTest, GetOrCreateExisting) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/a/b"));
 
     json_t* v = celix_json_pointer_get_or_create(doc, &p);
@@ -831,7 +804,6 @@ TEST(PointerTest, EscapeOrderMatters) {
     /* "~01" in a pointer string means: unescape ~0→~, then the '1' is just '1'
      * So "/m~01n" should resolve to the key "m~1n", NOT "m~0" + "1n" or anything else. */
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/m~01n"));
     EXPECT_EQ(1u, celix_json_pointer_depth(&p));
     /* ~0 → ~, then 1 remains as-is */
@@ -847,7 +819,6 @@ TEST(PointerTest, EscapedPathResolution) {
 
     /* "/a~1b" should resolve to key "a/b" */
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/a~1b"));
     json_t* v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
@@ -855,7 +826,6 @@ TEST(PointerTest, EscapedPathResolution) {
     celix_json_pointer_clear(&p);
 
     /* "/m~0n" should resolve to key "m~n" */
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/m~0n"));
     v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
@@ -873,7 +843,6 @@ TEST(PointerTest, EmptyToken) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/foo/"));
     EXPECT_EQ(2u, celix_json_pointer_depth(&p));
     EXPECT_STREQ("foo", celix_json_pointer_token(&p, 0));
@@ -895,7 +864,6 @@ TEST(PointerTest, PercentEncodingIsLiteral) {
     ASSERT_NE(nullptr, doc);
 
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/%25"));
     json_t* v = celix_json_pointer_get(doc, &p);
     ASSERT_NE(nullptr, v);
@@ -910,7 +878,6 @@ TEST(PointerTest, PercentEncodingIsLiteral) {
 TEST(PointerTest, SetCreatesIntermediateObjects) {
     json_t* doc = json_object();
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/config/database/host"));
 
     EXPECT_EQ(0, celix_json_pointer_set(doc, &p, json_string("localhost")));
@@ -927,8 +894,6 @@ TEST(PointerTest, SetCreatesIntermediateObjects) {
 
 TEST(PointerTest, CompareDifferentOrder) {
     celix_json_pointer_t a, b;
-    memset(&a, 0, sizeof(a));
-    memset(&b, 0, sizeof(b));
     celix_json_pointer_init(&a, "/a/b");
     celix_json_pointer_init(&b, "/b/a");
     EXPECT_FALSE(celix_json_pointer_equals(&a, &b));
@@ -938,8 +903,6 @@ TEST(PointerTest, CompareDifferentOrder) {
 
 TEST(PointerTest, CompareDifferentLength) {
     celix_json_pointer_t a, b;
-    memset(&a, 0, sizeof(a));
-    memset(&b, 0, sizeof(b));
     celix_json_pointer_init(&a, "/a/b");
     celix_json_pointer_init(&b, "/a/b/c");
     EXPECT_FALSE(celix_json_pointer_equals(&a, &b));
@@ -967,7 +930,6 @@ TEST(PointerTest, PushWithSlashReEncoded) {
 TEST(PointerTest, SetNewIncrementsRef) {
     json_t* doc = json_object();
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/x"));
 
     json_t* val = json_string("hello");
@@ -989,7 +951,6 @@ TEST(PointerTest, SetNewIncrementsRef) {
 
 TEST(PointerTest, StackReuse) {
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
 
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/a/b"));
     EXPECT_EQ(2u, celix_json_pointer_depth(&p));
@@ -1056,7 +1017,6 @@ TEST(PointerTest, Contains) {
 
 TEST(PointerTest, ContainsNullDoc) {
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/x"));
 
     /* NULL doc is safe — treated as not containing */
@@ -1096,13 +1056,11 @@ TEST(PointerTest, InitLeadingZeroNonNumeric) {
     /* Leading-zero token containing a non-digit is NOT an RFC 6901 array index —
      * it is a plain object key, so init must succeed. */
     celix_json_pointer_t p;
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/0a"));
     EXPECT_EQ(1u, celix_json_pointer_depth(&p));
     EXPECT_STREQ("0a", celix_json_pointer_token(&p, 0));
     celix_json_pointer_clear(&p);
 
-    memset(&p, 0, sizeof(p));
     ASSERT_EQ(0, celix_json_pointer_init(&p, "/01a"));
     EXPECT_EQ(1u, celix_json_pointer_depth(&p));
     EXPECT_STREQ("01a", celix_json_pointer_token(&p, 0));
@@ -1552,3 +1510,54 @@ TEST(PointerTest, CopyLongPointer) {
     celix_json_pointer_destroy(p);
     celix_json_pointer_destroy(copy);
 }
+
+/* ── Automatic cleanup (celix_auto / celix_autoptr) ──────────────────────── */
+
+TEST(PointerTest, AutoptrCreate) {
+    /* celix_autoptr → celix_json_pointer_destroy() runs at scope exit. */
+    celix_autoptr(celix_json_pointer_t) p = celix_json_pointer_create("/a/b");
+    ASSERT_NE(nullptr, p);
+    EXPECT_EQ(2u, celix_json_pointer_depth(p));
+    EXPECT_STREQ("a", celix_json_pointer_token(p, 0));
+    EXPECT_STREQ("b", celix_json_pointer_token(p, 1));
+
+    celix_autoptr(celix_json_pointer_t) root = celix_json_pointer_create("");
+    ASSERT_NE(nullptr, root);
+    EXPECT_EQ(0u, celix_json_pointer_depth(root));
+}
+
+TEST(PointerTest, AutoptrNullIsSafe) {
+    /* "/a~" is an invalid escape → create fails → NULL.
+     * The cleanup function must skip NULL values without crashing. */
+    celix_autoptr(celix_json_pointer_t) p = celix_json_pointer_create("/a~");
+    EXPECT_EQ(nullptr, p);
+}
+
+TEST(PointerTest, AutoInit) {
+    /* celix_auto → celix_json_pointer_clear() runs at scope exit. */
+    celix_auto(celix_json_pointer_t) p;
+    ASSERT_EQ(0, celix_json_pointer_init(&p, "/a/b"));
+    EXPECT_EQ(2u, celix_json_pointer_depth(&p));
+    EXPECT_STREQ("b", celix_json_pointer_token(&p, 1));
+
+    ASSERT_EQ(0, celix_json_pointer_push(&p, "c"));
+    EXPECT_EQ(3u, celix_json_pointer_depth(&p));
+    celix_json_pointer_pop(&p);
+    EXPECT_EQ(2u, celix_json_pointer_depth(&p));
+}
+
+TEST(PointerTest, AutoInitFailureIsSafe) {
+    /* Failed init leaves the struct cleared; scope exit calls clear()
+     * again on the zeroed struct — must be a no-op. */
+    celix_auto(celix_json_pointer_t) p;
+    EXPECT_EQ(-1, celix_json_pointer_init(&p, "/a~"));
+    EXPECT_EQ(0u, celix_json_pointer_depth(&p));
+}
+
+TEST(PointerTest, AutoZeroedStructSafe) {
+    /* clear() on a fully zeroed struct must be safe — the property
+     * celix_auto relies on for its scope-exit cleanup. */
+    celix_auto(celix_json_pointer_t) p{};
+    EXPECT_EQ(nullptr, p.tokens);
+    EXPECT_EQ(0u, p.len);
+}
diff --git a/libs/jansson_ext/include/celix_jansson_pointer.h b/libs/jansson_ext/include/celix_jansson_pointer.h
index 629d48889..b086c6460 100644
--- a/libs/jansson_ext/include/celix_jansson_pointer.h
+++ b/libs/jansson_ext/include/celix_jansson_pointer.h
@@ -17,10 +17,10 @@
  * under the License.
  */
 #ifndef CELIX_CELIX_JANSSON_POINTER_H
-
-#include "celix_jansson_ext_export.h"
 #define CELIX_CELIX_JANSSON_POINTER_H
 
+#include "celix_cleanup.h"
+#include "celix_jansson_ext_export.h"
 #include <jansson.h>
 #include <stddef.h>
 
@@ -65,16 +65,18 @@ CELIX_JANSSON_EXT_EXPORT celix_json_pointer_t* celix_json_pointer_create(const c
 /**
  * Initialize a stack-allocated pointer from a string.
  *
- * The pointer struct must be zeroed (e.g., with memset) before the first
- * call to this function.  Re-initialization is safe after calling
- * celix_json_pointer_clear() — the struct does not need to be re-zeroed.
+ * The struct is zeroed first, so the initial call is safe on uninitialized
+ * stack memory — no pre-zeroing (memset) is required.  However, to
+ * re-initialize a pointer that already holds data, call
+ * celix_json_pointer_clear() first; init only zeroes the struct and does not
+ * free previously allocated tokens, so re-init without clear leaks.
  *
  * On error (-1), all partially allocated resources are automatically
  * cleaned up and the pointer is left in a cleared (empty) state.
  * The caller does NOT need to call celix_json_pointer_clear() after a
  * failed init.
  *
- * @param ptr      Pointer to stack-allocated celix_json_pointer_t (must be zeroed first)
+ * @param ptr      Pointer to stack-allocated celix_json_pointer_t
  * @param ptr_str  RFC 6901 pointer string (must start with '/', or be "" for root)
  * @return 0 on success, -1 on parse failure or allocation error
  */
@@ -100,6 +102,12 @@ CELIX_JANSSON_EXT_EXPORT void celix_json_pointer_destroy(celix_json_pointer_t* p
  */
 CELIX_JANSSON_EXT_EXPORT void celix_json_pointer_clear(celix_json_pointer_t* ptr);
 
+/**
+ * @brief Add support for `celix_autoptr` and `celix_auto`.
+ */
+CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_json_pointer_t, celix_json_pointer_destroy)
+CELIX_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(celix_json_pointer_t, celix_json_pointer_clear)
+
 /* ── Inspection ────────────────────────────────────────────────────────── */
 
 /**
diff --git a/libs/jansson_ext/include/celix_json_patch.h b/libs/jansson_ext/include/celix_json_patch.h
index 2af6779f1..2694b9c26 100644
--- a/libs/jansson_ext/include/celix_json_patch.h
+++ b/libs/jansson_ext/include/celix_json_patch.h
@@ -17,9 +17,9 @@
  * under the License.
  */
 #ifndef CELIX_CELIX_JSON_PATCH_H
+#define CELIX_CELIX_JSON_PATCH_H
 
 #include "celix_jansson_ext_export.h"
-#define CELIX_CELIX_JSON_PATCH_H
 
 #include <jansson.h>
 #include <stddef.h>
@@ -32,14 +32,18 @@ extern "C" {
  * Append an "add" operation (RFC 6902) to the patch array.
  *
  * The patch array is modified in-place.  The @p value is consumed —
- * ownership is transferred to the patch (steal semantics).
+ * ownership is transferred to the patch (steal semantics).  On failure
+ * (invalid @p patch, or out-of-memory while building the operation) the
+ * @p value is released and ownership is not transferred.
  * The stored value will be deep-copied later when the patch is applied
  * via celix_json_patch_apply().
  *
  * @param patch     The JSON Patch array (json_t* array, modified in-place)
  * @param path_str  JSON Pointer path for the operation (e.g., "/a/b")
- * @param value     The value to set at the path (ownership is taken)
- * @return 0 on success, -1 if @p patch is NULL or not an array
+ * @param value     The value to set at the path (ownership is taken on
+ *                  success, released on failure)
+ * @return 0 on success, -1 if @p patch is NULL or not an array, or on
+ *         out-of-memory
  */
 CELIX_JANSSON_EXT_EXPORT int celix_json_patch_add(json_t* patch, const char* path_str, json_t* value);
 
@@ -47,13 +51,17 @@ CELIX_JANSSON_EXT_EXPORT int celix_json_patch_add(json_t* patch, const char* pat
  * Append a "replace" operation (RFC 6902) to the patch array.
  *
  * Like celix_json_patch_add(), @p value is consumed — ownership is
- * transferred to the patch.  At application time the value is deep-copied
- * into the target document.
+ * transferred to the patch.  On failure (invalid @p patch, or
+ * out-of-memory while building the operation) the @p value is released
+ * and ownership is not transferred.
+ * At application time the value is deep-copied into the target document.
  *
  * @param patch     The JSON Patch array (json_t* array, modified in-place)
  * @param path_str  JSON Pointer path for the operation (e.g., "/a/b")
- * @param value     The new value (ownership is taken)
- * @return 0 on success, -1 if @p patch is NULL or not an array
+ * @param value     The new value (ownership is taken on success, released
+ *                  on failure)
+ * @return 0 on success, -1 if @p patch is NULL or not an array, or on
+ *         out-of-memory
  */
 CELIX_JANSSON_EXT_EXPORT int celix_json_patch_replace(json_t* patch, const char* path_str, json_t* value);
 
@@ -62,7 +70,8 @@ CELIX_JANSSON_EXT_EXPORT int celix_json_patch_replace(json_t* patch, const char*
  *
  * @param patch     The JSON Patch array (json_t* array, modified in-place)
  * @param path_str  JSON Pointer path of the value to remove
- * @return 0 on success, -1 if @p patch is NULL or not an array
+ * @return 0 on success, -1 if @p patch is NULL or not an array, or on
+ *         out-of-memory
  */
 CELIX_JANSSON_EXT_EXPORT int celix_json_patch_remove(json_t* patch, const char* path_str);
 
@@ -89,7 +98,8 @@ CELIX_JANSSON_EXT_EXPORT void celix_json_patch_truncate(json_t* patch, size_t ol
  * @param original  The original JSON document (not modified)
  * @param patch     JSON Patch array (e.g., from celix_jansson_schema_validate)
  * @return A new json_t* with the patch applied, or NULL on error.
- *         The caller must json_decref() the result.
+ *         On out-of-memory the partially built result is released and
+ *         NULL is returned.  The caller must json_decref() the result.
  */
 CELIX_JANSSON_EXT_EXPORT json_t* celix_json_patch_apply(json_t* original, json_t* patch);
 
diff --git a/libs/jansson_ext/src/celix_jansson_pointer.c b/libs/jansson_ext/src/celix_jansson_pointer.c
index 47c6b0eb6..15d0efc9b 100644
--- a/libs/jansson_ext/src/celix_jansson_pointer.c
+++ b/libs/jansson_ext/src/celix_jansson_pointer.c
@@ -53,7 +53,8 @@ celix_json_pointer_t* celix_json_pointer_create(const char* ptr_str) {
 int celix_json_pointer_init(celix_json_pointer_t* p, const char* ptr_str) {
     if (!p)
         return -1;
-    celix_json_pointer_clear(p);
+    memset(p, 0, sizeof(*p)); /* safe on uninitialized stack memory; callers
+                                 reusing a live pointer must clear() it first */
     if (!ptr_str || *ptr_str == '\0')
         return 0;
     if (*ptr_str != '/')
diff --git a/libs/jansson_ext/src/celix_jansson_schema.c b/libs/jansson_ext/src/celix_jansson_schema.c
index 6f646b673..bd601fb0a 100644
--- a/libs/jansson_ext/src/celix_jansson_schema.c
+++ b/libs/jansson_ext/src/celix_jansson_schema.c
@@ -2155,7 +2155,6 @@ static celix_jansson_schema_node_t* resolve_document_fragment(
     /* Re-attach fragment to the URI */
     if (fragment[0] == '/') {
         celix_json_pointer_t ptr;
-        memset(&ptr, 0, sizeof(ptr));
         if (celix_json_pointer_init(&ptr, fragment) == 0) {
             bool push_ok = true;
             for (size_t i = 0; i < ptr.len; i++) {
diff --git a/libs/jansson_ext/src/celix_json_patch.c b/libs/jansson_ext/src/celix_json_patch.c
index 389ab73b3..91c352b53 100644
--- a/libs/jansson_ext/src/celix_json_patch.c
+++ b/libs/jansson_ext/src/celix_json_patch.c
@@ -19,6 +19,7 @@
 #include "celix_json_patch.h"
 #include "celix_jansson_schema.h"
 #include "celix_jansson_pointer.h"
+#include "celix_cleanup.h"
 #include <stdlib.h>
 #include <string.h>
 
@@ -26,44 +27,83 @@ int celix_json_patch_add(json_t* patch, const char* path_str, json_t* value) {
     if (!patch || !json_is_array(patch))
         return -1;
 
-    json_t* op = json_object();
+    /* val/op are auto-released on every early return */
+    json_auto_t* val = value;
+    json_auto_t* op = json_object();
     if (!op)
         return -1;
 
-    json_object_set_new(op, "op", json_string("add"));
-    json_object_set_new(op, "path", json_string(path_str));
-    json_object_set_new(op, "value", value);
+    json_auto_t* op_name = json_string("add");
+    if (!op_name)
+        return -1;
+    if (json_object_set_new(op, "op", celix_steal_ptr(op_name)) != 0)
+        return -1;
+
+    json_auto_t* path = json_string(path_str);
+    if (!path)
+        return -1;
+    if (json_object_set_new(op, "path", celix_steal_ptr(path)) != 0)
+        return -1;
+
+    if (json_object_set_new(op, "value", celix_steal_ptr(val)) != 0)
+        return -1;
 
-    return json_array_append_new(patch, op);
+    /* on failure the op (and with it the value) is consumed */
+    return json_array_append_new(patch, celix_steal_ptr(op));
 }
 
 int celix_json_patch_replace(json_t* patch, const char* path_str, json_t* value) {
     if (!patch || !json_is_array(patch))
         return -1;
 
-    json_t* op = json_object();
+    /* val/op are auto-released on every early return */
+    json_auto_t* val = value;
+    json_auto_t* op = json_object();
     if (!op)
         return -1;
 
-    json_object_set_new(op, "op", json_string("replace"));
-    json_object_set_new(op, "path", json_string(path_str));
-    json_object_set_new(op, "value", value);
+    json_auto_t* op_name = json_string("replace");
+    if (!op_name)
+        return -1;
+    if (json_object_set_new(op, "op", celix_steal_ptr(op_name)) != 0)
+        return -1;
+
+    json_auto_t* path = json_string(path_str);
+    if (!path)
+        return -1;
+    if (json_object_set_new(op, "path", celix_steal_ptr(path)) != 0)
+        return -1;
 
-    return json_array_append_new(patch, op);
+    if (json_object_set_new(op, "value", celix_steal_ptr(val)) != 0)
+        return -1;
+
+    /* on failure the op (and with it the value) is consumed */
+    return json_array_append_new(patch, celix_steal_ptr(op));
 }
 
 int celix_json_patch_remove(json_t* patch, const char* path_str) {
     if (!patch || !json_is_array(patch))
         return -1;
 
-    json_t* op = json_object();
+    /* op is auto-released on every early return */
+    json_auto_t* op = json_object();
     if (!op)
         return -1;
 
-    json_object_set_new(op, "op", json_string("remove"));
-    json_object_set_new(op, "path", json_string(path_str));
+    json_auto_t* op_name = json_string("remove");
+    if (!op_name)
+        return -1;
+    if (json_object_set_new(op, "op", celix_steal_ptr(op_name)) != 0)
+        return -1;
 
-    return json_array_append_new(patch, op);
+    json_auto_t* path = json_string(path_str);
+    if (!path)
+        return -1;
+    if (json_object_set_new(op, "path", celix_steal_ptr(path)) != 0)
+        return -1;
+
+    /* on failure the op is consumed */
+    return json_array_append_new(patch, celix_steal_ptr(op));
 }
 
 void celix_json_patch_truncate(json_t* patch, size_t old_size) {
@@ -80,7 +120,8 @@ json_t* celix_json_patch_apply(json_t* original, json_t* patch) {
     if (!original || !patch || !json_is_array(patch))
         return NULL;
 
-    json_t* result = json_deep_copy(original);
+    /* result is auto-released on the error path */
+    json_auto_t* result = json_deep_copy(original);
     if (!result)
         return NULL;
 
@@ -94,22 +135,22 @@ json_t* celix_json_patch_apply(json_t* original, json_t* patch) {
         if (!op_type || !path_str)
             continue;
 
-        /* Parse the path */
-        celix_json_pointer_t ptr;
-        memset(&ptr, 0, sizeof(ptr));
+        /* Parse the path; ptr is auto-cleared when the loop body exits,
+         * including on continue and on the error jump below */
+        celix_auto(celix_json_pointer_t) ptr;
         if (celix_json_pointer_init(&ptr, path_str) != 0)
             continue;
 
         if (strcmp(op_type, "add") == 0 || strcmp(op_type, "replace") == 0) {
-            if (!value) {
-                celix_json_pointer_clear(&ptr);
+            if (!value)
                 continue;
-            }
 
             if (ptr.len == 0) {
-                /* Root replacement */
+                /* Root replacement: copy first, then swap the ownership */
                 json_decref(result);
                 result = json_deep_copy(value);
+                if (!result)
+                    return NULL;
             } else {
                 /* Find parent */
                 const char* last = ptr.tokens[ptr.len - 1];
@@ -121,19 +162,32 @@ json_t* celix_json_patch_apply(json_t* original, json_t* patch) {
                         child = json_object_get(parent, ptr.tokens[j]);
                         if (!child) {
                             child = json_object();
-                            json_object_set_new(parent, ptr.tokens[j], child);
+                            if (!child)
+                                return NULL;
+                            /* child is consumed by a failed set_new */
+                            if (json_object_set_new(parent, ptr.tokens[j], child) != 0)
+                                return NULL;
                         }
                     } else if (json_is_array(parent)) {
                         char* end;
                         long idx = strtol(ptr.tokens[j], &end, 10);
                         if (*end != '\0')
                             break;
-                        while ((size_t)idx > json_array_size(parent))
-                            json_array_append_new(parent, json_null());
+                        while ((size_t)idx > json_array_size(parent)) {
+                            json_t* pad = json_null();
+                            if (!pad)
+                                return NULL;
+                            /* pad is consumed by a failed append */
+                            if (json_array_append_new(parent, pad) != 0)
+                                return NULL;
+                        }
                         child = json_array_get(parent, (size_t)idx);
                         if (!child) {
                             child = json_object();
-                            json_array_append_new(parent, child);
+                            if (!child)
+                                return NULL;
+                            if (json_array_append_new(parent, child) != 0)
+                                return NULL;
                         }
                     }
                     parent = child;
@@ -142,31 +196,57 @@ json_t* celix_json_patch_apply(json_t* original, json_t* patch) {
                 }
 
                 if (parent && json_is_object(parent)) {
-                    if (strcmp(op_type, "replace") == 0 && json_object_get(parent, last))
-                        json_object_set(parent, last, value);
-                    else if (strcmp(op_type, "add") == 0)
-                        json_object_set(parent, last, value);
+                    if (strcmp(op_type, "replace") == 0 && json_object_get(parent, last)) {
+                        /* the incref'd copy is consumed by a failed set_new */
+                        if (json_object_set_new(parent, last, json_incref(value)) != 0)
+                            return NULL;
+                    } else if (strcmp(op_type, "add") == 0) {
+                        /* the incref'd copy is consumed by a failed set_new */
+                        if (json_object_set_new(parent, last, json_incref(value)) != 0)
+                            return NULL;
+                    }
                 } else if (parent && json_is_array(parent)) {
                     char* end;
                     long idx = strtol(last, &end, 10);
                     if (*end == '\0' && idx >= 0) {
-                        if (strcmp(op_type, "replace") == 0 && (size_t)idx < json_array_size(parent))
-                            json_array_set_new(parent, (size_t)idx, json_deep_copy(value));
-                        else if (strcmp(op_type, "add") == 0) {
-                            while ((size_t)idx > json_array_size(parent))
-                                json_array_append_new(parent, json_null());
-                            if ((size_t)idx == json_array_size(parent))
-                                json_array_append(parent, value);
-                            else
-                                json_array_insert_new(parent, (size_t)idx, json_deep_copy(value));
+                        if (strcmp(op_type, "replace") == 0 && (size_t)idx < json_array_size(parent)) {
+                            json_t* copy = json_deep_copy(value);
+                            if (!copy)
+                                return NULL;
+                            /* copy is consumed by a failed set_new */
+                            if (json_array_set_new(parent, (size_t)idx, copy) != 0)
+                                return NULL;
+                        } else if (strcmp(op_type, "add") == 0) {
+                            while ((size_t)idx > json_array_size(parent)) {
+                                json_t* pad = json_null();
+                                if (!pad)
+                                    return NULL;
+                                if (json_array_append_new(parent, pad) != 0)
+                                    return NULL;
+                            }
+                            if ((size_t)idx == json_array_size(parent)) {
+                                /* the incref'd copy is consumed by a failed append */
+                                if (json_array_append_new(parent, json_incref(value)) != 0)
+                                    return NULL;
+                            } else {
+                                json_t* copy = json_deep_copy(value);
+                                if (!copy)
+                                    return NULL;
+                                /* copy is consumed by a failed insert_new */
+                                if (json_array_insert_new(parent, (size_t)idx, copy) != 0)
+                                    return NULL;
+                            }
                         }
                     }
                 }
             }
         } else if (strcmp(op_type, "remove") == 0) {
             if (ptr.len == 0) {
+                /* Root removal: create the null first, then swap the ownership */
                 json_decref(result);
                 result = json_null();
+                if (!result)
+                    return NULL;
             } else {
                 const char* last = ptr.tokens[ptr.len - 1];
                 json_t* parent = result;
@@ -190,9 +270,7 @@ json_t* celix_json_patch_apply(json_t* original, json_t* patch) {
                 }
             }
         }
-
-        celix_json_pointer_clear(&ptr);
     }
 
-    return result;
+    return celix_steal_ptr(result);
 }