From 358d23f7d4f4f92eb970b1f29551af6fc18b49df Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 1 Jul 2026 19:39:23 +0200 Subject: [PATCH 01/35] feat(internal): scope observer --- src/sentry_core.c | 36 ++- src/sentry_scope.c | 55 +++- src/sentry_scope.h | 78 ++++++ tests/unit/test_scope.c | 585 ++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 15 +- 5 files changed, 760 insertions(+), 9 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 2dc25f3e8f..7ece4f3049 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -900,6 +900,7 @@ sentry_set_release_n(const char *release, size_t release_len) 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, release, release_len); } } @@ -918,6 +919,8 @@ sentry_set_environment_n(const char *environment, size_t environment_len) = 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, environment, environment_len); } } @@ -982,9 +985,7 @@ sentry_set_tag_n( void sentry_remove_tag(const char *key) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_remove_by_key(scope->tags, key); - } + sentry_remove_tag_n(key, sentry__guarded_strlen(key)); } void @@ -992,6 +993,7 @@ sentry_remove_tag_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->tags, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_tag, key, key_len); } } @@ -1016,6 +1018,8 @@ sentry_remove_extra(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->extra, key); + SENTRY_SCOPE_NOTIFY( + scope, remove_extra, key, sentry__guarded_strlen(key)); } } @@ -1024,6 +1028,7 @@ sentry_remove_extra_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->extra, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_extra, key, key_len); } } @@ -1097,6 +1102,8 @@ sentry__set_propagation_context(const char *key, sentry_value_t value) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_set_by_key(scope->propagation_context, key, value); + SENTRY_SCOPE_NOTIFY( + scope, set_context, key, sentry__guarded_strlen(key), value); } } @@ -1131,6 +1138,8 @@ sentry_remove_context(const char *key) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key(scope->contexts, key); + SENTRY_SCOPE_NOTIFY( + scope, remove_context, key, sentry__guarded_strlen(key)); } } @@ -1139,6 +1148,7 @@ sentry_remove_context_n(const char *key, size_t key_len) { SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_remove_by_key_n(scope->contexts, key, key_len); + SENTRY_SCOPE_NOTIFY(scope, remove_context, key, key_len); } } @@ -1175,6 +1185,7 @@ sentry_remove_fingerprint(void) SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->fingerprint); scope->fingerprint = sentry_value_new_null(); + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, scope->fingerprint); } } @@ -1229,6 +1240,9 @@ sentry_regenerate_trace(void) generate_propagation_context(scope->propagation_context); scope->trace_managed = false; sentry__scope_update_dsc(scope, options); + SENTRY_SCOPE_NOTIFY(scope, set_context, "trace", + sentry__guarded_strlen("trace"), + sentry_value_get_by_key(scope->propagation_context, "trace")); } } } @@ -1243,6 +1257,8 @@ sentry_set_transaction(const char *transaction) if (scope->transaction_object) { sentry_transaction_set_name(scope->transaction_object, transaction); } + SENTRY_SCOPE_NOTIFY(scope, set_transaction, transaction, + sentry__guarded_strlen(transaction)); } } @@ -1258,6 +1274,8 @@ sentry_set_transaction_n(const char *transaction, size_t transaction_len) sentry_transaction_set_name_n( scope->transaction_object, transaction, transaction_len); } + SENTRY_SCOPE_NOTIFY( + scope, set_transaction, transaction, transaction_len); } } @@ -1911,6 +1929,9 @@ add_attachment(sentry_attachment_t *attachment) } SENTRY_WITH_SCOPE_MUT (scope) { attachment = sentry__attachments_add(&scope->attachments, attachment); + if (attachment) { + SENTRY_SCOPE_NOTIFY(scope, add_attachment, attachment); + } } return attachment; } @@ -1948,12 +1969,14 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - if (options->backend && options->backend->remove_attachment_func) { - for (sentry_attachment_t *it = scope->attachments; it; - it = it->next) { + for (sentry_attachment_t *it = scope->attachments; it; + it = it->next) { + if (options->backend + && options->backend->remove_attachment_func) { options->backend->remove_attachment_func( options->backend, it); } + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, it); } sentry__attachments_free(scope->attachments); scope->attachments = NULL; @@ -1971,6 +1994,7 @@ sentry_remove_attachment(sentry_attachment_t *attachment) } } SENTRY_WITH_SCOPE_MUT (scope) { + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); sentry__attachments_remove(&scope->attachments, attachment); } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 1a6f54a55a..43502ea6f0 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -88,6 +88,9 @@ init_scope(sentry_scope_t *scope) scope->transaction_object = NULL; scope->span = NULL; scope->trace_managed = true; + scope->observers = NULL; + scope->num_observers = 0; + scope->is_notifying = false; } static sentry_scope_t * @@ -127,6 +130,12 @@ cleanup_scope(sentry_scope_t *scope) sentry__attachments_free(scope->attachments); sentry__transaction_decref(scope->transaction_object); sentry__span_decref(scope->span); + for (size_t i = 0; i < scope->num_observers; i++) { + sentry_free(scope->observers[i]); + } + sentry_free(scope->observers); + scope->observers = NULL; + scope->num_observers = 0; } void @@ -169,6 +178,37 @@ sentry__scope_flush_unlock(void) } } +sentry_scope_observer_t * +sentry__scope_observer_new(void) +{ + return SENTRY_MAKE(sentry_scope_observer_t); +} + +void +sentry__scope_add_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer) +{ + if (!observer) { + return; + } + + size_t new_count = scope->num_observers + 1; + sentry_scope_observer_t **new_array + = sentry__calloc(new_count, sizeof(sentry_scope_observer_t *)); + if (!new_array) { + sentry_free(observer); + return; + } + if (scope->observers) { + memcpy(new_array, scope->observers, + scope->num_observers * sizeof(sentry_scope_observer_t *)); + sentry_free(scope->observers); + } + new_array[scope->num_observers] = observer; + scope->observers = new_array; + scope->num_observers = new_count; +} + sentry_scope_t * sentry_local_scope_new(void) { @@ -516,6 +556,7 @@ void sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) { sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb); + SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); } void @@ -523,12 +564,14 @@ sentry_scope_set_user(sentry_scope_t *scope, sentry_value_t user) { sentry_value_decref(scope->user); scope->user = user; + SENTRY_SCOPE_NOTIFY(scope, set_user, user); } void sentry_scope_set_tag(sentry_scope_t *scope, const char *key, const char *value) { - sentry_value_set_by_key(scope->tags, key, sentry_value_new_string(value)); + sentry_scope_set_tag_n(scope, key, sentry__guarded_strlen(key), value, + sentry__guarded_strlen(value)); } void @@ -537,13 +580,14 @@ sentry_scope_set_tag_n(sentry_scope_t *scope, const char *key, size_t key_len, { sentry_value_set_by_key_n( scope->tags, key, key_len, sentry_value_new_string_n(value, value_len)); + SENTRY_SCOPE_NOTIFY(scope, set_tag, key, key_len, value, value_len); } void sentry_scope_set_extra( sentry_scope_t *scope, const char *key, sentry_value_t value) { - sentry_value_set_by_key(scope->extra, key, value); + sentry_scope_set_extra_n(scope, key, sentry__guarded_strlen(key), value); } void @@ -551,6 +595,7 @@ sentry_scope_set_extra_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { sentry_value_set_by_key_n(scope->extra, key, key_len, value); + SENTRY_SCOPE_NOTIFY(scope, set_extra, key, key_len, value); } void @@ -590,6 +635,8 @@ sentry_scope_set_context( sentry_scope_t *scope, const char *key, sentry_value_t value) { sentry_value_set_by_key(scope->contexts, key, value); + SENTRY_SCOPE_NOTIFY( + scope, set_context, key, sentry__guarded_strlen(key), value); } void @@ -597,6 +644,7 @@ sentry_scope_set_context_n(sentry_scope_t *scope, const char *key, size_t key_len, sentry_value_t value) { sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + SENTRY_SCOPE_NOTIFY(scope, set_context, key, key_len, value); } void @@ -633,6 +681,7 @@ sentry__scope_set_fingerprint_va( sentry_value_decref(scope->fingerprint); scope->fingerprint = fingerprint_value; + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprint_value); } void @@ -687,12 +736,14 @@ sentry_scope_set_fingerprints( sentry_value_decref(scope->fingerprint); scope->fingerprint = fingerprints; + SENTRY_SCOPE_NOTIFY(scope, set_fingerprint, fingerprints); } void sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { scope->level = level; + SENTRY_SCOPE_NOTIFY(scope, set_level, level); } sentry_attachment_t * diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 6047fa8035..47c56ee568 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -8,6 +8,45 @@ #include "sentry_session.h" #include "sentry_value.h" +/** + * Scope observer — one callback per scope property. + * + * Implementors set the function pointers they care about. NULL pointers are + * skipped. Callbacks are invoked while the scope lock is held. + * + * Ownership: the scope takes ownership of the observer pointer on + * registration; the caller must not free it after that point. + */ +typedef struct sentry_scope_observer_s { + void *data; + + void (*set_release)(void *data, const char *release, size_t release_len); + void (*set_environment)( + void *data, const char *environment, size_t environment_len); + void (*set_transaction)( + void *data, const char *transaction, size_t transaction_len); + void (*set_fingerprint)(void *data, sentry_value_t fingerprint); + void (*set_level)(void *data, sentry_level_t level); + void (*set_user)(void *data, sentry_value_t user); + + void (*add_breadcrumb)(void *data, sentry_value_t breadcrumb); + + void (*set_tag)(void *data, const char *key, size_t key_len, + const char *value, size_t value_len); + void (*remove_tag)(void *data, const char *key, size_t key_len); + + void (*set_extra)( + void *data, const char *key, size_t key_len, sentry_value_t value); + void (*remove_extra)(void *data, const char *key, size_t key_len); + + void (*set_context)( + void *data, const char *key, size_t key_len, sentry_value_t value); + void (*remove_context)(void *data, const char *key, size_t key_len); + + void (*add_attachment)(void *data, sentry_attachment_t *attachment); + void (*remove_attachment)(void *data, sentry_attachment_t *attachment); +} sentry_scope_observer_t; + /** * This represents the current scope. */ @@ -39,6 +78,10 @@ struct sentry_scope_s { sentry_transaction_t *transaction_object; sentry_span_t *span; bool trace_managed; + + sentry_scope_observer_t **observers; + size_t num_observers; + bool is_notifying; }; /** @@ -128,6 +171,41 @@ void sentry__scope_remove_attribute_n( for (sentry_scope_t *Scope = sentry__scope_lock(); Scope; \ sentry__scope_unlock(), Scope = NULL) +/** + * Allocate and zero-initialize a scope observer. + * + * Returns NULL on allocation failure. The caller sets whichever callback + * function pointers and the `data` pointer they need, then registers the + * observer with `sentry__scope_add_observer`, which takes ownership. + */ +sentry_scope_observer_t *sentry__scope_observer_new(void); + +/** + * Register a scope observer. + * + * Takes ownership of `observer`; the caller must not use or free it after + * this call. Must be called while holding the scope lock (i.e., inside + * SENTRY_WITH_SCOPE_MUT). Registration order is respected — observers are + * notified in registration order. + */ +void sentry__scope_add_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer); + +/** Re-entrancy guard: set while notifying observers. */ +#define SENTRY_SCOPE_NOTIFY(scope, callback, ...) \ + do { \ + if ((scope)->is_notifying) \ + break; \ + (scope)->is_notifying = true; \ + for (size_t _i = 0; _i < (scope)->num_observers; _i++) { \ + sentry_scope_observer_t *_observer = (scope)->observers[_i]; \ + if (_observer->callback) { \ + _observer->callback(_observer->data, __VA_ARGS__); \ + } \ + } \ + (scope)->is_notifying = false; \ + } while (0) + /** * Rebuilds the scope's dynamic sampling context (DSC) from the SDK options * and the current propagation context. The previous DSC is discarded. diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 0ac3951ff9..1f11da71e1 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1132,3 +1132,588 @@ SENTRY_TEST(scope_local_attributes) sentry_close(); } + +typedef struct { + sentry_value_t release; + sentry_value_t environment; + sentry_value_t transaction; + sentry_value_t fingerprint; + sentry_level_t level; + sentry_value_t user; + sentry_value_t breadcrumbs; + sentry_value_t tags; + sentry_value_t extras; + sentry_value_t contexts; + sentry_value_t attachments; + bool was_called; +} test_observer_data_t; + +static void +observe_set_release(void *data, const char *release, size_t release_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->release = sentry_value_new_string_n(release, release_len); + d->was_called = true; +} + +static void +observe_set_environment( + void *data, const char *environment, size_t environment_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->environment = sentry_value_new_string_n(environment, environment_len); + d->was_called = true; +} + +static void +observe_set_transaction( + void *data, const char *transaction, size_t transaction_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->transaction = sentry_value_new_string_n(transaction, transaction_len); + d->was_called = true; +} + +static void +observe_set_fingerprint(void *data, sentry_value_t fingerprint) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (!sentry_value_is_null(d->fingerprint)) { + sentry_value_decref(d->fingerprint); + } + sentry_value_incref(fingerprint); + d->fingerprint = fingerprint; + d->was_called = true; +} + +static void +observe_set_level(void *data, sentry_level_t level) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->level = level; + d->was_called = true; +} + +static void +observe_add_attachment(void *data, sentry_attachment_t *attachment) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->attachments)) { + d->attachments = sentry_value_new_list(); + } + sentry_value_t obj = sentry_value_new_object(); + const char *filename = sentry__attachment_get_filename(attachment); + if (filename) { + sentry_value_set_by_key( + obj, "filename", sentry_value_new_string(filename)); + } + if (attachment->buf) { + sentry_value_set_by_key(obj, "buf", + sentry_value_new_string_n(attachment->buf, attachment->buf_len)); + } + sentry_value_append(d->attachments, obj); + d->was_called = true; +} + +static void +observe_remove_attachment(void *data, sentry_attachment_t *attachment) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->attachments)) { + d->attachments = sentry_value_new_list(); + } + sentry_value_t obj = sentry_value_new_object(); + const char *filename = sentry__attachment_get_filename(attachment); + if (filename) { + sentry_value_set_by_key( + obj, "filename", sentry_value_new_string(filename)); + } + sentry_value_set_by_key(obj, "removed", sentry_value_new_bool(true)); + sentry_value_append(d->attachments, obj); + d->was_called = true; +} + +static void +observe_set_user(void *data, sentry_value_t user) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + d->user = user; + d->was_called = true; +} + +static void +observe_add_breadcrumb(void *data, sentry_value_t breadcrumb) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->breadcrumbs)) { + d->breadcrumbs = sentry_value_new_list(); + } + sentry_value_incref(breadcrumb); + sentry_value_append(d->breadcrumbs, breadcrumb); + d->was_called = true; +} + +static void +observe_set_tag(void *data, const char *key, size_t key_len, const char *value, + size_t value_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->tags)) { + d->tags = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->tags, key, key_len, sentry_value_new_string_n(value, value_len)); + d->was_called = true; +} + +static void +observe_remove_tag(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->tags)) { + d->tags = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->tags, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +static void +observe_set_extra( + void *data, const char *key, size_t key_len, sentry_value_t value) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->extras)) { + d->extras = sentry_value_new_object(); + } + sentry_value_incref(value); + sentry_value_set_by_key_n(d->extras, key, key_len, value); + d->was_called = true; +} + +static void +observe_remove_extra(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->extras)) { + d->extras = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->extras, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +static void +observe_set_context( + void *data, const char *key, size_t key_len, sentry_value_t value) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->contexts)) { + d->contexts = sentry_value_new_object(); + } + sentry_value_incref(value); + sentry_value_set_by_key_n(d->contexts, key, key_len, value); + d->was_called = true; +} + +static void +observe_remove_context(void *data, const char *key, size_t key_len) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->contexts)) { + d->contexts = sentry_value_new_object(); + } + sentry_value_set_by_key_n( + d->contexts, key, key_len, sentry_value_new_string("(removed)")); + d->was_called = true; +} + +SENTRY_TEST(scope_observer_null) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_tag("my-tag", "my-value"); + TEST_CHECK(d.was_called); + + d.was_called = false; + sentry_remove_tag("my-tag"); + TEST_CHECK(!d.was_called); + + sentry_value_decref(d.tags); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_multiple) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d1 = { .tags = sentry_value_new_null() }; + test_observer_data_t d2 = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer1 = sentry__scope_observer_new(); + observer1->data = &d1; + observer1->set_tag = observe_set_tag; + + sentry_scope_observer_t *observer2 = sentry__scope_observer_new(); + observer2->data = &d2; + observer2->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer1); + sentry__scope_add_observer(scope, observer2); + } + + sentry_set_tag("multi", "test"); + TEST_CHECK(d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d1.tags, "multi")), + "test"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d2.tags, "multi")), + "test"); + + sentry_value_decref(d1.tags); + sentry_value_decref(d2.tags); + sentry_close(); +} + +SENTRY_TEST(scope_observer_release) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_release = observe_set_release; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_release("my-release"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.release), "my-release"); + + sentry_value_decref(d.release); + sentry_close(); +} + +SENTRY_TEST(scope_observer_environment) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_environment = observe_set_environment; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_environment("my-env"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(d.environment), "my-env"); + + sentry_value_decref(d.environment); + sentry_close(); +} + +SENTRY_TEST(scope_observer_transaction) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_transaction = observe_set_transaction; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_transaction("my-transaction"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(d.transaction), "my-transaction"); + + sentry_value_decref(d.transaction); + sentry_close(); +} + +SENTRY_TEST(scope_observer_fingerprint) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_fingerprint = observe_set_fingerprint; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_fingerprint("my-fingerprint", NULL); + TEST_CHECK(d.was_called); + TEST_CHECK(!sentry_value_is_null(d.fingerprint)); + TEST_CHECK_JSON_VALUE(d.fingerprint, "[\"my-fingerprint\"]"); + + d.was_called = false; + sentry_remove_fingerprint(); + TEST_CHECK(d.was_called); + TEST_CHECK(sentry_value_is_null(d.fingerprint)); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_level) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_level = observe_set_level; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_level(SENTRY_LEVEL_WARNING); + TEST_CHECK(d.was_called); + TEST_CHECK(d.level == SENTRY_LEVEL_WARNING); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_user) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { 0 }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_user = observe_set_user; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t user = sentry_value_new_object(); + sentry_value_set_by_key(user, "id", sentry_value_new_string("user123")); + sentry_set_user(user); + TEST_CHECK(d.was_called); + TEST_CHECK(!sentry_value_is_null(d.user)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.user, "id")), + "user123"); + + d.was_called = false; + sentry_remove_user(); + TEST_CHECK(d.was_called); + TEST_CHECK(sentry_value_is_null(d.user)); + + sentry_close(); +} + +SENTRY_TEST(scope_observer_breadcrumbs) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .breadcrumbs = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->add_breadcrumb = observe_add_breadcrumb; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_add_breadcrumb( + sentry_value_new_breadcrumb(NULL, "first breadcrumb")); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 1); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 0), "message")), + "first breadcrumb"); + + sentry_add_breadcrumb( + sentry_value_new_breadcrumb("warning", "second breadcrumb")); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.breadcrumbs), 2); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 1), "message")), + "second breadcrumb"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key( + sentry_value_get_by_index(d.breadcrumbs, 1), "type")), + "warning"); + + sentry_value_decref(d.breadcrumbs); + sentry_close(); +} + +SENTRY_TEST(scope_observer_tags) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .tags = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_tag = observe_set_tag; + observer->remove_tag = observe_remove_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_set_tag("my-tag", "my-value"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.tags, "my-tag")), + "my-value"); + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_value_get_by_key(d.tags, "my-tag")), 8); + + d.was_called = false; + sentry_remove_tag("my-tag"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.tags, "my-tag")), + "(removed)"); + + sentry_value_decref(d.tags); + sentry_close(); +} + +SENTRY_TEST(scope_observer_extras) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .extras = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_extra = observe_set_extra; + observer->remove_extra = observe_remove_extra; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t val = sentry_value_new_string("extra-value"); + sentry_set_extra("my-extra", val); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.extras, "my-extra")), + "extra-value"); + + d.was_called = false; + sentry_remove_extra("my-extra"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d.extras, "my-extra")), + "(removed)"); + + sentry_value_decref(d.extras); + sentry_close(); +} + +SENTRY_TEST(scope_observer_contexts) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .contexts = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_context = observe_set_context; + observer->remove_context = observe_remove_context; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_value_t ctx = sentry_value_new_object(); + sentry_value_set_by_key(ctx, "type", sentry_value_new_string("device")); + sentry_set_context("my-context", ctx); + TEST_CHECK(d.was_called); + sentry_value_t received = sentry_value_get_by_key(d.contexts, "my-context"); + TEST_CHECK(!sentry_value_is_null(received)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "type")), + "device"); + + d.was_called = false; + sentry_remove_context("my-context"); + TEST_CHECK(d.was_called); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + d.contexts, "my-context")), + "(removed)"); + + sentry_value_decref(d.contexts); + sentry_close(); +} + +SENTRY_TEST(scope_observer_attachments) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d = { .attachments = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->add_attachment = observe_add_attachment; + observer->remove_attachment = observe_remove_attachment; + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_add_observer(scope, observer); + } + + sentry_attachment_t *attachment = sentry_attach_bytes("buf", 3, "test.txt"); + TEST_CHECK(d.was_called); + TEST_CHECK(attachment != NULL); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 1); + sentry_value_t added = sentry_value_get_by_index(d.attachments, 0); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(added, "buf")), "buf"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(added, "filename")), + "test.txt"); + + d.was_called = false; + sentry_remove_attachment(attachment); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 2); + sentry_value_t removed = sentry_value_get_by_index(d.attachments, 1); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(removed, "filename")), + "test.txt"); + TEST_CHECK( + sentry_value_is_true(sentry_value_get_by_key(removed, "removed"))); + + sentry_value_decref(d.attachments); + sentry_close(); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 86addc6da0..7e31f205d3 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -285,14 +285,27 @@ XX(sampling_decision) XX(sampling_transaction) XX(scope_breadcrumbs) XX(scope_contexts) -XX(scope_update_context) XX(scope_extra) XX(scope_fingerprint) XX(scope_fingerprint_n) XX(scope_global_attributes) XX(scope_level) XX(scope_local_attributes) +XX(scope_observer_attachments) +XX(scope_observer_breadcrumbs) +XX(scope_observer_contexts) +XX(scope_observer_environment) +XX(scope_observer_extras) +XX(scope_observer_fingerprint) +XX(scope_observer_level) +XX(scope_observer_multiple) +XX(scope_observer_null) +XX(scope_observer_release) +XX(scope_observer_tags) +XX(scope_observer_transaction) +XX(scope_observer_user) XX(scope_tags) +XX(scope_update_context) XX(scope_user) XX(scope_user_id) XX(scoped_txn) From 93d18b7ebc05fd0c4c400752a16b8057d1af1962 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 19:14:03 +0200 Subject: [PATCH 02/35] sentry_scope_update_context: add missing notify --- src/sentry_scope.c | 1 + tests/unit/test_scope.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 43502ea6f0..ffd38d495a 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -667,6 +667,7 @@ sentry_scope_update_context_n(sentry_scope_t *scope, const char *key, sentry__value_merge_objects(value, context); sentry_value_set_by_key_n(scope->contexts, key, key_len, value); } + SENTRY_SCOPE_NOTIFY(scope, set_context, key, key_len, value); } void diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 1f11da71e1..9e965acb00 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1666,6 +1666,19 @@ SENTRY_TEST(scope_observer_contexts) sentry_value_as_string(sentry_value_get_by_key(received, "type")), "device"); + sentry_value_t update = sentry_value_new_object(); + sentry_value_set_by_key(update, "version", sentry_value_new_string("1.0")); + d.was_called = false; + sentry_update_context("my-context", update); + TEST_CHECK(d.was_called); + received = sentry_value_get_by_key(d.contexts, "my-context"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "type")), + "device"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(received, "version")), + "1.0"); + d.was_called = false; sentry_remove_context("my-context"); TEST_CHECK(d.was_called); From cb79cfc2327361b5ff689a2044f322a2b4daede0 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 19:19:38 +0200 Subject: [PATCH 03/35] sentry__ringbuffer_append returns -1 on failure --- src/sentry_scope.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index ffd38d495a..238298bb60 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -555,8 +555,9 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, void sentry_scope_add_breadcrumb(sentry_scope_t *scope, sentry_value_t breadcrumb) { - sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb); - SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); + if (sentry__ringbuffer_append(scope->breadcrumbs, breadcrumb) == 0) { + SENTRY_SCOPE_NOTIFY(scope, add_breadcrumb, breadcrumb); + } } void From 25361b8f530ccc013284a4285d0af0c6cfb447ab Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 19:38:49 +0200 Subject: [PATCH 04/35] don't notify duplicate attachments --- src/sentry_attachment.c | 10 ++++++---- src/sentry_attachment.h | 3 ++- src/sentry_core.c | 32 +++++++++++++++++++++++--------- tests/unit/test_scope.c | 21 +++++++++++++++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/sentry_attachment.c b/src/sentry_attachment.c index 57d11bb4bc..c337a02f87 100644 --- a/src/sentry_attachment.c +++ b/src/sentry_attachment.c @@ -262,12 +262,12 @@ sentry__attachments_add_path(sentry_attachment_t **attachments_ptr, return sentry__attachments_add(attachments_ptr, attachment); } -void +bool sentry__attachments_remove( sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment) { if (!attachment) { - return; + return false; } sentry_attachment_t **next_ptr = attachments_ptr; @@ -275,12 +275,14 @@ sentry__attachments_remove( for (sentry_attachment_t *it = *attachments_ptr; it; it = it->next) { if (it == attachment) { *next_ptr = it->next; - sentry__attachment_free(it); - return; + it->next = NULL; + return true; } next_ptr = &it->next; } + + return false; } static sentry_attachment_t * diff --git a/src/sentry_attachment.h b/src/sentry_attachment.h index 0004164e76..0ddf96f35d 100644 --- a/src/sentry_attachment.h +++ b/src/sentry_attachment.h @@ -90,8 +90,9 @@ sentry_attachment_t *sentry__attachments_add_path( /** * Removes an attachment from the attachments list at `attachments_ptr`. + * Returns true if the attachment was found and removed. */ -void sentry__attachments_remove( +bool sentry__attachments_remove( sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment); /** diff --git a/src/sentry_core.c b/src/sentry_core.c index 7ece4f3049..eef6946d5b 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1922,15 +1922,22 @@ sentry_capture_minidump_n(const char *path, size_t path_len) static sentry_attachment_t * add_attachment(sentry_attachment_t *attachment) { + if (!attachment) { + return NULL; + } + SENTRY_WITH_OPTIONS (options) { if (options->backend && options->backend->add_attachment_func) { options->backend->add_attachment_func(options->backend, attachment); } } SENTRY_WITH_SCOPE_MUT (scope) { - attachment = sentry__attachments_add(&scope->attachments, attachment); - if (attachment) { + sentry_attachment_t *new_attachment + = sentry__attachments_add(&scope->attachments, attachment); + if (attachment == new_attachment) { SENTRY_SCOPE_NOTIFY(scope, add_attachment, attachment); + } else { + attachment = new_attachment; // existing/duplicate } } return attachment; @@ -1987,16 +1994,23 @@ sentry_clear_attachments(void) void sentry_remove_attachment(sentry_attachment_t *attachment) { + if (!attachment) { + return; + } + SENTRY_WITH_OPTIONS (options) { - if (options->backend && options->backend->remove_attachment_func) { - options->backend->remove_attachment_func( - options->backend, attachment); + SENTRY_WITH_SCOPE_MUT (scope) { + if (sentry__attachments_remove(&scope->attachments, attachment)) { + if (options->backend + && options->backend->remove_attachment_func) { + options->backend->remove_attachment_func( + options->backend, attachment); + } + SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); + sentry__attachment_free(attachment); + } } } - SENTRY_WITH_SCOPE_MUT (scope) { - SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); - sentry__attachments_remove(&scope->attachments, attachment); - } } #ifdef SENTRY_PLATFORM_WINDOWS diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 9e965acb00..b45978e2eb 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1727,6 +1727,27 @@ SENTRY_TEST(scope_observer_attachments) TEST_CHECK( sentry_value_is_true(sentry_value_get_by_key(removed, "removed"))); + attachment = sentry_attach_file("test.txt"); + TEST_CHECK(d.was_called); + TEST_CHECK(attachment != NULL); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 3); + + d.was_called = false; + sentry_attachment_t *duplicate = sentry_attach_file("test.txt"); + TEST_CHECK(duplicate == attachment); + TEST_CHECK(!d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 3); + + d.was_called = false; + sentry_remove_attachment(attachment); + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 4); + + d.was_called = false; + sentry_remove_attachment(duplicate); + TEST_CHECK(!d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.attachments), 4); + sentry_value_decref(d.attachments); sentry_close(); } From 67d61d1a0d92c602fbb53d7d66dea815167647cf Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 19:46:13 +0200 Subject: [PATCH 05/35] sentry_transaction_start: notify --- src/sentry_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sentry_core.c b/src/sentry_core.c index eef6946d5b..092cf1ce3f 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1348,6 +1348,10 @@ sentry_transaction_start_ts(sentry_transaction_context_t *opaque_tx_ctx, sentry_value_remove_by_key(tx, "parent_span_id"); sentry_value_remove_by_key(tx, "sampled"); sentry__scope_update_dsc(scope, options); + SENTRY_SCOPE_NOTIFY(scope, set_context, "trace", + sentry__guarded_strlen("trace"), + sentry_value_get_by_key( + scope->propagation_context, "trace")); } } } From 3f556447516dd9e45843937c364a4303836c21bd Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 20:17:07 +0200 Subject: [PATCH 06/35] notity scope-level attachment changes --- src/sentry_core.c | 8 +------- src/sentry_scope.c | 28 ++++++++++++++++++++++------ src/sentry_scope.h | 3 +++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 092cf1ce3f..7837663f81 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1936,13 +1936,7 @@ add_attachment(sentry_attachment_t *attachment) } } SENTRY_WITH_SCOPE_MUT (scope) { - sentry_attachment_t *new_attachment - = sentry__attachments_add(&scope->attachments, attachment); - if (attachment == new_attachment) { - SENTRY_SCOPE_NOTIFY(scope, add_attachment, attachment); - } else { - attachment = new_attachment; // existing/duplicate - } + attachment = sentry__scope_add_attachment(scope, attachment); } return attachment; } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 238298bb60..f3ddef89d6 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -748,6 +748,22 @@ sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) SENTRY_SCOPE_NOTIFY(scope, set_level, level); } +sentry_attachment_t * +sentry__scope_add_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment) +{ + if (!attachment) { + return NULL; + } + + sentry_attachment_t *added + = sentry__attachments_add(&scope->attachments, attachment); + if (added == attachment) { + SENTRY_SCOPE_NOTIFY(scope, add_attachment, attachment); + } + return added; +} + sentry_attachment_t * sentry_scope_attach_file(sentry_scope_t *scope, const char *path) { @@ -759,8 +775,8 @@ sentry_attachment_t * sentry_scope_attach_file_n( sentry_scope_t *scope, const char *path, size_t path_len) { - return sentry__attachments_add_path(&scope->attachments, - sentry__path_from_str_n(path, path_len), NULL, NULL); + return sentry__scope_add_attachment(scope, + sentry__attachment_from_path(sentry__path_from_str_n(path, path_len))); } sentry_attachment_t * @@ -775,7 +791,7 @@ sentry_attachment_t * sentry_scope_attach_bytes_n(sentry_scope_t *scope, const char *buf, size_t buf_len, const char *filename, size_t filename_len) { - return sentry__attachments_add(&scope->attachments, + return sentry__scope_add_attachment(scope, sentry__attachment_from_buffer( buf, buf_len, sentry__path_from_str_n(filename, filename_len))); } @@ -792,8 +808,8 @@ sentry_attachment_t * sentry_scope_attach_filew_n( sentry_scope_t *scope, const wchar_t *path, size_t path_len) { - return sentry__attachments_add_path(&scope->attachments, - sentry__path_from_wstr_n(path, path_len), NULL, NULL); + return sentry__scope_add_attachment(scope, + sentry__attachment_from_path(sentry__path_from_wstr_n(path, path_len))); } sentry_attachment_t * @@ -809,7 +825,7 @@ sentry_attachment_t * sentry_scope_attach_bytesw_n(sentry_scope_t *scope, const char *buf, size_t buf_len, const wchar_t *filename, size_t filename_len) { - return sentry__attachments_add(&scope->attachments, + return sentry__scope_add_attachment(scope, sentry__attachment_from_buffer( buf, buf_len, sentry__path_from_wstr_n(filename, filename_len))); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 47c56ee568..f5199d922f 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -157,6 +157,9 @@ void sentry__scope_remove_attribute(sentry_scope_t *scope, const char *key); void sentry__scope_remove_attribute_n( sentry_scope_t *scope, const char *key, size_t key_len); +sentry_attachment_t *sentry__scope_add_attachment( + sentry_scope_t *scope, sentry_attachment_t *attachment); + /** * These are convenience macros to automatically lock/unlock the global scope * inside a code block. From 06635d5d44918a87e618985a128c13227baa02e1 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 3 Jul 2026 20:25:03 +0200 Subject: [PATCH 07/35] fix sentry_clear_attachments --- src/sentry_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 7837663f81..292118720a 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1974,8 +1974,9 @@ sentry_clear_attachments(void) { SENTRY_WITH_OPTIONS (options) { SENTRY_WITH_SCOPE_MUT (scope) { - for (sentry_attachment_t *it = scope->attachments; it; - it = it->next) { + sentry_attachment_t *attachments = scope->attachments; + scope->attachments = NULL; + for (sentry_attachment_t *it = attachments; it; it = it->next) { if (options->backend && options->backend->remove_attachment_func) { options->backend->remove_attachment_func( @@ -1983,8 +1984,7 @@ sentry_clear_attachments(void) } SENTRY_SCOPE_NOTIFY(scope, remove_attachment, it); } - sentry__attachments_free(scope->attachments); - scope->attachments = NULL; + sentry__attachments_free(attachments); } } } From 534a7233702c85743f9fe3819085ed925e3394fd Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 11:35:15 +0200 Subject: [PATCH 08/35] add_observer: return bool --- src/sentry_scope.c | 7 ++++--- src/sentry_scope.h | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index f3ddef89d6..04a31d49ff 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -184,12 +184,12 @@ sentry__scope_observer_new(void) return SENTRY_MAKE(sentry_scope_observer_t); } -void +bool sentry__scope_add_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer) { if (!observer) { - return; + return false; } size_t new_count = scope->num_observers + 1; @@ -197,7 +197,7 @@ sentry__scope_add_observer( = sentry__calloc(new_count, sizeof(sentry_scope_observer_t *)); if (!new_array) { sentry_free(observer); - return; + return false; } if (scope->observers) { memcpy(new_array, scope->observers, @@ -207,6 +207,7 @@ sentry__scope_add_observer( new_array[scope->num_observers] = observer; scope->observers = new_array; scope->num_observers = new_count; + return true; } sentry_scope_t * diff --git a/src/sentry_scope.h b/src/sentry_scope.h index f5199d922f..56f633ba27 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -186,12 +186,11 @@ sentry_scope_observer_t *sentry__scope_observer_new(void); /** * Register a scope observer. * - * Takes ownership of `observer`; the caller must not use or free it after - * this call. Must be called while holding the scope lock (i.e., inside - * SENTRY_WITH_SCOPE_MUT). Registration order is respected — observers are - * notified in registration order. + * Takes ownership of `observer`; the caller must not free it after this call. + * Must be called while holding the scope lock. Registration order is respected + * — observers are notified in registration order. */ -void sentry__scope_add_observer( +bool sentry__scope_add_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer); /** Re-entrancy guard: set while notifying observers. */ From af522d32cdfa7583ff27bd8d0814e9150a5a9a63 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 11:35:41 +0200 Subject: [PATCH 09/35] add remove_observer --- src/sentry_scope.c | 26 ++++++++++++++++++++++++++ src/sentry_scope.h | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 04a31d49ff..54d123d97e 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -210,6 +210,32 @@ sentry__scope_add_observer( return true; } +void +sentry__scope_remove_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer) +{ + if (!observer || !scope->observers) { + return; + } + + for (size_t i = 0; i < scope->num_observers; i++) { + if (scope->observers[i] != observer) { + continue; + } + + sentry_free(observer); + for (size_t j = i + 1; j < scope->num_observers; j++) { + scope->observers[j - 1] = scope->observers[j]; + } + scope->num_observers--; + if (scope->num_observers == 0) { + sentry_free(scope->observers); + scope->observers = NULL; + } + return; + } +} + sentry_scope_t * sentry_local_scope_new(void) { diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 56f633ba27..77cee536ec 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -193,6 +193,15 @@ sentry_scope_observer_t *sentry__scope_observer_new(void); bool sentry__scope_add_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer); +/** + * Remove a scope observer. + * + * Frees `observer` if it is registered. Must be called while holding the scope + * lock. Does nothing if `observer` is NULL or not registered. + */ +void sentry__scope_remove_observer( + sentry_scope_t *scope, sentry_scope_observer_t *observer); + /** Re-entrancy guard: set while notifying observers. */ #define SENTRY_SCOPE_NOTIFY(scope, callback, ...) \ do { \ From e6a547383a4f93d577c218d28c9ae3dfccb68538 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 11:37:54 +0200 Subject: [PATCH 10/35] update tests --- tests/unit/test_scope.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index b45978e2eb..9b0c496177 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1370,8 +1370,8 @@ SENTRY_TEST(scope_observer_multiple) observer2->set_tag = observe_set_tag; SENTRY_WITH_SCOPE_MUT (scope) { - sentry__scope_add_observer(scope, observer1); - sentry__scope_add_observer(scope, observer2); + TEST_CHECK(sentry__scope_add_observer(scope, observer1)); + TEST_CHECK(sentry__scope_add_observer(scope, observer2)); } sentry_set_tag("multi", "test"); @@ -1384,6 +1384,19 @@ SENTRY_TEST(scope_observer_multiple) sentry_value_as_string(sentry_value_get_by_key(d2.tags, "multi")), "test"); + d1.was_called = false; + d2.was_called = false; + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_remove_observer(scope, observer1); + } + + sentry_set_tag("multi", "again"); + TEST_CHECK(!d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(d2.tags, "multi")), + "again"); + sentry_value_decref(d1.tags); sentry_value_decref(d2.tags); sentry_close(); From 8174983870472e5f59d6ec448cc02fdcecc15de3 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 12:58:10 +0200 Subject: [PATCH 11/35] prevent array shift during notify iteration --- src/sentry_scope.c | 38 ++++++++++++++++++++++++ src/sentry_scope.h | 15 +++++----- tests/unit/test_scope.c | 65 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 1 + 4 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 54d123d97e..6802168081 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -224,6 +224,11 @@ sentry__scope_remove_observer( } sentry_free(observer); + if (scope->is_notifying) { + // avoid shifting the array while SENTRY_SCOPE_NOTIFY is iterating + scope->observers[i] = NULL; + return; + } for (size_t j = i + 1; j < scope->num_observers; j++) { scope->observers[j - 1] = scope->observers[j]; } @@ -236,6 +241,39 @@ sentry__scope_remove_observer( } } +size_t +sentry__scope_begin_notify(sentry_scope_t *scope) +{ + if (scope->is_notifying) { + return 0; + } + scope->is_notifying = true; + return scope->num_observers; +} + +void +sentry__scope_end_notify(sentry_scope_t *scope) +{ + scope->is_notifying = false; + if (!scope->observers) { + return; + } + + // removals during notification leave tombstones to avoid shifting the array + // while it is being iterated + size_t j = 0; + for (size_t i = 0; i < scope->num_observers; i++) { + if (scope->observers[i]) { + scope->observers[j++] = scope->observers[i]; + } + } + scope->num_observers = j; + if (scope->num_observers == 0) { + sentry_free(scope->observers); + scope->observers = NULL; + } +} + sentry_scope_t * sentry_local_scope_new(void) { diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 77cee536ec..1859a905e6 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -202,19 +202,20 @@ bool sentry__scope_add_observer( void sentry__scope_remove_observer( sentry_scope_t *scope, sentry_scope_observer_t *observer); -/** Re-entrancy guard: set while notifying observers. */ +size_t sentry__scope_begin_notify(sentry_scope_t *scope); +void sentry__scope_end_notify(sentry_scope_t *scope); + +/** Notify observers registered before this notification started. */ #define SENTRY_SCOPE_NOTIFY(scope, callback, ...) \ do { \ - if ((scope)->is_notifying) \ - break; \ - (scope)->is_notifying = true; \ - for (size_t _i = 0; _i < (scope)->num_observers; _i++) { \ + size_t _end = sentry__scope_begin_notify(scope); \ + for (size_t _i = 0; _i < _end && _i < (scope)->num_observers; _i++) { \ sentry_scope_observer_t *_observer = (scope)->observers[_i]; \ - if (_observer->callback) { \ + if (_observer && _observer->callback) { \ _observer->callback(_observer->data, __VA_ARGS__); \ } \ } \ - (scope)->is_notifying = false; \ + sentry__scope_end_notify(scope); \ } while (0) /** diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 9b0c496177..29014b21d2 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1148,6 +1148,12 @@ typedef struct { bool was_called; } test_observer_data_t; +typedef struct { + test_observer_data_t *self_data; + sentry_scope_observer_t *self; + sentry_scope_observer_t *added; +} reentrant_observer_data_t; + static void observe_set_release(void *data, const char *release, size_t release_len) { @@ -1266,6 +1272,18 @@ observe_set_tag(void *data, const char *key, size_t key_len, const char *value, d->was_called = true; } +static void +observe_set_tag_remove_self_and_add(void *data, const char *key, size_t key_len, + const char *value, size_t value_len) +{ + reentrant_observer_data_t *d = (reentrant_observer_data_t *)data; + observe_set_tag(d->self_data, key, key_len, value, value_len); + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__scope_remove_observer(scope, d->self); + TEST_CHECK(sentry__scope_add_observer(scope, d->added)); + } +} + static void observe_remove_tag(void *data, const char *key, size_t key_len) { @@ -1402,6 +1420,53 @@ SENTRY_TEST(scope_observer_multiple) sentry_close(); } +SENTRY_TEST(scope_observer_mutate) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + test_observer_data_t d1 = { .tags = sentry_value_new_null() }; + test_observer_data_t d2 = { .tags = sentry_value_new_null() }; + test_observer_data_t d3 = { .tags = sentry_value_new_null() }; + reentrant_observer_data_t reentrant = { .self_data = &d1 }; + + sentry_scope_observer_t *observer1 = sentry__scope_observer_new(); + reentrant.self = observer1; + observer1->data = &reentrant; + observer1->set_tag = observe_set_tag_remove_self_and_add; + + sentry_scope_observer_t *observer2 = sentry__scope_observer_new(); + observer2->data = &d2; + observer2->set_tag = observe_set_tag; + + sentry_scope_observer_t *observer3 = sentry__scope_observer_new(); + reentrant.added = observer3; + observer3->data = &d3; + observer3->set_tag = observe_set_tag; + + SENTRY_WITH_SCOPE_MUT (scope) { + TEST_CHECK(sentry__scope_add_observer(scope, observer1)); + TEST_CHECK(sentry__scope_add_observer(scope, observer2)); + } + + sentry_set_tag("reentrant", "first"); + TEST_CHECK(d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK(!d3.was_called); + + d1.was_called = false; + d2.was_called = false; + sentry_set_tag("reentrant", "second"); + TEST_CHECK(!d1.was_called); + TEST_CHECK(d2.was_called); + TEST_CHECK(d3.was_called); + + sentry_value_decref(d1.tags); + sentry_value_decref(d2.tags); + sentry_value_decref(d3.tags); + sentry_close(); +} + SENTRY_TEST(scope_observer_release) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 7e31f205d3..965bda59ac 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -299,6 +299,7 @@ XX(scope_observer_extras) XX(scope_observer_fingerprint) XX(scope_observer_level) XX(scope_observer_multiple) +XX(scope_observer_mutate) XX(scope_observer_null) XX(scope_observer_release) XX(scope_observer_tags) From c3077195662b3e463a02e149528299bb94a6bf6a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 13:45:35 +0200 Subject: [PATCH 12/35] fix nested notify --- src/sentry_scope.c | 11 +++++------ src/sentry_scope.h | 2 +- tests/unit/test_scope.c | 8 +++++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 6802168081..a79b756072 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -90,7 +90,7 @@ init_scope(sentry_scope_t *scope) scope->trace_managed = true; scope->observers = NULL; scope->num_observers = 0; - scope->is_notifying = false; + scope->is_notifying = 0; } static sentry_scope_t * @@ -244,17 +244,16 @@ sentry__scope_remove_observer( size_t sentry__scope_begin_notify(sentry_scope_t *scope) { - if (scope->is_notifying) { - return 0; - } - scope->is_notifying = true; + scope->is_notifying++; return scope->num_observers; } void sentry__scope_end_notify(sentry_scope_t *scope) { - scope->is_notifying = false; + if (--scope->is_notifying > 0) { + return; + } if (!scope->observers) { return; } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 1859a905e6..389ed75e5e 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -81,7 +81,7 @@ struct sentry_scope_s { sentry_scope_observer_t **observers; size_t num_observers; - bool is_notifying; + size_t is_notifying; }; /** diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 29014b21d2..2c0396f5d6 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1150,6 +1150,7 @@ typedef struct { typedef struct { test_observer_data_t *self_data; + test_observer_data_t *nested_data; sentry_scope_observer_t *self; sentry_scope_observer_t *added; } reentrant_observer_data_t; @@ -1282,6 +1283,9 @@ observe_set_tag_remove_self_and_add(void *data, const char *key, size_t key_len, sentry__scope_remove_observer(scope, d->self); TEST_CHECK(sentry__scope_add_observer(scope, d->added)); } + d->nested_data->was_called = false; + sentry_set_extra("nested", sentry_value_new_string("notify")); + TEST_CHECK(d->nested_data->was_called); } static void @@ -1428,7 +1432,8 @@ SENTRY_TEST(scope_observer_mutate) test_observer_data_t d1 = { .tags = sentry_value_new_null() }; test_observer_data_t d2 = { .tags = sentry_value_new_null() }; test_observer_data_t d3 = { .tags = sentry_value_new_null() }; - reentrant_observer_data_t reentrant = { .self_data = &d1 }; + reentrant_observer_data_t reentrant + = { .self_data = &d1, .nested_data = &d2 }; sentry_scope_observer_t *observer1 = sentry__scope_observer_new(); reentrant.self = observer1; @@ -1438,6 +1443,7 @@ SENTRY_TEST(scope_observer_mutate) sentry_scope_observer_t *observer2 = sentry__scope_observer_new(); observer2->data = &d2; observer2->set_tag = observe_set_tag; + observer2->set_extra = observe_set_extra; sentry_scope_observer_t *observer3 = sentry__scope_observer_new(); reentrant.added = observer3; From f5eedc8028b8f9b6e802cbd40bcecf30179a09ab Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 13:58:14 +0200 Subject: [PATCH 13/35] test no flush --- tests/unit/test_scope.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 2c0396f5d6..0be25809cb 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1279,12 +1279,15 @@ observe_set_tag_remove_self_and_add(void *data, const char *key, size_t key_len, { reentrant_observer_data_t *d = (reentrant_observer_data_t *)data; observe_set_tag(d->self_data, key, key_len, value, value_len); - SENTRY_WITH_SCOPE_MUT (scope) { + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { sentry__scope_remove_observer(scope, d->self); TEST_CHECK(sentry__scope_add_observer(scope, d->added)); } d->nested_data->was_called = false; - sentry_set_extra("nested", sentry_value_new_string("notify")); + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry_scope_set_extra( + scope, "nested", sentry_value_new_string("notify")); + } TEST_CHECK(d->nested_data->was_called); } From 925c8e1f947b99c96bb84f23ee1713fd837a0ed6 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 14:07:24 +0200 Subject: [PATCH 14/35] was_notifying (tsan) --- src/sentry_scope.c | 4 ++++ tests/unit/test_scope.c | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sentry_scope.c b/src/sentry_scope.c index a79b756072..8c75ee7962 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -168,7 +168,11 @@ sentry__scope_unlock(void) void sentry__scope_flush_unlock(void) { + bool was_notifying = g_scope.is_notifying > 0; sentry__scope_unlock(); + if (was_notifying) { + return; + } SENTRY_WITH_OPTIONS (options) { // we try to unlock the scope as soon as possible. The // backend will do its own `WITH_SCOPE` internally. diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 0be25809cb..56bfdf7d26 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1284,10 +1284,7 @@ observe_set_tag_remove_self_and_add(void *data, const char *key, size_t key_len, TEST_CHECK(sentry__scope_add_observer(scope, d->added)); } d->nested_data->was_called = false; - SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { - sentry_scope_set_extra( - scope, "nested", sentry_value_new_string("notify")); - } + sentry_set_extra("nested", sentry_value_new_string("notify")); TEST_CHECK(d->nested_data->was_called); } From 2833b81531f79523bf31e64b4ac1e7660d637856 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 4 Jul 2026 20:53:38 +0200 Subject: [PATCH 15/35] trace context --- src/sentry_core.c | 7 +++++ src/sentry_scope.c | 44 ++++++++++++++++++++------- src/sentry_scope.h | 7 +++++ tests/unit/test_scope.c | 66 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 1 + 5 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 292118720a..5329c26be5 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1422,6 +1422,9 @@ sentry__transaction_finish_value( if (sentry__string_eq(tx_id, scope_tx_id)) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; + if (!scope->trace_managed) { + sentry__scope_notify_trace_context(scope); + } } } // if the SDK manages the trace (rather than the user or a downstream @@ -1434,6 +1437,7 @@ sentry__transaction_finish_value( sentry_value_set_by_key( sentry_value_get_by_key(scope->propagation_context, "trace"), "trace_id", txn_trace_id); + sentry__scope_notify_trace_context(scope); } } // The sampling decision should already be made for transactions @@ -1498,6 +1502,7 @@ sentry_set_transaction_object(sentry_transaction_t *tx) sentry__transaction_decref(scope->transaction_object); sentry__transaction_incref(tx); scope->transaction_object = tx; + sentry__scope_notify_trace_context(scope); } } @@ -1510,6 +1515,7 @@ sentry_set_span(sentry_span_t *span) sentry__span_decref(scope->span); sentry__span_incref(span); scope->span = span; + sentry__scope_notify_trace_context(scope); } } @@ -1671,6 +1677,7 @@ sentry_span_finish_ts(sentry_span_t *opaque_span, uint64_t timestamp) if (sentry__string_eq(span_id, scope_span_id)) { sentry__span_decref(scope->span); scope->span = NULL; + sentry__scope_notify_trace_context(scope); } } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 8c75ee7962..b197455ba5 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -458,6 +458,39 @@ get_span_or_transaction(const sentry_scope_t *scope) } } +static sentry_value_t +get_scope_trace_context(const sentry_scope_t *scope) +{ + sentry_value_t scoped_txn_or_span = get_span_or_transaction(scope); + sentry_value_t trace_context + = sentry__value_get_trace_context(scoped_txn_or_span); + if (sentry_value_is_null(trace_context)) { + return trace_context; + } + + sentry_value_t data = sentry_value_get_by_key(scoped_txn_or_span, "data"); + if (!sentry_value_is_null(data)) { + sentry_value_incref(data); + sentry_value_set_by_key(trace_context, "data", data); + } + return trace_context; +} + +void +sentry__scope_notify_trace_context(sentry_scope_t *scope) +{ + sentry_value_t trace_context = get_scope_trace_context(scope); + if (sentry_value_is_null(trace_context)) { + // Fall back to the propagation trace so observers see the same trace + // context as an event with scope applied. + trace_context + = sentry_value_get_by_key(scope->propagation_context, "trace"); + sentry_value_incref(trace_context); + } + SENTRY_SCOPE_NOTIFY(scope, set_trace_context, trace_context); + sentry_value_decref(trace_context); +} + #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void) @@ -554,23 +587,14 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted - sentry_value_t scoped_txn_or_span = sentry_value_new_null(); sentry_value_t scope_trace = sentry_value_new_null(); if (!is_transaction) { - scoped_txn_or_span = get_span_or_transaction(scope); - scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); + scope_trace = get_scope_trace_context(scope); } if (!sentry_value_is_null(scope_trace)) { if (sentry_value_is_null(contexts)) { contexts = sentry_value_new_object(); } - sentry_value_t scoped_txn_or_span_data - = sentry_value_get_by_key(scoped_txn_or_span, "data"); - if (!sentry_value_is_null(scoped_txn_or_span_data)) { - sentry_value_incref(scoped_txn_or_span_data); - sentry_value_set_by_key( - scope_trace, "data", scoped_txn_or_span_data); - } sentry_value_set_by_key(contexts, "trace", scope_trace); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 389ed75e5e..9d1f6b3d87 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -42,6 +42,7 @@ typedef struct sentry_scope_observer_s { void (*set_context)( void *data, const char *key, size_t key_len, sentry_value_t value); void (*remove_context)(void *data, const char *key, size_t key_len); + void (*set_trace_context)(void *data, sentry_value_t value); void (*add_attachment)(void *data, sentry_attachment_t *attachment); void (*remove_attachment)(void *data, sentry_attachment_t *attachment); @@ -225,6 +226,12 @@ void sentry__scope_end_notify(sentry_scope_t *scope); void sentry__scope_update_dsc( sentry_scope_t *scope, const sentry_options_t *options); +/** + * Notifies observers with the trace context derived from the active scoped span + * or transaction, falling back to the propagation context when neither is set. + */ +void sentry__scope_notify_trace_context(sentry_scope_t *scope); + /** * Replaces the scope's dynamic sampling context (DSC) with a verbatim copy * of the incoming object. Used when continuing an upstream trace: per the diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 56bfdf7d26..4d793fd03e 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1144,6 +1144,7 @@ typedef struct { sentry_value_t tags; sentry_value_t extras; sentry_value_t contexts; + sentry_value_t trace_contexts; sentry_value_t attachments; bool was_called; } test_observer_data_t; @@ -1350,6 +1351,18 @@ observe_remove_context(void *data, const char *key, size_t key_len) d->was_called = true; } +static void +observe_set_trace_context(void *data, sentry_value_t trace_context) +{ + test_observer_data_t *d = (test_observer_data_t *)data; + if (sentry_value_is_null(d->trace_contexts)) { + d->trace_contexts = sentry_value_new_list(); + } + sentry_value_incref(trace_context); + sentry_value_append(d->trace_contexts, trace_context); + d->was_called = true; +} + SENTRY_TEST(scope_observer_null) { SENTRY_TEST_OPTIONS_NEW(options); @@ -1774,6 +1787,59 @@ SENTRY_TEST(scope_observer_contexts) sentry_close(); } +SENTRY_TEST(scope_observer_trace_context) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + test_observer_data_t d = { .trace_contexts = sentry_value_new_null() }; + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + observer->data = &d; + observer->set_trace_context = observe_set_trace_context; + + SENTRY_WITH_SCOPE_MUT (scope) { + TEST_CHECK(sentry__scope_add_observer(scope, observer)); + } + + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("root", "op"); + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + sentry_transaction_set_data(tx, "tx-data", sentry_value_new_string("root")); + sentry_set_transaction_object(tx); + + TEST_CHECK(d.was_called); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 1); + sentry_value_t trace = sentry_value_get_by_index(d.trace_contexts, 0); + TEST_CHECK( + !sentry_value_is_null(sentry_value_get_by_key(trace, "trace_id"))); + TEST_CHECK( + !sentry_value_is_null(sentry_value_get_by_key(trace, "span_id"))); + sentry_value_t data = sentry_value_get_by_key(trace, "data"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(data, "tx-data")), + "root"); + + sentry_span_t *child = sentry_transaction_start_child(tx, "child", "desc"); + sentry_span_set_data(child, "span-data", sentry_value_new_string("child")); + sentry_set_span(child); + + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 2); + trace = sentry_value_get_by_index(d.trace_contexts, 1); + data = sentry_value_get_by_key(trace, "data"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(data, "span-data")), + "child"); + + sentry_span_finish(child); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 3); + + sentry_transaction_finish(tx); + sentry_value_decref(d.trace_contexts); + sentry_close(); +} + SENTRY_TEST(scope_observer_attachments) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 965bda59ac..f16b745ae7 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -303,6 +303,7 @@ XX(scope_observer_mutate) XX(scope_observer_null) XX(scope_observer_release) XX(scope_observer_tags) +XX(scope_observer_trace_context) XX(scope_observer_transaction) XX(scope_observer_user) XX(scope_tags) From f015370d5f9ae1efb67b7bbea08ca52672a064bd Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 16:23:23 +0200 Subject: [PATCH 16/35] Revert "trace context" This reverts commit 2d983e7cf9e27075247deb30abb5f86c21e82810 because it's annoyingly complicated and probably not needed. --- src/sentry_core.c | 7 ----- src/sentry_scope.c | 44 +++++++-------------------- src/sentry_scope.h | 7 ----- tests/unit/test_scope.c | 66 ----------------------------------------- tests/unit/tests.inc | 1 - 5 files changed, 10 insertions(+), 115 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 5329c26be5..292118720a 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1422,9 +1422,6 @@ sentry__transaction_finish_value( if (sentry__string_eq(tx_id, scope_tx_id)) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; - if (!scope->trace_managed) { - sentry__scope_notify_trace_context(scope); - } } } // if the SDK manages the trace (rather than the user or a downstream @@ -1437,7 +1434,6 @@ sentry__transaction_finish_value( sentry_value_set_by_key( sentry_value_get_by_key(scope->propagation_context, "trace"), "trace_id", txn_trace_id); - sentry__scope_notify_trace_context(scope); } } // The sampling decision should already be made for transactions @@ -1502,7 +1498,6 @@ sentry_set_transaction_object(sentry_transaction_t *tx) sentry__transaction_decref(scope->transaction_object); sentry__transaction_incref(tx); scope->transaction_object = tx; - sentry__scope_notify_trace_context(scope); } } @@ -1515,7 +1510,6 @@ sentry_set_span(sentry_span_t *span) sentry__span_decref(scope->span); sentry__span_incref(span); scope->span = span; - sentry__scope_notify_trace_context(scope); } } @@ -1677,7 +1671,6 @@ sentry_span_finish_ts(sentry_span_t *opaque_span, uint64_t timestamp) if (sentry__string_eq(span_id, scope_span_id)) { sentry__span_decref(scope->span); scope->span = NULL; - sentry__scope_notify_trace_context(scope); } } } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index b197455ba5..8c75ee7962 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -458,39 +458,6 @@ get_span_or_transaction(const sentry_scope_t *scope) } } -static sentry_value_t -get_scope_trace_context(const sentry_scope_t *scope) -{ - sentry_value_t scoped_txn_or_span = get_span_or_transaction(scope); - sentry_value_t trace_context - = sentry__value_get_trace_context(scoped_txn_or_span); - if (sentry_value_is_null(trace_context)) { - return trace_context; - } - - sentry_value_t data = sentry_value_get_by_key(scoped_txn_or_span, "data"); - if (!sentry_value_is_null(data)) { - sentry_value_incref(data); - sentry_value_set_by_key(trace_context, "data", data); - } - return trace_context; -} - -void -sentry__scope_notify_trace_context(sentry_scope_t *scope) -{ - sentry_value_t trace_context = get_scope_trace_context(scope); - if (sentry_value_is_null(trace_context)) { - // Fall back to the propagation trace so observers see the same trace - // context as an event with scope applied. - trace_context - = sentry_value_get_by_key(scope->propagation_context, "trace"); - sentry_value_incref(trace_context); - } - SENTRY_SCOPE_NOTIFY(scope, set_trace_context, trace_context); - sentry_value_decref(trace_context); -} - #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(void) @@ -587,14 +554,23 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted + sentry_value_t scoped_txn_or_span = sentry_value_new_null(); sentry_value_t scope_trace = sentry_value_new_null(); if (!is_transaction) { - scope_trace = get_scope_trace_context(scope); + scoped_txn_or_span = get_span_or_transaction(scope); + scope_trace = sentry__value_get_trace_context(scoped_txn_or_span); } if (!sentry_value_is_null(scope_trace)) { if (sentry_value_is_null(contexts)) { contexts = sentry_value_new_object(); } + sentry_value_t scoped_txn_or_span_data + = sentry_value_get_by_key(scoped_txn_or_span, "data"); + if (!sentry_value_is_null(scoped_txn_or_span_data)) { + sentry_value_incref(scoped_txn_or_span_data); + sentry_value_set_by_key( + scope_trace, "data", scoped_txn_or_span_data); + } sentry_value_set_by_key(contexts, "trace", scope_trace); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 9d1f6b3d87..389ed75e5e 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -42,7 +42,6 @@ typedef struct sentry_scope_observer_s { void (*set_context)( void *data, const char *key, size_t key_len, sentry_value_t value); void (*remove_context)(void *data, const char *key, size_t key_len); - void (*set_trace_context)(void *data, sentry_value_t value); void (*add_attachment)(void *data, sentry_attachment_t *attachment); void (*remove_attachment)(void *data, sentry_attachment_t *attachment); @@ -226,12 +225,6 @@ void sentry__scope_end_notify(sentry_scope_t *scope); void sentry__scope_update_dsc( sentry_scope_t *scope, const sentry_options_t *options); -/** - * Notifies observers with the trace context derived from the active scoped span - * or transaction, falling back to the propagation context when neither is set. - */ -void sentry__scope_notify_trace_context(sentry_scope_t *scope); - /** * Replaces the scope's dynamic sampling context (DSC) with a verbatim copy * of the incoming object. Used when continuing an upstream trace: per the diff --git a/tests/unit/test_scope.c b/tests/unit/test_scope.c index 4d793fd03e..56bfdf7d26 100644 --- a/tests/unit/test_scope.c +++ b/tests/unit/test_scope.c @@ -1144,7 +1144,6 @@ typedef struct { sentry_value_t tags; sentry_value_t extras; sentry_value_t contexts; - sentry_value_t trace_contexts; sentry_value_t attachments; bool was_called; } test_observer_data_t; @@ -1351,18 +1350,6 @@ observe_remove_context(void *data, const char *key, size_t key_len) d->was_called = true; } -static void -observe_set_trace_context(void *data, sentry_value_t trace_context) -{ - test_observer_data_t *d = (test_observer_data_t *)data; - if (sentry_value_is_null(d->trace_contexts)) { - d->trace_contexts = sentry_value_new_list(); - } - sentry_value_incref(trace_context); - sentry_value_append(d->trace_contexts, trace_context); - d->was_called = true; -} - SENTRY_TEST(scope_observer_null) { SENTRY_TEST_OPTIONS_NEW(options); @@ -1787,59 +1774,6 @@ SENTRY_TEST(scope_observer_contexts) sentry_close(); } -SENTRY_TEST(scope_observer_trace_context) -{ - SENTRY_TEST_OPTIONS_NEW(options); - sentry_options_set_traces_sample_rate(options, 1.0); - sentry_init(options); - - test_observer_data_t d = { .trace_contexts = sentry_value_new_null() }; - sentry_scope_observer_t *observer = sentry__scope_observer_new(); - observer->data = &d; - observer->set_trace_context = observe_set_trace_context; - - SENTRY_WITH_SCOPE_MUT (scope) { - TEST_CHECK(sentry__scope_add_observer(scope, observer)); - } - - sentry_transaction_context_t *tx_ctx - = sentry_transaction_context_new("root", "op"); - sentry_transaction_t *tx - = sentry_transaction_start(tx_ctx, sentry_value_new_null()); - sentry_transaction_set_data(tx, "tx-data", sentry_value_new_string("root")); - sentry_set_transaction_object(tx); - - TEST_CHECK(d.was_called); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 1); - sentry_value_t trace = sentry_value_get_by_index(d.trace_contexts, 0); - TEST_CHECK( - !sentry_value_is_null(sentry_value_get_by_key(trace, "trace_id"))); - TEST_CHECK( - !sentry_value_is_null(sentry_value_get_by_key(trace, "span_id"))); - sentry_value_t data = sentry_value_get_by_key(trace, "data"); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(data, "tx-data")), - "root"); - - sentry_span_t *child = sentry_transaction_start_child(tx, "child", "desc"); - sentry_span_set_data(child, "span-data", sentry_value_new_string("child")); - sentry_set_span(child); - - TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 2); - trace = sentry_value_get_by_index(d.trace_contexts, 1); - data = sentry_value_get_by_key(trace, "data"); - TEST_CHECK_STRING_EQUAL( - sentry_value_as_string(sentry_value_get_by_key(data, "span-data")), - "child"); - - sentry_span_finish(child); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(d.trace_contexts), 3); - - sentry_transaction_finish(tx); - sentry_value_decref(d.trace_contexts); - sentry_close(); -} - SENTRY_TEST(scope_observer_attachments) { SENTRY_TEST_OPTIONS_NEW(options); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index f16b745ae7..965bda59ac 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -303,7 +303,6 @@ XX(scope_observer_mutate) XX(scope_observer_null) XX(scope_observer_release) XX(scope_observer_tags) -XX(scope_observer_trace_context) XX(scope_observer_transaction) XX(scope_observer_user) XX(scope_tags) From 9e91329588b6e0486847cdee9c60ea2032dac97b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 14:13:54 +0200 Subject: [PATCH 17/35] feat: add sentry_options_add_integration API Adds a sentry_integration_t struct with a register_func callback that is called from sentry_init() while the scope lock is held. Integrations can use this to create and register scope observers. sentry_options_add_integration() stores integrations in options, which takes ownership. An optional free_func is called before the integration struct is freed. --- include/sentry.h | 21 +++++++++++++++++++++ src/sentry_core.c | 8 ++++++++ src/sentry_integration.h | 23 +++++++++++++++++++++++ src/sentry_options.c | 34 ++++++++++++++++++++++++++++++++++ src/sentry_options.h | 3 +++ 5 files changed, 89 insertions(+) create mode 100644 src/sentry_integration.h diff --git a/include/sentry.h b/include/sentry.h index 441833d154..3a4b19abb1 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -860,6 +860,15 @@ SENTRY_API void sentry_capture_envelope(sentry_envelope_t *envelope); struct sentry_options_s; typedef struct sentry_options_s sentry_options_t; +/** + * This represents an integration that can be configured via options and is + * automatically wired up during `sentry_init`. + * + * See `sentry_options_add_integration`. + */ +struct sentry_integration_s; +typedef struct sentry_integration_s sentry_integration_t; + /** * This represents an interface for user-defined transports. * @@ -1993,6 +2002,18 @@ SENTRY_API uint64_t sentry_options_get_transfer_timeout(sentry_options_t *opts); SENTRY_API void sentry_options_set_backend( sentry_options_t *opts, sentry_backend_t *backend); +/** + * Adds an integration to the options. + * + * The integration's `register_func` callback will be invoked during + * `sentry_init` (while the scope lock is held), allowing the integration to + * set up scope observers and other SDK internals. + * + * Takes ownership of `integration`. + */ +SENTRY_EXPERIMENTAL_API void sentry_options_add_integration( + sentry_options_t *opts, sentry_integration_t *integration); + /* -- Global/Scope APIs -- */ /** diff --git a/src/sentry_core.c b/src/sentry_core.c index 292118720a..4e7ae41a5d 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -228,6 +228,14 @@ sentry_init(sentry_options_t *options) scope->breadcrumbs, options->max_breadcrumbs); sentry__scope_update_dsc(scope, options); + + for (size_t i = 0; i < options->num_integrations; i++) { + sentry_integration_t *integration = options->integrations[i]; + if (integration->register_func) { + integration->register_func( + integration->data, scope, options); + } + } } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); diff --git a/src/sentry_integration.h b/src/sentry_integration.h new file mode 100644 index 0000000000..972989e6fa --- /dev/null +++ b/src/sentry_integration.h @@ -0,0 +1,23 @@ +#ifndef SENTRY_INTEGRATION_H_INCLUDED +#define SENTRY_INTEGRATION_H_INCLUDED + +#include "sentry_boot.h" + +/** + * Integration callback that is invoked during `sentry_init` while the scope + * lock is held. Implementors use this to, e.g., create and register a scope + * observer. + */ +typedef struct sentry_integration_s { + void *data; + + void (*register_func)( + void *data, sentry_scope_t *scope, const sentry_options_t *options); + + /** + * Optional cleanup callback, invoked before the integration struct is freed. + */ + void (*free_func)(void *data); +} sentry_integration_t; + +#endif diff --git a/src/sentry_options.c b/src/sentry_options.c index 01d1ab1f7b..3bfb79ec7a 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -156,6 +156,15 @@ sentry_options_free(sentry_options_t *opts) sentry__attachments_free(opts->attachments); sentry__run_free(opts->run); + for (size_t i = 0; i < opts->num_integrations; i++) { + sentry_integration_t *integration = opts->integrations[i]; + if (integration->free_func) { + integration->free_func(integration->data); + } + sentry_free(integration); + } + sentry_free(opts->integrations); + sentry_free(opts); } @@ -930,6 +939,31 @@ sentry_options_set_backend(sentry_options_t *opts, sentry_backend_t *backend) opts->backend = backend; } +void +sentry_options_add_integration( + sentry_options_t *opts, sentry_integration_t *integration) +{ + if (!integration) { + return; + } + + size_t new_count = opts->num_integrations + 1; + sentry_integration_t **new_array + = sentry__calloc(new_count, sizeof(sentry_integration_t *)); + if (!new_array) { + sentry_free(integration); + return; + } + if (opts->integrations) { + memcpy(new_array, opts->integrations, + opts->num_integrations * sizeof(sentry_integration_t *)); + sentry_free(opts->integrations); + } + new_array[opts->num_integrations] = integration; + opts->integrations = new_array; + opts->num_integrations = new_count; +} + void sentry_options_set_enable_logs(sentry_options_t *opts, int enable_logs) { diff --git a/src/sentry_options.h b/src/sentry_options.h index 0947ef452a..e9f848964c 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -5,6 +5,7 @@ #include "sentry_attachment.h" #include "sentry_database.h" +#include "sentry_integration.h" #include "sentry_logger.h" #include "sentry_session.h" #include "sentry_utils.h" @@ -95,6 +96,8 @@ struct sentry_options_s { not exposed through the options API */ struct sentry_backend_s *backend; sentry_session_t *session; + sentry_integration_t **integrations; + size_t num_integrations; long refcount; uint64_t shutdown_timeout; From 9150556ce47665ac6470b7ae85bcd5517577e6cf Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 14:17:19 +0200 Subject: [PATCH 18/35] feat: add WER integration via scope observer Adds sentry_integration_wer_new() which creates an integration that uses a scope observer to sync sentry scope state to Windows Error Reporting: - Tags are synced via WerRegisterCustomMetadata (loaded dynamically for compatibility with pre-19H1 Windows 10 builds). - File attachments are registered with WerRegisterFile. - Buffer attachments are registered with WerRegisterMemoryBlock. When tags or attachments are removed via the sentry API, the corresponding WER registrations are cleaned up. --- include/sentry.h | 9 + src/CMakeLists.txt | 21 +- src/integrations/sentry_integration_wer.c | 228 ++++++++++++++++++++++ 3 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 src/integrations/sentry_integration_wer.c diff --git a/include/sentry.h b/include/sentry.h index 3a4b19abb1..04ed3162a6 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -2014,6 +2014,15 @@ SENTRY_API void sentry_options_set_backend( SENTRY_EXPERIMENTAL_API void sentry_options_add_integration( sentry_options_t *opts, sentry_integration_t *integration); +/** + * Creates a WER (Windows Error Reporting) integration that syncs scope tags + * and attachments to WER via `WerRegisterCustomMetadata`, `WerRegisterFile`, + * and `WerRegisterMemoryBlock`. + * + * Use with `sentry_options_add_integration`. + */ +SENTRY_EXPERIMENTAL_API sentry_integration_t *sentry_integration_wer_new(void); + /* -- Global/Scope APIs -- */ /** diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 20a8036243..2a7066a4e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,16 +83,17 @@ sentry_target_sources_cwd(sentry ) # generic platform / path / symbolizer -if(WIN32) - sentry_target_sources_cwd(sentry - sentry_random.c - sentry_random.h - sentry_windows_dbghelp.c - sentry_windows_dbghelp.h - path/sentry_path_windows.c - process/sentry_process_windows.c - symbolizer/sentry_symbolizer_windows.c - ) + if(WIN32) + sentry_target_sources_cwd(sentry + sentry_random.c + sentry_random.h + sentry_windows_dbghelp.c + sentry_windows_dbghelp.h + integrations/sentry_integration_wer.c + path/sentry_path_windows.c + process/sentry_process_windows.c + symbolizer/sentry_symbolizer_windows.c + ) elseif(NX OR PROSPERO) sentry_target_sources_cwd(sentry sentry_unix_spinlock.h diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c new file mode 100644 index 0000000000..5273d78f33 --- /dev/null +++ b/src/integrations/sentry_integration_wer.c @@ -0,0 +1,228 @@ +#include "sentry_boot.h" + +#ifdef SENTRY_PLATFORM_WINDOWS + +# include "sentry_alloc.h" +# include "sentry_attachment.h" +# include "sentry_integration.h" +# include "sentry_logger.h" +# include "sentry_scope.h" +# include "sentry_string.h" + +# include + +# ifndef WER_FILE_ANONYMOUS_DATA +# define WER_FILE_ANONYMOUS_DATA 0x2 +# endif + +typedef struct { + HMODULE wer_dll; + HRESULT(WINAPI *WerRegisterCustomMetadata_fn)(PCWSTR key, PCWSTR value); + HRESULT(WINAPI *WerUnregisterCustomMetadata_fn)(PCWSTR key); +} wer_state_t; + +static HRESULT +wer_set_tag(void *data, const char *key, size_t key_len, const char *value, + size_t value_len) +{ + wer_state_t *state = (wer_state_t *)data; + if (!state->WerRegisterCustomMetadata_fn) { + return S_OK; + } + if (!key || !value) { + return S_OK; + } + + char *key_n = sentry__string_clone_n(key, key_len); + char *value_n = sentry__string_clone_n(value, value_len); + wchar_t *key_w = sentry__string_to_wstr(key_n); + wchar_t *value_w = sentry__string_to_wstr(value_n); + sentry_free(key_n); + sentry_free(value_n); + + if (!key_w || !value_w) { + sentry_free(key_w); + sentry_free(value_w); + return E_OUTOFMEMORY; + } + + HRESULT hr = state->WerRegisterCustomMetadata_fn(key_w, value_w); + if (FAILED(hr)) { + SENTRY_WARNF("WerRegisterCustomMetadata failed: hr=0x%08lx", + (unsigned long)hr); + } + + sentry_free(value_w); + sentry_free(key_w); + return hr; +} + +static HRESULT +wer_remove_tag(void *data, const char *key, size_t key_len) +{ + wer_state_t *state = (wer_state_t *)data; + if (!state->WerUnregisterCustomMetadata_fn) { + return S_OK; + } + if (!key) { + return S_OK; + } + + char *key_n = sentry__string_clone_n(key, key_len); + wchar_t *key_w = sentry__string_to_wstr(key_n); + sentry_free(key_n); + + if (!key_w) { + return E_OUTOFMEMORY; + } + + HRESULT hr = state->WerUnregisterCustomMetadata_fn(key_w); + if (FAILED(hr)) { + SENTRY_WARNF("WerUnregisterCustomMetadata failed: hr=0x%08lx", + (unsigned long)hr); + } + + sentry_free(key_w); + return hr; +} + +static HRESULT +wer_add_attachment(void *data, sentry_attachment_t *attachment) +{ + (void)data; + + if (!attachment) { + return S_OK; + } + + if (attachment->path) { + const wchar_t *path_w = attachment->path->path_w; + if (!path_w) { + return S_OK; + } + HRESULT hr + = WerRegisterFile(path_w, WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA); + if (FAILED(hr)) { + SENTRY_WARNF("WerRegisterFile failed: hr=0x%08lx", + (unsigned long)hr); + } + return hr; + } + + if (attachment->buf && attachment->buf_len > 0) { + if (attachment->buf_len > MAXDWORD) { + SENTRY_WARNF("WerRegisterMemoryBlock: buffer too large (%zu bytes)", + attachment->buf_len); + return E_INVALIDARG; + } + HRESULT hr = WerRegisterMemoryBlock( + (PVOID)attachment->buf, (DWORD)attachment->buf_len); + if (FAILED(hr)) { + SENTRY_WARNF("WerRegisterMemoryBlock failed: hr=0x%08lx", + (unsigned long)hr); + } + return hr; + } + + return S_OK; +} + +static HRESULT +wer_remove_attachment(void *data, sentry_attachment_t *attachment) +{ + (void)data; + + if (!attachment) { + return S_OK; + } + + if (attachment->path) { + const wchar_t *path_w = attachment->path->path_w; + if (!path_w) { + return S_OK; + } + HRESULT hr = WerUnregisterFile(path_w); + if (FAILED(hr)) { + SENTRY_WARNF("WerUnregisterFile failed: hr=0x%08lx", + (unsigned long)hr); + } + return hr; + } + + if (attachment->buf) { + HRESULT hr = WerUnregisterMemoryBlock((PVOID)attachment->buf); + if (FAILED(hr)) { + SENTRY_WARNF("WerUnregisterMemoryBlock failed: hr=0x%08lx", + (unsigned long)hr); + } + return hr; + } + + return S_OK; +} + +static void +wer_free(void *data) +{ + wer_state_t *state = (wer_state_t *)data; + if (state->wer_dll) { + FreeLibrary(state->wer_dll); + } + sentry_free(state); +} + +static void +wer_register(void *data, sentry_scope_t *scope, const sentry_options_t *options) +{ + (void)options; + + wer_state_t *state = (wer_state_t *)data; + state->wer_dll = LoadLibraryW(L"wer.dll"); + if (state->wer_dll) { + state->WerRegisterCustomMetadata_fn = (HRESULT(WINAPI *)(PCWSTR, PCWSTR)) + GetProcAddress(state->wer_dll, "WerRegisterCustomMetadata"); + state->WerUnregisterCustomMetadata_fn + = (HRESULT(WINAPI *)(PCWSTR)) + GetProcAddress(state->wer_dll, "WerUnregisterCustomMetadata"); + } + + if (!state->WerRegisterCustomMetadata_fn) { + SENTRY_DEBUG("WerRegisterCustomMetadata not available; " + "tag sync to WER will be skipped"); + } + + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + if (!observer) { + return; + } + observer->data = state; + observer->set_tag = wer_set_tag; + observer->remove_tag = wer_remove_tag; + observer->add_attachment = wer_add_attachment; + observer->remove_attachment = wer_remove_attachment; + + sentry__scope_add_observer(scope, observer); +} + +sentry_integration_t * +sentry_integration_wer_new(void) +{ + wer_state_t *state = SENTRY_MAKE(wer_state_t); + if (!state) { + return NULL; + } + + sentry_integration_t *integration = SENTRY_MAKE(sentry_integration_t); + if (!integration) { + sentry_free(state); + return NULL; + } + + integration->data = state; + integration->register_func = wer_register; + integration->free_func = wer_free; + + return integration; +} + +#endif /* SENTRY_PLATFORM_WINDOWS */ From 752a119c2705c40d3f8c939b262d131e47442a97 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 14:58:43 +0200 Subject: [PATCH 19/35] refactor: use Qt-style compile flag for WER integration Replace the generic sentry_integration_t approach with a simpler pattern matching the existing Qt integration (SENTRY_INTEGRATION_QT): - Remove sentry_integration_t, sentry_options_add_integration(), and all the dynamic array management in options. - Add a SENTRY_INTEGRATION_WER CMake option that conditionally compiles the WER integration source and sets the compile definition. - sentry_integration_setup_wer(scope, options) is called from sentry_init() inside SENTRY_WITH_SCOPE_MUT. - The WER integration uses static globals for wer.dll function pointers instead of a heap-allocated state struct. --- CMakeLists.txt | 7 ++ include/sentry.h | 30 ------ src/CMakeLists.txt | 8 +- src/integrations/sentry_integration_wer.c | 116 ++++++++-------------- src/integrations/sentry_integration_wer.h | 15 +++ src/sentry_core.c | 14 +-- src/sentry_integration.h | 23 ----- src/sentry_options.c | 34 ------- src/sentry_options.h | 3 - 9 files changed, 78 insertions(+), 172 deletions(-) create mode 100644 src/integrations/sentry_integration_wer.h delete mode 100644 src/sentry_integration.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bacef22283..f07a9994e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -952,6 +952,13 @@ if(SENTRY_INTEGRATION_QT) target_link_libraries(sentry PRIVATE Qt${Qt_VERSION_MAJOR}::Core) endif() +option(SENTRY_INTEGRATION_WER "Build WER (Windows Error Reporting) integration") +if(SENTRY_INTEGRATION_WER) + if(NOT WIN32) + message(FATAL_ERROR "SENTRY_INTEGRATION_WER requires Windows") + endif() +endif() + include(CMakePackageConfigHelpers) configure_package_config_file(sentry-config.cmake.in sentry-config.cmake INSTALL_DESTINATION "${CMAKE_INSTALL_CMAKEDIR}") diff --git a/include/sentry.h b/include/sentry.h index 04ed3162a6..441833d154 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -860,15 +860,6 @@ SENTRY_API void sentry_capture_envelope(sentry_envelope_t *envelope); struct sentry_options_s; typedef struct sentry_options_s sentry_options_t; -/** - * This represents an integration that can be configured via options and is - * automatically wired up during `sentry_init`. - * - * See `sentry_options_add_integration`. - */ -struct sentry_integration_s; -typedef struct sentry_integration_s sentry_integration_t; - /** * This represents an interface for user-defined transports. * @@ -2002,27 +1993,6 @@ SENTRY_API uint64_t sentry_options_get_transfer_timeout(sentry_options_t *opts); SENTRY_API void sentry_options_set_backend( sentry_options_t *opts, sentry_backend_t *backend); -/** - * Adds an integration to the options. - * - * The integration's `register_func` callback will be invoked during - * `sentry_init` (while the scope lock is held), allowing the integration to - * set up scope observers and other SDK internals. - * - * Takes ownership of `integration`. - */ -SENTRY_EXPERIMENTAL_API void sentry_options_add_integration( - sentry_options_t *opts, sentry_integration_t *integration); - -/** - * Creates a WER (Windows Error Reporting) integration that syncs scope tags - * and attachments to WER via `WerRegisterCustomMetadata`, `WerRegisterFile`, - * and `WerRegisterMemoryBlock`. - * - * Use with `sentry_options_add_integration`. - */ -SENTRY_EXPERIMENTAL_API sentry_integration_t *sentry_integration_wer_new(void); - /* -- Global/Scope APIs -- */ /** diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a7066a4e5..0509f4d974 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -89,7 +89,6 @@ sentry_target_sources_cwd(sentry sentry_random.h sentry_windows_dbghelp.c sentry_windows_dbghelp.h - integrations/sentry_integration_wer.c path/sentry_path_windows.c process/sentry_process_windows.c symbolizer/sentry_symbolizer_windows.c @@ -307,6 +306,13 @@ if(SENTRY_INTEGRATION_QT) integrations/sentry_integration_qt.h ) endif() +if(SENTRY_INTEGRATION_WER) + target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) + sentry_target_sources_cwd(sentry + integrations/sentry_integration_wer.c + integrations/sentry_integration_wer.h + ) +endif() # screenshot if(SENTRY_SCREENSHOT_WINDOWS) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 5273d78f33..0f5d78ecaf 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -1,12 +1,10 @@ -#include "sentry_boot.h" +#include "sentry_integration_wer.h" #ifdef SENTRY_PLATFORM_WINDOWS # include "sentry_alloc.h" # include "sentry_attachment.h" -# include "sentry_integration.h" # include "sentry_logger.h" -# include "sentry_scope.h" # include "sentry_string.h" # include @@ -15,18 +13,35 @@ # define WER_FILE_ANONYMOUS_DATA 0x2 # endif -typedef struct { - HMODULE wer_dll; - HRESULT(WINAPI *WerRegisterCustomMetadata_fn)(PCWSTR key, PCWSTR value); - HRESULT(WINAPI *WerUnregisterCustomMetadata_fn)(PCWSTR key); -} wer_state_t; +static HMODULE g_wer_dll = NULL; +static HRESULT(WINAPI *g_WerRegisterCustomMetadata)(PCWSTR, PCWSTR) = NULL; +static HRESULT(WINAPI *g_WerUnregisterCustomMetadata)(PCWSTR) = NULL; + +static void +ensure_wer_loaded(void) +{ + if (g_wer_dll) { + return; + } + g_wer_dll = LoadLibraryW(L"wer.dll"); + if (g_wer_dll) { + g_WerRegisterCustomMetadata = (HRESULT(WINAPI *)(PCWSTR, + PCWSTR))GetProcAddress(g_wer_dll, "WerRegisterCustomMetadata"); + g_WerUnregisterCustomMetadata = (HRESULT(WINAPI *)( + PCWSTR))GetProcAddress(g_wer_dll, "WerUnregisterCustomMetadata"); + } + if (!g_WerRegisterCustomMetadata) { + SENTRY_DEBUG("WerRegisterCustomMetadata not available; " + "tag sync to WER will be skipped"); + } +} static HRESULT wer_set_tag(void *data, const char *key, size_t key_len, const char *value, size_t value_len) { - wer_state_t *state = (wer_state_t *)data; - if (!state->WerRegisterCustomMetadata_fn) { + (void)data; + if (!g_WerRegisterCustomMetadata) { return S_OK; } if (!key || !value) { @@ -46,10 +61,10 @@ wer_set_tag(void *data, const char *key, size_t key_len, const char *value, return E_OUTOFMEMORY; } - HRESULT hr = state->WerRegisterCustomMetadata_fn(key_w, value_w); + HRESULT hr = g_WerRegisterCustomMetadata(key_w, value_w); if (FAILED(hr)) { - SENTRY_WARNF("WerRegisterCustomMetadata failed: hr=0x%08lx", - (unsigned long)hr); + SENTRY_WARNF( + "WerRegisterCustomMetadata failed: hr=0x%08lx", (unsigned long)hr); } sentry_free(value_w); @@ -60,8 +75,8 @@ wer_set_tag(void *data, const char *key, size_t key_len, const char *value, static HRESULT wer_remove_tag(void *data, const char *key, size_t key_len) { - wer_state_t *state = (wer_state_t *)data; - if (!state->WerUnregisterCustomMetadata_fn) { + (void)data; + if (!g_WerUnregisterCustomMetadata) { return S_OK; } if (!key) { @@ -76,7 +91,7 @@ wer_remove_tag(void *data, const char *key, size_t key_len) return E_OUTOFMEMORY; } - HRESULT hr = state->WerUnregisterCustomMetadata_fn(key_w); + HRESULT hr = g_WerUnregisterCustomMetadata(key_w); if (FAILED(hr)) { SENTRY_WARNF("WerUnregisterCustomMetadata failed: hr=0x%08lx", (unsigned long)hr); @@ -100,11 +115,11 @@ wer_add_attachment(void *data, sentry_attachment_t *attachment) if (!path_w) { return S_OK; } - HRESULT hr - = WerRegisterFile(path_w, WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA); + HRESULT hr = WerRegisterFile( + path_w, WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA); if (FAILED(hr)) { - SENTRY_WARNF("WerRegisterFile failed: hr=0x%08lx", - (unsigned long)hr); + SENTRY_WARNF( + "WerRegisterFile failed: hr=0x%08lx", (unsigned long)hr); } return hr; } @@ -118,8 +133,8 @@ wer_add_attachment(void *data, sentry_attachment_t *attachment) HRESULT hr = WerRegisterMemoryBlock( (PVOID)attachment->buf, (DWORD)attachment->buf_len); if (FAILED(hr)) { - SENTRY_WARNF("WerRegisterMemoryBlock failed: hr=0x%08lx", - (unsigned long)hr); + SENTRY_WARNF( + "WerRegisterMemoryBlock failed: hr=0x%08lx", (unsigned long)hr); } return hr; } @@ -143,8 +158,8 @@ wer_remove_attachment(void *data, sentry_attachment_t *attachment) } HRESULT hr = WerUnregisterFile(path_w); if (FAILED(hr)) { - SENTRY_WARNF("WerUnregisterFile failed: hr=0x%08lx", - (unsigned long)hr); + SENTRY_WARNF( + "WerUnregisterFile failed: hr=0x%08lx", (unsigned long)hr); } return hr; } @@ -161,41 +176,15 @@ wer_remove_attachment(void *data, sentry_attachment_t *attachment) return S_OK; } -static void -wer_free(void *data) -{ - wer_state_t *state = (wer_state_t *)data; - if (state->wer_dll) { - FreeLibrary(state->wer_dll); - } - sentry_free(state); -} - -static void -wer_register(void *data, sentry_scope_t *scope, const sentry_options_t *options) +void +sentry_integration_wer_setup(sentry_scope_t *scope) { - (void)options; - - wer_state_t *state = (wer_state_t *)data; - state->wer_dll = LoadLibraryW(L"wer.dll"); - if (state->wer_dll) { - state->WerRegisterCustomMetadata_fn = (HRESULT(WINAPI *)(PCWSTR, PCWSTR)) - GetProcAddress(state->wer_dll, "WerRegisterCustomMetadata"); - state->WerUnregisterCustomMetadata_fn - = (HRESULT(WINAPI *)(PCWSTR)) - GetProcAddress(state->wer_dll, "WerUnregisterCustomMetadata"); - } - - if (!state->WerRegisterCustomMetadata_fn) { - SENTRY_DEBUG("WerRegisterCustomMetadata not available; " - "tag sync to WER will be skipped"); - } + ensure_wer_loaded(); sentry_scope_observer_t *observer = sentry__scope_observer_new(); if (!observer) { return; } - observer->data = state; observer->set_tag = wer_set_tag; observer->remove_tag = wer_remove_tag; observer->add_attachment = wer_add_attachment; @@ -204,25 +193,4 @@ wer_register(void *data, sentry_scope_t *scope, const sentry_options_t *options) sentry__scope_add_observer(scope, observer); } -sentry_integration_t * -sentry_integration_wer_new(void) -{ - wer_state_t *state = SENTRY_MAKE(wer_state_t); - if (!state) { - return NULL; - } - - sentry_integration_t *integration = SENTRY_MAKE(sentry_integration_t); - if (!integration) { - sentry_free(state); - return NULL; - } - - integration->data = state; - integration->register_func = wer_register; - integration->free_func = wer_free; - - return integration; -} - #endif /* SENTRY_PLATFORM_WINDOWS */ diff --git a/src/integrations/sentry_integration_wer.h b/src/integrations/sentry_integration_wer.h new file mode 100644 index 0000000000..ff704b1163 --- /dev/null +++ b/src/integrations/sentry_integration_wer.h @@ -0,0 +1,15 @@ +#ifndef SENTRY_INTEGRATION_WER_H_INCLUDED +#define SENTRY_INTEGRATION_WER_H_INCLUDED + +#include "sentry_scope.h" + +/** + * Sets up the WER integration, creating and registering a scope observer + * that syncs tags and attachments to Windows Error Reporting. + * + * Must be called while holding the scope lock (i.e., from inside + * SENTRY_WITH_SCOPE_MUT). + */ +void sentry_integration_wer_setup(sentry_scope_t *scope); + +#endif diff --git a/src/sentry_core.c b/src/sentry_core.c index 4e7ae41a5d..0925ce385d 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -38,6 +38,10 @@ # include "integrations/sentry_integration_qt.h" #endif +#ifdef SENTRY_INTEGRATION_WER +# include "integrations/sentry_integration_wer.h" +#endif + static sentry_options_t *g_options = NULL; #ifdef SENTRY__MUTEX_INIT_DYN SENTRY__MUTEX_INIT_DYN(g_options_lock) @@ -229,13 +233,9 @@ sentry_init(sentry_options_t *options) sentry__scope_update_dsc(scope, options); - for (size_t i = 0; i < options->num_integrations; i++) { - sentry_integration_t *integration = options->integrations[i]; - if (integration->register_func) { - integration->register_func( - integration->data, scope, options); - } - } +#ifdef SENTRY_INTEGRATION_WER + sentry_integration_wer_setup(scope); +#endif } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); diff --git a/src/sentry_integration.h b/src/sentry_integration.h deleted file mode 100644 index 972989e6fa..0000000000 --- a/src/sentry_integration.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef SENTRY_INTEGRATION_H_INCLUDED -#define SENTRY_INTEGRATION_H_INCLUDED - -#include "sentry_boot.h" - -/** - * Integration callback that is invoked during `sentry_init` while the scope - * lock is held. Implementors use this to, e.g., create and register a scope - * observer. - */ -typedef struct sentry_integration_s { - void *data; - - void (*register_func)( - void *data, sentry_scope_t *scope, const sentry_options_t *options); - - /** - * Optional cleanup callback, invoked before the integration struct is freed. - */ - void (*free_func)(void *data); -} sentry_integration_t; - -#endif diff --git a/src/sentry_options.c b/src/sentry_options.c index 3bfb79ec7a..01d1ab1f7b 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -156,15 +156,6 @@ sentry_options_free(sentry_options_t *opts) sentry__attachments_free(opts->attachments); sentry__run_free(opts->run); - for (size_t i = 0; i < opts->num_integrations; i++) { - sentry_integration_t *integration = opts->integrations[i]; - if (integration->free_func) { - integration->free_func(integration->data); - } - sentry_free(integration); - } - sentry_free(opts->integrations); - sentry_free(opts); } @@ -939,31 +930,6 @@ sentry_options_set_backend(sentry_options_t *opts, sentry_backend_t *backend) opts->backend = backend; } -void -sentry_options_add_integration( - sentry_options_t *opts, sentry_integration_t *integration) -{ - if (!integration) { - return; - } - - size_t new_count = opts->num_integrations + 1; - sentry_integration_t **new_array - = sentry__calloc(new_count, sizeof(sentry_integration_t *)); - if (!new_array) { - sentry_free(integration); - return; - } - if (opts->integrations) { - memcpy(new_array, opts->integrations, - opts->num_integrations * sizeof(sentry_integration_t *)); - sentry_free(opts->integrations); - } - new_array[opts->num_integrations] = integration; - opts->integrations = new_array; - opts->num_integrations = new_count; -} - void sentry_options_set_enable_logs(sentry_options_t *opts, int enable_logs) { diff --git a/src/sentry_options.h b/src/sentry_options.h index e9f848964c..0947ef452a 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -5,7 +5,6 @@ #include "sentry_attachment.h" #include "sentry_database.h" -#include "sentry_integration.h" #include "sentry_logger.h" #include "sentry_session.h" #include "sentry_utils.h" @@ -96,8 +95,6 @@ struct sentry_options_s { not exposed through the options API */ struct sentry_backend_s *backend; sentry_session_t *session; - sentry_integration_t **integrations; - size_t num_integrations; long refcount; uint64_t shutdown_timeout; From d9f8d56039f781449fa83600f4242bc07ed34953 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:32:55 +0200 Subject: [PATCH 20/35] Allow SENTRY_INTEGRATION_WER to be enabled on non-Windows platforms The source file is already guarded with #ifdef SENTRY_PLATFORM_WINDOWS, so it quietly becomes a no-op on other platforms without needing a FATAL_ERROR. --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f07a9994e1..8f2f039f71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -953,11 +953,6 @@ if(SENTRY_INTEGRATION_QT) endif() option(SENTRY_INTEGRATION_WER "Build WER (Windows Error Reporting) integration") -if(SENTRY_INTEGRATION_WER) - if(NOT WIN32) - message(FATAL_ERROR "SENTRY_INTEGRATION_WER requires Windows") - endif() -endif() include(CMakePackageConfigHelpers) configure_package_config_file(sentry-config.cmake.in sentry-config.cmake From b9d892e692e85326f1aad4b216ef4a39b7b1eff4 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:35:17 +0200 Subject: [PATCH 21/35] fix: only define SENTRY_INTEGRATION_WER on Windows --- src/CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0509f4d974..d573f27142 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -306,13 +306,15 @@ if(SENTRY_INTEGRATION_QT) integrations/sentry_integration_qt.h ) endif() -if(SENTRY_INTEGRATION_WER) - target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) - sentry_target_sources_cwd(sentry - integrations/sentry_integration_wer.c - integrations/sentry_integration_wer.h - ) -endif() + if(SENTRY_INTEGRATION_WER) + if(WIN32) + target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) + endif() + sentry_target_sources_cwd(sentry + integrations/sentry_integration_wer.c + integrations/sentry_integration_wer.h + ) + endif() # screenshot if(SENTRY_SCREENSHOT_WINDOWS) From 58701b07350842a1fd02257840f7ba32dfd7dab8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:35:38 +0200 Subject: [PATCH 22/35] Skip compiling WER integration sources on non-Windows --- src/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d573f27142..085f0cc192 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -309,11 +309,11 @@ endif() if(SENTRY_INTEGRATION_WER) if(WIN32) target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) + sentry_target_sources_cwd(sentry + integrations/sentry_integration_wer.c + integrations/sentry_integration_wer.h + ) endif() - sentry_target_sources_cwd(sentry - integrations/sentry_integration_wer.c - integrations/sentry_integration_wer.h - ) endif() # screenshot From cf78549a9282127f3de36d42cdd3c5cca2d6ac83 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:36:12 +0200 Subject: [PATCH 23/35] Simplify SENTRY_INTEGRATION_WER guard to single condition --- src/CMakeLists.txt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 085f0cc192..8d6c700bb1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -306,15 +306,13 @@ if(SENTRY_INTEGRATION_QT) integrations/sentry_integration_qt.h ) endif() - if(SENTRY_INTEGRATION_WER) - if(WIN32) - target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) - sentry_target_sources_cwd(sentry - integrations/sentry_integration_wer.c - integrations/sentry_integration_wer.h - ) - endif() - endif() +if(SENTRY_INTEGRATION_WER AND WIN32) + target_compile_definitions(sentry PRIVATE SENTRY_INTEGRATION_WER) + sentry_target_sources_cwd(sentry + integrations/sentry_integration_wer.c + integrations/sentry_integration_wer.h + ) +endif() # screenshot if(SENTRY_SCREENSHOT_WINDOWS) From deb2fcf2d27eeab00edf7d9beb81bc6bafca1f72 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:37:01 +0200 Subject: [PATCH 24/35] Remove redundant SENTRY_PLATFORM_WINDOWS guard from wer integration --- src/integrations/sentry_integration_wer.c | 32 ++++++++++------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 0f5d78ecaf..265a513a03 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -1,34 +1,32 @@ #include "sentry_integration_wer.h" -#ifdef SENTRY_PLATFORM_WINDOWS +#include "sentry_alloc.h" +#include "sentry_attachment.h" +#include "sentry_logger.h" +#include "sentry_string.h" -# include "sentry_alloc.h" -# include "sentry_attachment.h" -# include "sentry_logger.h" -# include "sentry_string.h" +#include -# include +#ifndef WER_FILE_ANONYMOUS_DATA +# define WER_FILE_ANONYMOUS_DATA 0x2 +#endif -# ifndef WER_FILE_ANONYMOUS_DATA -# define WER_FILE_ANONYMOUS_DATA 0x2 -# endif - -static HMODULE g_wer_dll = NULL; +static HMODULE g_kernel32 = NULL; static HRESULT(WINAPI *g_WerRegisterCustomMetadata)(PCWSTR, PCWSTR) = NULL; static HRESULT(WINAPI *g_WerUnregisterCustomMetadata)(PCWSTR) = NULL; static void ensure_wer_loaded(void) { - if (g_wer_dll) { + if (g_WerRegisterCustomMetadata) { return; } - g_wer_dll = LoadLibraryW(L"wer.dll"); - if (g_wer_dll) { + g_kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (g_kernel32) { g_WerRegisterCustomMetadata = (HRESULT(WINAPI *)(PCWSTR, - PCWSTR))GetProcAddress(g_wer_dll, "WerRegisterCustomMetadata"); + PCWSTR))GetProcAddress(g_kernel32, "WerRegisterCustomMetadata"); g_WerUnregisterCustomMetadata = (HRESULT(WINAPI *)( - PCWSTR))GetProcAddress(g_wer_dll, "WerUnregisterCustomMetadata"); + PCWSTR))GetProcAddress(g_kernel32, "WerUnregisterCustomMetadata"); } if (!g_WerRegisterCustomMetadata) { SENTRY_DEBUG("WerRegisterCustomMetadata not available; " @@ -192,5 +190,3 @@ sentry_integration_wer_setup(sentry_scope_t *scope) sentry__scope_add_observer(scope, observer); } - -#endif /* SENTRY_PLATFORM_WINDOWS */ From 0be42f2156c3a4964cf8716786dcb66eca506d0e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:37:51 +0200 Subject: [PATCH 25/35] Make kernel32 module handle a local variable --- src/integrations/sentry_integration_wer.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 265a513a03..0095e1031b 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -11,7 +11,6 @@ # define WER_FILE_ANONYMOUS_DATA 0x2 #endif -static HMODULE g_kernel32 = NULL; static HRESULT(WINAPI *g_WerRegisterCustomMetadata)(PCWSTR, PCWSTR) = NULL; static HRESULT(WINAPI *g_WerUnregisterCustomMetadata)(PCWSTR) = NULL; @@ -21,12 +20,12 @@ ensure_wer_loaded(void) if (g_WerRegisterCustomMetadata) { return; } - g_kernel32 = GetModuleHandleW(L"kernel32.dll"); - if (g_kernel32) { + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) { g_WerRegisterCustomMetadata = (HRESULT(WINAPI *)(PCWSTR, - PCWSTR))GetProcAddress(g_kernel32, "WerRegisterCustomMetadata"); + PCWSTR))GetProcAddress(kernel32, "WerRegisterCustomMetadata"); g_WerUnregisterCustomMetadata = (HRESULT(WINAPI *)( - PCWSTR))GetProcAddress(g_kernel32, "WerUnregisterCustomMetadata"); + PCWSTR))GetProcAddress(kernel32, "WerUnregisterCustomMetadata"); } if (!g_WerRegisterCustomMetadata) { SENTRY_DEBUG("WerRegisterCustomMetadata not available; " From b2e3724de3aa467c136515e5abc1ced0796f43d8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:38:41 +0200 Subject: [PATCH 26/35] Add clarifying comment about Windows 10 version 1703+ requirement --- src/integrations/sentry_integration_wer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 0095e1031b..268b5f32eb 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -11,6 +11,9 @@ # define WER_FILE_ANONYMOUS_DATA 0x2 #endif +// WerRegisterCustomMetadata / WerUnregisterCustomMetadata require +// Windows 10, version 1703+. They are loaded at runtime so the +// integration degrades gracefully on older builds. static HRESULT(WINAPI *g_WerRegisterCustomMetadata)(PCWSTR, PCWSTR) = NULL; static HRESULT(WINAPI *g_WerUnregisterCustomMetadata)(PCWSTR) = NULL; From e0257a41106ecbadf823cdb62a9c92d9006202f8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:39:56 +0200 Subject: [PATCH 27/35] Explain WER_FILE_ANONYMOUS_DATA fallback --- src/integrations/sentry_integration_wer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 268b5f32eb..f37608898a 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -7,6 +7,8 @@ #include +// WER_FILE_ANONYMOUS_DATA was added in the Windows 8 SDK; +// provide a fallback for older SDKs. #ifndef WER_FILE_ANONYMOUS_DATA # define WER_FILE_ANONYMOUS_DATA 0x2 #endif From eab6e81001ed653be058485fa8fc9829d211919e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 2 Jul 2026 15:43:01 +0200 Subject: [PATCH 28/35] tweak --- src/integrations/sentry_integration_wer.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index f37608898a..1d5fe4bdab 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -7,20 +7,17 @@ #include -// WER_FILE_ANONYMOUS_DATA was added in the Windows 8 SDK; -// provide a fallback for older SDKs. +// Windows 8+ SDK #ifndef WER_FILE_ANONYMOUS_DATA # define WER_FILE_ANONYMOUS_DATA 0x2 #endif -// WerRegisterCustomMetadata / WerUnregisterCustomMetadata require -// Windows 10, version 1703+. They are loaded at runtime so the -// integration degrades gracefully on older builds. +// Windows 10 1703+ static HRESULT(WINAPI *g_WerRegisterCustomMetadata)(PCWSTR, PCWSTR) = NULL; static HRESULT(WINAPI *g_WerUnregisterCustomMetadata)(PCWSTR) = NULL; static void -ensure_wer_loaded(void) +wer_init(void) { if (g_WerRegisterCustomMetadata) { return; @@ -181,7 +178,7 @@ wer_remove_attachment(void *data, sentry_attachment_t *attachment) void sentry_integration_wer_setup(sentry_scope_t *scope) { - ensure_wer_loaded(); + wer_init(); sentry_scope_observer_t *observer = sentry__scope_observer_new(); if (!observer) { From a7ded91941a666b774e1efce77f1ee04cdcb5d78 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 16:32:34 +0200 Subject: [PATCH 29/35] fix indentation --- src/CMakeLists.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d6c700bb1..57c57dead8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,16 +83,16 @@ sentry_target_sources_cwd(sentry ) # generic platform / path / symbolizer - if(WIN32) - sentry_target_sources_cwd(sentry - sentry_random.c - sentry_random.h - sentry_windows_dbghelp.c - sentry_windows_dbghelp.h - path/sentry_path_windows.c - process/sentry_process_windows.c - symbolizer/sentry_symbolizer_windows.c - ) +if(WIN32) + sentry_target_sources_cwd(sentry + sentry_random.c + sentry_random.h + sentry_windows_dbghelp.c + sentry_windows_dbghelp.h + path/sentry_path_windows.c + process/sentry_process_windows.c + symbolizer/sentry_symbolizer_windows.c + ) elseif(NX OR PROSPERO) sentry_target_sources_cwd(sentry sentry_unix_spinlock.h From 0159184b7762f77cd6a4ca6397ff6d49b055008d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 18:17:02 +0200 Subject: [PATCH 30/35] integration test --- tests/test_integration_wer.py | 192 ++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 tests/test_integration_wer.py diff --git a/tests/test_integration_wer.py b/tests/test_integration_wer.py new file mode 100644 index 0000000000..cca803a9c9 --- /dev/null +++ b/tests/test_integration_wer.py @@ -0,0 +1,192 @@ +import ctypes +from ctypes import wintypes +import os +import subprocess +import sys + +from pathlib import Path + +import pytest + +from . import run +from .assertions import wait_for +from .conditions import has_breakpad, has_crashpad, has_native, is_qemu + +pytestmark = [ + pytest.mark.skipif( + sys.platform != "win32" or bool(os.environ.get("TEST_MINGW")), + reason="WER integration tests are only available in MSVC Windows builds", + ), + pytest.mark.with_wer, +] + +S_OK = 0 + +E_STORE_USER_ARCHIVE = 0 +E_STORE_USER_QUEUE = 1 +E_STORE_MACHINE_ARCHIVE = 2 +E_STORE_MACHINE_QUEUE = 3 + + +class WerStore: + def __init__(self): + self._wer = ctypes.WinDLL("wer.dll") + self._wer.WerStoreOpen.argtypes = [ + ctypes.c_int, + ctypes.POINTER(wintypes.HANDLE), + ] + self._wer.WerStoreOpen.restype = ctypes.c_long + self._wer.WerStoreClose.argtypes = [wintypes.HANDLE] + self._wer.WerStoreClose.restype = None + self._wer.WerStoreGetFirstReportKey.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(ctypes.c_wchar_p), + ] + self._wer.WerStoreGetFirstReportKey.restype = ctypes.c_long + self._wer.WerStoreGetNextReportKey.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(ctypes.c_wchar_p), + ] + self._wer.WerStoreGetNextReportKey.restype = ctypes.c_long + self._wer.WerFreeString.argtypes = [ctypes.c_wchar_p] + self._wer.WerFreeString.restype = None + + def report_dirs(self): + for store_type in ( + E_STORE_USER_ARCHIVE, + E_STORE_USER_QUEUE, + E_STORE_MACHINE_ARCHIVE, + E_STORE_MACHINE_QUEUE, + ): + handle = wintypes.HANDLE() + hr = self._wer.WerStoreOpen(store_type, ctypes.byref(handle)) + if hr != S_OK: + continue + + try: + key = ctypes.c_wchar_p() + hr = self._wer.WerStoreGetFirstReportKey(handle, ctypes.byref(key)) + while hr == S_OK and key.value: + report_dir = key.value + self._wer.WerFreeString(key) + yield Path(report_dir) + + key = ctypes.c_wchar_p() + hr = self._wer.WerStoreGetNextReportKey( + handle, ctypes.byref(key) + ) + finally: + self._wer.WerStoreClose(handle) + + +def wait_for_wer_report(store, test_id): + seen_report_dirs = 0 + readable_reports = 0 + matching_report = None + + def find_report(): + nonlocal matching_report + nonlocal seen_report_dirs + nonlocal readable_reports + + for report_dir in store.report_dirs(): + seen_report_dirs += 1 + report_path = report_dir / "Report.wer" + try: + report = report_path.read_text(encoding="utf-16-le") + except OSError: + continue + + readable_reports += 1 + report_lower = report.lower() + if ( + "test.id" in report_lower + and test_id in report + ): + matching_report = (report_path, report) + return True + + return False + + if wait_for(find_report): + return matching_report + + details = [ + f"searched {seen_report_dirs} WER report directories", + f"read {readable_reports} Report.wer files", + ] + pytest.fail( + f"WER report with test.id={test_id} was not found ({', '.join(details)})" + ) + + +@pytest.mark.parametrize( + "backend", + [ + "none", + "inproc", + pytest.param( + "breakpad", + marks=[ + pytest.mark.skipif( + not has_breakpad or is_qemu, reason="breakpad backend not available" + ), + pytest.mark.xfail( + reason="breakpad swallows the exception", + strict=True, + ), + ], + ), + pytest.param( + "crashpad", + marks=[ + pytest.mark.skipif( + not has_crashpad, reason="crashpad backend not available" + ), + pytest.mark.xfail( + reason="crashpad handler terminates the process", + strict=True, + ), + ], + ), + pytest.param( + "native", + marks=pytest.mark.skipif( + not has_native or is_qemu, reason="native backend not available" + ), + id="native", + ), + ], +) +def test_wer_custom_metadata(cmake, backend): + tmp_path = cmake( + ["sentry_example"], + { + "SENTRY_BACKEND": backend, + "SENTRY_TRANSPORT": "none", + "SENTRY_INTEGRATION_WER": "ON", + }, + ) + + completed = run( + tmp_path, + "sentry_example", + ["e2e-test", "crash"], + expect_failure=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + test_id = None + for line in completed.stdout.splitlines(): + if line.startswith("TEST_ID:"): + test_id = line.removeprefix("TEST_ID:") + break + assert test_id, completed.stdout + + report_path, report = wait_for_wer_report(WerStore(), test_id) + assert report_path.name == "Report.wer" + assert "expected-tag" in report + assert "some value" in report + assert "not-expected-tag" not in report From dd4df1515ca96aaf91c20addc66abf1d0dff8a6c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 19:23:23 +0200 Subject: [PATCH 31/35] pre-init attachments --- src/integrations/sentry_integration_wer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 1d5fe4bdab..14e165c2a4 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -189,5 +189,10 @@ sentry_integration_wer_setup(sentry_scope_t *scope) observer->add_attachment = wer_add_attachment; observer->remove_attachment = wer_remove_attachment; - sentry__scope_add_observer(scope, observer); + if (sentry__scope_add_observer(scope, observer)) { + for (sentry_attachment_t *attachment = scope->attachments; attachment; + attachment = attachment->next) { + wer_add_attachment(NULL, attachment); + } + } } From bea66a23a8d70b5b2f4e6c0a0425dd5f01b7a3dd Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 19:26:12 +0200 Subject: [PATCH 32/35] make format --- tests/test_integration_wer.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_integration_wer.py b/tests/test_integration_wer.py index cca803a9c9..3e5efc802d 100644 --- a/tests/test_integration_wer.py +++ b/tests/test_integration_wer.py @@ -72,9 +72,7 @@ def report_dirs(self): yield Path(report_dir) key = ctypes.c_wchar_p() - hr = self._wer.WerStoreGetNextReportKey( - handle, ctypes.byref(key) - ) + hr = self._wer.WerStoreGetNextReportKey(handle, ctypes.byref(key)) finally: self._wer.WerStoreClose(handle) @@ -99,10 +97,7 @@ def find_report(): readable_reports += 1 report_lower = report.lower() - if ( - "test.id" in report_lower - and test_id in report - ): + if "test.id" in report_lower and test_id in report: matching_report = (report_path, report) return True From c7fc095354747c31016d2a4e4b4100d7fc4f350a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 20:46:07 +0200 Subject: [PATCH 33/35] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 790e0db36e..dcf0c4a2a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features** + +- Windows: add WER integration for syncing tags and attachments to WER. ([#1837](https://github.com/getsentry/sentry-native/pull/1837)) + ## 0.15.3 **Features** From b20034460fd4b3a1e6535f56ec614bae282a4662 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 20:54:40 +0200 Subject: [PATCH 34/35] fix signatures --- src/integrations/sentry_integration_wer.c | 43 ++++++++++------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/integrations/sentry_integration_wer.c b/src/integrations/sentry_integration_wer.c index 14e165c2a4..fee237bcaf 100644 --- a/src/integrations/sentry_integration_wer.c +++ b/src/integrations/sentry_integration_wer.c @@ -35,16 +35,16 @@ wer_init(void) } } -static HRESULT +static void wer_set_tag(void *data, const char *key, size_t key_len, const char *value, size_t value_len) { (void)data; if (!g_WerRegisterCustomMetadata) { - return S_OK; + return; } if (!key || !value) { - return S_OK; + return; } char *key_n = sentry__string_clone_n(key, key_len); @@ -57,7 +57,7 @@ wer_set_tag(void *data, const char *key, size_t key_len, const char *value, if (!key_w || !value_w) { sentry_free(key_w); sentry_free(value_w); - return E_OUTOFMEMORY; + return; } HRESULT hr = g_WerRegisterCustomMetadata(key_w, value_w); @@ -68,18 +68,17 @@ wer_set_tag(void *data, const char *key, size_t key_len, const char *value, sentry_free(value_w); sentry_free(key_w); - return hr; } -static HRESULT +static void wer_remove_tag(void *data, const char *key, size_t key_len) { (void)data; if (!g_WerUnregisterCustomMetadata) { - return S_OK; + return; } if (!key) { - return S_OK; + return; } char *key_n = sentry__string_clone_n(key, key_len); @@ -87,7 +86,7 @@ wer_remove_tag(void *data, const char *key, size_t key_len) sentry_free(key_n); if (!key_w) { - return E_OUTOFMEMORY; + return; } HRESULT hr = g_WerUnregisterCustomMetadata(key_w); @@ -97,22 +96,21 @@ wer_remove_tag(void *data, const char *key, size_t key_len) } sentry_free(key_w); - return hr; } -static HRESULT +static void wer_add_attachment(void *data, sentry_attachment_t *attachment) { (void)data; if (!attachment) { - return S_OK; + return; } if (attachment->path) { const wchar_t *path_w = attachment->path->path_w; if (!path_w) { - return S_OK; + return; } HRESULT hr = WerRegisterFile( path_w, WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA); @@ -120,14 +118,14 @@ wer_add_attachment(void *data, sentry_attachment_t *attachment) SENTRY_WARNF( "WerRegisterFile failed: hr=0x%08lx", (unsigned long)hr); } - return hr; + return; } if (attachment->buf && attachment->buf_len > 0) { if (attachment->buf_len > MAXDWORD) { SENTRY_WARNF("WerRegisterMemoryBlock: buffer too large (%zu bytes)", attachment->buf_len); - return E_INVALIDARG; + return; } HRESULT hr = WerRegisterMemoryBlock( (PVOID)attachment->buf, (DWORD)attachment->buf_len); @@ -135,32 +133,30 @@ wer_add_attachment(void *data, sentry_attachment_t *attachment) SENTRY_WARNF( "WerRegisterMemoryBlock failed: hr=0x%08lx", (unsigned long)hr); } - return hr; + return; } - - return S_OK; } -static HRESULT +static void wer_remove_attachment(void *data, sentry_attachment_t *attachment) { (void)data; if (!attachment) { - return S_OK; + return; } if (attachment->path) { const wchar_t *path_w = attachment->path->path_w; if (!path_w) { - return S_OK; + return; } HRESULT hr = WerUnregisterFile(path_w); if (FAILED(hr)) { SENTRY_WARNF( "WerUnregisterFile failed: hr=0x%08lx", (unsigned long)hr); } - return hr; + return; } if (attachment->buf) { @@ -169,10 +165,7 @@ wer_remove_attachment(void *data, sentry_attachment_t *attachment) SENTRY_WARNF("WerUnregisterMemoryBlock failed: hr=0x%08lx", (unsigned long)hr); } - return hr; } - - return S_OK; } void From 48dffcf62378c3cb1c8ac483be6fa4e339c52522 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 6 Jul 2026 21:58:14 +0200 Subject: [PATCH 35/35] add warning --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f2f039f71..56db3d020b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -953,6 +953,12 @@ if(SENTRY_INTEGRATION_QT) endif() option(SENTRY_INTEGRATION_WER "Build WER (Windows Error Reporting) integration") +if(SENTRY_INTEGRATION_WER AND WIN32 + AND (SENTRY_BACKEND_BREAKPAD OR SENTRY_BACKEND_CRASHPAD)) + message(WARNING + "SENTRY_BACKEND=${SENTRY_BACKEND} is not compatible with WER. " + "Use SENTRY_BACKEND=none, inproc, or native with SENTRY_INTEGRATION_WER.") +endif() include(CMakePackageConfigHelpers) configure_package_config_file(sentry-config.cmake.in sentry-config.cmake