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);
}