From ff438c4011cc64f79549864795d7850c61058a45 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 21 Aug 2026 09:42:34 +0200 Subject: [PATCH 1/2] feat(scope): add missing public mutators Add scope-specific release, environment, and transaction setters plus tag, extra, context, and attachment removals. Route the corresponding global APIs through the new functions. This makes reusable scope mutation more symmetric with the global scope API. Console SDKs currently update some of these fields through private scope access, coupling them to internals and making scope refactors (RW-locks) harder than it needs to be. :) --- include/sentry.h | 31 ++++++++- src/sentry_core.c | 56 +++------------ src/sentry_scope.c | 125 ++++++++++++++++++++++++++++++++++ tests/unit/test_attachments.c | 10 +++ tests/unit/test_scope.c | 75 ++++++++++++++++++++ tests/unit/tests.inc | 3 + 6 files changed, 251 insertions(+), 49 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 5e2cb5155b..e657c4a6e9 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2540,6 +2540,10 @@ SENTRY_API void sentry_remove_user(void); */ SENTRY_API void sentry_set_release(const char *release); SENTRY_API void sentry_set_release_n(const char *release, size_t release_len); +SENTRY_API void sentry_scope_set_release( + sentry_scope_t *scope, const char *release); +SENTRY_API void sentry_scope_set_release_n( + sentry_scope_t *scope, const char *release, size_t release_len); /** * Sets the environment after the SDK has been initialized. To apply the new @@ -2548,6 +2552,10 @@ SENTRY_API void sentry_set_release_n(const char *release, size_t release_len); SENTRY_API void sentry_set_environment(const char *environment); SENTRY_API void sentry_set_environment_n( const char *environment, size_t environment_len); +SENTRY_API void sentry_scope_set_environment( + sentry_scope_t *scope, const char *environment); +SENTRY_API void sentry_scope_set_environment_n( + sentry_scope_t *scope, const char *environment, size_t environment_len); /** * Sets a tag. @@ -2575,6 +2583,9 @@ SENTRY_API void sentry_scope_set_tags( */ SENTRY_API void sentry_remove_tag(const char *key); SENTRY_API void sentry_remove_tag_n(const char *key, size_t key_len); +SENTRY_API void sentry_scope_remove_tag(sentry_scope_t *scope, const char *key); +SENTRY_API void sentry_scope_remove_tag_n( + sentry_scope_t *scope, const char *key, size_t key_len); /** * Sets extra information. @@ -2592,6 +2603,10 @@ SENTRY_API void sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, */ SENTRY_API void sentry_remove_extra(const char *key); SENTRY_API void sentry_remove_extra_n(const char *key, size_t key_len); +SENTRY_API void sentry_scope_remove_extra( + sentry_scope_t *scope, const char *key); +SENTRY_API void sentry_scope_remove_extra_n( + sentry_scope_t *scope, const char *key, size_t key_len); /** * Sets attributes created with `sentry_value_new_attribute` to be applied to @@ -2649,6 +2664,10 @@ SENTRY_API void sentry_scope_update_context_n(sentry_scope_t *scope, */ SENTRY_API void sentry_remove_context(const char *key); SENTRY_API void sentry_remove_context_n(const char *key, size_t key_len); +SENTRY_API void sentry_scope_remove_context( + sentry_scope_t *scope, const char *key); +SENTRY_API void sentry_scope_remove_context_n( + sentry_scope_t *scope, const char *key, size_t key_len); /** * Sets the event fingerprint. @@ -2712,6 +2731,10 @@ SENTRY_EXPERIMENTAL_API void sentry_regenerate_trace(void); SENTRY_API void sentry_set_transaction(const char *transaction); SENTRY_API void sentry_set_transaction_n( const char *transaction, size_t transaction_len); +SENTRY_API void sentry_scope_set_transaction( + sentry_scope_t *scope, const char *transaction); +SENTRY_API void sentry_scope_set_transaction_n( + sentry_scope_t *scope, const char *transaction, size_t transaction_len); /** * Sets the event level. @@ -3242,7 +3265,8 @@ typedef struct sentry_attachment_s sentry_attachment_t; * * The returned `sentry_attachment_t` is owned by the SDK and will remain valid * until the attachment is removed with `sentry_remove_attachment` or - * `sentry_close` is called. + * `sentry_scope_remove_attachment`, or its owning scope is freed with + * `sentry_scope_free` or `sentry_close`. * * See the NOTE on attachments above for restrictions of this API. */ @@ -3274,7 +3298,8 @@ SENTRY_API sentry_attachment_t *sentry_scope_attach_file_n( * * The returned `sentry_attachment_t` is owned by the SDK and will remain valid * until the attachment is removed with `sentry_remove_attachment` or - * `sentry_close` is called. + * `sentry_scope_remove_attachment`, or its owning scope is freed with + * `sentry_scope_free` or `sentry_close`. * * See the NOTE on attachments above for restrictions of this API. */ @@ -3299,6 +3324,8 @@ SENTRY_API void sentry_clear_attachments(void); * See the NOTE on attachments above for restrictions of this API. */ SENTRY_API void sentry_remove_attachment(sentry_attachment_t *attachment); +SENTRY_API void sentry_scope_remove_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment); #ifdef SENTRY_PLATFORM_WINDOWS /** diff --git a/src/sentry_core.c b/src/sentry_core.c index b87e36c599..09d870aed4 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -983,11 +983,7 @@ void sentry_set_release_n(const char *release, size_t release_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->release); - scope->release = sentry__string_clone_n(release, release_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "release", - sentry_value_new_string(scope->release)); - SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); + sentry_scope_set_release_n(scope, release, release_len); } } @@ -1001,12 +997,7 @@ void sentry_set_environment_n(const char *environment, size_t environment_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->environment); - scope->environment - = sentry__string_clone_n(environment, environment_len); - sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", - sentry_value_new_string(scope->environment)); - SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); + sentry_scope_set_environment_n(scope, environment, environment_len); } } @@ -1086,9 +1077,7 @@ void sentry_remove_tag(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->tags, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); - } + sentry_scope_remove_tag(scope, key); } } @@ -1096,12 +1085,7 @@ void sentry_remove_tag_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k - = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); - } - sentry_free(k); + sentry_scope_remove_tag_n(scope, key, key_len); } } @@ -1125,9 +1109,7 @@ void sentry_remove_extra(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->extra, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); - } + sentry_scope_remove_extra(scope, key); } } @@ -1135,12 +1117,7 @@ void sentry_remove_extra_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k - = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); - } - sentry_free(k); + sentry_scope_remove_extra_n(scope, key, key_len); } } @@ -1241,9 +1218,7 @@ void sentry_remove_context(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { - if (sentry_value_remove_by_key(scope->contexts, key) == 0) { - SENTRY_SCOPE_NOTIFY(scope, remove_context, key); - } + sentry_scope_remove_context(scope, key); } } @@ -1251,12 +1226,7 @@ void sentry_remove_context_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { - char *k = sentry__value_remove_and_take_key_n( - scope->contexts, key, key_len); - if (k) { - SENTRY_SCOPE_NOTIFY(scope, remove_context, k); - } - sentry_free(k); + sentry_scope_remove_context_n(scope, key, key_len); } } @@ -1360,15 +1330,7 @@ void sentry_set_transaction_n(const char *transaction, size_t transaction_len) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_free(scope->transaction); - scope->transaction - = sentry__string_clone_n(transaction, transaction_len); - - if (scope->transaction_object) { - sentry_transaction_set_name_n( - scope->transaction_object, transaction, transaction_len); - } - SENTRY_SCOPE_NOTIFY(scope, set_transaction, scope->transaction); + sentry_scope_set_transaction_n(scope, transaction, transaction_len); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 0bb7057e14..eef62c09d5 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -806,6 +806,25 @@ sentry_scope_set_tags(sentry_scope_t *scope, sentry_value_t tags) sentry_value_decref(tags); } +void +sentry_scope_remove_tag(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->tags, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_tag, key); + } +} + +void +sentry_scope_remove_tag_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k = sentry__value_remove_and_take_key_n(scope->tags, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_tag, k); + } + sentry_free(k); +} + void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) @@ -825,6 +844,25 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, } } +void +sentry_scope_remove_extra(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->extra, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, key); + } +} + +void +sentry_scope_remove_extra_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k = sentry__value_remove_and_take_key_n(scope->extra, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_extra, k); + } + sentry_free(k); +} + void sentry_scope_set_attribute( sentry_scope_t *scope, const char *key, sentry_value_t attribute) @@ -879,6 +917,26 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, } } +void +sentry_scope_remove_context(sentry_scope_t *scope, const char *key) +{ + if (sentry_value_remove_by_key(scope->contexts, key) == 0) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, key); + } +} + +void +sentry_scope_remove_context_n( + sentry_scope_t *scope, const char *key, size_t key_len) +{ + char *k + = sentry__value_remove_and_take_key_n(scope->contexts, key, key_len); + if (k) { + SENTRY_SCOPE_NOTIFY(scope, remove_context, k); + } + sentry_free(k); +} + void sentry_scope_update_context( sentry_scope_t *scope, const char *key, sentry_value_t value) @@ -909,6 +967,62 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, SENTRY_SCOPE_NOTIFY(scope, set_context, k, value); } +void +sentry_scope_set_release_n( + sentry_scope_t *scope, const char *release, size_t release_len) +{ + sentry_free(scope->release); + scope->release = sentry__string_clone_n(release, release_len); + sentry_value_set_by_key(scope->dynamic_sampling_context, "release", + sentry_value_new_string(scope->release)); + SENTRY_SCOPE_NOTIFY(scope, set_release, scope->release); +} + +void +sentry_scope_set_release(sentry_scope_t *scope, const char *release) +{ + sentry_scope_set_release_n(scope, release, sentry__guarded_strlen(release)); +} + +void +sentry_scope_set_environment_n( + sentry_scope_t *scope, const char *environment, size_t environment_len) +{ + sentry_free(scope->environment); + scope->environment = sentry__string_clone_n(environment, environment_len); + sentry_value_set_by_key(scope->dynamic_sampling_context, "environment", + sentry_value_new_string(scope->environment)); + SENTRY_SCOPE_NOTIFY(scope, set_environment, scope->environment); +} + +void +sentry_scope_set_environment(sentry_scope_t *scope, const char *environment) +{ + sentry_scope_set_environment_n( + scope, environment, sentry__guarded_strlen(environment)); +} + +void +sentry_scope_set_transaction_n( + sentry_scope_t *scope, const char *transaction, size_t transaction_len) +{ + sentry_free(scope->transaction); + scope->transaction = sentry__string_clone_n(transaction, transaction_len); + + if (scope->transaction_object) { + sentry_transaction_set_name_n( + scope->transaction_object, transaction, transaction_len); + } + SENTRY_SCOPE_NOTIFY(scope, set_transaction, scope->transaction); +} + +void +sentry_scope_set_transaction(sentry_scope_t *scope, const char *transaction) +{ + sentry_scope_set_transaction_n( + scope, transaction, sentry__guarded_strlen(transaction)); +} + void sentry__scope_set_fingerprint_va( sentry_scope_t *scope, const char *fingerprint, va_list va) @@ -1033,6 +1147,17 @@ sentry__scope_add_attachment( return added; } +void +sentry_scope_remove_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment) +{ + if (attachment + && sentry__attachments_remove(&scope->attachments, attachment)) { + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); + sentry__attachment_free(attachment); + } +} + sentry_attachment_t * sentry_scope_attach_file(sentry_scope_t *scope, const char *path) { diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index db8cf657a1..f337f98fef 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -176,6 +176,16 @@ SENTRY_TEST(attachments_add_dedupe) SENTRY_TEST(attachments_add_remove) { + sentry_scope_t *scope = sentry_scope_new(); + sentry_attachment_t *scoped_attachment + = sentry_scope_attach_bytes(scope, "payload", 7, "file.bin"); + TEST_CHECK(scoped_attachment != NULL); + TEST_CHECK(scope->attachments != NULL); + sentry_scope_remove_attachment(scope, scoped_attachment); + TEST_CHECK(scope->attachments == NULL); + sentry_scope_remove_attachment(scope, NULL); + sentry_scope_free(scope); + SENTRY_TEST_OPTIONS_NEW(options); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".a.txt"); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".c.txt"); diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index e21e79e87a..ca6f0daab3 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -67,6 +67,16 @@ SENTRY_TEST(scope_contexts) local_scope, "local", sentry_value_new_string("local")); sentry_scope_set_context( local_scope, "scope", sentry_value_new_string("local")); + sentry_scope_set_context( + local_scope, "removed", sentry_value_new_string("removed")); + sentry_scope_set_context( + local_scope, "n-removed", sentry_value_new_string("removed")); + sentry_scope_remove_context(local_scope, "removed"); + sentry_scope_remove_context_n(local_scope, "n-removed-trailing", 9); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->contexts, "removed"))); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->contexts, "n-removed"))); // event: // {"all":"event","event":"event"} @@ -327,6 +337,16 @@ SENTRY_TEST(scope_extra) local_scope, "local", sentry_value_new_string("local")); sentry_scope_set_extra( local_scope, "scope", sentry_value_new_string("local")); + sentry_scope_set_extra( + local_scope, "removed", sentry_value_new_string("removed")); + sentry_scope_set_extra( + local_scope, "n-removed", sentry_value_new_string("removed")); + sentry_scope_remove_extra(local_scope, "removed"); + sentry_scope_remove_extra_n(local_scope, "n-removed-trailing", 9); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->extra, "removed"))); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->extra, "n-removed"))); // event: // {"all":"event","event":"event"} @@ -573,6 +593,14 @@ SENTRY_TEST(scope_tags) sentry_value_set_by_key( local_tags, "scope", sentry_value_new_string("local")); sentry_scope_set_tags(local_scope, local_tags); + sentry_scope_set_tag(local_scope, "removed", "removed"); + sentry_scope_set_tag(local_scope, "n-removed", "removed"); + sentry_scope_remove_tag(local_scope, "removed"); + sentry_scope_remove_tag_n(local_scope, "n-removed-trailing", 9); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->tags, "removed"))); + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key(local_scope->tags, "n-removed"))); // event: // {"all":"event","event":"event"} @@ -1352,6 +1380,53 @@ SENTRY_TEST(scope_local_attributes) sentry_close(); } +SENTRY_TEST(scope_release) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_set_release(scope, "my-release"); + TEST_CHECK_STRING_EQUAL(scope->release, "my-release"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + scope->dynamic_sampling_context, "release")), + "my-release"); + } + + sentry_close(); +} + +SENTRY_TEST(scope_environment) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_set_environment(scope, "my-environment"); + TEST_CHECK_STRING_EQUAL(scope->environment, "my-environment"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + scope->dynamic_sampling_context, "environment")), + "my-environment"); + } + + sentry_close(); +} + +SENTRY_TEST(scope_transaction) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_set_transaction(scope, "my-transaction"); + TEST_CHECK_STRING_EQUAL(scope->transaction, "my-transaction"); + } + + sentry_close(); +} + typedef struct { sentry_value_t release; sentry_value_t environment; diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 6baf02471c..1326ff67f9 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -357,6 +357,7 @@ XX(scope_clone_keeps_bound_span) XX(scope_clone_preserves_data) XX(scope_clone_shares_span) XX(scope_contexts) +XX(scope_environment) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n) @@ -383,11 +384,13 @@ XX(scope_observer_user) XX(scope_ownership) XX(scope_propagation_context) XX(scope_rebind_same_object) +XX(scope_release) XX(scope_remove_fingerprint_capture) XX(scope_set_attribute_invalid_decref_value) XX(scope_set_attribute_null_key_decref_value) XX(scope_tags) XX(scope_tags_flush) +XX(scope_transaction) XX(scope_update_context) XX(scope_user) XX(scope_user_id) From 600c4b3dc31fce849d1117845872c5ffd8db3a38 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 21 Aug 2026 10:03:26 +0200 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eaef44afb..496418d677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Add `sentry_set_tags` and `sentry_scope_set_tags` for updating multiple tags with a single scope flush, improving bulk-update performance. ([#1993](https://github.com/getsentry/sentry-native/pull/1993)) - Add `sentry_get_last_event_id` and `sentry_scope_get_last_event_id` for retrieving the last event ID captured with the global or given scope, respectively. ([#1992](https://github.com/getsentry/sentry-native/pull/1992)) - Native/Unix: The native crash daemon now loads `libcurl` dynamically at runtime by default when `SENTRY_LINK_CURL=AUTO`, avoiding `libcurl` linker work during process startup and significantly speeding up startup time. Explicitly set `SENTRY_LINK_CURL=ON` to link it directly. ([#1955](https://github.com/getsentry/sentry-native/pull/1955)) +- Add missing public scope mutators: `set_release`, `set_environment`, `set_transaction`, `remove_tag`, `remove_extra`, `remove_context`, and `remove_attachment`. ([#2011](https://github.com/getsentry/sentry-native/pull/2011)) **Deprecations**: