From 3cb24a22a25b642a15a2c9e105ff3506af5e7cf0 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 1 Dec 2020 11:13:31 +0100 Subject: [PATCH] feat: Support modifying attachments after init Moves the attachments to the scope, and adds `sentry_add_attachment` and `sentry_remove_attachment` and wstr variants that modify this attachment list after calling init. Attachments are identified by their path. --- CHANGELOG.md | 6 ++ README.md | 4 +- include/sentry.h | 46 ++++++++ src/CMakeLists.txt | 2 + src/backends/sentry_backend_crashpad.cpp | 1 + src/path/sentry_path.c | 13 +++ src/sentry_attachment.c | 105 ++++++++++++++++++ src/sentry_attachment.h | 43 ++++++++ src/sentry_core.c | 59 +++++++--- src/sentry_envelope.c | 4 +- src/sentry_options.c | 35 +----- src/sentry_options.h | 11 +- src/sentry_path.h | 5 + src/sentry_scope.c | 3 + src/sentry_scope.h | 3 + tests/unit/test_attachments.c | 132 +++++++++++++++++++++++ tests/unit/tests.inc | 2 + 17 files changed, 415 insertions(+), 59 deletions(-) create mode 100644 src/sentry_attachment.c create mode 100644 src/sentry_attachment.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 238bf70c1..fedf5aff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features**: + +- The `sentry_add_attachment` and `sentry_remove_attachment` (and their wide-string variants) have been added to modify the list of attachments that are sent along with sentry events after a call to `sentry_init`. + ## 0.4.4 **Features**: diff --git a/README.md b/README.md index b95cf265a..8f568d774 100644 --- a/README.md +++ b/README.md @@ -290,10 +290,12 @@ Other important configuration options include: ## Known Limitations -- The crashpad backend currently has no support for notifying the crashing +- The crashpad backend on macOS currently has no support for notifying the crashing process, and can thus not properly terminate sessions or call the registered `before_send` hook. It will also lose any events that have been queued for sending at time of crash. +- When using the crashpad backend, the list of attachments that will be sent + along with crashes is frozen at the time of `sentry_init`. ## Development diff --git a/include/sentry.h b/include/sentry.h index 93baaef63..4f26ddd92 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -13,6 +13,17 @@ * encoding, typically ANSI on Windows, UTF-8 macOS, and the locale encoding on * Linux; and they provide wchar-compatible alternatives on Windows which are * preferred. + * + * NOTE on attachments: + * + * Attachments are read lazily at the time of `sentry_capture_event` or at time + * of a hard crash. Relative attachment paths will be resolved according to the + * current working directory at the time of envelope creation. + * When adding and removing attachments, they are matched according to their + * given `path`. No normalization is performed. + * When using the `crashpad` backend, the list of attachments that will be added + * at the time of a hard crash will be frozen at the time of `sentry_init`, and + * later modifications will not be reflected. */ #ifndef SENTRY_H_INCLUDED @@ -821,6 +832,8 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces( * `path` is assumed to be in platform-specific filesystem path encoding. * API Users on windows are encouraged to use `sentry_options_add_attachmentw` * instead. + * + * See the NOTE on attachments above for restrictions of this API. */ SENTRY_API void sentry_options_add_attachment( sentry_options_t *opts, const char *path); @@ -1050,6 +1063,39 @@ SENTRY_API void sentry_remove_transaction(void); */ SENTRY_API void sentry_set_level(sentry_level_t level); +/** + * Adds a new attachment to be sent along. + * + * `path` is assumed to be in platform-specific filesystem path encoding. + * API Users on windows are encouraged to use `sentry_add_attachmentw` instead. + * + * See the NOTE on attachments above for restrictions of this API. + */ +SENTRY_API void sentry_add_attachment(const char *path); + +/** + * Removes a previously added attachment. + * + * `path` is assumed to be in platform-specific filesystem path encoding. + * API Users on windows are encouraged to use `sentry_remove_attachmentw` + * instead. + * + * See the NOTE on attachments above for restrictions of this API. + */ +SENTRY_API void sentry_remove_attachment(const char *path); + +#ifdef SENTRY_PLATFORM_WINDOWS +/** + * Wide char version of `sentry_add_attachment`. + */ +SENTRY_API void sentry_add_attachmentw(const wchar_t *path); + +/** + * Wide char version of `sentry_remove_attachment`. + */ +SENTRY_API void sentry_remove_attachmentw(const wchar_t *path); +#endif + /** * Starts a new session. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70505656e..8e08332b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,8 @@ sentry_target_sources_cwd(sentry sentry_alloc.c sentry_alloc.h + sentry_attachment.c + sentry_attachment.h sentry_backend.c sentry_backend.h sentry_boot.h diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 2c99202f1..59ecf6348 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -2,6 +2,7 @@ extern "C" { #include "sentry_boot.h" #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" diff --git a/src/path/sentry_path.c b/src/path/sentry_path.c index ed86da3e6..b55ebeb27 100644 --- a/src/path/sentry_path.c +++ b/src/path/sentry_path.c @@ -11,6 +11,19 @@ sentry__path_free(sentry_path_t *path) sentry_free(path); } +bool +sentry__path_eq(const sentry_path_t *path_a, const sentry_path_t *path_b) +{ + size_t i = 0; + while (path_a->path[i] == path_b->path[i]) { + if (path_a->path[i] == (sentry_pathchar_t)0) { + return true; + } + i++; + } + return false; +} + int sentry__path_remove_all(const sentry_path_t *path) { diff --git a/src/sentry_attachment.c b/src/sentry_attachment.c new file mode 100644 index 000000000..08f657dbd --- /dev/null +++ b/src/sentry_attachment.c @@ -0,0 +1,105 @@ +#include "sentry_attachment.h" +#include "sentry_alloc.h" +#include "sentry_envelope.h" +#include "sentry_options.h" +#include "sentry_path.h" +#include "sentry_value.h" + +static void +sentry__attachment_free(sentry_attachment_t *attachment) +{ + sentry__path_free(attachment->path); + sentry_free(attachment); +} + +void +sentry__attachments_free(sentry_attachment_t *attachments) +{ + sentry_attachment_t *next_attachment = attachments; + while (next_attachment) { + sentry_attachment_t *attachment = next_attachment; + next_attachment = attachment->next; + + sentry__attachment_free(attachment); + } +} + +void +sentry__attachment_add( + sentry_attachment_t **attachments_ptr, sentry_path_t *path) +{ + if (!path) { + return; + } + sentry_attachment_t *attachment = SENTRY_MAKE(sentry_attachment_t); + if (!attachment) { + sentry__path_free(path); + return; + } + attachment->path = path; + attachment->next = NULL; + + sentry_attachment_t **next_ptr = attachments_ptr; + + for (sentry_attachment_t *last_attachment = *attachments_ptr; + last_attachment; last_attachment = last_attachment->next) { + if (sentry__path_eq(last_attachment->path, path)) { + sentry__attachment_free(attachment); + return; + } + + next_ptr = &last_attachment->next; + } + + *next_ptr = attachment; +} + +void +sentry__attachment_remove( + sentry_attachment_t **attachments_ptr, sentry_path_t *path) +{ + sentry_attachment_t **next_ptr = attachments_ptr; + + for (sentry_attachment_t *attachment = *attachments_ptr; attachment; + attachment = attachment->next) { + if (sentry__path_eq(attachment->path, path)) { + *next_ptr = attachment->next; + sentry__attachment_free(attachment); + goto out; + } + + next_ptr = &attachment->next; + } + +out: + sentry__path_free(path); +} + +/** + * Reads the attachments from disk and adds them to the `envelope`. + */ +void +sentry__apply_attachments_to_envelope( + sentry_envelope_t *envelope, const sentry_attachment_t *attachments) +{ + if (!attachments) { + return; + } + + SENTRY_TRACE("adding attachments to envelope"); + for (const sentry_attachment_t *attachment = attachments; attachment; + attachment = attachment->next) { + sentry_envelope_item_t *item = sentry__envelope_add_from_path( + envelope, attachment->path, "attachment"); + if (!item) { + continue; + } + sentry__envelope_item_set_header(item, "filename", +#ifdef SENTRY_PLATFORM_WINDOWS + sentry__value_new_string_from_wstr( +#else + sentry_value_new_string( +#endif + sentry__path_filename(attachment->path))); + } +} diff --git a/src/sentry_attachment.h b/src/sentry_attachment.h new file mode 100644 index 000000000..048caac2c --- /dev/null +++ b/src/sentry_attachment.h @@ -0,0 +1,43 @@ +#ifndef SENTRY_ATTACHMENT_H_INCLUDED +#define SENTRY_ATTACHMENT_H_INCLUDED + +#include "sentry_boot.h" + +typedef struct sentry_path_s sentry_path_t; +typedef struct sentry_options_s sentry_options_t; +typedef struct sentry_envelope_s sentry_envelope_t; + +/** + * This is a linked list of all the attachments registered via + * `sentry_options_add_attachment`. + */ +typedef struct sentry_attachment_s sentry_attachment_t; +struct sentry_attachment_s { + sentry_path_t *path; + sentry_attachment_t *next; +}; + +/** + * Frees the linked list of `attachments`. + */ +void sentry__attachments_free(sentry_attachment_t *attachments); + +/** + * Adds an attachment to the attachments list at `attachments_ptr`. + */ +void sentry__attachment_add( + sentry_attachment_t **attachments_ptr, sentry_path_t *path); + +/** + * Removes an attachment from the attachments list at `attachments_ptr`. + */ +void sentry__attachment_remove( + sentry_attachment_t **attachments_ptr, sentry_path_t *path); + +/** + * Reads the attachments from disk and adds them to the `envelope`. + */ +void sentry__apply_attachments_to_envelope( + sentry_envelope_t *envelope, const sentry_attachment_t *attachments); + +#endif diff --git a/src/sentry_core.c b/src/sentry_core.c index fc58bf158..2123be5e8 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -5,6 +5,7 @@ #include #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -144,7 +145,8 @@ sentry_init(sentry_options_t *options) // the only way to get a reference to the scope is by locking it, the macro // does all that at once, including invoking the backends scope flush hook SENTRY_WITH_SCOPE_MUT (scope) { - (void)scope; + scope->attachments = options->attachments; + options->attachments = NULL; } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); @@ -360,21 +362,8 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, goto fail; } - SENTRY_TRACE("adding attachments to envelope"); - for (sentry_attachment_t *attachment = options->attachments; attachment; - attachment = attachment->next) { - sentry_envelope_item_t *item = sentry__envelope_add_from_path( - envelope, attachment->path, "attachment"); - if (!item) { - continue; - } - sentry__envelope_item_set_header(item, "filename", -#ifdef SENTRY_PLATFORM_WINDOWS - sentry__value_new_string_from_wstr( -#else - sentry_value_new_string( -#endif - sentry__path_filename(attachment->path))); + SENTRY_WITH_SCOPE (scope) { + sentry__apply_attachments_to_envelope(envelope, scope->attachments); } return envelope; @@ -560,3 +549,41 @@ sentry_set_level(sentry_level_t level) scope->level = level; } } + +void +sentry_add_attachment(const char *path) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__attachment_add( + &scope->attachments, sentry__path_from_str(path)); + } +} + +void +sentry_remove_attachment(const char *path) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__attachment_remove( + &scope->attachments, sentry__path_from_str(path)); + } +} + +#ifdef SENTRY_PLATFORM_WINDOWS +void +sentry_add_attachmentw(const wchar_t *path) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__attachment_add( + &scope->attachments, sentry__path_from_wstr(path)); + } +} + +void +sentry_remove_attachmentw(const wchar_t *path) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__attachment_remove( + &scope->attachments, sentry__path_from_wstr(path)); + } +} +#endif diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index f9ba5ff2a..952d47f7f 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -364,7 +364,9 @@ sentry_envelope_serialize(const sentry_envelope_t *envelope, size_t *size_out) sentry__envelope_serialize_into_stringbuilder(envelope, &sb); - *size_out = sentry__stringbuilder_len(&sb); + if (size_out) { + *size_out = sentry__stringbuilder_len(&sb); + } return sentry__stringbuilder_into_string(&sb); } diff --git a/src/sentry_options.c b/src/sentry_options.c index 6e1f10390..f9e924bc2 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -1,5 +1,6 @@ #include "sentry_options.h" #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_database.h" #include "sentry_logger.h" @@ -58,13 +59,6 @@ sentry__options_incref(sentry_options_t *options) return options; } -void -sentry__attachment_free(sentry_attachment_t *attachment) -{ - sentry__path_free(attachment->path); - sentry_free(attachment); -} - void sentry_options_free(sentry_options_t *opts) { @@ -83,13 +77,8 @@ sentry_options_free(sentry_options_t *opts) sentry_transport_free(opts->transport); sentry__backend_free(opts->backend); - sentry_attachment_t *next_attachment = opts->attachments; - while (next_attachment) { - sentry_attachment_t *attachment = next_attachment; - next_attachment = attachment->next; + sentry__attachments_free(opts->attachments); - sentry__attachment_free(attachment); - } sentry__run_free(opts->run); sentry_free(opts); @@ -283,26 +272,10 @@ sentry_options_set_system_crash_reporter_enabled( opts->system_crash_reporter_enabled = !!enabled; } -static void -add_attachment(sentry_options_t *opts, sentry_path_t *path) -{ - if (!path) { - return; - } - sentry_attachment_t *attachment = SENTRY_MAKE(sentry_attachment_t); - if (!attachment) { - sentry__path_free(path); - return; - } - attachment->path = path; - attachment->next = opts->attachments; - opts->attachments = attachment; -} - void sentry_options_add_attachment(sentry_options_t *opts, const char *path) { - add_attachment(opts, sentry__path_from_str(path)); + sentry__attachment_add(&opts->attachments, sentry__path_from_str(path)); } void @@ -323,7 +296,7 @@ sentry_options_set_database_path(sentry_options_t *opts, const char *path) void sentry_options_add_attachmentw(sentry_options_t *opts, const wchar_t *path) { - add_attachment(opts, sentry__path_from_wstr(path)); + sentry__attachment_add(&opts->attachments, sentry__path_from_wstr(path)); } void diff --git a/src/sentry_options.h b/src/sentry_options.h index 04d5ec95f..2a3976dc5 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -10,20 +10,11 @@ // https://docs.sentry.io/error-reporting/configuration/?platform=native#shutdown-timeout #define SENTRY_DEFAULT_SHUTDOWN_TIMEOUT 2000 +typedef struct sentry_attachment_s sentry_attachment_t; typedef struct sentry_path_s sentry_path_t; typedef struct sentry_run_s sentry_run_t; struct sentry_backend_s; -/** - * This is a linked list of all the attachments registered via - * `sentry_options_add_attachment`. - */ -typedef struct sentry_attachment_s sentry_attachment_t; -struct sentry_attachment_s { - sentry_path_t *path; - sentry_attachment_t *next; -}; - /** * This is the main options struct, which is being accessed throughout all of * the sentry internals. diff --git a/src/sentry_path.h b/src/sentry_path.h index 8cedd1d21..f04302a6f 100644 --- a/src/sentry_path.h +++ b/src/sentry_path.h @@ -90,6 +90,11 @@ void sentry__path_free(sentry_path_t *path); */ const sentry_pathchar_t *sentry__path_filename(const sentry_path_t *path); +/** + * Returns whether the two paths are equal. + */ +bool sentry__path_eq(const sentry_path_t *path_a, const sentry_path_t *path_b); + /** * Returns whether the last path segment matches `filename`. */ diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 4313a964a..09ad9094a 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1,4 +1,5 @@ #include "sentry_scope.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -72,6 +73,7 @@ get_scope(void) g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); g_scope.session = NULL; + g_scope.attachments = NULL; g_scope_initialized = true; @@ -92,6 +94,7 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.contexts); sentry_value_decref(g_scope.breadcrumbs); sentry_value_decref(g_scope.client_sdk); + sentry__attachments_free(g_scope.attachments); } sentry__mutex_unlock(&g_lock); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index e22cc945d..5b3345fdc 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -6,6 +6,8 @@ #include "sentry_session.h" #include "sentry_value.h" +typedef struct sentry_attachment_s sentry_attachment_t; + /** * This represents the current scope. */ @@ -20,6 +22,7 @@ typedef struct sentry_scope_s { sentry_level_t level; sentry_value_t client_sdk; sentry_session_t *session; + sentry_attachment_t *attachments; } sentry_scope_t; /** diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 6a84413b3..c71e25da3 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -1,5 +1,7 @@ +#include "sentry_attachment.h" #include "sentry_envelope.h" #include "sentry_path.h" +#include "sentry_scope.h" #include "sentry_string.h" #include "sentry_testsupport.h" #include @@ -91,3 +93,133 @@ SENTRY_TEST(lazy_attachments) TEST_CHECK_INT_EQUAL(testdata.called, 2); } + +SENTRY_TEST(attachments_add_dedupe) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_add_attachment(options, PREFIX ".a.txt"); + sentry_options_add_attachment(options, PREFIX ".b.txt"); + + sentry_init(options); + + sentry_add_attachment(PREFIX ".a.txt"); + sentry_add_attachment(PREFIX ".b.txt"); + sentry_add_attachment(PREFIX ".c.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_add_attachmentw(L".a.txt"); + sentry_add_attachmentw(L".b.txt"); + sentry_add_attachmentw(L".c.txt"); +#endif + + sentry_path_t *path_a = sentry__path_from_str(PREFIX ".a.txt"); + sentry_path_t *path_b = sentry__path_from_str(PREFIX ".b.txt"); + sentry_path_t *path_c = sentry__path_from_str(PREFIX ".c.txt"); + + sentry__path_write_buffer(path_a, "aaa", 3); + sentry__path_write_buffer(path_b, "bbb", 3); + sentry__path_write_buffer(path_c, "ccc", 3); + + sentry_envelope_t *envelope = sentry__envelope_new(); + SENTRY_WITH_SCOPE (scope) { + sentry__apply_attachments_to_envelope(envelope, scope->attachments); + } + char *serialized = sentry_envelope_serialize(envelope, NULL); + sentry_envelope_free(envelope); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}\nbbb\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".c.txt\"}" + "\nccc"); + + sentry_free(serialized); + + sentry_shutdown(); + + sentry__path_remove(path_a); + sentry__path_remove(path_b); + sentry__path_remove(path_c); + + sentry__path_free(path_a); + sentry__path_free(path_b); + sentry__path_free(path_c); +} + +SENTRY_TEST(attachments_add_remove) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_add_attachment(options, PREFIX ".a.txt"); + sentry_options_add_attachment(options, PREFIX ".c.txt"); + sentry_options_add_attachment(options, PREFIX ".b.txt"); + + sentry_init(options); + + sentry_add_attachment(PREFIX ".c.txt"); + sentry_add_attachment(PREFIX ".d.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_add_attachmentw(L".e.txt"); + sentry_add_attachmentw(L".d.txt"); +#endif + + sentry_remove_attachment(PREFIX ".c.txt"); + sentry_remove_attachment(PREFIX ".d.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_remove_attachmentw(L".e.txt"); + sentry_remove_attachmentw(L".d.txt"); +#endif + + sentry_path_t *path_a = sentry__path_from_str(PREFIX ".a.txt"); + sentry_path_t *path_b = sentry__path_from_str(PREFIX ".b.txt"); + sentry_path_t *path_c = sentry__path_from_str(PREFIX ".c.txt"); + + sentry__path_write_buffer(path_a, "aaa", 3); + sentry__path_write_buffer(path_b, "bbb", 3); + sentry__path_write_buffer(path_c, "ccc", 3); + + sentry_envelope_t *envelope; + char *serialized; + + envelope = sentry__envelope_new(); + SENTRY_WITH_SCOPE (scope) { + sentry__apply_attachments_to_envelope(envelope, scope->attachments); + } + serialized = sentry_envelope_serialize(envelope, NULL); + sentry_envelope_free(envelope); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}" + "\nbbb"); + + sentry_free(serialized); + + sentry_remove_attachment(PREFIX ".a.txt"); + sentry_remove_attachment(PREFIX ".b.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_remove_attachmentw(L".b.txt"); + sentry_remove_attachmentw(L".a.txt"); +#endif + + envelope = sentry__envelope_new(); + SENTRY_WITH_SCOPE (scope) { + sentry__apply_attachments_to_envelope(envelope, scope->attachments); + } + serialized = sentry_envelope_serialize(envelope, NULL); + sentry_envelope_free(envelope); + + TEST_CHECK_STRING_EQUAL(serialized, "{}"); + + sentry_free(serialized); + + sentry_shutdown(); + + sentry__path_remove(path_a); + sentry__path_remove(path_b); + sentry__path_remove(path_c); + + sentry__path_free(path_a); + sentry__path_free(path_b); + sentry__path_free(path_c); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index ff1d4ef1f..24ae05eff 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -1,3 +1,5 @@ +XX(attachments_add_dedupe) +XX(attachments_add_remove) XX(background_worker) XX(basic_consent_tracking) XX(basic_function_transport)