From 8566170690e6de237431b4dde55c14b61bc52240 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 14 Sep 2026 07:23:46 +0200 Subject: [PATCH] ref: Use scope observers for backend attachments Move attachment tracking to scope observers so the redundant attachment callbacks can be removed from the backend API. Observers notify backends after attachments have been accepted and frozen, keeping backend state consistent with the scope. The old backend hooks run before scope insertion, allowing backend state to be updated for attachments the scope later rejects. This can leave orphaned files on disk or stale Crashpad registrations. AI review bots identified concrete consequences of this ordering during the attachment API work: - Deduplication: https://github.com/getsentry/sentry-native/pull/1974#discussion_r3764131307 - Validation: https://github.com/getsentry/sentry-native/pull/1974#discussion_r3965401687 Register attachment observers during backend startup and remove them during shutdown, including when the backend is reinstalled. --- src/backends/sentry_backend_crashpad.cpp | 222 ++++++++++++----------- src/backends/sentry_backend_native.c | 174 ++++++++++-------- src/sentry_backend.h | 3 - src/sentry_core.c | 36 +--- tests/unit/test_attachments.c | 22 ++- 5 files changed, 237 insertions(+), 220 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 70cbdf6308..7133fa2f19 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -148,6 +148,7 @@ typedef struct { std::atomic crashed; std::atomic scope_flush; sentry_uuid_t crash_event_id; + sentry_scope_observer_t *scope_observer; } crashpad_state_t; static void crashpad_backend_add_breadcrumb(sentry_backend_t *backend, @@ -880,6 +881,99 @@ process_completed_reports( } } +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) +static sentry_path_t * +make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) +{ + if (!sentry__attachment_get_bytes(attachment, nullptr)) { + return sentry__attachment_make_path(attachment); + } + + sentry_uuid_t id = sentry__attachment_get_id(attachment); + const char *filename = sentry__attachment_get_filename(attachment); + if (!run_path || sentry_uuid_is_nil(&id) + || sentry__string_empty(filename)) { + return nullptr; + } + + char uuid[37]; + sentry_uuid_as_string(&id, uuid); + sentry_path_t *dir = sentry__path_join_str(run_path, uuid); + sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : nullptr; + sentry_path_t *parent = path ? sentry__path_dir(path) : nullptr; + bool valid = parent && sentry__path_eq(parent, dir); + sentry__path_free(parent); + sentry__path_free(dir); + if (!valid) { + sentry__path_free(path); + return nullptr; + } + return path; +} + +static void +add_attachment(void *state, sentry_value_t attachment) +{ + auto *data = static_cast(state); + if (!data || !data->client) { + return; + } + + size_t bytes_len = 0; + const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); + sentry_path_t *path = make_attachment_path(data->run_path, attachment); + if (!path) { + const char *filename = sentry__attachment_get_filename(attachment); + SENTRY_WARNF("failed to create path for crashpad attachment \"%s\"", + filename ? filename : ""); + return; + } + + if (bytes) { + sentry_path_t *dir = sentry__path_dir(path); + int rv = dir ? sentry__path_create_dir_all(dir) : 1; + sentry__path_free(dir); + if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { + SENTRY_WARNF( + "failed to write crashpad attachment \"%s\"", path->path); + sentry__path_remove(path); + sentry__path_free(path); + return; + } + } + data->client->AddAttachment(base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); + sentry__path_free(path); +} + +static void +remove_attachment(void *state, sentry_value_t attachment) +{ + auto *data = static_cast(state); + if (!data || !data->client) { + return; + } + sentry_path_t *path = make_attachment_path(data->run_path, attachment); + if (!path) { + return; + } + data->client->RemoveAttachment( + base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); + + if (sentry__attachment_get_bytes(attachment, nullptr)) { + if (sentry__path_remove(path) != 0) { + SENTRY_WARNF( + "failed to remove crashpad attachment \"%s\"", path->path); + } + if (sentry_path_t *dir = sentry__path_dir(path)) { + sentry__path_remove(dir); + sentry__path_free(dir); + } + } + sentry__path_free(path); +} +#endif + static int crashpad_backend_startup( sentry_backend_t *backend, const sentry_options_t *options) @@ -1118,12 +1212,38 @@ crashpad_backend_startup( crashpad::TriState::kEnabled); } +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + if (observer) { + observer->data = data; + observer->add_attachment = add_attachment; + observer->remove_attachment = remove_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + if (sentry__scope_add_observer(scope, observer)) { + data->scope_observer = observer; + } + } + } +#endif + return 0; } static void crashpad_backend_shutdown(sentry_backend_t *backend) { + auto *data = static_cast(backend->data); +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ + || defined(SENTRY_PLATFORM_MACOS) + if (data->scope_observer) { + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, data->scope_observer); + } + data->scope_observer = nullptr; + } +#endif + #ifdef SENTRY_PLATFORM_LINUX // restore signal handlers to their default state for (const auto signal : g_CrashSignals) { @@ -1133,7 +1253,7 @@ crashpad_backend_shutdown(sentry_backend_t *backend) } #endif - crashpad_state_dtor(static_cast(backend->data)); + crashpad_state_dtor(data); #ifdef SENTRY_PLATFORM_LINUX g_signal_stack.ss_flags = SS_DISABLE; @@ -1304,101 +1424,6 @@ crashpad_backend_prune_database(sentry_backend_t *backend) crashpad::PruneCrashReportDatabase(data->db, &condition); } -#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ - || defined(SENTRY_PLATFORM_MACOS) -static sentry_path_t * -make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) -{ - if (!sentry__attachment_get_bytes(attachment, nullptr)) { - return sentry__attachment_make_path(attachment); - } - - sentry_uuid_t id = sentry__attachment_get_id(attachment); - const char *filename = sentry__attachment_get_filename(attachment); - if (!run_path || sentry_uuid_is_nil(&id) - || sentry__string_empty(filename)) { - return nullptr; - } - - char uuid[37]; - sentry_uuid_as_string(&id, uuid); - sentry_path_t *dir = sentry__path_join_str(run_path, uuid); - sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : nullptr; - sentry_path_t *parent = path ? sentry__path_dir(path) : nullptr; - bool valid = parent && sentry__path_eq(parent, dir); - sentry__path_free(parent); - sentry__path_free(dir); - if (!valid) { - sentry__path_free(path); - return nullptr; - } - return path; -} - -static void -crashpad_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *UNUSED(options)) -{ - auto *data = static_cast(backend->data); - if (!data || !data->client) { - return; - } - - size_t bytes_len = 0; - const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); - sentry_path_t *path = make_attachment_path(data->run_path, attachment); - if (!path) { - const char *filename = sentry__attachment_get_filename(attachment); - SENTRY_WARNF("failed to create path for crashpad attachment \"%s\"", - filename ? filename : ""); - return; - } - - if (bytes) { - sentry_path_t *dir = sentry__path_dir(path); - int rv = dir ? sentry__path_create_dir_all(dir) : 1; - sentry__path_free(dir); - if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { - SENTRY_WARNF( - "failed to write crashpad attachment \"%s\"", path->path); - sentry__path_remove(path); - sentry__path_free(path); - return; - } - } - data->client->AddAttachment(base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); - sentry__path_free(path); -} - -static void -crashpad_backend_remove_attachment( - sentry_backend_t *backend, sentry_value_t attachment) -{ - auto *data = static_cast(backend->data); - if (!data || !data->client) { - return; - } - sentry_path_t *path = make_attachment_path(data->run_path, attachment); - if (!path) { - return; - } - data->client->RemoveAttachment( - base::FilePath(SENTRY_PATH_PLATFORM_STR(path))); - - if (sentry__attachment_get_bytes(attachment, nullptr)) { - if (sentry__path_remove(path) != 0) { - SENTRY_WARNF( - "failed to remove crashpad attachment \"%s\"", path->path); - } - if (sentry_path_t *dir = sentry__path_dir(path)) { - sentry__path_remove(dir); - sentry__path_free(dir); - } - } - sentry__path_free(path); -} -#endif - void sentry__backend_preload(void) { @@ -1430,11 +1455,6 @@ sentry__backend_new(void) backend->get_last_crash_func = crashpad_backend_last_crash; backend->process_old_run_func = crashpad_backend_process_old_run; backend->prune_database_func = crashpad_backend_prune_database; -#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) \ - || defined(SENTRY_PLATFORM_MACOS) - backend->add_attachment_func = crashpad_backend_add_attachment; - backend->remove_attachment_func = crashpad_backend_remove_attachment; -#endif backend->data = data; backend->can_capture_after_shutdown = true; diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 7db9f5489c..974c66b0ea 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -205,20 +205,21 @@ wer_register_module(uint64_t app_tid, const sentry_options_t *options) typedef struct { sentry_crash_ipc_t *ipc; pid_t daemon_pid; + sentry_path_t *run_path; sentry_path_t *event_path; sentry_path_t *breadcrumb1_path; sentry_path_t *breadcrumb2_path; sentry_path_t *envelope_path; size_t num_breadcrumbs; volatile long crashed; + sentry_scope_observer_t *scope_observer; } native_backend_state_t; static void native_backend_flush_scope( sentry_backend_t *backend, const sentry_options_t *options); static void native_backend_add_breadcrumb(sentry_backend_t *backend, sentry_value_t breadcrumb, const sentry_options_t *options); -static void native_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *options); +static void add_attachment(void *data, sentry_value_t attachment); static void native_backend_preload_scope( @@ -229,8 +230,8 @@ native_backend_preload_scope( sentry_value_t attachments = sentry__scope_load_attachments(scope); size_t attachment_count = sentry_value_get_length(attachments); for (size_t i = 0; i < attachment_count; i++) { - native_backend_add_attachment( - backend, sentry_value_get_by_index(attachments, i), options); + add_attachment( + backend->data, sentry_value_get_by_index(attachments, i)); } sentry_value_decref(attachments); breadcrumbs = sentry__scope_breadcrumbs_to_list(scope); @@ -244,6 +245,76 @@ native_backend_preload_scope( sentry_value_decref(breadcrumbs); } +/** + * Creates an attachment path, deriving a unique path in the run directory for + * buffer attachments. + */ +static sentry_path_t * +make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) +{ + if (!sentry__attachment_get_bytes(attachment, NULL)) { + return sentry__attachment_make_path(attachment); + } + + sentry_uuid_t id = sentry__attachment_get_id(attachment); + const char *filename = sentry__attachment_get_filename(attachment); + if (!run_path || sentry_uuid_is_nil(&id) + || sentry__string_empty(filename)) { + return NULL; + } + + char uuid[37]; + sentry_uuid_as_string(&id, uuid); + sentry_path_t *dir = sentry__path_join_str(run_path, uuid); + sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : NULL; + sentry_path_t *parent = path ? sentry__path_dir(path) : NULL; + bool valid = parent && sentry__path_eq(parent, dir); + sentry__path_free(parent); + sentry__path_free(dir); + if (!valid) { + sentry__path_free(path); + return NULL; + } + return path; +} + +static void +add_attachment(void *data, sentry_value_t attachment) +{ + native_backend_state_t *state = (native_backend_state_t *)data; + if (!state) { + return; + } + + // For buffer attachments, derive a path in the run directory and write to + // disk + size_t bytes_len = 0; + const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); + if (bytes) { + sentry_path_t *path = make_attachment_path(state->run_path, attachment); + if (!path) { + const char *filename = sentry__attachment_get_filename(attachment); + SENTRY_WARNF("failed to create path for native backend attachment " + "\"%s\"", + filename ? filename : ""); + return; + } + sentry_path_t *dir = sentry__path_dir(path); + int rv = dir ? sentry__path_create_dir_all(dir) : 1; + sentry__path_free(dir); + // Write buffer to disk + if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { + SENTRY_WARNF( + "failed to write native backend attachment \"%s\"", path->path); + sentry__path_remove(path); + } + sentry__path_free(path); + } + // For file attachments, the path is already set and points to the actual + // file. The crash daemon will read these files from their original + // locations. +} + static bool native_backend_process_old_run(sentry_backend_t *backend, const sentry_options_t *options, const sentry_path_t *run_path) @@ -624,6 +695,7 @@ native_backend_startup( return 1; } backend->data = state; + state->run_path = sentry__path_clone(options->run->run_path); // Initialize IPC (protected by global synchronization for concurrent // access) @@ -638,6 +710,7 @@ native_backend_startup( #endif if (!state->ipc) { SENTRY_WARN("failed to initialize crash IPC"); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -652,6 +725,7 @@ native_backend_startup( SENTRY_WARNF("failed to acquire mutex for context setup: %lu", GetLastError()); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -664,6 +738,7 @@ native_backend_startup( SENTRY_WARNF("failed to acquire semaphore for context setup: %s", strerror(errno)); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -852,6 +927,7 @@ native_backend_startup( < 0) { SENTRY_WARN("failed to initialize crash handler"); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -885,6 +961,7 @@ native_backend_startup( # endif SENTRY_WARN("failed to start crash daemon"); sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -954,6 +1031,7 @@ native_backend_startup( } # endif sentry__crash_ipc_free(state->ipc); + sentry__path_free(state->run_path); sentry_free(state); backend->data = NULL; return 1; @@ -961,6 +1039,16 @@ native_backend_startup( #endif SENTRY_DEBUG("native backend started successfully"); + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + if (observer) { + observer->data = state; + observer->add_attachment = add_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + if (sentry__scope_add_observer(scope, observer)) { + state->scope_observer = observer; + } + } + } return 0; } @@ -974,6 +1062,13 @@ native_backend_shutdown(sentry_backend_t *backend) return; } + if (state->scope_observer) { + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + sentry__scope_remove_observer(scope, state->scope_observer); + } + state->scope_observer = NULL; + } + #if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX) wer_unregister_module(); #endif @@ -1100,43 +1195,11 @@ native_backend_free(sentry_backend_t *backend) sentry__path_free(state->breadcrumb1_path); sentry__path_free(state->breadcrumb2_path); sentry__path_free(state->envelope_path); + sentry__path_free(state->run_path); sentry_free(state); } -/** - * Creates an attachment path, deriving a unique path in the run directory for - * buffer attachments. - */ -static sentry_path_t * -make_attachment_path(const sentry_path_t *run_path, sentry_value_t attachment) -{ - if (!sentry__attachment_get_bytes(attachment, NULL)) { - return sentry__attachment_make_path(attachment); - } - - sentry_uuid_t id = sentry__attachment_get_id(attachment); - const char *filename = sentry__attachment_get_filename(attachment); - if (!run_path || sentry_uuid_is_nil(&id) - || sentry__string_empty(filename)) { - return NULL; - } - - char uuid[37]; - sentry_uuid_as_string(&id, uuid); - sentry_path_t *dir = sentry__path_join_str(run_path, uuid); - sentry_path_t *path = dir ? sentry__path_join_str(dir, filename) : NULL; - sentry_path_t *parent = path ? sentry__path_dir(path) : NULL; - bool valid = parent && sentry__path_eq(parent, dir); - sentry__path_free(parent); - sentry__path_free(dir); - if (!valid) { - sentry__path_free(path); - return NULL; - } - return path; -} - // Writes the scope's attachment list to /__sentry-attachments so the // crash daemon can locate and append them to the crash envelope. static void @@ -1333,42 +1396,6 @@ native_backend_add_breadcrumb(sentry_backend_t *backend, } } -static void -native_backend_add_attachment(sentry_backend_t *backend, - sentry_value_t attachment, const sentry_options_t *options) -{ - (void)backend; // Unused - - // For buffer attachments, derive a path in the run directory and write to - // disk - size_t bytes_len = 0; - const char *bytes = sentry__attachment_get_bytes(attachment, &bytes_len); - if (bytes) { - sentry_path_t *path - = make_attachment_path(options->run->run_path, attachment); - if (!path) { - const char *filename = sentry__attachment_get_filename(attachment); - SENTRY_WARNF("failed to create path for native backend attachment " - "\"%s\"", - filename ? filename : ""); - return; - } - sentry_path_t *dir = sentry__path_dir(path); - int rv = dir ? sentry__path_create_dir_all(dir) : 1; - sentry__path_free(dir); - // Write buffer to disk - if (rv != 0 || sentry__path_write_buffer(path, bytes, bytes_len) != 0) { - SENTRY_WARNF( - "failed to write native backend attachment \"%s\"", path->path); - sentry__path_remove(path); - } - sentry__path_free(path); - } - // For file attachments, the path is already set and points to the actual - // file. The crash daemon will read these files from their original - // locations. -} - /** * Handle exception - called from signal handler via sentry_handle_exception * This processes the event with on_crash/before_send hooks and ends the session @@ -1541,7 +1568,6 @@ sentry__backend_new(void) backend->except_func = native_backend_except; backend->flush_scope_func = native_backend_flush_scope; backend->add_breadcrumb_func = native_backend_add_breadcrumb; - backend->add_attachment_func = native_backend_add_attachment; backend->user_consent_changed_func = native_backend_user_consent_changed; backend->process_old_run_func = native_backend_process_old_run; backend->can_capture_after_shutdown = false; diff --git a/src/sentry_backend.h b/src/sentry_backend.h index 7eec78d3de..c1e54288ad 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -29,9 +29,6 @@ struct sentry_backend_s { bool (*process_old_run_func)(sentry_backend_t *, const sentry_options_t *options, const sentry_path_t *run_path); void (*prune_database_func)(sentry_backend_t *); - void (*add_attachment_func)( - sentry_backend_t *, sentry_value_t, const sentry_options_t *options); - void (*remove_attachment_func)(sentry_backend_t *, sentry_value_t); void *data; // Whether this backend still runs after shutdown_func was called. bool can_capture_after_shutdown; diff --git a/src/sentry_core.c b/src/sentry_core.c index 9e8cb50455..b178b043d1 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -2107,18 +2107,7 @@ sentry_add_attachment(sentry_value_t attachment) sentry_value_t added = sentry_value_new_null(); SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = sentry__scope_load_attachments(scope); - added = sentry__attachments_find(attachments, attachment); - if (sentry_value_is_null(added)) { - if (options->backend && options->backend->add_attachment_func) { - options->backend->add_attachment_func( - options->backend, attachment, options); - } - added = sentry__scope_add_attachment(scope, attachment); - } else { - sentry_value_decref(attachment); - } - sentry_value_decref(attachments); + added = sentry__scope_add_attachment(scope, attachment); } sentry_options_free((sentry_options_t *)options); sentry_uuid_t uuid = sentry__attachment_get_id(added); @@ -2163,11 +2152,6 @@ sentry_clear_attachments(void) for (size_t i = 0; i < len; i++) { sentry_value_t attachment = sentry_value_get_by_index(attachments, i); - if (options->backend - && options->backend->remove_attachment_func) { - options->backend->remove_attachment_func( - options->backend, attachment); - } SENTRY_SCOPE_NOTIFY(scope, remove_attachment, attachment); } sentry_value_decref(attachments); @@ -2182,22 +2166,8 @@ sentry_remove_attachment(sentry_uuid_t attachment_id) return; } - SENTRY_WITH_OPTIONS (options) { - SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_t attachments = sentry__scope_load_attachments(scope); - sentry_value_t removed - = sentry__attachments_remove(attachments, &attachment_id); - if (!sentry_value_is_null(removed)) { - if (options->backend - && options->backend->remove_attachment_func) { - options->backend->remove_attachment_func( - options->backend, removed); - } - SENTRY_SCOPE_NOTIFY(scope, remove_attachment, removed); - } - sentry_value_decref(removed); - sentry_value_decref(attachments); - } + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_scope_remove_attachment(scope, attachment_id); } } diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index bd75f1b1c2..5240f21338 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -24,13 +24,11 @@ add_scope_attachments(sentry_envelope_t *envelope) } static void -count_backend_attachment(sentry_backend_t *backend, sentry_value_t attachment, - const sentry_options_t *options) +count_attachment(void *data, sentry_value_t attachment) { - size_t *count = (size_t *)backend->data; + size_t *count = (size_t *)data; (*count)++; - TEST_CHECK(!sentry_value_is_frozen(attachment)); - TEST_CHECK(!!options); + TEST_CHECK(sentry_value_is_frozen(attachment)); } SENTRY_TEST(attachment_placeholder) @@ -146,11 +144,9 @@ SENTRY_TEST(lazy_attachments) SENTRY_TEST(attachments_add_dedupe) { SENTRY_TEST_OPTIONS_NEW(options); - size_t backend_add_count = 0; + size_t add_count = 0; sentry_backend_t *backend = SENTRY_MAKE(sentry_backend_t); TEST_ASSERT(!!backend); - backend->data = &backend_add_count; - backend->add_attachment_func = count_backend_attachment; sentry_options_set_backend(options, backend); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".a.txt"); sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".b.txt"); @@ -160,6 +156,14 @@ SENTRY_TEST(attachments_add_dedupe) sentry_init(options); + sentry_scope_observer_t *observer = sentry__scope_observer_new(); + TEST_ASSERT(!!observer); + observer->data = &add_count; + observer->add_attachment = count_attachment; + SENTRY_WITH_SCOPE_MUT_NO_FLUSH (scope) { + TEST_ASSERT(sentry__scope_add_observer(scope, observer)); + } + sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".a.txt"); sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".b.txt"); sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".c.txt"); @@ -168,7 +172,7 @@ SENTRY_TEST(attachments_add_dedupe) sentry_attach_filew(SENTRY_TEST_PATH_PREFIX L".b.txt"); sentry_attach_filew(SENTRY_TEST_PATH_PREFIX L".c.txt"); #endif - TEST_CHECK_INT_EQUAL(backend_add_count, 1); + TEST_CHECK_INT_EQUAL(add_count, 1); sentry_path_t *path_a = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".a.txt");