From 69b85eaaef73800f0bcf07095f7b43126cb00de7 Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:43:51 +0200 Subject: [PATCH 1/6] add sentry_merge_context --- include/sentry.h | 8 +++++++ src/sentry_core.c | 29 ++++++++++++++++++++++ tests/unit/test_scope.c | 53 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 1 + 4 files changed, 91 insertions(+) diff --git a/include/sentry.h b/include/sentry.h index 28c3cde0ce..e12b4330b0 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2290,6 +2290,14 @@ SENTRY_API void sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value); SENTRY_API void sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value); +/** + * Merges a context object with the existing context object. + * Existing keys within the context object will not be overwritten. + * If the context object does not exist yet, it will be created. + */ +SENTRY_API void sentry_merge_context(const char *key, sentry_value_t value); +SENTRY_API void sentry_merge_context_n( + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. diff --git a/src/sentry_core.c b/src/sentry_core.c index 6fdc4116dd..a4448588a4 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1075,6 +1075,35 @@ sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) } } +void +sentry_merge_context(const char *key, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_t context = sentry_value_get_by_key(scope->contexts, key); + if (sentry_value_is_null(context)) { + sentry_value_set_by_key(scope->contexts, key, value); + } else { + sentry__value_merge_objects(context, value); + sentry_value_decref(value); + } + } +} + +void +sentry_merge_context_n(const char *key, size_t key_len, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_t context + = sentry_value_get_by_key_n(scope->contexts, key, key_len); + if (sentry_value_is_null(context)) { + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + } else { + sentry__value_merge_objects(context, value); + sentry_value_decref(value); + } + } +} + void sentry__set_propagation_context(const char *key, sentry_value_t value) { diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index fa74592f20..6ec7cb9789 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -100,6 +100,59 @@ SENTRY_TEST(scope_contexts) sentry_close(); } +SENTRY_TEST(scope_merge_context) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + // merge into empty: context does not exist yet + { + sentry_value_t device = sentry_value_new_object(); + sentry_value_set_by_key( + device, "model", sentry_value_new_string("Xbox Series X")); + sentry_value_set_by_key( + device, "family", sentry_value_new_string("Xbox")); + sentry_merge_context("device", device); + + SENTRY_WITH_SCOPE (scope) { + sentry_value_t ctx + = sentry_value_get_by_key(scope->contexts, "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), + "Xbox Series X"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), + "Xbox"); + } + } + + // merge does not overwrite existing keys + { + sentry_value_t extra = sentry_value_new_object(); + sentry_value_set_by_key(extra, "model", sentry_value_new_string("PC")); + sentry_value_set_by_key( + extra, "cpu_description", sentry_value_new_string("some cpu")); + sentry_merge_context("device", extra); + + SENTRY_WITH_SCOPE (scope) { + sentry_value_t ctx + = sentry_value_get_by_key(scope->contexts, "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), + "Xbox Series X"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), + "Xbox"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string( + sentry_value_get_by_key(ctx, "cpu_description")), + "some cpu"); + } + } + + sentry_close(); +} + SENTRY_TEST(scope_extra) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 8adce0aadf..b0ca464afe 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -284,6 +284,7 @@ XX(sampling_decision) XX(sampling_transaction) XX(scope_breadcrumbs) XX(scope_contexts) +XX(scope_merge_context) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n) From 75414e3ae2091529c67370250d09c1c9c009ae90 Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:47:40 +0200 Subject: [PATCH 2/6] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dbc8549ed..1137195b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features** - Android: Expose app-hang detection through the NDK bindings via `NdkOptions.setEnableAppHangTracking()` and `NdkOptions.setAppHangTimeoutMillis()`. ([#1823](https://github.com/getsentry/sentry-native/pull/1823)) +- Add `sentry_merge_context` to avoid overwriting existing keys in already set context objects. ([#1835](https://github.com/getsentry/sentry-native/pull/1835)) **Fixes**: From 4d36ebabb3d71de88f524eb2805722ef70325a0e Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:30:45 +0200 Subject: [PATCH 3/6] avoid unnecessary repeated code --- src/sentry_core.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 06fdd47fb9..8c86eaf75a 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1078,15 +1078,7 @@ sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) void sentry_merge_context(const char *key, sentry_value_t value) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t context = sentry_value_get_by_key(scope->contexts, key); - if (sentry_value_is_null(context)) { - sentry_value_set_by_key(scope->contexts, key, value); - } else { - sentry__value_merge_objects(context, value); - sentry_value_decref(value); - } - } + sentry_merge_context_n(key, sentry__guarded_strlen(key), value); } void From 6be9f0bdec874be38a4cad2acb8ee17a844f18e0 Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:42:10 +0200 Subject: [PATCH 4/6] add scoped variants --- include/sentry.h | 4 ++++ src/sentry_core.c | 13 ++++--------- src/sentry_scope.c | 22 ++++++++++++++++++++++ tests/unit/test_scope.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 9ddb5a349d..6bc93c373b 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2308,6 +2308,10 @@ SENTRY_API void sentry_scope_set_context_n(sentry_scope_t *scope, SENTRY_API void sentry_merge_context(const char *key, sentry_value_t value); SENTRY_API void sentry_merge_context_n( const char *key, size_t key_len, sentry_value_t value); +SENTRY_API void sentry_scope_merge_context( + sentry_scope_t *scope, const char *key, sentry_value_t value); +SENTRY_API void sentry_scope_merge_context_n(sentry_scope_t *scope, + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. diff --git a/src/sentry_core.c b/src/sentry_core.c index 8c86eaf75a..d04a078f41 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1078,21 +1078,16 @@ sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) void sentry_merge_context(const char *key, sentry_value_t value) { - sentry_merge_context_n(key, sentry__guarded_strlen(key), value); + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_merge_context(scope, key, value); + } } void sentry_merge_context_n(const char *key, size_t key_len, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t context - = sentry_value_get_by_key_n(scope->contexts, key, key_len); - if (sentry_value_is_null(context)) { - sentry_value_set_by_key_n(scope->contexts, key, key_len, value); - } else { - sentry__value_merge_objects(context, value); - sentry_value_decref(value); - } + sentry_scope_merge_context_n(scope, key, key_len, value); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 4ea51b60e8..5fb7c0a27b 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -599,6 +599,28 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } +void +sentry_scope_merge_context( + sentry_scope_t *scope, const char *key, sentry_value_t value) +{ + sentry_scope_merge_context_n( + scope, key, sentry__guarded_strlen(key), value); +} + +void +sentry_scope_merge_context_n(sentry_scope_t *scope, const char *key, + size_t key_len, sentry_value_t value) +{ + sentry_value_t context + = sentry_value_get_by_key_n(scope->contexts, key, key_len); + if (sentry_value_is_null(context)) { + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + } else { + sentry__value_merge_objects(context, value); + sentry_value_decref(value); + } +} + void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va) diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 6ec7cb9789..db003e9e0b 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -150,6 +150,37 @@ SENTRY_TEST(scope_merge_context) } } + // scoped merge into empty + { + sentry_scope_t *local_scope = sentry_local_scope_new(); + + sentry_value_t os = sentry_value_new_object(); + sentry_value_set_by_key(os, "name", sentry_value_new_string("SteamOS")); + sentry_scope_merge_context(local_scope, "os", os); + + sentry_value_t ctx + = sentry_value_get_by_key(local_scope->contexts, "os"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), + "SteamOS"); + + // scoped merge does not overwrite existing keys + sentry_value_t os2 = sentry_value_new_object(); + sentry_value_set_by_key(os2, "name", sentry_value_new_string("Linux")); + sentry_value_set_by_key(os2, "version", sentry_value_new_string("6.1")); + sentry_scope_merge_context(local_scope, "os", os2); + + ctx = sentry_value_get_by_key(local_scope->contexts, "os"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), + "SteamOS"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(ctx, "version")), + "6.1"); + + sentry__scope_free(local_scope); + } + sentry_close(); } From 8e3b7001d91084d63a2cadc4ee41b17b70b72ea3 Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:51:30 +0200 Subject: [PATCH 5/6] change `merge_context` to `update_context` - does the same but different, updating overwrites values of keys that collide & adds the new passed-in-value's keys to the context too. (before, it would NOT overwrite, only add) --- include/sentry.h | 15 ++++++++------- src/sentry_core.c | 8 ++++---- src/sentry_scope.c | 10 +++++----- tests/unit/test_scope.c | 22 +++++++++++----------- tests/unit/tests.inc | 2 +- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 6bc93c373b..2c41e9dfd2 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2301,16 +2301,17 @@ SENTRY_API void sentry_scope_set_context( SENTRY_API void sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value); /** - * Merges a context object with the existing context object. - * Existing keys within the context object will not be overwritten. - * If the context object does not exist yet, it will be created. + * Updates a context object by merging the provided values into it. + * Keys in `value` take precedence over keys in the context, updating the + * existing values. Keys in the existing context that are not present in `value` + * are preserved. If the context object does not exist yet, it will be created. */ -SENTRY_API void sentry_merge_context(const char *key, sentry_value_t value); -SENTRY_API void sentry_merge_context_n( +SENTRY_API void sentry_update_context(const char *key, sentry_value_t value); +SENTRY_API void sentry_update_context_n( const char *key, size_t key_len, sentry_value_t value); -SENTRY_API void sentry_scope_merge_context( +SENTRY_API void sentry_scope_update_context( sentry_scope_t *scope, const char *key, sentry_value_t value); -SENTRY_API void sentry_scope_merge_context_n(sentry_scope_t *scope, +SENTRY_API void sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value); /** diff --git a/src/sentry_core.c b/src/sentry_core.c index d04a078f41..e169f7308f 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1076,18 +1076,18 @@ sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) } void -sentry_merge_context(const char *key, sentry_value_t value) +sentry_update_context(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_scope_merge_context(scope, key, value); + sentry_scope_update_context(scope, key, value); } } void -sentry_merge_context_n(const char *key, size_t key_len, sentry_value_t value) +sentry_update_context_n(const char *key, size_t key_len, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_scope_merge_context_n(scope, key, key_len, value); + sentry_scope_update_context_n(scope, key, key_len, value); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 5fb7c0a27b..1a6f54a55a 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -600,15 +600,15 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, } void -sentry_scope_merge_context( +sentry_scope_update_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_scope_merge_context_n( + sentry_scope_update_context_n( scope, key, sentry__guarded_strlen(key), value); } void -sentry_scope_merge_context_n(sentry_scope_t *scope, const char *key, +sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { sentry_value_t context @@ -616,8 +616,8 @@ sentry_scope_merge_context_n(sentry_scope_t *scope, const char *key, if (sentry_value_is_null(context)) { sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } else { - sentry__value_merge_objects(context, value); - sentry_value_decref(value); + sentry__value_merge_objects(value, context); + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } } diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index db003e9e0b..0ac3951ff9 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -100,19 +100,19 @@ SENTRY_TEST(scope_contexts) sentry_close(); } -SENTRY_TEST(scope_merge_context) +SENTRY_TEST(scope_update_context) { SENTRY_TEST_OPTIONS_NEW(options); sentry_init(options); - // merge into empty: context does not exist yet + // update into empty: context does not exist yet { sentry_value_t device = sentry_value_new_object(); sentry_value_set_by_key( device, "model", sentry_value_new_string("Xbox Series X")); sentry_value_set_by_key( device, "family", sentry_value_new_string("Xbox")); - sentry_merge_context("device", device); + sentry_update_context("device", device); SENTRY_WITH_SCOPE (scope) { sentry_value_t ctx @@ -126,20 +126,20 @@ SENTRY_TEST(scope_merge_context) } } - // merge does not overwrite existing keys + // update should overwrite existing keys { sentry_value_t extra = sentry_value_new_object(); sentry_value_set_by_key(extra, "model", sentry_value_new_string("PC")); sentry_value_set_by_key( extra, "cpu_description", sentry_value_new_string("some cpu")); - sentry_merge_context("device", extra); + sentry_update_context("device", extra); SENTRY_WITH_SCOPE (scope) { sentry_value_t ctx = sentry_value_get_by_key(scope->contexts, "device"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "model")), - "Xbox Series X"); + "PC"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "family")), "Xbox"); @@ -150,13 +150,13 @@ SENTRY_TEST(scope_merge_context) } } - // scoped merge into empty + // scoped update into empty { sentry_scope_t *local_scope = sentry_local_scope_new(); sentry_value_t os = sentry_value_new_object(); sentry_value_set_by_key(os, "name", sentry_value_new_string("SteamOS")); - sentry_scope_merge_context(local_scope, "os", os); + sentry_scope_update_context(local_scope, "os", os); sentry_value_t ctx = sentry_value_get_by_key(local_scope->contexts, "os"); @@ -164,16 +164,16 @@ SENTRY_TEST(scope_merge_context) sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), "SteamOS"); - // scoped merge does not overwrite existing keys + // scoped update overwrites existing keys sentry_value_t os2 = sentry_value_new_object(); sentry_value_set_by_key(os2, "name", sentry_value_new_string("Linux")); sentry_value_set_by_key(os2, "version", sentry_value_new_string("6.1")); - sentry_scope_merge_context(local_scope, "os", os2); + sentry_scope_update_context(local_scope, "os", os2); ctx = sentry_value_get_by_key(local_scope->contexts, "os"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "name")), - "SteamOS"); + "Linux"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(ctx, "version")), "6.1"); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index bca3bc3b87..86addc6da0 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -285,7 +285,7 @@ XX(sampling_decision) XX(sampling_transaction) XX(scope_breadcrumbs) XX(scope_contexts) -XX(scope_merge_context) +XX(scope_update_context) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n) From 7ddd241ae7074b695974c8a20265e3a94dcabf47 Mon Sep 17 00:00:00 2001 From: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:52:24 +0200 Subject: [PATCH 6/6] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a0a54c2b..6897d20bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Android: Expose app-hang detection through the NDK bindings via `NdkOptions.setEnableAppHangTracking()` and `NdkOptions.setAppHangTimeoutMillis()`. ([#1823](https://github.com/getsentry/sentry-native/pull/1823)) - Windows: add wide-char versions of `sentry_capture_minidump` and `sentry_capture_minidump_n`. ([#1827](https://github.com/getsentry/sentry-native/pull/1827)) -- Add `sentry_merge_context` to avoid overwriting existing keys in already set context objects. ([#1835](https://github.com/getsentry/sentry-native/pull/1835)) +- Add `sentry_update_context` to allow updating already set context objects with new key-value pairs, as well as new values for existing keys. ([#1835](https://github.com/getsentry/sentry-native/pull/1835)) **Fixes**: