From a84c10ac10768aefe281e381da95c7693d27f3e8 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Fri, 7 Jan 2022 13:51:06 -0500 Subject: [PATCH 001/207] feat(tracing): Allow users to bind a transaction to the global scope (#632) This adds in the ability to specify a transaction (aka a trace) to be associated with all events being sent to sentry. --- include/sentry.h | 35 +++++++++++++++++++++--- src/sentry_core.c | 47 +++++++++++++++++++++++++------- src/sentry_core.h | 2 +- src/sentry_scope.c | 13 +++++---- src/sentry_scope.h | 13 ++++----- tests/unit/test_tracing.c | 56 ++++++++++++++++++++++++++++++++++++++- tests/unit/tests.inc | 1 + 7 files changed, 141 insertions(+), 26 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 9ee40058b3..834e12450c 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1300,7 +1300,19 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( /** * Starts a new Transaction based on the provided context, restored from an * external integration (i.e. a span from a different SDK) or manually - * constructed by a user. + * constructed by a user. Returns a Transaction, which is expected to be + * manually managed by the caller. Manual management involves ensuring that + * `sentry_transaction_finish` is invoked for the Transaction, and that the + * caller manually starts and finishes any child Spans as needed on the + * Transaction. + * + * `sentry_transaction_finish` must be called in order for this Transaction to + * be sent to sentry. + * + * To ensure that any Events or Message Events are associated with this + * Transaction while it is active, invoke and pass in the Transaction returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. * * Takes ownership of `transaction_context`. */ @@ -1308,15 +1320,32 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_transaction_start( sentry_value_t transaction_context); /** - * Finishes and sends a transaction to sentry. The event ID of the transaction + * Finishes and sends a Transaction to sentry. The event ID of the Transaction * will be returned if this was successful; A nil UUID will be returned * otherwise. * * Always takes ownership of `transaction`, regardless of whether the operation - * was successful or not. + * was successful or not. If `sentry_set_span` was invoked with `transaction`, + * this will remove the */ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( sentry_value_t transaction); + +/** + * Sets the Span (actually Transaction) so any Events sent while the Transaction + * is active will be associated with the Transaction. + * + * If the Transaction being passed in is unsampled, it will still be associated + * with any new Events. This will lead to some Events pointing to orphan or + * missing traces in sentry, see + * https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#orphan-traces-and-broken-subtraces + * + * This increases the number of references pointing to the transaction. + * Invoke `sentry_transaction_finish` to remove the Span set by this function as + * well as its reference by passing in the same Transaction as the one passed + * into this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_value_t transaction); #endif #ifdef __cplusplus diff --git a/src/sentry_core.c b/src/sentry_core.c index 10935f965c..381af6fa57 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -522,7 +522,7 @@ sentry__prepare_transaction(const sentry_options_t *options, sentry_envelope_t *envelope = NULL; SENTRY_WITH_SCOPE (scope) { - SENTRY_TRACE("merging scope into event"); + SENTRY_TRACE("merging scope into transaction"); // Don't include debugging info sentry_scope_mode_t mode = SENTRY_SCOPE_ALL & ~SENTRY_SCOPE_MODULES & ~SENTRY_SCOPE_STACKTRACES; @@ -540,6 +540,7 @@ sentry__prepare_transaction(const sentry_options_t *options, return envelope; fail: + SENTRY_WARN("dropping transaction"); sentry_envelope_free(envelope); sentry_value_decref(transaction); return NULL; @@ -740,7 +741,6 @@ sentry_transaction_start(sentry_value_t tx_cxt) sentry_value_t tx = sentry_value_new_event(); sentry_value_remove_by_key(tx, "timestamp"); - // TODO(tracing): stuff transaction into the scope bool should_sample = sentry__should_send_transaction(tx_cxt); sentry_value_set_by_key( tx, "sampled", sentry_value_new_bool(should_sample)); @@ -757,7 +757,7 @@ sentry_transaction_start(sentry_value_t tx_cxt) sentry_value_set_by_key( tx, "trace_id", sentry_value_get_by_key_owned(tx_cxt, "trace_id")); sentry_value_set_by_key( - tx, "span_id", sentry_value_get_by_key_owned(tx_cxt, "trace_id")); + tx, "span_id", sentry_value_get_by_key_owned(tx_cxt, "span_id")); sentry_value_set_by_key(tx, "transaction", sentry_value_get_by_key_owned(tx_cxt, "transaction")); sentry_value_set_by_key( @@ -767,24 +767,37 @@ sentry_transaction_start(sentry_value_t tx_cxt) sentry__msec_time_to_iso8601(sentry__msec_time()))); sentry_value_decref(tx_cxt); - return tx; } sentry_uuid_t sentry_transaction_finish(sentry_value_t tx) { - // The sampling decision should already be made for transactions during - // their construction. No need to recalculate here. See + if (sentry_value_is_null(tx)) { + SENTRY_DEBUG("no transaction available to finish"); + goto fail; + } + + SENTRY_WITH_SCOPE_MUT (scope) { + const char *tx_id + = sentry_value_as_string(sentry_value_get_by_key(tx, "trace_id")); + const char *scope_tx_id = sentry_value_as_string( + sentry_value_get_by_key(scope->span, "trace_id")); + if (sentry__string_eq(tx_id, scope_tx_id)) { + sentry_value_decref(scope->span); + scope->span = sentry_value_new_null(); + } + } + // The sampling decision should already be made for transactions + // during their construction. No need to recalculate here. See // `sentry__should_skip_transaction`. sentry_value_t sampled = sentry_value_get_by_key(tx, "sampled"); - if (!sentry_value_is_null(sampled) && !sentry_value_is_true(sampled)) { + if (!sentry_value_is_true(sampled)) { SENTRY_DEBUG("throwing away transaction due to sample rate or " "user-provided sampling value in transaction context"); - sentry_value_decref(tx); - // TODO(tracing): remove from scope - return sentry_uuid_nil(); + goto fail; } + sentry_value_remove_by_key(tx, "sampled"); sentry_value_set_by_key(tx, "type", sentry_value_new_string("transaction")); sentry_value_set_by_key(tx, "timestamp", @@ -815,5 +828,19 @@ sentry_transaction_finish(sentry_value_t tx) // This takes ownership of the transaction, generates an event ID, merges // scope return sentry__capture_event(tx); + +fail: + sentry_value_decref(tx); + return sentry_uuid_nil(); +} + +void +sentry_set_span(sentry_value_t transaction) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_decref(scope->span); + sentry_value_incref(transaction); + scope->span = transaction; + } } #endif diff --git a/src/sentry_core.h b/src/sentry_core.h index 3732c722b0..c1c9000ba6 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -118,7 +118,7 @@ void sentry__options_unlock(void); for (const sentry_options_t *Options = sentry__options_getref(); Options; \ sentry_options_free((sentry_options_t *)Options), Options = NULL) -// these for now are only needed for tests +// these for now are only needed outside of core for tests #ifdef SENTRY_UNITTEST bool sentry__roll_dice(double probability); bool sentry__should_send_transaction(sentry_value_t tx_cxt); diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 389ddfceba..5a4731c539 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -238,13 +238,16 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } #ifdef SENTRY_PERFORMANCE_MONITORING -void -sentry__scope_set_span(sentry_value_t span) +# ifdef SENTRY_UNITTEST +sentry_value_t +sentry__scope_get_span() { - // TODO: implement this function and get rid of this line. - (void)span; - return; + SENTRY_WITH_SCOPE (scope) { + return scope->span; + } + return sentry_value_new_null(); } +# endif #endif void diff --git a/src/sentry_scope.h b/src/sentry_scope.h index feb347f99a..4fe3e05285 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -78,12 +78,6 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, const sentry_options_t *options, sentry_value_t event, sentry_scope_mode_t mode); -/** - * Sets the span (actually transaction) on the scope. An internal way to pass - * around contextual information needed from a transaction into other events. - */ -void sentry__scope_set_span(sentry_value_t span); - /** * These are convenience macros to automatically lock/unlock a scope inside a * code block. @@ -99,3 +93,10 @@ void sentry__scope_set_span(sentry_value_t span); sentry__scope_unlock(), Scope = NULL) #endif + +#ifdef SENTRY_PERFORMANCE_MONITORING +// this is only used in unit tests +#ifdef SENTRY_UNITTEST +sentry_value_t sentry__scope_get_span(); +#endif +#endif diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 103d94675a..c4f222ffe1 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -1,3 +1,4 @@ +#include "sentry_scope.h" #include "sentry_testsupport.h" #include "sentry_tracing.h" #include "sentry_uuid.h" @@ -214,7 +215,7 @@ SENTRY_TEST(transport_sampling_transactions) sentry_close(); - // well, its random after all + // exact value is nondeterministic because of rng TEST_CHECK(called_transport > 50 && called_transport < 100); TEST_CHECK(called_transport == sent_transactions); } @@ -258,5 +259,58 @@ SENTRY_TEST(transactions_skip_before_send) TEST_CHECK_INT_EQUAL(called_beforesend, 0); } +static void +before_transport(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(multiple_transactions) +{ + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport = sentry_transport_new(before_transport); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_set_span(tx); + + sentry_value_t scope_tx = sentry__scope_get_span(); + CHECK_STRING_PROPERTY(scope_tx, "transaction", "wow!"); + + sentry_uuid_t event_id = sentry_transaction_finish(tx); + scope_tx = sentry__scope_get_span(); + TEST_CHECK(sentry_value_is_null(scope_tx)); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + // Set transaction on scope twice, back-to-back without finishing the first + // one + tx_cxt = sentry_value_new_transaction_context("whoa!", NULL); + tx = sentry_transaction_start(tx_cxt); + sentry_set_span(tx); + sentry_value_decref(tx); + tx_cxt = sentry_value_new_transaction_context("wowee!", NULL); + tx = sentry_transaction_start(tx_cxt); + sentry_set_span(tx); + scope_tx = sentry__scope_get_span(); + CHECK_STRING_PROPERTY(scope_tx, "transaction", "wowee!"); + event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called_transport, 2); +} #undef IS_NULL #undef CHECK_STRING_PROPERTY diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index c85f2ac247..cd63ffcf99 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -30,6 +30,7 @@ XX(module_finder) XX(mpack_newlines) XX(mpack_removed_tags) XX(multiple_inits) +XX(multiple_transactions) XX(os) XX(page_allocator) XX(path_basics) From f0e22767ac91fd871b1b72e44c9a8fa85bacf45b Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Sat, 8 Jan 2022 00:16:49 -0500 Subject: [PATCH 002/207] feat(tracing): Basic span support with nesting (#634) --- .gitignore | 1 + Makefile | 13 +- examples/example.c | 18 ++- include/sentry.h | 41 ++++++ src/sentry_core.c | 106 +++++++++++++ src/sentry_core.h | 3 + src/sentry_tracing.c | 1 + src/sentry_tracing.h | 2 + src/sentry_value.c | 20 ++- tests/unit/test_tracing.c | 302 +++++++++++++++++++++++++++++++++++++- tests/unit/tests.inc | 6 + 11 files changed, 498 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index f5c8ce904a..f121c7d220 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ CMakeSettings.json /build /build* leak-build +unit-build # Run files .sentry-native diff --git a/Makefile b/Makefile index 71e63094d3..6925df596c 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,17 @@ build: build/Makefile @cmake --build build --parallel .PHONY: build -build/sentry_test_unit: build - @cmake --build build --target sentry_test_unit --parallel - test: update-test-discovery test-integration .PHONY: test -test-unit: update-test-discovery build/sentry_test_unit - ./build/sentry_test_unit +test-unit: update-test-discovery CMakeLists.txt + @mkdir -p unit-build + @cd unit-build; cmake \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=$(PWD)/unit-build \ + -DSENTRY_BACKEND=none \ + .. + @cmake --build unit-build --target sentry_test_unit --parallel + ./unit-build/sentry_test_unit .PHONY: test-unit test-integration: setup-venv diff --git a/examples/example.c b/examples/example.c index f036149082..8d9a4d6869 100644 --- a/examples/example.c +++ b/examples/example.c @@ -97,6 +97,10 @@ main(int argc, char **argv) if (has_arg(argc, argv, "capture-transaction")) { sentry_options_set_traces_sample_rate(options, 1.0); } + + if (has_arg(argc, argv, "child-spans")) { + sentry_options_set_max_spans(options, 5); + } #endif sentry_init(options); @@ -217,14 +221,24 @@ main(int argc, char **argv) #ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { sentry_value_t tx_ctx - = sentry_value_new_transaction_context("I'm a little teapot", + = sentry_value_new_transaction_context("little.teapot", "Short and stout here is my handle and here is my spout"); if (has_arg(argc, argv, "unsample-tx")) { sentry_transaction_context_set_sampled(tx_ctx, 0); } - sentry_value_t tx = sentry_transaction_start(tx_ctx); + + if (has_arg(argc, argv, "child-spans")) { + sentry_value_t child_ctx + = sentry_span_start_child(tx, "littler.teapot", NULL); + sentry_value_t grandchild_ctx + = sentry_span_start_child(child_ctx, "littlest.teapot", NULL); + + sentry_span_finish(tx, grandchild_ctx); + sentry_span_finish(tx, child_ctx); + } + sentry_transaction_finish(tx); } #endif diff --git a/include/sentry.h b/include/sentry.h index 834e12450c..e83fb9006d 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1346,6 +1346,47 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( * into this function. */ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_value_t transaction); + +/** + * Starts a new Span. + * + * Either the return value of `sentry_start_transaction` or + * `sentry_span_start_child` may be passed in as `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + */ +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_span_start_child( + sentry_value_t parent, char *operation, char *description); + +/** + * Finishes a Span. + * + * Returns a value that should be passed into `sentry_span_finish`. Not + * finishing the Span means it will be discarded, and will not be sent to + * sentry. + * + * `root_transaction` is either the parent Transaction of the Span, or + * the ancestor Transaction of the Span if the Span is not a direct descendant + * of a Transaction. + * + * This takes ownership of `span`, as child Spans must always occur within the + * total duration of a parent span and cannot take a longer amount of time to + * complete than the parent span they belong to. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_finish( + sentry_value_t root_transaction, sentry_value_t span); #endif #ifdef __cplusplus diff --git a/src/sentry_core.c b/src/sentry_core.c index 381af6fa57..926dbdbd15 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -803,6 +803,8 @@ sentry_transaction_finish(sentry_value_t tx) sentry_value_set_by_key(tx, "timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); + // TODO: This might not actually be necessary. Revisit after talking to + // the relay team about this. sentry_value_set_by_key(tx, "level", sentry_value_new_string("info")); sentry_value_t name = sentry_value_get_by_key(tx, "transaction"); @@ -843,4 +845,108 @@ sentry_set_span(sentry_value_t transaction) scope->span = transaction; } } + +sentry_value_t +sentry_span_start_child( + sentry_value_t parent, char *operation, char *description) +{ + size_t max_spans = SENTRY_SPANS_MAX; + SENTRY_WITH_OPTIONS (options) { + max_spans = options->max_spans; + } + + if (sentry_value_is_null(parent)) { + SENTRY_DEBUG("no transaction available to create a child under"); + goto fail; + } + + if (!sentry_value_is_null(sentry_value_get_by_key(parent, "timestamp"))) { + SENTRY_DEBUG("span's parent is already finished, not creating span"); + goto fail; + } + + // Aggressively discard spans if a transaction is unsampled to avoid + // wasting memory + sentry_value_t sampled = sentry_value_get_by_key(parent, "sampled"); + if (!sentry_value_is_true(sampled)) { + SENTRY_DEBUG("span's parent is unsampled, not creating span"); + goto fail; + } + sentry_value_t spans = sentry_value_get_by_key(parent, "spans"); + // This only checks that the number of _completed_ spans matches the number + // of max spans. This means that the number of in-flight spans can exceed + // the max number of spans. + if (sentry_value_get_length(spans) >= max_spans) { + SENTRY_DEBUG("reached maximum number of spans for transaction, not " + "creating span"); + goto fail; + } + + sentry_value_t child = sentry__value_new_span(parent, operation); + sentry_value_set_by_key( + child, "description", sentry_value_new_string(description)); + sentry_value_set_by_key(child, "start_timestamp", + sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1)); + + return child; + +fail: + return sentry_value_new_null(); +} + +void +sentry_span_finish(sentry_value_t root_transaction, sentry_value_t span) +{ + if (sentry_value_is_null(root_transaction) || sentry_value_is_null(span)) { + SENTRY_DEBUG( + "missing root transaction or span to finish, aborting span finish"); + goto fail; + } + + if (!sentry_value_is_null( + sentry_value_get_by_key(root_transaction, "timestamp"))) { + SENTRY_DEBUG("span's root transaction is already finished, aborting " + "span finish"); + goto fail; + } + + if (!sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))) { + SENTRY_DEBUG("span is already finished, aborting span finish"); + goto fail; + } + + // tough luck if this span actually doesn't belong on the specified + // transaction, i.e. its trace id doesn't match the root transaction's trace + // id + sentry_value_set_by_key(span, "timestamp", + sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_remove_by_key(span, "sampled"); + + size_t max_spans = SENTRY_SPANS_MAX; + SENTRY_WITH_OPTIONS (options) { + max_spans = options->max_spans; + } + + sentry_value_t spans = sentry_value_get_by_key(root_transaction, "spans"); + + if (sentry_value_get_length(spans) >= max_spans) { + SENTRY_DEBUG("reached maximum number of spans for transaction, " + "discarding span"); + goto fail; + } + + if (sentry_value_is_null(spans)) { + spans = sentry_value_new_list(); + sentry_value_set_by_key(root_transaction, "spans", spans); + } + sentry_value_append(spans, span); + return; + +fail: + sentry_value_decref(span); + return; +} #endif diff --git a/src/sentry_core.h b/src/sentry_core.h index c1c9000ba6..90377d5738 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -5,6 +5,7 @@ #include "sentry_logger.h" #define SENTRY_BREADCRUMBS_MAX 100 +#define SENTRY_SPANS_MAX 1000 #if defined(__GNUC__) && (__GNUC__ >= 4) # define MUST_USE __attribute__((warn_unused_result)) @@ -121,7 +122,9 @@ void sentry__options_unlock(void); // these for now are only needed outside of core for tests #ifdef SENTRY_UNITTEST bool sentry__roll_dice(double probability); +# ifdef SENTRY_PERFORMANCE_MONITORING bool sentry__should_send_transaction(sentry_value_t tx_cxt); +# endif #endif #endif diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 6db48033b6..2643c8039d 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -27,6 +27,7 @@ sentry__span_get_trace_context(sentry_value_t span) PLACE_VALUE("description", span); PLACE_VALUE("status", span); + // TODO: freeze this return trace_context; #undef PLACE_VALUE diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index de37c9a97d..aecb7ab084 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -10,4 +10,6 @@ * See https://develop.sentry.dev/sdk/event-payloads/transaction/#examples */ sentry_value_t sentry__span_get_trace_context(sentry_value_t span); + +sentry_value_t sentry__span_get_span_context(sentry_value_t span); #endif diff --git a/src/sentry_value.c b/src/sentry_value.c index 1cd86b59ee..f4bf51f3db 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1133,15 +1133,26 @@ sentry__value_new_span(sentry_value_t parent, const char *operation) sentry_value_t span = sentry_value_new_object(); sentry_transaction_context_set_operation(span, operation); + + sentry_uuid_t span_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + span, "span_id", sentry__value_new_span_uuid(&span_id)); + sentry_value_set_by_key(span, "status", sentry_value_new_string("ok")); + // Span creation is currently aggressively pruned prior to this function so + // once we're in here we definitely know that the span and its parent + // transaction are sampled. + // Sampling decisions inherited from traces created in other SDKs should be + // taken care of `continue_from_headers`, spans don't need to worry about + // (inheriting) forced sampling decisions, and transactions cannot be + // children of other transactions, so no inheriting of the sampling field is + // needed. if (!sentry_value_is_null(parent)) { sentry_value_set_by_key(span, "trace_id", sentry_value_get_by_key_owned(parent, "trace_id")); sentry_value_set_by_key(span, "parent_span_id", sentry_value_get_by_key_owned(parent, "span_id")); - sentry_value_set_by_key( - span, "sampled", sentry_value_get_by_key_owned(parent, "sampled")); } return span; @@ -1152,16 +1163,11 @@ sentry_value_new_transaction_context(const char *name, const char *operation) { sentry_value_t transaction_context = sentry__value_new_span(sentry_value_new_null(), operation); - sentry_transaction_context_set_name(transaction_context, name); sentry_uuid_t trace_id = sentry_uuid_new_v4(); sentry_value_set_by_key(transaction_context, "trace_id", sentry__value_new_internal_uuid(&trace_id)); - sentry_uuid_t span_id = sentry_uuid_new_v4(); - sentry_value_set_by_key( - transaction_context, "span_id", sentry__value_new_span_uuid(&span_id)); - sentry_transaction_context_set_name(transaction_context, name); return transaction_context; diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index c4f222ffe1..08bc9d93b5 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -51,8 +51,8 @@ SENTRY_TEST(basic_transaction) TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); sentry_value_decref(tx_cxt); - tx_cxt = sentry_value_new_transaction_context("", ""); TEST_CHECK(!sentry_value_is_null(tx_cxt)); + tx_cxt = sentry_value_new_transaction_context("", ""); CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); CHECK_STRING_PROPERTY(tx_cxt, "op", ""); TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); @@ -312,5 +312,305 @@ SENTRY_TEST(multiple_transactions) TEST_CHECK_INT_EQUAL(called_transport, 2); } + +SENTRY_TEST(basic_spans) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 3); + sentry_init(options); + + // Starting a child with no active transaction should fail + sentry_value_t parentless_child + = sentry_span_start_child(sentry_value_new_null(), NULL, NULL); + TEST_CHECK(sentry_value_is_null(parentless_child)); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + TEST_CHECK(!sentry_value_is_null(child)); + + // Peek into the transaction's span list and make sure everything is + // good + const char *trace_id + = sentry_value_as_string(sentry_value_get_by_key(tx, "trace_id")); + const char *parent_span_id + = sentry_value_as_string(sentry_value_get_by_key(tx, "span_id")); + // Don't track the span yet + TEST_CHECK(IS_NULL(tx, "spans")); + + // Sanity check that child isn't finished yet + TEST_CHECK(IS_NULL(child, "timestamp")); + // Now finishing + sentry_span_finish(tx, child); + + TEST_CHECK(!IS_NULL(tx, "spans")); + sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); + // Make sure the span inherited everything correctly + CHECK_STRING_PROPERTY(stored_child, "trace_id", trace_id); + CHECK_STRING_PROPERTY(stored_child, "parent_span_id", parent_span_id); + CHECK_STRING_PROPERTY(stored_child, "op", "honk"); + CHECK_STRING_PROPERTY(stored_child, "description", "goose"); + // Should be finished + TEST_CHECK(!IS_NULL(stored_child, "timestamp")); + + sentry_value_decref(tx); + + sentry_close(); +} + +SENTRY_TEST(spans_on_scope) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 3); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_set_span(tx); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + TEST_CHECK(!sentry_value_is_null(child)); + + // Peek into the transaction's span list and make sure everything is + // good + sentry_value_t scope_tx = sentry__scope_get_span(); + const char *trace_id + = sentry_value_as_string(sentry_value_get_by_key(scope_tx, "trace_id")); + const char *parent_span_id + = sentry_value_as_string(sentry_value_get_by_key(scope_tx, "span_id")); + // Don't track the span yet + TEST_CHECK(IS_NULL(scope_tx, "spans")); + + // Sanity check that child isn't finished yet + TEST_CHECK(IS_NULL(child, "timestamp")); + + sentry_span_finish(tx, child); + + scope_tx = sentry__scope_get_span(); + TEST_CHECK(!IS_NULL(scope_tx, "spans")); + sentry_value_t spans = sentry_value_get_by_key(scope_tx, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); + // Make sure the span inherited everything correctly + CHECK_STRING_PROPERTY(stored_child, "trace_id", trace_id); + CHECK_STRING_PROPERTY(stored_child, "parent_span_id", parent_span_id); + CHECK_STRING_PROPERTY(stored_child, "op", "honk"); + CHECK_STRING_PROPERTY(stored_child, "description", "goose"); + // Should be finished + TEST_CHECK(!IS_NULL(stored_child, "timestamp")); + + sentry_value_decref(tx); + + sentry_close(); +} + +SENTRY_TEST(child_spans) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 3); + sentry_init(options); + + // Finishing a nonexistent span doesn't explode anything + sentry_value_t fake_span + = sentry__value_new_span(sentry_value_new_null(), NULL); + sentry_value_t fake_tx = sentry_value_new_null(); + sentry_span_finish(fake_tx, fake_span); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + TEST_CHECK(!sentry_value_is_null(child)); + // Shouldn't be added to spans yet + TEST_CHECK(IS_NULL(tx, "spans")); + + sentry_value_t grandchild = sentry_span_start_child(child, "beep", "car"); + TEST_CHECK(!sentry_value_is_null(grandchild)); + // Shouldn't be added to spans yet + TEST_CHECK(IS_NULL(tx, "spans")); + + sentry_span_finish(tx, grandchild); + + // Make sure everything on the transaction looks good, check grandchild + const char *trace_id + = sentry_value_as_string(sentry_value_get_by_key(tx, "trace_id")); + const char *parent_span_id + = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); + + TEST_CHECK(!IS_NULL(tx, "spans")); + sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_t stored_grandchild = sentry_value_get_by_index(spans, 0); + CHECK_STRING_PROPERTY(stored_grandchild, "trace_id", trace_id); + CHECK_STRING_PROPERTY(stored_grandchild, "parent_span_id", parent_span_id); + CHECK_STRING_PROPERTY(stored_grandchild, "op", "beep"); + CHECK_STRING_PROPERTY(stored_grandchild, "description", "car"); + // Should be finished + TEST_CHECK(!IS_NULL(stored_grandchild, "timestamp")); + + sentry_span_finish(tx, child); + spans = sentry_value_get_by_key(tx, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); + + sentry_value_decref(tx); + + sentry_close(); +} + +SENTRY_TEST(overflow_spans) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 1); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + const char *child_span_id + = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); + + // Shouldn't be added to spans yet + TEST_CHECK(IS_NULL(tx, "spans")); + + sentry_value_t overflow_child + = sentry_span_start_child(child, "beep", "car"); + TEST_CHECK(!sentry_value_is_null(overflow_child)); + // Shouldn't be added to spans yet + TEST_CHECK(IS_NULL(tx, "spans")); + + sentry_span_finish(tx, child); + + TEST_CHECK(!IS_NULL(tx, "spans")); + sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); + CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); + + sentry_value_t second_overflow_child + = sentry_span_start_child(child, "ring", "bicycle"); + TEST_CHECK(sentry_value_is_null(second_overflow_child)); + + sentry_span_finish(tx, overflow_child); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_decref(tx); + + sentry_close(); +} + +SENTRY_TEST(wrong_spans_on_transaction_is_ok) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 5); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + const char *child_span_id + = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); + + sentry_value_t lingering_child = sentry_span_start_child(tx, "beep", "car"); + + sentry_value_t tx_cxt_other + = sentry_value_new_transaction_context("whoa!", NULL); + sentry_value_t tx_other = sentry_transaction_start(tx_cxt_other); + + sentry_span_finish(tx_other, child); + + // doesn't care if the child has been finished on the wrong transaction + TEST_CHECK(IS_NULL(tx, "spans")); + TEST_CHECK(!IS_NULL(tx_other, "spans")); + + sentry_value_t spans = sentry_value_get_by_key(tx_other, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); + + sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); + CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); + + sentry_uuid_t event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + // doesn't care if the child belonged to a different, already finished + // transaction + sentry_span_finish(tx_other, lingering_child); + TEST_CHECK(!IS_NULL(tx_other, "spans")); + spans = sentry_value_get_by_key(tx_other, "spans"); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); + + sentry_value_decref(tx_other); + + sentry_close(); +} + +static void +check_spans(sentry_envelope_t *envelope, void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_t transaction = sentry_envelope_get_transaction(envelope); + TEST_CHECK(!sentry_value_is_null(transaction)); + + size_t span_count = sentry_value_get_length( + sentry_value_get_by_key(transaction, "spans")); + TEST_CHECK_INT_EQUAL(span_count, 1); + + sentry_envelope_free(envelope); +} + +SENTRY_TEST(drop_unfinished_spans) +{ + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_transport_t *transport = sentry_transport_new(check_spans); + sentry_transport_set_state(transport, &called_transport); + sentry_options_set_transport(options, transport); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 2); + sentry_init(options); + + sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); + sentry_value_t tx = sentry_transaction_start(tx_cxt); + + sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + TEST_CHECK(!sentry_value_is_null(child)); + + sentry_value_t grandchild = sentry_span_start_child(child, "beep", "car"); + TEST_CHECK(!sentry_value_is_null(grandchild)); + sentry_span_finish(tx, grandchild); + + // spans are only added to transactions upon completion + TEST_CHECK_INT_EQUAL( + sentry_value_get_length(sentry_value_get_by_key(tx, "spans")), 1); + + sentry_uuid_t event_id = sentry_transaction_finish(tx); + TEST_CHECK(!sentry_uuid_is_nil(&event_id)); + + sentry_value_decref(child); + + sentry_close(); + + TEST_CHECK_INT_EQUAL(called_transport, 1); +} + #undef IS_NULL #undef CHECK_STRING_PROPERTY diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index cd63ffcf99..42cbed1c66 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -6,13 +6,16 @@ XX(basic_http_request_preparation_for_event) XX(basic_http_request_preparation_for_event_with_attachment) XX(basic_http_request_preparation_for_minidump) XX(basic_http_request_preparation_for_transaction) +XX(basic_spans) XX(basic_tracing_context) XX(basic_transaction) XX(buildid_fallback) +XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) XX(custom_logger) +XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) XX(dsn_store_url_with_path) @@ -32,6 +35,7 @@ XX(mpack_removed_tags) XX(multiple_inits) XX(multiple_transactions) XX(os) +XX(overflow_spans) XX(page_allocator) XX(path_basics) XX(path_current_exe) @@ -48,6 +52,7 @@ XX(sampling_transaction) XX(serialize_envelope) XX(session_basics) XX(slice) +XX(spans_on_scope) XX(symbolizer) XX(task_queue) XX(transaction_name_backfill_on_finish) @@ -77,3 +82,4 @@ XX(value_object) XX(value_string) XX(value_unicode) XX(value_wrong_type) +XX(wrong_spans_on_transaction_is_ok) From c6f35ddadaf5691db9b9c74f871538e260005d2e Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Sat, 8 Jan 2022 02:43:20 -0500 Subject: [PATCH 003/207] feat(tracing): Retroactive unit test improvements (#637) Retroactively applies some macros created in previous PRs to the rest of the tracing test suite. No functional changes. --- tests/unit/test_tracing.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 08bc9d93b5..fba47f25fe 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -28,14 +28,10 @@ SENTRY_TEST(basic_tracing_context) sentry_value_t trace_context = sentry__span_get_trace_context(span); TEST_CHECK(!sentry_value_is_null(trace_context)); - TEST_CHECK(!sentry_value_is_null( - sentry_value_get_by_key(trace_context, "trace_id"))); - TEST_CHECK(!sentry_value_is_null( - sentry_value_get_by_key(trace_context, "span_id"))); + TEST_CHECK(!IS_NULL(trace_context, "trace_id")); + TEST_CHECK(!IS_NULL(trace_context, "span_id")); - const char *span_op - = sentry_value_as_string(sentry_value_get_by_key(trace_context, "op")); - TEST_CHECK_STRING_EQUAL(span_op, "honk.beep"); + CHECK_STRING_PROPERTY(trace_context, "op", "honk.beep"); sentry_value_decref(trace_context); sentry_value_decref(span); @@ -127,17 +123,12 @@ send_transaction_envelope_test_basic(sentry_envelope_t *envelope, void *data) sentry_value_t tx = sentry_envelope_get_transaction(envelope); TEST_CHECK(!sentry_value_is_null(tx)); - const char *event_id - = sentry_value_as_string(sentry_value_get_by_key(tx, "event_id")); - TEST_CHECK_STRING_EQUAL(event_id, "4c035723-8638-4c3a-923f-2ab9d08b4018"); + CHECK_STRING_PROPERTY( + tx, "event_id", "4c035723-8638-4c3a-923f-2ab9d08b4018"); if (*called == 1) { - const char *type - = sentry_value_as_string(sentry_value_get_by_key(tx, "type")); - TEST_CHECK_STRING_EQUAL(type, "transaction"); - const char *name = sentry_value_as_string( - sentry_value_get_by_key(tx, "transaction")); - TEST_CHECK_STRING_EQUAL(name, "honk"); + CHECK_STRING_PROPERTY(tx, "type", "transaction"); + CHECK_STRING_PROPERTY(tx, "transaction", "honk"); } sentry_envelope_free(envelope); From 9b88510802c3309bcddc61515fac7ab343a235aa Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Tue, 11 Jan 2022 03:02:15 -0500 Subject: [PATCH 004/207] fix(tracing): Actually set the operation on a transaction (#647) --- src/sentry_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sentry_core.c b/src/sentry_core.c index 926dbdbd15..0583ef1452 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -760,6 +760,8 @@ sentry_transaction_start(sentry_value_t tx_cxt) tx, "span_id", sentry_value_get_by_key_owned(tx_cxt, "span_id")); sentry_value_set_by_key(tx, "transaction", sentry_value_get_by_key_owned(tx_cxt, "transaction")); + sentry_value_set_by_key( + tx, "op", sentry_value_get_by_key_owned(tx_cxt, "op")); sentry_value_set_by_key( tx, "status", sentry_value_get_by_key_owned(tx_cxt, "status")); sentry_value_set_by_key(tx, "start_timestamp", From 3e4d3a4efb200218240c149cf1afccd7a361b787 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 11 Jan 2022 12:41:14 +0100 Subject: [PATCH 005/207] fix: Read section names safely (#641) Avoids a potential segfault reading the section names, and also falls back when `vm_readv` lacks permissions. --- src/modulefinder/sentry_modulefinder_linux.c | 29 +++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/modulefinder/sentry_modulefinder_linux.c b/src/modulefinder/sentry_modulefinder_linux.c index 9629912b05..6be2d2a5d5 100644 --- a/src/modulefinder/sentry_modulefinder_linux.c +++ b/src/modulefinder/sentry_modulefinder_linux.c @@ -87,14 +87,17 @@ read_safely(void *dst, void *src, size_t size) ssize_t nread = process_vm_readv(pid, local, 1, remote, 1, 0); bool rv = nread == (ssize_t)size; - // The syscall is only available in Linux 3.2, meaning Android 17. - // If that is the case, just fall back to an unsafe memcpy. -#if defined(__ANDROID_API__) && __ANDROID_API__ < 17 - if (!rv && errno == EINVAL) { + // The syscall can fail with `EPERM` if we lack permissions for this syscall + // (which is the case when running in Docker for example, + // See https://github.com/getsentry/sentry-native/issues/578). + // Also, the syscall is only available in Linux 3.2, meaning Android 17. + // In that case we get an `EINVAL`. + // + // In either of these cases, just fall back to an unsafe `memcpy`. + if (!rv && (errno == EPERM || errno == EINVAL)) { memcpy(dst, src, size); rv = true; } -#endif return rv; } @@ -315,15 +318,15 @@ get_code_id_from_text_fallback(const sentry_module_t *module) elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, sizeof(Elf64_Shdr))); - const char *names = sentry__module_get_addr( - module, strheader.sh_offset, strheader.sh_entsize); - ENSURE(names); for (int i = 0; i < elf.e_shnum; i++) { Elf64_Shdr header; ENSURE(sentry__module_read_safely(&header, module, elf.e_shoff + elf.e_shentsize * i, sizeof(Elf64_Shdr))); - const char *name = names + header.sh_name; + char name[6]; + ENSURE(sentry__module_read_safely(name, module, + strheader.sh_offset + header.sh_name, sizeof(name))); + name[5] = '\0'; if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { text = sentry__module_get_addr( module, header.sh_offset, header.sh_size); @@ -341,15 +344,15 @@ get_code_id_from_text_fallback(const sentry_module_t *module) elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, sizeof(Elf32_Shdr))); - const char *names = sentry__module_get_addr( - module, strheader.sh_offset, strheader.sh_entsize); - ENSURE(names); for (int i = 0; i < elf.e_shnum; i++) { Elf32_Shdr header; ENSURE(sentry__module_read_safely(&header, module, elf.e_shoff + elf.e_shentsize * i, sizeof(Elf32_Shdr))); - const char *name = names + header.sh_name; + char name[6]; + ENSURE(sentry__module_read_safely(name, module, + strheader.sh_offset + header.sh_name, sizeof(name))); + name[5] = '\0'; if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { text = sentry__module_get_addr( module, header.sh_offset, header.sh_size); From c5c31e56d36bed37fa5422750a591f44502edb41 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 11 Jan 2022 14:37:01 +0100 Subject: [PATCH 006/207] fix: Revert to mmap-ing modules in the modulefinder (#642) --- src/modulefinder/sentry_modulefinder_linux.c | 91 +++++++++++++++++++- src/modulefinder/sentry_modulefinder_linux.h | 6 ++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/modulefinder/sentry_modulefinder_linux.c b/src/modulefinder/sentry_modulefinder_linux.c index 6be2d2a5d5..b37722de42 100644 --- a/src/modulefinder/sentry_modulefinder_linux.c +++ b/src/modulefinder/sentry_modulefinder_linux.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,6 +43,50 @@ static sentry_value_t g_modules = { 0 }; static sentry_slice_t LINUX_GATE = { "linux-gate.so", 13 }; +bool +sentry__mmap_file(sentry_mmap_t *rv, const char *path) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) { + goto fail; + } + + struct stat sb; + if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode)) { + goto fail; + } + + rv->len = sb.st_size; + if (rv->len == 0) { + goto fail; + } + + rv->ptr = mmap(NULL, rv->len, PROT_READ, MAP_PRIVATE, fd, 0); + if (rv->ptr == MAP_FAILED) { + goto fail; + } + + close(fd); + + return true; + +fail: + if (fd > 0) { + close(fd); + } + rv->ptr = NULL; + rv->len = 0; + return false; +} + +void +sentry__mmap_close(sentry_mmap_t *m) +{ + munmap(m->ptr, m->len); + m->ptr = NULL; + m->len = 0; +} + /** * Checks that `start_offset` + `size` is a valid contiguous mapping in the * mapped regions, and returns the translated pointer corresponding to @@ -112,6 +158,10 @@ sentry__module_read_safely(void *dst, const sentry_module_t *module, if (!src) { return false; } + if (module->is_mmapped) { + memcpy(dst, src, (size_t)size); + return true; + } return read_safely(dst, src, (size_t)size); } @@ -422,13 +472,46 @@ sentry__procmaps_module_to_value(const sentry_module_t *module) const sentry_mapped_region_t *first_mapping = &module->mappings[0]; const sentry_mapped_region_t *last_mapping = &module->mappings[module->num_mappings - 1]; + uint64_t module_size + = last_mapping->addr + last_mapping->size - first_mapping->addr; + sentry_value_set_by_key( mod_val, "image_addr", sentry__value_new_addr(first_mapping->addr)); - sentry_value_set_by_key(mod_val, "image_size", - sentry_value_new_int32( - last_mapping->addr + last_mapping->size - first_mapping->addr)); + sentry_value_set_by_key( + mod_val, "image_size", sentry_value_new_int32(module_size)); + + // At least on the android API-16, x86 simulator, the linker apparently + // does not load the complete file into memory. Or at least, the section + // headers which are located at the end of the file are not loaded, and + // we would be poking into invalid memory. To be safe, we mmap the + // complete file from disk, so we have the on-disk layout, and are + // independent of how the runtime linker would load or re-order any + // sections. The exception here is the linux-gate, which is not an + // actual file on disk, so we actually poke at its memory. + if (sentry__slice_eq(module->file, LINUX_GATE)) { + sentry__procmaps_read_ids_from_elf(mod_val, module); + } else { + char *filename = sentry__slice_to_owned(module->file); + sentry_mmap_t mm; + if (!sentry__mmap_file(&mm, filename)) { + sentry_free(filename); + sentry_value_decref(mod_val); + return sentry_value_new_null(); + } + sentry_free(filename); - sentry__procmaps_read_ids_from_elf(mod_val, module); + sentry_module_t mmapped_module; + memset(&mmapped_module, 0, sizeof(sentry_module_t)); + mmapped_module.is_mmapped = true; + mmapped_module.num_mappings = 1; + mmapped_module.mappings[0].addr + = (uint64_t)mm.ptr + module->offset_in_inode; + mmapped_module.mappings[0].size = mm.len - module->offset_in_inode; + + sentry__procmaps_read_ids_from_elf(mod_val, &mmapped_module); + + sentry__mmap_close(&mm); + } return mod_val; } diff --git a/src/modulefinder/sentry_modulefinder_linux.h b/src/modulefinder/sentry_modulefinder_linux.h index 5204158c04..7894f918e0 100644 --- a/src/modulefinder/sentry_modulefinder_linux.h +++ b/src/modulefinder/sentry_modulefinder_linux.h @@ -27,8 +27,14 @@ typedef struct { uint64_t offset_in_inode; uint64_t mappings_inode; uint8_t num_mappings; + bool is_mmapped; } sentry_module_t; +typedef struct { + void *ptr; + size_t len; +} sentry_mmap_t; + #ifdef SENTRY_UNITTEST bool sentry__procmaps_read_ids_from_elf( sentry_value_t value, const sentry_module_t *module); From cd50ccaec3233091356237a5ba7a1eafe5c37a2d Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Tue, 11 Jan 2022 15:18:44 +0100 Subject: [PATCH 007/207] tracing: Add transaction/span tag methods [NATIVE-442] (#626) --- include/sentry.h | 53 ++++++++++++++++++++++++++++++++++ src/sentry_value.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/include/sentry.h b/include/sentry.h index e83fb9006d..f8867c7048 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1387,6 +1387,59 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_span_start_child( */ SENTRY_EXPERIMENTAL_API void sentry_span_finish( sentry_value_t root_transaction, sentry_value_t span); + +/** + * Sets a tag on a transaction to the given string value. + * + * Tags longer than 200 bytes will be truncated. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( + sentry_value_t transaction, const char *tag, const char *value); + +/** + * Removes a tag from a transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( + sentry_value_t transaction, const char *tag); + +/** + * Sets the given key in a transaction's "data" section to the given value. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( + sentry_value_t transaction, const char *key, sentry_value_t value); + +/** + * Removes a key from a transaction's "data" section. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( + sentry_value_t transaction, const char *key); + +/** + * Sets a tag on a span to the given string value. + * + * Tags longer than 200 bytes will be truncated. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( + sentry_value_t span, const char *tag, const char *value); + +/** + * Removes a tag from a span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( + sentry_value_t span, const char *tag); + +/** + * Sets the given key in a span's "data" section to the given value. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_data( + sentry_value_t span, const char *key, sentry_value_t value); + +/** + * Removes a key from a span's "data" section. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( + sentry_value_t span, const char *key); + #endif #ifdef __cplusplus diff --git a/src/sentry_value.c b/src/sentry_value.c index f4bf51f3db..7a22b91f94 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1254,3 +1254,75 @@ sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) sentry_event_add_thread(event, thread); } + +void +sentry_span_set_tag(sentry_value_t span, const char *tag, const char *value) +{ + sentry_value_t tags = sentry_value_get_by_key(span, "tags"); + if (sentry_value_is_null(tags)) { + tags = sentry_value_new_object(); + sentry_value_set_by_key(span, "tags", tags); + } + + char *s = sentry__string_clonen(value, 200); + if (s) { + sentry_value_set_by_key(tags, tag, sentry__value_new_string_owned(s)); + } else { + sentry_value_set_by_key(tags, tag, sentry_value_new_null()); + } +} + +void +sentry_span_remove_tag(sentry_value_t span, const char *tag) +{ + sentry_value_t tags = sentry_value_get_by_key(span, "tags"); + if (!sentry_value_is_null(tags)) { + sentry_value_remove_by_key(tags, tag); + } +} + +void +sentry_span_set_data(sentry_value_t span, const char *key, sentry_value_t value) +{ + sentry_value_t data = sentry_value_get_by_key(span, "data"); + if (sentry_value_is_null(data)) { + data = sentry_value_new_object(); + sentry_value_set_by_key(span, "data", data); + } + sentry_value_set_by_key(data, key, value); +} + +void +sentry_span_remove_data(sentry_value_t span, const char *key) +{ + sentry_value_t data = sentry_value_get_by_key(span, "data"); + if (!sentry_value_is_null(data)) { + sentry_value_remove_by_key(data, key); + } +} + +void +sentry_transaction_set_tag( + sentry_value_t transaction, const char *tag, const char *value) +{ + sentry_span_set_tag(transaction, tag, value); +} + +void +sentry_transaction_remove_tag(sentry_value_t transaction, const char *tag) +{ + sentry_span_remove_tag(transaction, tag); +} + +void +sentry_transaction_set_data( + sentry_value_t transaction, const char *key, sentry_value_t value) +{ + sentry_span_set_data(transaction, key, value); +} + +void +sentry_transaction_remove_data(sentry_value_t transaction, const char *key) +{ + sentry_span_remove_data(transaction, key); +} From 3b72e6a48096836e1c42c19aea5193f08a6ad372 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Wed, 12 Jan 2022 15:38:21 +0100 Subject: [PATCH 008/207] feat(tracing): Allow transaction renaming [NATIVE-438] (#651) --- include/sentry.h | 6 ++++++ src/sentry_value.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/include/sentry.h b/include/sentry.h index f8867c7048..8eeb94ec31 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1440,6 +1440,12 @@ SENTRY_EXPERIMENTAL_API void sentry_span_set_data( SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( sentry_value_t span, const char *key); +/** + * Sets a transaction's name. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( + sentry_value_t tx, const char *name); + #endif #ifdef __cplusplus diff --git a/src/sentry_value.c b/src/sentry_value.c index 7a22b91f94..f97bda262e 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1326,3 +1326,9 @@ sentry_transaction_remove_data(sentry_value_t transaction, const char *key) { sentry_span_remove_data(transaction, key); } + +void +sentry_transaction_set_name(sentry_value_t tx, const char *name) +{ + sentry_value_set_by_key(tx, "transaction", sentry_value_new_string(name)); +} From c01555ca6e14db00a2b353ff8944bd2d3398309e Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Thu, 13 Jan 2022 00:27:35 -0500 Subject: [PATCH 009/207] feat(tracing): Introduce structs for performance monitoring constructs (#649) --- examples/example.c | 12 +- include/sentry.h | 98 +++++++--- src/sentry_core.c | 136 +++++++------- src/sentry_scope.c | 12 +- src/sentry_scope.h | 2 +- src/sentry_tracing.c | 357 +++++++++++++++++++++++++++++++++++-- src/sentry_tracing.h | 35 +++- src/sentry_value.c | 156 ---------------- src/sentry_value.h | 12 +- tests/unit/test_sampling.c | 15 +- tests/unit/test_tracing.c | 300 ++++++++++++++++++------------- 11 files changed, 734 insertions(+), 401 deletions(-) diff --git a/examples/example.c b/examples/example.c index 8d9a4d6869..fac1fcafaa 100644 --- a/examples/example.c +++ b/examples/example.c @@ -220,19 +220,19 @@ main(int argc, char **argv) #ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { - sentry_value_t tx_ctx - = sentry_value_new_transaction_context("little.teapot", + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("little.teapot", "Short and stout here is my handle and here is my spout"); if (has_arg(argc, argv, "unsample-tx")) { sentry_transaction_context_set_sampled(tx_ctx, 0); } - sentry_value_t tx = sentry_transaction_start(tx_ctx); + sentry_transaction_t *tx = sentry_transaction_start(tx_ctx); if (has_arg(argc, argv, "child-spans")) { - sentry_value_t child_ctx - = sentry_span_start_child(tx, "littler.teapot", NULL); - sentry_value_t grandchild_ctx + sentry_span_t *child_ctx + = sentry_transaction_start_child(tx, "littler.teapot", NULL); + sentry_span_t *grandchild_ctx = sentry_span_start_child(child_ctx, "littlest.teapot", NULL); sentry_span_finish(tx, grandchild_ctx); diff --git a/include/sentry.h b/include/sentry.h index 8eeb94ec31..53675fabca 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1243,6 +1243,31 @@ SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( /* -- Performance Monitoring/Tracing APIs -- */ +/** + * A sentry Transaction Context. + * + * See Transaction Interface under + * https://develop.sentry.dev/sdk/performance/#new-span-and-transaction-classes + */ +struct sentry_transaction_context_s; +typedef struct sentry_transaction_context_s sentry_transaction_context_t; + +/** + * A sentry Transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/transaction/ + */ +struct sentry_transaction_s; +typedef struct sentry_transaction_s sentry_transaction_t; + +/** + * A sentry Span. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ + */ +struct sentry_span_s; +typedef struct sentry_span_s sentry_span_t; + /** * Constructs a new Transaction Context. The returned value needs to be passed * into `sentry_transaction_start` in order to be recorded and sent to sentry. @@ -1257,15 +1282,15 @@ SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( * for an explanation of `operation`, in addition to other properties and * actions that can be performed on a Transaction. */ -SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_transaction_context( - const char *name, const char *operation); +SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * +sentry_transaction_context_new(const char *name, const char *operation); /** * Sets the `name` on a Transaction Context, which will be used in the * Transaction constructed off of the context. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( - sentry_value_t transaction, const char *name); + sentry_transaction_context_t *tx_cxt, const char *name); /** * Sets the `operation` on a Transaction Context, which will be used in the @@ -1275,7 +1300,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( * conventions on `operation`s. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( - sentry_value_t transaction, const char *operation); + sentry_transaction_context_t *tx_cxt, const char *operation); /** * Sets the `sampled` field on a Transaction Context, which will be used in the @@ -1286,7 +1311,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( * child spans will never be sent to sentry. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( - sentry_value_t transaction, int sampled); + sentry_transaction_context_t *tx_cxt, int sampled); /** * Removes the sampled field on a Transaction Context, which will be used in the @@ -1295,7 +1320,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( * The Transaction will use the sampling rate as defined in `sentry_options`. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( - sentry_value_t transaction); + sentry_transaction_context_t *tx_cxt); /** * Starts a new Transaction based on the provided context, restored from an @@ -1316,8 +1341,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( * * Takes ownership of `transaction_context`. */ -SENTRY_EXPERIMENTAL_API sentry_value_t sentry_transaction_start( - sentry_value_t transaction_context); +SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( + sentry_transaction_context_t *tx_cxt); /** * Finishes and sends a Transaction to sentry. The event ID of the Transaction @@ -1329,7 +1354,7 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_transaction_start( * this will remove the */ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( - sentry_value_t transaction); + sentry_transaction_t *tx); /** * Sets the Span (actually Transaction) so any Events sent while the Transaction @@ -1345,13 +1370,13 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( * well as its reference by passing in the same Transaction as the one passed * into this function. */ -SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_value_t transaction); +SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); /** * Starts a new Span. * - * Either the return value of `sentry_start_transaction` or - * `sentry_span_start_child` may be passed in as `parent`. + * The return value of `sentry_start_transaction` should be passed in as + * `parent`. * * Both `operation` and `description` can be null, but it is recommended to * supply the former. See @@ -1367,26 +1392,43 @@ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_value_t transaction); * sentry. `sentry_value_null` will be returned if the child Span could not be * created. */ -SENTRY_EXPERIMENTAL_API sentry_value_t sentry_span_start_child( - sentry_value_t parent, char *operation, char *description); +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( + sentry_transaction_t *parent, char *operation, char *description); /** - * Finishes a Span. + * Starts a new Span. + * + * The return value of `sentry_span_start_child` may be passed in as `parent`. + * + * Both `operation` and `description` can be null, but it is recommended to + * supply the former. See + * https://develop.sentry.dev/sdk/performance/span-operations/ for conventions + * around operations. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for a description of + * the created Span's properties and expectations for `operation` and + * `description`. * * Returns a value that should be passed into `sentry_span_finish`. Not * finishing the Span means it will be discarded, and will not be sent to - * sentry. + * sentry. `sentry_value_null` will be returned if the child Span could not be + * created. + */ +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( + sentry_span_t *parent, char *operation, char *description); + +/** + * Finishes a Span. * * `root_transaction` is either the parent Transaction of the Span, or * the ancestor Transaction of the Span if the Span is not a direct descendant * of a Transaction. * * This takes ownership of `span`, as child Spans must always occur within the - * total duration of a parent span and cannot take a longer amount of time to - * complete than the parent span they belong to. + * total duration of a parent Span and cannot outlive their parent Spans. */ SENTRY_EXPERIMENTAL_API void sentry_span_finish( - sentry_value_t root_transaction, sentry_value_t span); + sentry_transaction_t *root_transaction, sentry_span_t *span); /** * Sets a tag on a transaction to the given string value. @@ -1394,25 +1436,25 @@ SENTRY_EXPERIMENTAL_API void sentry_span_finish( * Tags longer than 200 bytes will be truncated. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( - sentry_value_t transaction, const char *tag, const char *value); + sentry_transaction_t *transaction, const char *tag, const char *value); /** * Removes a tag from a transaction. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( - sentry_value_t transaction, const char *tag); + sentry_transaction_t *transaction, const char *tag); /** * Sets the given key in a transaction's "data" section to the given value. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( - sentry_value_t transaction, const char *key, sentry_value_t value); + sentry_transaction_t *transaction, const char *key, sentry_value_t value); /** * Removes a key from a transaction's "data" section. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( - sentry_value_t transaction, const char *key); + sentry_transaction_t *transaction, const char *key); /** * Sets a tag on a span to the given string value. @@ -1420,31 +1462,31 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( * Tags longer than 200 bytes will be truncated. */ SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( - sentry_value_t span, const char *tag, const char *value); + sentry_span_t *span, const char *tag, const char *value); /** * Removes a tag from a span. */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( - sentry_value_t span, const char *tag); + sentry_span_t *span, const char *tag); /** * Sets the given key in a span's "data" section to the given value. */ SENTRY_EXPERIMENTAL_API void sentry_span_set_data( - sentry_value_t span, const char *key, sentry_value_t value); + sentry_span_t *span, const char *key, sentry_value_t value); /** * Removes a key from a span's "data" section. */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( - sentry_value_t span, const char *key); + sentry_span_t *span, const char *key); /** * Sets a transaction's name. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( - sentry_value_t tx, const char *name); + sentry_transaction_t *transaction, const char *name); #endif diff --git a/src/sentry_core.c b/src/sentry_core.c index 0583ef1452..44bcda2ea4 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -730,9 +730,11 @@ sentry_set_level(sentry_level_t level) } #ifdef SENTRY_PERFORMANCE_MONITORING -sentry_value_t -sentry_transaction_start(sentry_value_t tx_cxt) +sentry_transaction_t * +sentry_transaction_start(sentry_transaction_context_t *opaque_tx_cxt) { + sentry_value_t tx_cxt = opaque_tx_cxt->inner; + // TODO: it would be nice if we could just merge tx_cxt into tx. // `sentry_value_new_transaction_event()` is also an option, but risks // causing more confusion as there's already a @@ -768,26 +770,32 @@ sentry_transaction_start(sentry_value_t tx_cxt) sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); - sentry_value_decref(tx_cxt); - return tx; + sentry__transaction_context_free(opaque_tx_cxt); + return sentry__transaction_new(tx); } sentry_uuid_t -sentry_transaction_finish(sentry_value_t tx) +sentry_transaction_finish(sentry_transaction_t *opaque_tx) { - if (sentry_value_is_null(tx)) { + if (!opaque_tx || sentry_value_is_null(opaque_tx->inner)) { SENTRY_DEBUG("no transaction available to finish"); goto fail; } + sentry_value_t tx = sentry__value_clone(opaque_tx->inner); + SENTRY_WITH_SCOPE_MUT (scope) { - const char *tx_id - = sentry_value_as_string(sentry_value_get_by_key(tx, "trace_id")); - const char *scope_tx_id = sentry_value_as_string( - sentry_value_get_by_key(scope->span, "trace_id")); - if (sentry__string_eq(tx_id, scope_tx_id)) { - sentry_value_decref(scope->span); - scope->span = sentry_value_new_null(); + if (scope->span) { + sentry_value_t scope_tx = scope->span->inner; + + const char *tx_id = sentry_value_as_string( + sentry_value_get_by_key(tx, "trace_id")); + const char *scope_tx_id = sentry_value_as_string( + sentry_value_get_by_key(scope_tx, "trace_id")); + if (sentry__string_eq(tx_id, scope_tx_id)) { + sentry__transaction_decref(scope->span); + scope->span = NULL; + } } } // The sampling decision should already be made for transactions @@ -797,6 +805,7 @@ sentry_transaction_finish(sentry_value_t tx) if (!sentry_value_is_true(sampled)) { SENTRY_DEBUG("throwing away transaction due to sample rate or " "user-provided sampling value in transaction context"); + sentry_value_decref(tx); goto fail; } sentry_value_remove_by_key(tx, "sampled"); @@ -816,7 +825,8 @@ sentry_transaction_finish(sentry_value_t tx) } // TODO: add tracestate - sentry_value_t trace_context = sentry__span_get_trace_context(tx); + sentry_value_t trace_context + = sentry__transaction_get_trace_context(opaque_tx); sentry_value_t contexts = sentry_value_new_object(); sentry_value_set_by_key(contexts, "trace", trace_context); sentry_value_set_by_key(tx, "contexts", contexts); @@ -829,84 +839,85 @@ sentry_transaction_finish(sentry_value_t tx) sentry_value_remove_by_key(tx, "description"); sentry_value_remove_by_key(tx, "status"); + sentry__transaction_decref(opaque_tx); + // This takes ownership of the transaction, generates an event ID, merges // scope return sentry__capture_event(tx); - fail: - sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); return sentry_uuid_nil(); } void -sentry_set_span(sentry_value_t transaction) +sentry_set_span(sentry_transaction_t *tx) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry_value_decref(scope->span); - sentry_value_incref(transaction); - scope->span = transaction; + sentry__transaction_decref(scope->span); + sentry__transaction_incref(tx); + scope->span = tx; } } -sentry_value_t -sentry_span_start_child( - sentry_value_t parent, char *operation, char *description) +sentry_span_t * +sentry_transaction_start_child( + sentry_transaction_t *opaque_parent, char *operation, char *description) { + if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { + SENTRY_DEBUG("no transaction available to create a child under"); + return NULL; + } + sentry_value_t parent = opaque_parent->inner; + + // TODO: consider snapshotting this value during tx creation and storing in + // tx and span size_t max_spans = SENTRY_SPANS_MAX; SENTRY_WITH_OPTIONS (options) { max_spans = options->max_spans; } - if (sentry_value_is_null(parent)) { - SENTRY_DEBUG("no transaction available to create a child under"); - goto fail; - } + return sentry__start_child(max_spans, parent, operation, description); + // TODO: add pointer to transaction on child span +} - if (!sentry_value_is_null(sentry_value_get_by_key(parent, "timestamp"))) { - SENTRY_DEBUG("span's parent is already finished, not creating span"); - goto fail; +sentry_span_t * +sentry_span_start_child( + sentry_span_t *opaque_parent, char *operation, char *description) +{ + // TODO: check and validate ref to ancestor transaction on parent span + if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { + SENTRY_DEBUG("no parent available to create a child under"); + return NULL; } + sentry_value_t parent = opaque_parent->inner; - // Aggressively discard spans if a transaction is unsampled to avoid - // wasting memory - sentry_value_t sampled = sentry_value_get_by_key(parent, "sampled"); - if (!sentry_value_is_true(sampled)) { - SENTRY_DEBUG("span's parent is unsampled, not creating span"); - goto fail; - } - sentry_value_t spans = sentry_value_get_by_key(parent, "spans"); - // This only checks that the number of _completed_ spans matches the number - // of max spans. This means that the number of in-flight spans can exceed - // the max number of spans. - if (sentry_value_get_length(spans) >= max_spans) { - SENTRY_DEBUG("reached maximum number of spans for transaction, not " - "creating span"); - goto fail; + // TODO: consider snapshotting this value during tx creation and storing in + // tx and span + size_t max_spans = SENTRY_SPANS_MAX; + SENTRY_WITH_OPTIONS (options) { + max_spans = options->max_spans; } - sentry_value_t child = sentry__value_new_span(parent, operation); - sentry_value_set_by_key( - child, "description", sentry_value_new_string(description)); - sentry_value_set_by_key(child, "start_timestamp", - sentry__value_new_string_owned( - sentry__msec_time_to_iso8601(sentry__msec_time()))); - sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1)); - - return child; - -fail: - return sentry_value_new_null(); + return sentry__start_child(max_spans, parent, operation, description); + // TODO: add pointer to ancestor transaction on child span } +// TODO: don't accept the root transaction as a param, the span should have a +// ref to it already in itself void -sentry_span_finish(sentry_value_t root_transaction, sentry_value_t span) +sentry_span_finish( + sentry_transaction_t *opaque_root_transaction, sentry_span_t *opaque_span) { - if (sentry_value_is_null(root_transaction) || sentry_value_is_null(span)) { + if (!opaque_root_transaction || !opaque_span + || sentry_value_is_null(opaque_root_transaction->inner) + || sentry_value_is_null(opaque_span->inner)) { SENTRY_DEBUG( "missing root transaction or span to finish, aborting span finish"); goto fail; } + sentry_value_t root_transaction = opaque_root_transaction->inner; + if (!sentry_value_is_null( sentry_value_get_by_key(root_transaction, "timestamp"))) { SENTRY_DEBUG("span's root transaction is already finished, aborting " @@ -914,8 +925,11 @@ sentry_span_finish(sentry_value_t root_transaction, sentry_value_t span) goto fail; } + sentry_value_t span = sentry__value_clone(opaque_span->inner); + if (!sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))) { SENTRY_DEBUG("span is already finished, aborting span finish"); + sentry_value_decref(span); goto fail; } @@ -937,6 +951,7 @@ sentry_span_finish(sentry_value_t root_transaction, sentry_value_t span) if (sentry_value_get_length(spans) >= max_spans) { SENTRY_DEBUG("reached maximum number of spans for transaction, " "discarding span"); + sentry_value_decref(span); goto fail; } @@ -945,10 +960,11 @@ sentry_span_finish(sentry_value_t root_transaction, sentry_value_t span) sentry_value_set_by_key(root_transaction, "spans", spans); } sentry_value_append(spans, span); + sentry__span_free(opaque_span); return; fail: - sentry_value_decref(span); + sentry__span_free(opaque_span); return; } #endif diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 5a4731c539..1d3ec4d73e 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -80,7 +80,7 @@ get_scope(void) g_scope.client_sdk = get_client_sdk(); #ifdef SENTRY_PERFORMANCE_MONITORING - g_scope.span = sentry_value_new_null(); + g_scope.span = NULL; #endif g_scope_initialized = true; @@ -104,7 +104,7 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.client_sdk); #ifdef SENTRY_PERFORMANCE_MONITORING - sentry_value_decref(g_scope.span); + sentry__transaction_decref(g_scope.span); #endif } sentry__mutex_unlock(&g_lock); @@ -243,7 +243,11 @@ sentry_value_t sentry__scope_get_span() { SENTRY_WITH_SCOPE (scope) { - return scope->span; + if (!scope->span) { + return sentry_value_new_null(); + } else { + return scope->span->inner; + } } return sentry_value_new_null(); } @@ -300,7 +304,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, #ifdef SENTRY_PERFORMANCE_MONITORING // TODO: better, more thorough deep merging sentry_value_t contexts = sentry__value_clone(scope->contexts); - sentry_value_t trace = sentry__span_get_trace_context(scope->span); + sentry_value_t trace = sentry__transaction_get_trace_context(scope->span); if (!sentry_value_is_null(trace)) { sentry_value_set_by_key(contexts, "trace", trace); } diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 4fe3e05285..2fe3f5aead 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -26,7 +26,7 @@ typedef struct sentry_scope_s { // and to avoid a conflict with the existing transaction field this is named // span. Whenever possible, `transaction` should pull its value from the // `name` property nested in this field. - sentry_value_t span; + sentry_transaction_t *span; #endif } sentry_scope_t; diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 2643c8039d..30d48930fc 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -1,11 +1,245 @@ +#include "sentry_boot.h" + +#include "sentry_alloc.h" +#include "sentry_logger.h" +#include "sentry_string.h" +#include "sentry_tracing.h" +#include "sentry_utils.h" #include "sentry_value.h" +#include + +sentry_value_t +sentry__value_new_span(sentry_value_t parent, const char *operation) +{ + sentry_value_t span = sentry_value_new_object(); + + sentry_value_set_by_key(span, "op", sentry_value_new_string(operation)); + + sentry_uuid_t span_id = sentry_uuid_new_v4(); + sentry_value_set_by_key( + span, "span_id", sentry__value_new_span_uuid(&span_id)); + + sentry_value_set_by_key(span, "status", sentry_value_new_string("ok")); + + // Span creation is currently aggressively pruned prior to this function so + // once we're in here we definitely know that the span and its parent + // transaction are sampled. + // Sampling decisions inherited from traces created in other SDKs should be + // taken care of `continue_from_headers`, spans don't need to worry about + // (inheriting) forced sampling decisions, and transactions cannot be + // children of other transactions, so no inheriting of the sampling field is + // needed. + if (!sentry_value_is_null(parent)) { + sentry_value_set_by_key(span, "trace_id", + sentry_value_get_by_key_owned(parent, "trace_id")); + sentry_value_set_by_key(span, "parent_span_id", + sentry_value_get_by_key_owned(parent, "span_id")); + } + + return span; +} + +sentry_value_t +sentry__value_transaction_context_new(const char *name, const char *operation) +{ + sentry_value_t transaction_context + = sentry__value_new_span(sentry_value_new_null(), operation); + + sentry_uuid_t trace_id = sentry_uuid_new_v4(); + sentry_value_set_by_key(transaction_context, "trace_id", + sentry__value_new_internal_uuid(&trace_id)); + + sentry_value_set_by_key( + transaction_context, "transaction", sentry_value_new_string(name)); + + return transaction_context; +} + +sentry_transaction_context_t * +sentry_transaction_context_new(const char *name, const char *operation) +{ + sentry_transaction_context_t *tx_cxt + = SENTRY_MAKE(sentry_transaction_context_t); + if (!tx_cxt) { + return NULL; + } + memset(tx_cxt, 0, sizeof(sentry_transaction_context_t)); + + sentry_value_t inner + = sentry__value_transaction_context_new(name, operation); + + if (sentry_value_is_null(inner)) { + return NULL; + } + + tx_cxt->inner = inner; + + return tx_cxt; +} + +void +sentry__transaction_context_free(sentry_transaction_context_t *tx_cxt) +{ + if (!tx_cxt) { + return; + } + if (sentry_value_refcount(tx_cxt->inner) <= 1) { + sentry_value_decref(tx_cxt->inner); + sentry_free(tx_cxt); + } else { + sentry_value_decref(tx_cxt->inner); + }; +} + +void +sentry_transaction_context_set_name( + sentry_transaction_context_t *tx_cxt, const char *name) +{ + sentry_value_set_by_key( + tx_cxt->inner, "transaction", sentry_value_new_string(name)); +} + +void +sentry_transaction_context_set_operation( + sentry_transaction_context_t *tx_cxt, const char *operation) +{ + sentry_value_set_by_key( + tx_cxt->inner, "op", sentry_value_new_string(operation)); +} + +void +sentry_transaction_context_set_sampled( + sentry_transaction_context_t *tx_cxt, int sampled) +{ + sentry_value_set_by_key( + tx_cxt->inner, "sampled", sentry_value_new_bool(sampled)); +} + +void +sentry_transaction_context_remove_sampled(sentry_transaction_context_t *tx_cxt) +{ + sentry_value_remove_by_key(tx_cxt->inner, "sampled"); +} + +sentry_transaction_t * +sentry__transaction_new(sentry_value_t inner) +{ + if (sentry_value_is_null(inner)) { + return NULL; + } + + sentry_transaction_t *tx = SENTRY_MAKE(sentry_transaction_t); + if (!tx) { + return NULL; + } + memset(tx, 0, sizeof(sentry_transaction_t)); + + tx->inner = inner; + + return tx; +} + +void +sentry__transaction_incref(sentry_transaction_t *tx) +{ + sentry_value_incref(tx->inner); +} + +void +sentry__transaction_decref(sentry_transaction_t *tx) +{ + if (!tx) { + return; + } + + if (sentry_value_refcount(tx->inner) <= 1) { + sentry_value_decref(tx->inner); + sentry_free(tx); + } else { + sentry_value_decref(tx->inner); + }; +} + +sentry_span_t * +sentry__span_new(sentry_value_t inner) +{ + if (sentry_value_is_null(inner)) { + return NULL; + } + + sentry_span_t *span = SENTRY_MAKE(sentry_span_t); + if (!span) { + return NULL; + } + memset(span, 0, sizeof(sentry_span_t)); + + span->inner = inner; + + return span; +} + +sentry_span_t * +sentry__start_child( + size_t max_spans, sentry_value_t parent, char *operation, char *description) +{ + if (!sentry_value_is_null(sentry_value_get_by_key(parent, "timestamp"))) { + SENTRY_DEBUG("span's parent is already finished, not creating span"); + goto fail; + } + + // Aggressively discard spans if a transaction is unsampled to avoid + // wasting memory + sentry_value_t sampled = sentry_value_get_by_key(parent, "sampled"); + if (!sentry_value_is_true(sampled)) { + SENTRY_DEBUG("span's parent is unsampled, not creating span"); + goto fail; + } + sentry_value_t spans = sentry_value_get_by_key(parent, "spans"); + // This only checks that the number of _completed_ spans matches the number + // of max spans. This means that the number of in-flight spans can exceed + // the max number of spans. + if (sentry_value_get_length(spans) >= max_spans) { + SENTRY_DEBUG("reached maximum number of spans for transaction, not " + "creating span"); + goto fail; + } + + sentry_value_t child = sentry__value_new_span(parent, operation); + sentry_value_set_by_key( + child, "description", sentry_value_new_string(description)); + sentry_value_set_by_key(child, "start_timestamp", + sentry__value_new_string_owned( + sentry__msec_time_to_iso8601(sentry__msec_time()))); + sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1)); + + return sentry__span_new(child); +fail: + return NULL; +} + +// TODO: for now, don't allow multiple references to spans. this should be +// revisited when sentry_transaction_t stores a list of sentry_span_t's instead +// of a list of sentry_value_t's. +void +sentry__span_free(sentry_span_t *span) +{ + if (!span) { + return; + } + sentry_value_decref(span->inner); + sentry_free(span); +} sentry_value_t -sentry__span_get_trace_context(sentry_value_t span) +sentry__transaction_get_trace_context(sentry_transaction_t *opaque_tx) { - if (sentry_value_is_null(span) - || sentry_value_is_null(sentry_value_get_by_key(span, "trace_id")) - || sentry_value_is_null(sentry_value_get_by_key(span, "span_id"))) { + if (!opaque_tx || sentry_value_is_null(opaque_tx->inner)) { + return sentry_value_new_null(); + } + + sentry_value_t tx = opaque_tx->inner; + if (sentry_value_is_null(sentry_value_get_by_key(tx, "trace_id")) + || sentry_value_is_null(sentry_value_get_by_key(tx, "span_id"))) { return sentry_value_new_null(); } @@ -20,15 +254,118 @@ sentry__span_get_trace_context(sentry_value_t span) } \ } while (0) - PLACE_VALUE("trace_id", span); - PLACE_VALUE("span_id", span); - PLACE_VALUE("parent_span_id", span); - PLACE_VALUE("op", span); - PLACE_VALUE("description", span); - PLACE_VALUE("status", span); + PLACE_VALUE("trace_id", tx); + PLACE_VALUE("span_id", tx); + PLACE_VALUE("parent_span_id", tx); + PLACE_VALUE("op", tx); + PLACE_VALUE("description", tx); + PLACE_VALUE("status", tx); // TODO: freeze this return trace_context; #undef PLACE_VALUE } + +void +sentry_transaction_set_name(sentry_transaction_t *tx, const char *name) +{ + sentry_value_set_by_key( + tx->inner, "transaction", sentry_value_new_string(name)); +} + +static void +set_tag(sentry_value_t item, const char *tag, const char *value) +{ + sentry_value_t tags = sentry_value_get_by_key(item, "tags"); + if (sentry_value_is_null(tags)) { + tags = sentry_value_new_object(); + sentry_value_set_by_key(item, "tags", tags); + } + + char *s = sentry__string_clonen(value, 200); + if (s) { + sentry_value_set_by_key(tags, tag, sentry__value_new_string_owned(s)); + } else { + sentry_value_set_by_key(tags, tag, sentry_value_new_null()); + } +} + +void +sentry_transaction_set_tag( + sentry_transaction_t *tx, const char *tag, const char *value) +{ + set_tag(tx->inner, tag, value); +} + +void +sentry_span_set_tag(sentry_span_t *span, const char *tag, const char *value) +{ + set_tag(span->inner, tag, value); +} + +static void +remove_tag(sentry_value_t item, const char *tag) +{ + sentry_value_t tags = sentry_value_get_by_key(item, "tags"); + if (!sentry_value_is_null(tags)) { + sentry_value_remove_by_key(tags, tag); + } +} + +void +sentry_transaction_remove_tag(sentry_transaction_t *tx, const char *tag) +{ + remove_tag(tx->inner, tag); +} + +void +sentry_span_remove_tag(sentry_span_t *span, const char *tag) +{ + remove_tag(span->inner, tag); +} + +static void +set_data(sentry_value_t item, const char *key, sentry_value_t value) +{ + sentry_value_t data = sentry_value_get_by_key(item, "data"); + if (sentry_value_is_null(data)) { + data = sentry_value_new_object(); + sentry_value_set_by_key(item, "data", data); + } + sentry_value_set_by_key(data, key, value); +} + +void +sentry_transaction_set_data( + sentry_transaction_t *tx, const char *key, sentry_value_t value) +{ + set_data(tx->inner, key, value); +} + +void +sentry_span_set_data(sentry_span_t *span, const char *key, sentry_value_t value) +{ + set_data(span->inner, key, value); +} + +static void +remove_data(sentry_value_t item, const char *key) +{ + sentry_value_t data = sentry_value_get_by_key(item, "data"); + if (!sentry_value_is_null(data)) { + sentry_value_remove_by_key(data, key); + } +} + +void +sentry_transaction_remove_data(sentry_transaction_t *tx, const char *key) +{ + remove_data(tx->inner, key); +} + +void +sentry_span_remove_data(sentry_span_t *span, const char *key) +{ + remove_data(span->inner, key); +} diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index aecb7ab084..47b621f76f 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -4,12 +4,43 @@ #include "sentry_boot.h" #include "sentry_value.h" +/** + * A span. + */ +typedef struct sentry_span_s { + sentry_value_t inner; +} sentry_span_t; + +/** + * A transaction context. + */ +typedef struct sentry_transaction_context_s { + sentry_value_t inner; +} sentry_transaction_context_t; + +/** + * A transaction. + */ +typedef struct sentry_transaction_s { + sentry_value_t inner; +} sentry_transaction_t; + +void sentry__transaction_context_free(sentry_transaction_context_t *tx_cxt); + +sentry_transaction_t *sentry__transaction_new(sentry_value_t inner); +void sentry__transaction_incref(sentry_transaction_t *tx); +void sentry__transaction_decref(sentry_transaction_t *tx); + +sentry_span_t *sentry__start_child(size_t max_spans, sentry_value_t parent, + char *operation, char *description); +void sentry__span_free(sentry_span_t *span); + /** * Returns an object containing tracing information extracted from a * transaction (/span) which should be included in an event. * See https://develop.sentry.dev/sdk/event-payloads/transaction/#examples */ -sentry_value_t sentry__span_get_trace_context(sentry_value_t span); +sentry_value_t sentry__transaction_get_trace_context( + sentry_transaction_t *span); -sentry_value_t sentry__span_get_span_context(sentry_value_t span); #endif diff --git a/src/sentry_value.c b/src/sentry_value.c index f97bda262e..2cfa86f5f0 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1126,84 +1126,6 @@ sentry_value_new_stacktrace(void **ips, size_t len) return stacktrace; } -#ifdef SENTRY_PERFORMANCE_MONITORING -sentry_value_t -sentry__value_new_span(sentry_value_t parent, const char *operation) -{ - sentry_value_t span = sentry_value_new_object(); - - sentry_transaction_context_set_operation(span, operation); - - sentry_uuid_t span_id = sentry_uuid_new_v4(); - sentry_value_set_by_key( - span, "span_id", sentry__value_new_span_uuid(&span_id)); - - sentry_value_set_by_key(span, "status", sentry_value_new_string("ok")); - - // Span creation is currently aggressively pruned prior to this function so - // once we're in here we definitely know that the span and its parent - // transaction are sampled. - // Sampling decisions inherited from traces created in other SDKs should be - // taken care of `continue_from_headers`, spans don't need to worry about - // (inheriting) forced sampling decisions, and transactions cannot be - // children of other transactions, so no inheriting of the sampling field is - // needed. - if (!sentry_value_is_null(parent)) { - sentry_value_set_by_key(span, "trace_id", - sentry_value_get_by_key_owned(parent, "trace_id")); - sentry_value_set_by_key(span, "parent_span_id", - sentry_value_get_by_key_owned(parent, "span_id")); - } - - return span; -} - -sentry_value_t -sentry_value_new_transaction_context(const char *name, const char *operation) -{ - sentry_value_t transaction_context - = sentry__value_new_span(sentry_value_new_null(), operation); - - sentry_uuid_t trace_id = sentry_uuid_new_v4(); - sentry_value_set_by_key(transaction_context, "trace_id", - sentry__value_new_internal_uuid(&trace_id)); - - sentry_transaction_context_set_name(transaction_context, name); - - return transaction_context; -} - -void -sentry_transaction_context_set_name( - sentry_value_t transaction_context, const char *name) -{ - sentry_value_set_by_key( - transaction_context, "transaction", sentry_value_new_string(name)); -} - -void -sentry_transaction_context_set_operation( - sentry_value_t transaction_context, const char *operation) -{ - sentry_value_set_by_key( - transaction_context, "op", sentry_value_new_string(operation)); -} - -void -sentry_transaction_context_set_sampled( - sentry_value_t transaction_context, int sampled) -{ - sentry_value_set_by_key( - transaction_context, "sampled", sentry_value_new_bool(sampled)); -} - -void -sentry_transaction_context_remove_sampled(sentry_value_t transaction_context) -{ - sentry_value_remove_by_key(transaction_context, "sampled"); -} -#endif - static sentry_value_t sentry__get_or_insert_values_list(sentry_value_t parent, const char *key) { @@ -1254,81 +1176,3 @@ sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) sentry_event_add_thread(event, thread); } - -void -sentry_span_set_tag(sentry_value_t span, const char *tag, const char *value) -{ - sentry_value_t tags = sentry_value_get_by_key(span, "tags"); - if (sentry_value_is_null(tags)) { - tags = sentry_value_new_object(); - sentry_value_set_by_key(span, "tags", tags); - } - - char *s = sentry__string_clonen(value, 200); - if (s) { - sentry_value_set_by_key(tags, tag, sentry__value_new_string_owned(s)); - } else { - sentry_value_set_by_key(tags, tag, sentry_value_new_null()); - } -} - -void -sentry_span_remove_tag(sentry_value_t span, const char *tag) -{ - sentry_value_t tags = sentry_value_get_by_key(span, "tags"); - if (!sentry_value_is_null(tags)) { - sentry_value_remove_by_key(tags, tag); - } -} - -void -sentry_span_set_data(sentry_value_t span, const char *key, sentry_value_t value) -{ - sentry_value_t data = sentry_value_get_by_key(span, "data"); - if (sentry_value_is_null(data)) { - data = sentry_value_new_object(); - sentry_value_set_by_key(span, "data", data); - } - sentry_value_set_by_key(data, key, value); -} - -void -sentry_span_remove_data(sentry_value_t span, const char *key) -{ - sentry_value_t data = sentry_value_get_by_key(span, "data"); - if (!sentry_value_is_null(data)) { - sentry_value_remove_by_key(data, key); - } -} - -void -sentry_transaction_set_tag( - sentry_value_t transaction, const char *tag, const char *value) -{ - sentry_span_set_tag(transaction, tag, value); -} - -void -sentry_transaction_remove_tag(sentry_value_t transaction, const char *tag) -{ - sentry_span_remove_tag(transaction, tag); -} - -void -sentry_transaction_set_data( - sentry_value_t transaction, const char *key, sentry_value_t value) -{ - sentry_span_set_data(transaction, key, value); -} - -void -sentry_transaction_remove_data(sentry_value_t transaction, const char *key) -{ - sentry_span_remove_data(transaction, key); -} - -void -sentry_transaction_set_name(sentry_value_t tx, const char *name) -{ - sentry_value_set_by_key(tx, "transaction", sentry_value_new_string(name)); -} diff --git a/src/sentry_value.h b/src/sentry_value.h index cba7853643..abe57cc857 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -61,13 +61,6 @@ sentry_value_t sentry__value_new_list_with_size(size_t size); */ sentry_value_t sentry__value_new_object_with_size(size_t size); -/** - * Constructs a new Span. - * - */ -sentry_value_t sentry__value_new_span( - sentry_value_t parent, const char *operation); - /** * This will parse the Value into a UUID, or return a `nil` UUID on error. * See also `sentry_uuid_from_string`. @@ -109,4 +102,9 @@ typedef struct sentry_jsonwriter_s sentry_jsonwriter_t; void sentry__jsonwriter_write_value( sentry_jsonwriter_t *jw, sentry_value_t value); +#ifdef SENTRY_PERFORMANCE_MONITORING +sentry_value_t sentry__value_new_span_uuid(const sentry_uuid_t *uuid); + +sentry_value_t sentry__value_new_internal_uuid(const sentry_uuid_t *uuid); +#endif #endif diff --git a/tests/unit/test_sampling.c b/tests/unit/test_sampling.c index bcedcbbbb4..749c5515fb 100644 --- a/tests/unit/test_sampling.c +++ b/tests/unit/test_sampling.c @@ -1,5 +1,5 @@ -#include "sentry_core.h" #include "sentry_testsupport.h" +#include "sentry_tracing.h" SENTRY_TEST(sampling_decision) { @@ -13,24 +13,25 @@ SENTRY_TEST(sampling_transaction) sentry_options_t *options = sentry_options_new(); TEST_CHECK(sentry_init(options) == 0); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("honk", NULL); + sentry_transaction_context_t *tx_cxt + = sentry_transaction_context_new("honk", NULL); sentry_transaction_context_set_sampled(tx_cxt, 0); - TEST_CHECK(sentry__should_send_transaction(tx_cxt) == false); + TEST_CHECK(sentry__should_send_transaction(tx_cxt->inner) == false); sentry_transaction_context_set_sampled(tx_cxt, 1); - TEST_CHECK(sentry__should_send_transaction(tx_cxt)); + TEST_CHECK(sentry__should_send_transaction(tx_cxt->inner)); // fall back to default in sentry options (0.0) if sampled isn't there sentry_transaction_context_remove_sampled(tx_cxt); - TEST_CHECK(sentry__should_send_transaction(tx_cxt) == false); + TEST_CHECK(sentry__should_send_transaction(tx_cxt->inner) == false); options = sentry_options_new(); sentry_options_set_traces_sample_rate(options, 1.0); TEST_CHECK(sentry_init(options) == 0); - TEST_CHECK(sentry__should_send_transaction(tx_cxt)); + TEST_CHECK(sentry__should_send_transaction(tx_cxt->inner)); - sentry_value_decref(tx_cxt); + sentry__transaction_context_free(tx_cxt); sentry_close(); } diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index fba47f25fe..85cbdbd1b8 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -11,22 +11,32 @@ SENTRY_TEST(basic_tracing_context) { - sentry_value_t span = sentry_value_new_object(); - TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + sentry_transaction_t *opaque_tx + = sentry__transaction_new(sentry_value_new_null()); + TEST_CHECK(!opaque_tx); - sentry_value_set_by_key(span, "op", sentry_value_new_string("honk.beep")); - TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + sentry_value_t tx = sentry_value_new_object(); + opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); + sentry_value_set_by_key(tx, "op", sentry_value_new_string("honk.beep")); + TEST_CHECK( + sentry_value_is_null(sentry__transaction_get_trace_context(opaque_tx))); sentry_uuid_t trace_id = sentry_uuid_new_v4(); sentry_value_set_by_key( - span, "trace_id", sentry__value_new_internal_uuid(&trace_id)); - TEST_CHECK(sentry_value_is_null(sentry__span_get_trace_context(span))); + tx, "trace_id", sentry__value_new_internal_uuid(&trace_id)); + sentry__transaction_decref(opaque_tx); + opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); + TEST_CHECK( + sentry_value_is_null(sentry__transaction_get_trace_context(opaque_tx))); sentry_uuid_t span_id = sentry_uuid_new_v4(); sentry_value_set_by_key( - span, "span_id", sentry__value_new_span_uuid(&span_id)); + tx, "span_id", sentry__value_new_span_uuid(&span_id)); + sentry__transaction_decref(opaque_tx); + opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); - sentry_value_t trace_context = sentry__span_get_trace_context(span); + sentry_value_t trace_context + = sentry__transaction_get_trace_context(opaque_tx); TEST_CHECK(!sentry_value_is_null(trace_context)); TEST_CHECK(!IS_NULL(trace_context, "trace_id")); TEST_CHECK(!IS_NULL(trace_context, "span_id")); @@ -34,44 +44,66 @@ SENTRY_TEST(basic_tracing_context) CHECK_STRING_PROPERTY(trace_context, "op", "honk.beep"); sentry_value_decref(trace_context); - sentry_value_decref(span); + sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); } SENTRY_TEST(basic_transaction) { - sentry_value_t tx_cxt = sentry_value_new_transaction_context(NULL, NULL); - TEST_CHECK(!sentry_value_is_null(tx_cxt)); - CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); - CHECK_STRING_PROPERTY(tx_cxt, "op", ""); - TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); - TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); - - sentry_value_decref(tx_cxt); - TEST_CHECK(!sentry_value_is_null(tx_cxt)); - tx_cxt = sentry_value_new_transaction_context("", ""); - CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); - CHECK_STRING_PROPERTY(tx_cxt, "op", ""); - TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); - TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); - - sentry_value_decref(tx_cxt); - tx_cxt = sentry_value_new_transaction_context("honk.beep", "beepbeep"); - CHECK_STRING_PROPERTY(tx_cxt, "transaction", "honk.beep"); - CHECK_STRING_PROPERTY(tx_cxt, "op", "beepbeep"); - TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); - TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); - - sentry_transaction_context_set_name(tx_cxt, ""); - CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); - - sentry_transaction_context_set_operation(tx_cxt, ""); - CHECK_STRING_PROPERTY(tx_cxt, "op", ""); - - sentry_transaction_context_set_sampled(tx_cxt, 1); - TEST_CHECK( - sentry_value_is_true(sentry_value_get_by_key(tx_cxt, "sampled")) == 1); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new(NULL, NULL); + sentry_value_t tx_cxt; + if (opaque_tx_cxt != NULL) { + tx_cxt = opaque_tx_cxt->inner; + TEST_CHECK(!sentry_value_is_null(tx_cxt)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + } else { + TEST_CHECK(opaque_tx_cxt != NULL); + } - sentry_value_decref(tx_cxt); + sentry__transaction_context_free(opaque_tx_cxt); + + opaque_tx_cxt = sentry_transaction_context_new("", ""); + if (opaque_tx_cxt != NULL) { + tx_cxt = opaque_tx_cxt->inner; + TEST_CHECK(!sentry_value_is_null(tx_cxt)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + } else { + TEST_CHECK(opaque_tx_cxt != NULL); + } + + sentry__transaction_context_free(opaque_tx_cxt); + + opaque_tx_cxt = sentry_transaction_context_new("honk.beep", "beepbeep"); + if (opaque_tx_cxt != NULL) { + tx_cxt = opaque_tx_cxt->inner; + TEST_CHECK(!sentry_value_is_null(tx_cxt)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", "honk.beep"); + CHECK_STRING_PROPERTY(tx_cxt, "op", "beepbeep"); + TEST_CHECK(!IS_NULL(tx_cxt, "trace_id")); + TEST_CHECK(!IS_NULL(tx_cxt, "span_id")); + + sentry_transaction_context_set_name(opaque_tx_cxt, ""); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + + sentry_transaction_context_set_operation(opaque_tx_cxt, ""); + CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + + sentry_transaction_context_set_sampled(opaque_tx_cxt, 1); + TEST_CHECK( + sentry_value_is_true(sentry_value_get_by_key(tx_cxt, "sampled")) + == 1); + } else { + TEST_CHECK(opaque_tx_cxt != NULL); + } + + sentry__transaction_context_free(opaque_tx_cxt); } static void @@ -101,12 +133,13 @@ SENTRY_TEST(transaction_name_backfill_on_finish) sentry_options_set_traces_sample_rate(options, 1.0); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context(NULL, NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *tx_cxt + = sentry_transaction_context_new(NULL, NULL); + sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); - tx_cxt = sentry_value_new_transaction_context("", ""); + tx_cxt = sentry_transaction_context_new("", ""); tx = sentry_transaction_start(tx_cxt); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -126,7 +159,7 @@ send_transaction_envelope_test_basic(sentry_envelope_t *envelope, void *data) CHECK_STRING_PROPERTY( tx, "event_id", "4c035723-8638-4c3a-923f-2ab9d08b4018"); - if (*called == 1) { + if (*called != 1) { CHECK_STRING_PROPERTY(tx, "type", "transaction"); CHECK_STRING_PROPERTY(tx, "transaction", "honk"); } @@ -150,22 +183,22 @@ SENTRY_TEST(basic_function_transport_transaction) sentry_options_set_require_user_consent(options, true); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context( + sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new( "How could you", "Don't capture this."); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); sentry_uuid_t event_id = sentry_transaction_finish(tx); // TODO: `sentry_capture_event` acts as if the event was sent if user // consent was not given TEST_CHECK(!sentry_uuid_is_nil(&event_id)); sentry_user_consent_give(); - tx_cxt = sentry_value_new_transaction_context("honk", "beep"); + tx_cxt = sentry_transaction_context_new("honk", "beep"); tx = sentry_transaction_start(tx_cxt); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); sentry_user_consent_revoke(); - tx_cxt = sentry_value_new_transaction_context( + tx_cxt = sentry_transaction_context_new( "How could you again", "Don't capture this either."); tx = sentry_transaction_start(tx_cxt); event_id = sentry_transaction_finish(tx); @@ -195,9 +228,9 @@ SENTRY_TEST(transport_sampling_transactions) uint64_t sent_transactions = 0; for (int i = 0; i < 100; i++) { - sentry_value_t tx_cxt - = sentry_value_new_transaction_context("honk", "beep"); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *tx_cxt + = sentry_transaction_context_new("honk", "beep"); + sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); sentry_uuid_t event_id = sentry_transaction_finish(tx); if (!sentry_uuid_is_nil(&event_id)) { sent_transactions += 1; @@ -238,9 +271,9 @@ SENTRY_TEST(transactions_skip_before_send) sentry_options_set_before_send(options, before_send, &called_beforesend); sentry_init(options); - sentry_value_t tx_cxt - = sentry_value_new_transaction_context("honk", "beep"); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *tx_cxt + = sentry_transaction_context_new("honk", "beep"); + sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -273,8 +306,9 @@ SENTRY_TEST(multiple_transactions) sentry_options_set_traces_sample_rate(options, 1.0); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); sentry_set_span(tx); sentry_value_t scope_tx = sentry__scope_get_span(); @@ -287,11 +321,11 @@ SENTRY_TEST(multiple_transactions) // Set transaction on scope twice, back-to-back without finishing the first // one - tx_cxt = sentry_value_new_transaction_context("whoa!", NULL); + tx_cxt = sentry_transaction_context_new("whoa!", NULL); tx = sentry_transaction_start(tx_cxt); sentry_set_span(tx); - sentry_value_decref(tx); - tx_cxt = sentry_value_new_transaction_context("wowee!", NULL); + sentry__transaction_decref(tx); + tx_cxt = sentry_transaction_context_new("wowee!", NULL); tx = sentry_transaction_start(tx_cxt); sentry_set_span(tx); scope_tx = sentry__scope_get_span(); @@ -312,14 +346,18 @@ SENTRY_TEST(basic_spans) sentry_init(options); // Starting a child with no active transaction should fail - sentry_value_t parentless_child - = sentry_span_start_child(sentry_value_new_null(), NULL, NULL); - TEST_CHECK(sentry_value_is_null(parentless_child)); - - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); - - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *parentless_child + = sentry_transaction_start_child(NULL, NULL, NULL); + TEST_CHECK(!parentless_child); + + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_value_t tx = opaque_tx->inner; + + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; TEST_CHECK(!sentry_value_is_null(child)); // Peek into the transaction's span list and make sure everything is @@ -334,7 +372,7 @@ SENTRY_TEST(basic_spans) // Sanity check that child isn't finished yet TEST_CHECK(IS_NULL(child, "timestamp")); // Now finishing - sentry_span_finish(tx, child); + sentry_span_finish(opaque_tx, opaque_child); TEST_CHECK(!IS_NULL(tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); @@ -349,7 +387,7 @@ SENTRY_TEST(basic_spans) // Should be finished TEST_CHECK(!IS_NULL(stored_child, "timestamp")); - sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); sentry_close(); } @@ -361,11 +399,14 @@ SENTRY_TEST(spans_on_scope) sentry_options_set_max_spans(options, 3); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); - sentry_set_span(tx); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_set_span(opaque_tx); - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; TEST_CHECK(!sentry_value_is_null(child)); // Peek into the transaction's span list and make sure everything is @@ -381,7 +422,7 @@ SENTRY_TEST(spans_on_scope) // Sanity check that child isn't finished yet TEST_CHECK(IS_NULL(child, "timestamp")); - sentry_span_finish(tx, child); + sentry_span_finish(opaque_tx, opaque_child); scope_tx = sentry__scope_get_span(); TEST_CHECK(!IS_NULL(scope_tx, "spans")); @@ -397,7 +438,7 @@ SENTRY_TEST(spans_on_scope) // Should be finished TEST_CHECK(!IS_NULL(stored_child, "timestamp")); - sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); sentry_close(); } @@ -409,26 +450,26 @@ SENTRY_TEST(child_spans) sentry_options_set_max_spans(options, 3); sentry_init(options); - // Finishing a nonexistent span doesn't explode anything - sentry_value_t fake_span - = sentry__value_new_span(sentry_value_new_null(), NULL); - sentry_value_t fake_tx = sentry_value_new_null(); - sentry_span_finish(fake_tx, fake_span); - - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_value_t tx = opaque_tx->inner; - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; TEST_CHECK(!sentry_value_is_null(child)); // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_value_t grandchild = sentry_span_start_child(child, "beep", "car"); + sentry_span_t *opaque_grandchild + = sentry_span_start_child(opaque_child, "beep", "car"); + sentry_value_t grandchild = opaque_grandchild->inner; TEST_CHECK(!sentry_value_is_null(grandchild)); // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_span_finish(tx, grandchild); + sentry_span_finish(opaque_tx, opaque_grandchild); // Make sure everything on the transaction looks good, check grandchild const char *trace_id @@ -448,11 +489,11 @@ SENTRY_TEST(child_spans) // Should be finished TEST_CHECK(!IS_NULL(stored_grandchild, "timestamp")); - sentry_span_finish(tx, child); + sentry_span_finish(opaque_tx, opaque_child); spans = sentry_value_get_by_key(tx, "spans"); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); - sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); sentry_close(); } @@ -464,23 +505,28 @@ SENTRY_TEST(overflow_spans) sentry_options_set_max_spans(options, 1); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_value_t tx = opaque_tx->inner; - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; const char *child_span_id = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_value_t overflow_child - = sentry_span_start_child(child, "beep", "car"); - TEST_CHECK(!sentry_value_is_null(overflow_child)); + sentry_span_t *opaque_drop_on_finish_child + = sentry_span_start_child(opaque_child, "beep", "car"); + sentry_value_t drop_on_finish_child = opaque_drop_on_finish_child->inner; + TEST_CHECK(!sentry_value_is_null(drop_on_finish_child)); // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_span_finish(tx, child); + sentry_span_finish(opaque_tx, opaque_child); TEST_CHECK(!IS_NULL(tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); @@ -489,14 +535,15 @@ SENTRY_TEST(overflow_spans) sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); - sentry_value_t second_overflow_child - = sentry_span_start_child(child, "ring", "bicycle"); - TEST_CHECK(sentry_value_is_null(second_overflow_child)); + sentry_span_finish(opaque_tx, opaque_drop_on_finish_child); + TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); - sentry_span_finish(tx, overflow_child); + sentry_span_t *opaque_drop_on_start_child + = sentry_transaction_start_child(opaque_tx, "ring", "bicycle"); + TEST_CHECK(!opaque_drop_on_start_child); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); - sentry_value_decref(tx); + sentry__transaction_decref(opaque_tx); sentry_close(); } @@ -508,22 +555,29 @@ SENTRY_TEST(wrong_spans_on_transaction_is_ok) sentry_options_set_max_spans(options, 5); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_value_t tx = opaque_tx->inner; - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; const char *child_span_id = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); - sentry_value_t lingering_child = sentry_span_start_child(tx, "beep", "car"); + sentry_span_t *opaque_lingering_child + = sentry_transaction_start_child(opaque_tx, "beep", "car"); - sentry_value_t tx_cxt_other - = sentry_value_new_transaction_context("whoa!", NULL); - sentry_value_t tx_other = sentry_transaction_start(tx_cxt_other); + sentry_transaction_context_t *tx_cxt_other + = sentry_transaction_context_new("whoa!", NULL); + sentry_transaction_t *opaque_tx_other + = sentry_transaction_start(tx_cxt_other); + sentry_value_t tx_other = opaque_tx_other->inner; - sentry_span_finish(tx_other, child); + sentry_span_finish(opaque_tx_other, opaque_child); - // doesn't care if the child has been finished on the wrong transaction + // doesn't care if the child was finished on the wrong transaction TEST_CHECK(IS_NULL(tx, "spans")); TEST_CHECK(!IS_NULL(tx_other, "spans")); @@ -533,17 +587,17 @@ SENTRY_TEST(wrong_spans_on_transaction_is_ok) sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); - sentry_uuid_t event_id = sentry_transaction_finish(tx); + sentry_uuid_t event_id = sentry_transaction_finish(opaque_tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); // doesn't care if the child belonged to a different, already finished // transaction - sentry_span_finish(tx_other, lingering_child); + sentry_span_finish(opaque_tx_other, opaque_lingering_child); TEST_CHECK(!IS_NULL(tx_other, "spans")); spans = sentry_value_get_by_key(tx_other, "spans"); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); - sentry_value_decref(tx_other); + sentry__transaction_decref(opaque_tx_other); sentry_close(); } @@ -579,24 +633,30 @@ SENTRY_TEST(drop_unfinished_spans) sentry_options_set_max_spans(options, 2); sentry_init(options); - sentry_value_t tx_cxt = sentry_value_new_transaction_context("wow!", NULL); - sentry_value_t tx = sentry_transaction_start(tx_cxt); + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_value_t tx = opaque_tx->inner; - sentry_value_t child = sentry_span_start_child(tx, "honk", "goose"); + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; TEST_CHECK(!sentry_value_is_null(child)); - sentry_value_t grandchild = sentry_span_start_child(child, "beep", "car"); + sentry_span_t *opaque_grandchild + = sentry_span_start_child(opaque_child, "beep", "car"); + sentry_value_t grandchild = opaque_grandchild->inner; TEST_CHECK(!sentry_value_is_null(grandchild)); - sentry_span_finish(tx, grandchild); + sentry_span_finish(opaque_tx, opaque_grandchild); // spans are only added to transactions upon completion TEST_CHECK_INT_EQUAL( sentry_value_get_length(sentry_value_get_by_key(tx, "spans")), 1); - sentry_uuid_t event_id = sentry_transaction_finish(tx); + sentry_uuid_t event_id = sentry_transaction_finish(opaque_tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); - sentry_value_decref(child); + sentry__span_free(opaque_child); sentry_close(); From 879ed59ec2eff16535e8a7cd6be894b163a341f5 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 13 Jan 2022 13:10:08 +0100 Subject: [PATCH 010/207] ref(tracing): Update name of attached transaction in set_transaction [NATIVE-444] (#652) --- include/sentry.h | 5 ----- src/sentry_core.c | 12 ++++++------ tests/unit/test_uninit.c | 1 - 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 53675fabca..d59c179db2 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1192,11 +1192,6 @@ SENTRY_API void sentry_remove_fingerprint(void); */ SENTRY_API void sentry_set_transaction(const char *transaction); -/** - * Removes the transaction. - */ -SENTRY_API void sentry_remove_transaction(void); - /** * Sets the event level. */ diff --git a/src/sentry_core.c b/src/sentry_core.c index 44bcda2ea4..99985b596a 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -712,13 +712,13 @@ sentry_set_transaction(const char *transaction) SENTRY_WITH_SCOPE_MUT (scope) { sentry_free(scope->transaction); scope->transaction = sentry__string_clone(transaction); - } -} -void -sentry_remove_transaction(void) -{ - sentry_set_transaction(NULL); +#ifdef SENTRY_PERFORMANCE_MONITORING + if (!sentry_value_is_null(scope->span)) { + sentry_transaction_set_name(scope->span, transaction); + } +#endif + } } void diff --git a/tests/unit/test_uninit.c b/tests/unit/test_uninit.c index f9a294c2b1..2cd0859d1f 100644 --- a/tests/unit/test_uninit.c +++ b/tests/unit/test_uninit.c @@ -25,7 +25,6 @@ SENTRY_TEST(uninitialized) sentry_set_fingerprint("foo", "bar", NULL); sentry_remove_fingerprint(); sentry_set_transaction("foo"); - sentry_remove_transaction(); sentry_set_level(SENTRY_LEVEL_DEBUG); sentry_start_session(); sentry_end_session(); From b6dc5815357b1779ec0bb1680ed67dcfeba33240 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 13 Jan 2022 20:44:13 +0100 Subject: [PATCH 011/207] feat(tracing): Allow setting custom span status [NATIVE-441] (#648) --- examples/example.c | 21 +++++++++---- include/sentry.h | 70 ++++++++++++++++++++++++++++++++++++++++++++ src/sentry_core.c | 2 +- src/sentry_tracing.c | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 6 deletions(-) diff --git a/examples/example.c b/examples/example.c index fac1fcafaa..4d76aeede9 100644 --- a/examples/example.c +++ b/examples/example.c @@ -229,14 +229,25 @@ main(int argc, char **argv) } sentry_transaction_t *tx = sentry_transaction_start(tx_ctx); + if (has_arg(argc, argv, "error-status")) { + sentry_transaction_set_status( + tx, SENTRY_SPAN_STATUS_INTERNAL_ERROR); + } + if (has_arg(argc, argv, "child-spans")) { - sentry_span_t *child_ctx + sentry_span_t *child = sentry_transaction_start_child(tx, "littler.teapot", NULL); - sentry_span_t *grandchild_ctx - = sentry_span_start_child(child_ctx, "littlest.teapot", NULL); + sentry_span_t *grandchild + = sentry_span_start_child(child, "littlest.teapot", NULL); + + if (has_arg(argc, argv, "error-status")) { + sentry_span_set_status(child, SENTRY_SPAN_STATUS_NOT_FOUND); + sentry_span_set_status( + grandchild, SENTRY_SPAN_STATUS_ALREADY_EXISTS); + } - sentry_span_finish(tx, grandchild_ctx); - sentry_span_finish(tx, child_ctx); + sentry_span_finish(tx, grandchild); + sentry_span_finish(tx, child); } sentry_transaction_finish(tx); diff --git a/include/sentry.h b/include/sentry.h index d59c179db2..9369e8f41b 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1483,6 +1483,76 @@ SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( sentry_transaction_t *transaction, const char *name); +/** + * The status of a span or transaction. + * + * See https://develop.sentry.dev/sdk/event-payloads/span/ for documentation. + */ +typedef enum { + // The operation completed successfully. + // HTTP status 100..299 + successful redirects from the 3xx range. + SENTRY_SPAN_STATUS_OK, + // The operation was cancelled (typically by the user). + SENTRY_SPAN_STATUS_CANCELLED, + // Unknown. Any non-standard HTTP status code. + // "We do not know whether the transaction failed or succeeded." + SENTRY_SPAN_STATUS_UNKNOWN, + // Client specified an invalid argument. 4xx. + // Note that this differs from FailedPrecondition. InvalidArgument + // indicates arguments that are problematic regardless of the + // state of the system. + SENTRY_SPAN_STATUS_INVALID_ARGUMENT, + // Deadline expired before operation could complete. + // For operations that change the state of the system, this error may be + // returned even if the operation has been completed successfully. + // HTTP redirect loops and 504 Gateway Timeout. + SENTRY_SPAN_STATUS_DEADLINE_EXCEEDED, + // 404 Not Found. Some requested entity (file or directory) was not found. + SENTRY_SPAN_STATUS_NOT_FOUND, + // Already exists (409) + // Some entity that we attempted to create already exists. + SENTRY_SPAN_STATUS_ALREADY_EXISTS, + // 403 Forbidden + // The caller does not have permission to execute the specified operation. + SENTRY_SPAN_STATUS_PERMISSION_DENIED, + // 429 Too Many Requests + // Some resource has been exhausted, perhaps a per-user quota or perhaps + // the entire file system is out of space. + SENTRY_SPAN_STATUS_RESOURCE_EXHAUSTED, + // Operation was rejected because the system is not in a state required for + // the operation's execution. + SENTRY_SPAN_STATUS_FAILED_PRECONDITION, + // The operation was aborted, typically due to a concurrency issue. + SENTRY_SPAN_STATUS_ABORTED, + // Operation was attempted past the valid range. + SENTRY_SPAN_STATUS_OUT_OF_RANGE, + // 501 Not Implemented + // Operation is not implemented or not enabled. + SENTRY_SPAN_STATUS_UNIMPLEMENTED, + // Other/generic 5xx + SENTRY_SPAN_STATUS_INTERNAL_ERROR, + // 503 Service Unavailable + SENTRY_SPAN_STATUS_UNAVAILABLE, + // Unrecoverable data loss or corruption + SENTRY_SPAN_STATUS_DATA_LOSS, + // 401 Unauthorized (actually does mean unauthenticated according to RFC + // 7235) + // Prefer PermissionDenied if a user is logged in. + SENTRY_SPAN_STATUS_UNAUTHENTICATED, +} sentry_span_status_t; + +/** + * Sets a span's status. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_set_status( + sentry_span_t *span, sentry_span_status_t status); + +/** + * Sets a transaction's status. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status( + sentry_transaction_t *tx, sentry_span_status_t status); + #endif #ifdef __cplusplus diff --git a/src/sentry_core.c b/src/sentry_core.c index 99985b596a..8bfaa2d5df 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -714,7 +714,7 @@ sentry_set_transaction(const char *transaction) scope->transaction = sentry__string_clone(transaction); #ifdef SENTRY_PERFORMANCE_MONITORING - if (!sentry_value_is_null(scope->span)) { + if (scope->span) { sentry_transaction_set_name(scope->span, transaction); } #endif diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 30d48930fc..92d16620bb 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -369,3 +369,65 @@ sentry_span_remove_data(sentry_span_t *span, const char *key) { remove_data(span->inner, key); } + +sentry_value_t +sentry_status_to_string(sentry_span_status_t status) +{ + switch (status) { + case SENTRY_SPAN_STATUS_OK: + return sentry_value_new_string("ok"); + case SENTRY_SPAN_STATUS_CANCELLED: + return sentry_value_new_string("cancelled"); + case SENTRY_SPAN_STATUS_UNKNOWN: + return sentry_value_new_string("unknown"); + case SENTRY_SPAN_STATUS_INVALID_ARGUMENT: + return sentry_value_new_string("invalid_argument"); + case SENTRY_SPAN_STATUS_DEADLINE_EXCEEDED: + return sentry_value_new_string("deadline_exceeded"); + case SENTRY_SPAN_STATUS_NOT_FOUND: + return sentry_value_new_string("not_found"); + case SENTRY_SPAN_STATUS_ALREADY_EXISTS: + return sentry_value_new_string("already_exists"); + case SENTRY_SPAN_STATUS_PERMISSION_DENIED: + return sentry_value_new_string("permission_denied"); + case SENTRY_SPAN_STATUS_RESOURCE_EXHAUSTED: + return sentry_value_new_string("resource_exhausted"); + case SENTRY_SPAN_STATUS_FAILED_PRECONDITION: + return sentry_value_new_string("failed_precondition"); + case SENTRY_SPAN_STATUS_ABORTED: + return sentry_value_new_string("aborted"); + case SENTRY_SPAN_STATUS_OUT_OF_RANGE: + return sentry_value_new_string("out_of_range"); + case SENTRY_SPAN_STATUS_UNIMPLEMENTED: + return sentry_value_new_string("unimplemented"); + case SENTRY_SPAN_STATUS_INTERNAL_ERROR: + return sentry_value_new_string("internal_error"); + case SENTRY_SPAN_STATUS_UNAVAILABLE: + return sentry_value_new_string("unavailable"); + case SENTRY_SPAN_STATUS_DATA_LOSS: + return sentry_value_new_string("data_loss"); + case SENTRY_SPAN_STATUS_UNAUTHENTICATED: + return sentry_value_new_string("unauthenticated"); + default: + return sentry_value_new_null(); + } +} + +void +set_status(sentry_value_t item, sentry_span_status_t status) +{ + sentry_value_set_by_key(item, "status", sentry_status_to_string(status)); +} + +void +sentry_span_set_status(sentry_span_t *span, sentry_span_status_t status) +{ + set_status(span->inner, status); +} + +void +sentry_transaction_set_status( + sentry_transaction_t *tx, sentry_span_status_t status) +{ + set_status(tx->inner, status); +} From 047cf98d993cc237a8cf7ecd9226b4712aad86e5 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 14 Jan 2022 09:57:23 +0100 Subject: [PATCH 012/207] feat: basic object merging (#650) This has a basic recursive merging of sentry_value_t objects. Generally values from the source object have preference, however when the values of a key are objects themselves in both the target and the source they are recursively merged. Co-authored-by: Betty Da --- src/sentry_core.c | 36 +++++++++----------------- src/sentry_scope.c | 43 +++++++++++++++++++++++++------ src/sentry_value.c | 35 ++++++++++++++++++++++++++ src/sentry_value.h | 16 ++++++++++++ tests/unit/test_value.c | 56 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 2 ++ 6 files changed, 156 insertions(+), 32 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 8bfaa2d5df..09a038b1a6 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -735,37 +735,25 @@ sentry_transaction_start(sentry_transaction_context_t *opaque_tx_cxt) { sentry_value_t tx_cxt = opaque_tx_cxt->inner; - // TODO: it would be nice if we could just merge tx_cxt into tx. - // `sentry_value_new_transaction_event()` is also an option, but risks - // causing more confusion as there's already a - // `sentry_value_new_transaction`. The ending timestamp is stripped as well - // to avoid misleading ourselves later down the line. + // If the parent span ID is some empty-ish value, just remove it + sentry_value_t parent_span + = sentry_value_get_by_key(tx_cxt, "parent_span_id"); + if (sentry_value_get_length(parent_span) < 1) { + sentry_value_remove_by_key(tx_cxt, "parent_span_id"); + } + + // The ending timestamp is stripped to avoid misleading ourselves later + // down the line, as it is the only way to determine whether a transaction + // has ended or not. sentry_value_t tx = sentry_value_new_event(); sentry_value_remove_by_key(tx, "timestamp"); + sentry__value_merge_objects(tx, tx_cxt); + bool should_sample = sentry__should_send_transaction(tx_cxt); sentry_value_set_by_key( tx, "sampled", sentry_value_new_bool(should_sample)); - // Avoid having this show up in the payload at all if it doesn't have a - // valid value - sentry_value_t parent_span - = sentry_value_get_by_key_owned(tx_cxt, "parent_span_id"); - if (sentry_value_get_length(parent_span) > 0) { - sentry_value_set_by_key(tx, "parent_span_id", parent_span); - } else { - sentry_value_decref(parent_span); - } - sentry_value_set_by_key( - tx, "trace_id", sentry_value_get_by_key_owned(tx_cxt, "trace_id")); - sentry_value_set_by_key( - tx, "span_id", sentry_value_get_by_key_owned(tx_cxt, "span_id")); - sentry_value_set_by_key(tx, "transaction", - sentry_value_get_by_key_owned(tx_cxt, "transaction")); - sentry_value_set_by_key( - tx, "op", sentry_value_get_by_key_owned(tx_cxt, "op")); - sentry_value_set_by_key( - tx, "status", sentry_value_get_by_key_owned(tx_cxt, "status")); sentry_value_set_by_key(tx, "start_timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 1d3ec4d73e..b2b7324703 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1,4 +1,5 @@ #include "sentry_scope.h" +#include "sentry.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -7,6 +8,7 @@ #include "sentry_string.h" #include "sentry_symbolizer.h" #include "sentry_sync.h" +#include "sentry_value.h" #include @@ -297,18 +299,43 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, PLACE_STRING("transaction", scope->transaction); PLACE_VALUE("sdk", scope->client_sdk); - // TODO: these should merge - PLACE_CLONED_VALUE("tags", scope->tags); - PLACE_CLONED_VALUE("extra", scope->extra); + sentry_value_t event_tags = sentry_value_get_by_key(event, "tags"); + if (sentry_value_is_null(event_tags)) { + if (!sentry_value_is_null(scope->tags)) { + PLACE_CLONED_VALUE("tags", scope->tags); + } + } else { + sentry__value_merge_objects(event_tags, scope->tags); + } + sentry_value_t event_extra = sentry_value_get_by_key(event, "extra"); + if (sentry_value_is_null(event_extra)) { + if (!sentry_value_is_null(scope->extra)) { + PLACE_CLONED_VALUE("extra", scope->extra); + } + } else { + sentry__value_merge_objects(event_extra, scope->extra); + } #ifdef SENTRY_PERFORMANCE_MONITORING - // TODO: better, more thorough deep merging sentry_value_t contexts = sentry__value_clone(scope->contexts); - sentry_value_t trace = sentry__transaction_get_trace_context(scope->span); - if (!sentry_value_is_null(trace)) { - sentry_value_set_by_key(contexts, "trace", trace); + // prep contexts sourced from scope; data about transaction on scope needs + // to be extracted and inserted + sentry_value_t scope_trace + = sentry__transaction_get_trace_context(scope->span); + if (!sentry_value_is_null(scope_trace)) { + if (sentry_value_is_null(contexts)) { + contexts = sentry_value_new_object(); + } + sentry_value_set_by_key(contexts, "trace", scope_trace); + } + + // merge contexts sourced from scope into the event + sentry_value_t event_contexts = sentry_value_get_by_key(event, "contexts"); + if (sentry_value_is_null(event_contexts)) { + PLACE_VALUE("contexts", contexts); + } else { + sentry__value_merge_objects(event_contexts, contexts); } - PLACE_VALUE("contexts", contexts); sentry_value_decref(contexts); #endif diff --git a/src/sentry_value.c b/src/sentry_value.c index 2cfa86f5f0..cbb367df9d 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -806,6 +806,41 @@ sentry_value_is_null(sentry_value_t value) return value._bits == CONST_NULL; } +int +sentry__value_merge_objects(sentry_value_t dst, sentry_value_t src) +{ + if (sentry_value_is_null(src)) { + return 0; + } + if (sentry_value_get_type(dst) != SENTRY_VALUE_TYPE_OBJECT + || sentry_value_get_type(src) != SENTRY_VALUE_TYPE_OBJECT + || sentry_value_is_frozen(dst)) { + return 1; + } + thing_t *thing = value_as_thing(src); + if (!thing) { + return 1; + } + obj_t *obj = thing->payload._ptr; + for (size_t i = 0; i < obj->len; i++) { + char *key = obj->pairs[i].k; + sentry_value_t src_val = obj->pairs[i].v; + sentry_value_t dst_val = sentry_value_get_by_key(dst, key); + if (sentry_value_get_type(dst_val) == SENTRY_VALUE_TYPE_OBJECT + && sentry_value_get_type(src_val) == SENTRY_VALUE_TYPE_OBJECT) { + if (sentry__value_merge_objects(dst_val, src_val) != 0) { + return 1; + } + } else { + if (sentry_value_set_by_key(dst, key, src_val) != 0) { + return 1; + } + sentry_value_incref(src_val); + } + } + return 0; +} + void sentry__jsonwriter_write_value(sentry_jsonwriter_t *jw, sentry_value_t value) { diff --git a/src/sentry_value.h b/src/sentry_value.h index abe57cc857..7dfa6eb8c2 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -89,6 +89,22 @@ sentry_value_t sentry__value_clone(sentry_value_t value); int sentry__value_append_bounded( sentry_value_t value, sentry_value_t v, size_t max); +/** + * Deep-merges object src into dst. + * + * For each key-value pair in the src object the same key in the dst object + * will be set to the value from src. If both the dst value and the src value + * are objects themselves they are stepped into recursively instead of + * overriding the entire dst object. + * + * If src is null nothing needs to be merged and this is handled gracefully, + * otherwise if dst is any other type than an object or src is neither an + * object nor null an error is returned. + * + * Returns 0 on success. + */ +int sentry__value_merge_objects(sentry_value_t dst, sentry_value_t src); + /** * Parse the given JSON string into a new Value. */ diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 783567f401..46170b1450 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -229,6 +229,62 @@ SENTRY_TEST(value_object) sentry_value_decref(val); } +SENTRY_TEST(value_object_merge) +{ + sentry_value_t dst = sentry_value_new_object(); + sentry_value_set_by_key(dst, "a", sentry_value_new_int32(1)); + sentry_value_set_by_key(dst, "b", sentry_value_new_int32(2)); + + sentry_value_t src = sentry_value_new_object(); + sentry_value_set_by_key(src, "b", sentry_value_new_int32(20)); + sentry_value_set_by_key(src, "c", sentry_value_new_int32(30)); + + int rv = sentry__value_merge_objects(dst, src); + TEST_CHECK_INT_EQUAL(rv, 0); + sentry_value_decref(src); + + sentry_value_t a = sentry_value_get_by_key(dst, "a"); + sentry_value_t b = sentry_value_get_by_key(dst, "b"); + sentry_value_t c = sentry_value_get_by_key(dst, "c"); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(a), 1); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(b), 20); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(c), 30); + + sentry_value_decref(dst); +} + +SENTRY_TEST(value_object_merge_nested) +{ + sentry_value_t dst = sentry_value_new_object(); + sentry_value_set_by_key(dst, "a", sentry_value_new_int32(1)); + sentry_value_t dst_nested = sentry_value_new_object(); + sentry_value_set_by_key(dst_nested, "ba", sentry_value_new_int32(1)); + sentry_value_set_by_key(dst_nested, "bb", sentry_value_new_int32(2)); + sentry_value_set_by_key(dst, "b", dst_nested); + + sentry_value_t src = sentry_value_new_object(); + sentry_value_t src_nested = sentry_value_new_object(); + sentry_value_set_by_key(src_nested, "bb", sentry_value_new_int32(20)); + sentry_value_set_by_key(src_nested, "bc", sentry_value_new_int32(30)); + sentry_value_set_by_key(src, "b", src_nested); + + int rv = sentry__value_merge_objects(dst, src); + TEST_CHECK_INT_EQUAL(rv, 0); + sentry_value_decref(src); + + sentry_value_t a = sentry_value_get_by_key(dst, "a"); + sentry_value_t nested = sentry_value_get_by_key(dst, "b"); + sentry_value_t ba = sentry_value_get_by_key(nested, "ba"); + sentry_value_t bb = sentry_value_get_by_key(nested, "bb"); + sentry_value_t bc = sentry_value_get_by_key(nested, "bc"); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(a), 1); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(ba), 1); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(bb), 20); + TEST_CHECK_INT_EQUAL(sentry_value_as_int32(bc), 30); + + sentry_value_decref(dst); +} + SENTRY_TEST(value_freezing) { sentry_value_t val = sentry_value_new_list(); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 42cbed1c66..2341c33586 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -79,6 +79,8 @@ XX(value_json_surrogates) XX(value_list) XX(value_null) XX(value_object) +XX(value_object_merge) +XX(value_object_merge_nested) XX(value_string) XX(value_unicode) XX(value_wrong_type) From 0e94e49f442b75da4cf4048e25d577cd581a7b09 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:48:54 -0500 Subject: [PATCH 013/207] feat(tracing): Spans now carry pointers to the Transactions they belong to (#656) --- examples/example.c | 4 +- include/sentry.h | 141 ++++++++++++++++++++++++++++---------- src/sentry_core.c | 39 ++++++----- src/sentry_tracing.c | 26 ++++--- src/sentry_tracing.h | 6 +- tests/unit/test_tracing.c | 71 +++---------------- tests/unit/tests.inc | 1 - 7 files changed, 159 insertions(+), 129 deletions(-) diff --git a/examples/example.c b/examples/example.c index 4d76aeede9..ada2e76bfa 100644 --- a/examples/example.c +++ b/examples/example.c @@ -246,8 +246,8 @@ main(int argc, char **argv) grandchild, SENTRY_SPAN_STATUS_ALREADY_EXISTS); } - sentry_span_finish(tx, grandchild); - sentry_span_finish(tx, child); + sentry_span_finish(grandchild); + sentry_span_finish(child); } sentry_transaction_finish(tx); diff --git a/include/sentry.h b/include/sentry.h index 9369e8f41b..0df2f9c4bc 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1276,6 +1276,12 @@ typedef struct sentry_span_s sentry_span_t; * Also see https://develop.sentry.dev/sdk/event-payloads/transaction/#anatomy * for an explanation of `operation`, in addition to other properties and * actions that can be performed on a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction Context + * if it needs to be mutated across threads. Methods operating on the + * Transaction Context will mention what kind of expectations they carry if they + * need to mutate or access the object in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * sentry_transaction_context_new(const char *name, const char *operation); @@ -1283,6 +1289,9 @@ sentry_transaction_context_new(const char *name, const char *operation); /** * Sets the `name` on a Transaction Context, which will be used in the * Transaction constructed off of the context. + * + * The Transaction Context should not be mutated by other functions while + * setting a name on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( sentry_transaction_context_t *tx_cxt, const char *name); @@ -1293,6 +1302,9 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( * * See https://develop.sentry.dev/sdk/performance/span-operations/ for * conventions on `operation`s. + * + * The Transaction Context should not be mutated by other functions while + * setting an operation on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( sentry_transaction_context_t *tx_cxt, const char *operation); @@ -1304,15 +1316,21 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( * When passed any value above 0, the Transaction will bypass all sampling * options and always be sent to sentry. If passed 0, this Transaction and its * child spans will never be sent to sentry. + * + * The Transaction Context should not be mutated by other functions while + * setting `sampled` on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( sentry_transaction_context_t *tx_cxt, int sampled); /** - * Removes the sampled field on a Transaction Context, which will be used in the - * Transaction constructed off of the context. + * Removes the `sampled` field on a Transaction Context, which will be used in + * the Transaction constructed off of the context. * * The Transaction will use the sampling rate as defined in `sentry_options`. + * + * The Transaction Context should not be mutated by other functions while + * removing `sampled`. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( sentry_transaction_context_t *tx_cxt); @@ -1320,21 +1338,29 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( /** * Starts a new Transaction based on the provided context, restored from an * external integration (i.e. a span from a different SDK) or manually - * constructed by a user. Returns a Transaction, which is expected to be - * manually managed by the caller. Manual management involves ensuring that - * `sentry_transaction_finish` is invoked for the Transaction, and that the - * caller manually starts and finishes any child Spans as needed on the - * Transaction. + * constructed by a user. * - * `sentry_transaction_finish` must be called in order for this Transaction to - * be sent to sentry. + * Returns a Transaction, which is expected to be manually managed by the + * caller. Manual management involves ensuring that `sentry_transaction_finish` + * is invoked for the Transaction, and that the caller manually starts and + * finishes any child Spans as needed on the Transaction. + * + * Not invoking `sentry_transaction_finish` with the returned Transaction means + * it will be discarded, and will not be sent to sentry. * * To ensure that any Events or Message Events are associated with this * Transaction while it is active, invoke and pass in the Transaction returned * by this function to `sentry_set_span`. Further documentation on this can be * found in `sentry_set_span`'s docstring. * - * Takes ownership of `transaction_context`. + * Takes ownership of `transaction_context`. A Transaction Context cannot be + * modified or re-used after it is used to start a Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Transaction if it + * needs to be mutated across threads. Methods operating on the Transaction will + * mention what kind of expectations they carry if they need to mutate or access + * the object in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( sentry_transaction_context_t *tx_cxt); @@ -1345,8 +1371,8 @@ SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( * otherwise. * * Always takes ownership of `transaction`, regardless of whether the operation - * was successful or not. If `sentry_set_span` was invoked with `transaction`, - * this will remove the + * was successful or not. A Transaction cannot be modified or re-used after it + * is finished. */ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( sentry_transaction_t *tx); @@ -1360,10 +1386,10 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( * missing traces in sentry, see * https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#orphan-traces-and-broken-subtraces * - * This increases the number of references pointing to the transaction. - * Invoke `sentry_transaction_finish` to remove the Span set by this function as - * well as its reference by passing in the same Transaction as the one passed - * into this function. + * This increases the number of references pointing to the transaction. Invoke + * `sentry_transaction_finish` to remove the Span set by this function as well + * as its reference by passing in the same Transaction as the one passed into + * this function. */ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); @@ -1386,6 +1412,14 @@ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); * finishing the Span means it will be discarded, and will not be sent to * sentry. `sentry_value_null` will be returned if the child Span could not be * created. + * + * This increases the number of references pointing to the Transaction. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( sentry_transaction_t *parent, char *operation, char *description); @@ -1408,6 +1442,12 @@ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( * finishing the Span means it will be discarded, and will not be sent to * sentry. `sentry_value_null` will be returned if the child Span could not be * created. + * + * The returned value is not thread-safe. Users are expected to ensure that + * appropriate locking mechanisms are implemented over the Span if it needs + * to be mutated across threads. Methods operating on the Span will mention what + * kind of expectations they carry if they need to mutate or access the object + * in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( sentry_span_t *parent, char *operation, char *description); @@ -1415,76 +1455,102 @@ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( /** * Finishes a Span. * - * `root_transaction` is either the parent Transaction of the Span, or - * the ancestor Transaction of the Span if the Span is not a direct descendant - * of a Transaction. + * This takes ownership of `span`. A Span cannot be modified or re-used after it + * is finished. * - * This takes ownership of `span`, as child Spans must always occur within the - * total duration of a parent Span and cannot outlive their parent Spans. + * This will mutate the `span`'s containing Transaction, so the containing + * Transaction should also not be mutated by other functions when finishing a + * span. */ -SENTRY_EXPERIMENTAL_API void sentry_span_finish( - sentry_transaction_t *root_transaction, sentry_span_t *span); +SENTRY_EXPERIMENTAL_API void sentry_span_finish(sentry_span_t *span); /** - * Sets a tag on a transaction to the given string value. + * Sets a tag on a Transaction to the given string value. * * Tags longer than 200 bytes will be truncated. + * + * The Transaction should not be mutated by other functions while a tag is being + * set on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( sentry_transaction_t *transaction, const char *tag, const char *value); /** - * Removes a tag from a transaction. + * Removes a tag from a Transaction. + * + * The Transaction should not be mutated by other functions while a tag is being + * removed from it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( sentry_transaction_t *transaction, const char *tag); /** - * Sets the given key in a transaction's "data" section to the given value. + * Sets the given key in a Transaction's "data" section to the given value. + * + * The Transaction should not be mutated by other functions while data is being + * set on it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( sentry_transaction_t *transaction, const char *key, sentry_value_t value); /** - * Removes a key from a transaction's "data" section. + * Removes a key from a Transaction's "data" section. + * + * The Transaction should not be mutated by other functions while data is being + * removed from it. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( sentry_transaction_t *transaction, const char *key); /** - * Sets a tag on a span to the given string value. + * Sets a tag on a Span to the given string value. * * Tags longer than 200 bytes will be truncated. + * + * The Span should not be mutated by other functions while a tag is being set on + * it. */ SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( sentry_span_t *span, const char *tag, const char *value); /** - * Removes a tag from a span. + * Removes a tag from a Span. + * + * The Span should not be mutated by other functions while a tag is being + * removed from it. */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( sentry_span_t *span, const char *tag); /** - * Sets the given key in a span's "data" section to the given value. + * Sets the given key in a Span's "data" section to the given value. + * + * The Span should not be mutated by other functions while data is being set on + * it. */ SENTRY_EXPERIMENTAL_API void sentry_span_set_data( sentry_span_t *span, const char *key, sentry_value_t value); /** - * Removes a key from a span's "data" section. + * Removes a key from a Span's "data" section. + * + * The Span should not be mutated by other functions while data is being removed + * from it. */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( sentry_span_t *span, const char *key); /** - * Sets a transaction's name. + * Sets a Transaction's name. + * + * The Transaction should not be mutated by other functions while setting its + * name. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( sentry_transaction_t *transaction, const char *name); /** - * The status of a span or transaction. + * The status of a Span or Transaction. * * See https://develop.sentry.dev/sdk/event-payloads/span/ for documentation. */ @@ -1542,13 +1608,18 @@ typedef enum { } sentry_span_status_t; /** - * Sets a span's status. + * Sets a Span's status. + * + * The Span should not be mutated by other functions while setting its status. */ SENTRY_EXPERIMENTAL_API void sentry_span_set_status( sentry_span_t *span, sentry_span_status_t status); /** - * Sets a transaction's status. + * Sets a Transaction's status. + * + * The Transaction should not be mutated by other functions while setting its + * status. */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status( sentry_transaction_t *tx, sentry_span_status_t status); diff --git a/src/sentry_core.c b/src/sentry_core.c index 09a038b1a6..17de5a8feb 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -864,17 +864,21 @@ sentry_transaction_start_child( max_spans = options->max_spans; } - return sentry__start_child(max_spans, parent, operation, description); - // TODO: add pointer to transaction on child span + sentry_value_t span + = sentry__value_span_new(max_spans, parent, operation, description); + return sentry__span_new(opaque_parent, span); } sentry_span_t * sentry_span_start_child( sentry_span_t *opaque_parent, char *operation, char *description) { - // TODO: check and validate ref to ancestor transaction on parent span if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { - SENTRY_DEBUG("no parent available to create a child under"); + SENTRY_DEBUG("no parent span available to create a child span under"); + return NULL; + } + if (!opaque_parent->transaction) { + SENTRY_DEBUG("no root transaction to create a child span under"); return NULL; } sentry_value_t parent = opaque_parent->inner; @@ -886,21 +890,25 @@ sentry_span_start_child( max_spans = options->max_spans; } - return sentry__start_child(max_spans, parent, operation, description); - // TODO: add pointer to ancestor transaction on child span + sentry_value_t span + = sentry__value_span_new(max_spans, parent, operation, description); + + return sentry__span_new(opaque_parent->transaction, span); } -// TODO: don't accept the root transaction as a param, the span should have a -// ref to it already in itself void -sentry_span_finish( - sentry_transaction_t *opaque_root_transaction, sentry_span_t *opaque_span) +sentry_span_finish(sentry_span_t *opaque_span) { - if (!opaque_root_transaction || !opaque_span - || sentry_value_is_null(opaque_root_transaction->inner) - || sentry_value_is_null(opaque_span->inner)) { + if (!opaque_span || sentry_value_is_null(opaque_span->inner)) { + SENTRY_DEBUG("no span to finish"); + goto fail; + } + + sentry_transaction_t *opaque_root_transaction = opaque_span->transaction; + if (!opaque_root_transaction + || sentry_value_is_null(opaque_root_transaction->inner)) { SENTRY_DEBUG( - "missing root transaction or span to finish, aborting span finish"); + "no root transaction to finish span on, aborting span finish"); goto fail; } @@ -921,9 +929,6 @@ sentry_span_finish( goto fail; } - // tough luck if this span actually doesn't belong on the specified - // transaction, i.e. its trace id doesn't match the root transaction's trace - // id sentry_value_set_by_key(span, "timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 92d16620bb..7eaac5f437 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -161,9 +161,9 @@ sentry__transaction_decref(sentry_transaction_t *tx) } sentry_span_t * -sentry__span_new(sentry_value_t inner) +sentry__span_new(sentry_transaction_t *tx, sentry_value_t inner) { - if (sentry_value_is_null(inner)) { + if (!tx || sentry_value_is_null(inner)) { return NULL; } @@ -175,11 +175,14 @@ sentry__span_new(sentry_value_t inner) span->inner = inner; + sentry__transaction_incref(tx); + span->transaction = tx; + return span; } -sentry_span_t * -sentry__start_child( +sentry_value_t +sentry__value_span_new( size_t max_spans, sentry_value_t parent, char *operation, char *description) { if (!sentry_value_is_null(sentry_value_get_by_key(parent, "timestamp"))) { @@ -195,9 +198,9 @@ sentry__start_child( goto fail; } sentry_value_t spans = sentry_value_get_by_key(parent, "spans"); - // This only checks that the number of _completed_ spans matches the number - // of max spans. This means that the number of in-flight spans can exceed - // the max number of spans. + // This only checks that the number of _completed_ spans matches the + // number of max spans. This means that the number of in-flight spans + // can exceed the max number of spans. if (sentry_value_get_length(spans) >= max_spans) { SENTRY_DEBUG("reached maximum number of spans for transaction, not " "creating span"); @@ -212,14 +215,14 @@ sentry__start_child( sentry__msec_time_to_iso8601(sentry__msec_time()))); sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1)); - return sentry__span_new(child); + return child; fail: - return NULL; + return sentry_value_new_null(); } // TODO: for now, don't allow multiple references to spans. this should be -// revisited when sentry_transaction_t stores a list of sentry_span_t's instead -// of a list of sentry_value_t's. +// revisited when sentry_transaction_t stores a list of sentry_span_t's +// instead of a list of sentry_value_t's. void sentry__span_free(sentry_span_t *span) { @@ -227,6 +230,7 @@ sentry__span_free(sentry_span_t *span) return; } sentry_value_decref(span->inner); + sentry__transaction_decref(span->transaction); sentry_free(span); } diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index 47b621f76f..c92fd75010 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -9,6 +9,8 @@ */ typedef struct sentry_span_s { sentry_value_t inner; + // The transaction the span is contained in. + sentry_transaction_t *transaction; } sentry_span_t; /** @@ -31,8 +33,10 @@ sentry_transaction_t *sentry__transaction_new(sentry_value_t inner); void sentry__transaction_incref(sentry_transaction_t *tx); void sentry__transaction_decref(sentry_transaction_t *tx); -sentry_span_t *sentry__start_child(size_t max_spans, sentry_value_t parent, +sentry_value_t sentry__value_span_new(size_t max_spans, sentry_value_t parent, char *operation, char *description); +sentry_span_t *sentry__span_new( + sentry_transaction_t *parent_tx, sentry_value_t inner); void sentry__span_free(sentry_span_t *span); /** diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 85cbdbd1b8..f370897859 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -372,7 +372,7 @@ SENTRY_TEST(basic_spans) // Sanity check that child isn't finished yet TEST_CHECK(IS_NULL(child, "timestamp")); // Now finishing - sentry_span_finish(opaque_tx, opaque_child); + sentry_span_finish(opaque_child); TEST_CHECK(!IS_NULL(tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); @@ -422,7 +422,7 @@ SENTRY_TEST(spans_on_scope) // Sanity check that child isn't finished yet TEST_CHECK(IS_NULL(child, "timestamp")); - sentry_span_finish(opaque_tx, opaque_child); + sentry_span_finish(opaque_child); scope_tx = sentry__scope_get_span(); TEST_CHECK(!IS_NULL(scope_tx, "spans")); @@ -469,7 +469,7 @@ SENTRY_TEST(child_spans) // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_span_finish(opaque_tx, opaque_grandchild); + sentry_span_finish(opaque_grandchild); // Make sure everything on the transaction looks good, check grandchild const char *trace_id @@ -489,7 +489,7 @@ SENTRY_TEST(child_spans) // Should be finished TEST_CHECK(!IS_NULL(stored_grandchild, "timestamp")); - sentry_span_finish(opaque_tx, opaque_child); + sentry_span_finish(opaque_child); spans = sentry_value_get_by_key(tx, "spans"); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); @@ -526,7 +526,7 @@ SENTRY_TEST(overflow_spans) // Shouldn't be added to spans yet TEST_CHECK(IS_NULL(tx, "spans")); - sentry_span_finish(opaque_tx, opaque_child); + sentry_span_finish(opaque_child); TEST_CHECK(!IS_NULL(tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(tx, "spans"); @@ -535,7 +535,7 @@ SENTRY_TEST(overflow_spans) sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); - sentry_span_finish(opaque_tx, opaque_drop_on_finish_child); + sentry_span_finish(opaque_drop_on_finish_child); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); sentry_span_t *opaque_drop_on_start_child @@ -548,60 +548,6 @@ SENTRY_TEST(overflow_spans) sentry_close(); } -SENTRY_TEST(wrong_spans_on_transaction_is_ok) -{ - sentry_options_t *options = sentry_options_new(); - sentry_options_set_traces_sample_rate(options, 1.0); - sentry_options_set_max_spans(options, 5); - sentry_init(options); - - sentry_transaction_context_t *opaque_tx_cxt - = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); - sentry_value_t tx = opaque_tx->inner; - - sentry_span_t *opaque_child - = sentry_transaction_start_child(opaque_tx, "honk", "goose"); - sentry_value_t child = opaque_child->inner; - const char *child_span_id - = sentry_value_as_string(sentry_value_get_by_key(child, "span_id")); - - sentry_span_t *opaque_lingering_child - = sentry_transaction_start_child(opaque_tx, "beep", "car"); - - sentry_transaction_context_t *tx_cxt_other - = sentry_transaction_context_new("whoa!", NULL); - sentry_transaction_t *opaque_tx_other - = sentry_transaction_start(tx_cxt_other); - sentry_value_t tx_other = opaque_tx_other->inner; - - sentry_span_finish(opaque_tx_other, opaque_child); - - // doesn't care if the child was finished on the wrong transaction - TEST_CHECK(IS_NULL(tx, "spans")); - TEST_CHECK(!IS_NULL(tx_other, "spans")); - - sentry_value_t spans = sentry_value_get_by_key(tx_other, "spans"); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); - - sentry_value_t stored_child = sentry_value_get_by_index(spans, 0); - CHECK_STRING_PROPERTY(stored_child, "span_id", child_span_id); - - sentry_uuid_t event_id = sentry_transaction_finish(opaque_tx); - TEST_CHECK(!sentry_uuid_is_nil(&event_id)); - - // doesn't care if the child belonged to a different, already finished - // transaction - sentry_span_finish(opaque_tx_other, opaque_lingering_child); - TEST_CHECK(!IS_NULL(tx_other, "spans")); - spans = sentry_value_get_by_key(tx_other, "spans"); - TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 2); - - sentry__transaction_decref(opaque_tx_other); - - sentry_close(); -} - static void check_spans(sentry_envelope_t *envelope, void *data) { @@ -647,7 +593,7 @@ SENTRY_TEST(drop_unfinished_spans) = sentry_span_start_child(opaque_child, "beep", "car"); sentry_value_t grandchild = opaque_grandchild->inner; TEST_CHECK(!sentry_value_is_null(grandchild)); - sentry_span_finish(opaque_tx, opaque_grandchild); + sentry_span_finish(opaque_grandchild); // spans are only added to transactions upon completion TEST_CHECK_INT_EQUAL( @@ -656,7 +602,8 @@ SENTRY_TEST(drop_unfinished_spans) sentry_uuid_t event_id = sentry_transaction_finish(opaque_tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); - sentry__span_free(opaque_child); + // check that nothing explodes if you do finish the lingering child + sentry_span_finish(opaque_child); sentry_close(); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 2341c33586..3460600ae7 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -84,4 +84,3 @@ XX(value_object_merge_nested) XX(value_string) XX(value_unicode) XX(value_wrong_type) -XX(wrong_spans_on_transaction_is_ok) From e39edcbb8152d95bddd6f4034d4dd39ecdf09ebd Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 18 Jan 2022 10:38:29 +0100 Subject: [PATCH 014/207] feat: Implement distributed trace propagation (NATIVE-304) (#657) This implements the `iter_headers` and something analogous to the `continue_from_headers` APIs. --- include/sentry.h | 35 ++++++++++++++ src/sentry_tracing.c | 75 ++++++++++++++++++++++++++++++ tests/unit/test_tracing.c | 98 ++++++++++++++++++++++++++++++++++++++- tests/unit/tests.inc | 1 + 4 files changed, 208 insertions(+), 1 deletion(-) diff --git a/include/sentry.h b/include/sentry.h index 0df2f9c4bc..dd68c755b8 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1335,6 +1335,17 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_sampled( SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( sentry_transaction_context_t *tx_cxt); +/** + * Update the Transaction Context with the given HTTP header key/value pair. + * + * This is used to propagate distributed tracing metadata from upstream + * services. Therefore, the headers of incoming requests should be fed into this + * function so that sentry is able to continue a trace that was started by an + * upstream service. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( + sentry_transaction_context_t *tx_cxt, const char *key, const char *value); + /** * Starts a new Transaction based on the provided context, restored from an * external integration (i.e. a span from a different SDK) or manually @@ -1624,6 +1635,30 @@ SENTRY_EXPERIMENTAL_API void sentry_span_set_status( SENTRY_EXPERIMENTAL_API void sentry_transaction_set_status( sentry_transaction_t *tx, sentry_span_status_t status); +/** + * Type of the `iter_headers` callback. + * + * The callback is being called with HTTP header key/value pairs. + * These headers can be attached to outgoing HTTP requests to propagate + * distributed tracing metadata to downstream services. + * + */ +typedef void (*sentry_iter_headers_function_t)( + const char *key, const char *value, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given span. + */ +SENTRY_EXPERIMENTAL_API void sentry_span_iter_headers(sentry_span_t *span, + sentry_iter_headers_function_t callback, void *userdata); + +/** + * Iterates the distributed tracing HTTP headers for the given transaction. + */ +SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( + sentry_transaction_t *tx, sentry_iter_headers_function_t callback, + void *userdata); + #endif #ifdef __cplusplus diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 7eaac5f437..a3354790fc 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -121,6 +121,47 @@ sentry_transaction_context_remove_sampled(sentry_transaction_context_t *tx_cxt) sentry_value_remove_by_key(tx_cxt->inner, "sampled"); } +void +sentry_transaction_context_update_from_header( + sentry_transaction_context_t *tx_cxt, const char *key, const char *value) +{ + if (!sentry__string_eq(key, "sentry-trace")) { + return; + } + + // https://develop.sentry.dev/sdk/performance/#header-sentry-trace + // sentry-trace = traceid-spanid(-sampled)? + const char *trace_id_start = value; + const char *trace_id_end = strchr(trace_id_start, '-'); + if (!trace_id_end) { + return; + } + + sentry_value_t inner = tx_cxt->inner; + + char *s + = sentry__string_clonen(trace_id_start, trace_id_end - trace_id_start); + sentry_value_t trace_id = sentry__value_new_string_owned(s); + sentry_value_set_by_key(inner, "trace_id", trace_id); + + const char *span_id_start = trace_id_end + 1; + const char *span_id_end = strchr(span_id_start, '-'); + if (!span_id_end) { + // no sampled flag + sentry_value_t parent_span_id = sentry_value_new_string(span_id_start); + sentry_value_set_by_key(inner, "parent_span_id", parent_span_id); + return; + } + // else: we have a sampled flag + + s = sentry__string_clonen(span_id_start, span_id_end - span_id_start); + sentry_value_t parent_span_id = sentry__value_new_string_owned(s); + sentry_value_set_by_key(inner, "parent_span_id", parent_span_id); + + bool sampled = *(span_id_end + 1) == '1'; + sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled)); +} + sentry_transaction_t * sentry__transaction_new(sentry_value_t inner) { @@ -435,3 +476,37 @@ sentry_transaction_set_status( { set_status(tx->inner, status); } + +static void +sentry__span_iter_headers(sentry_value_t span, + sentry_iter_headers_function_t callback, void *userdata) +{ + sentry_value_t trace_id = sentry_value_get_by_key(span, "trace_id"); + sentry_value_t span_id = sentry_value_get_by_key(span, "span_id"); + sentry_value_t sampled = sentry_value_get_by_key(span, "sampled"); + + if (sentry_value_is_null(trace_id) || sentry_value_is_null(span_id)) { + return; + } + + char buf[64]; + snprintf(buf, sizeof(buf), "%s-%s-%s", sentry_value_as_string(trace_id), + sentry_value_as_string(span_id), + sentry_value_is_true(sampled) ? "1" : "0"); + + callback("sentry-trace", buf, userdata); +} + +void +sentry_span_iter_headers(sentry_span_t *span, + sentry_iter_headers_function_t callback, void *userdata) +{ + sentry__span_iter_headers(span->inner, callback, userdata); +} + +void +sentry_transaction_iter_headers(sentry_transaction_t *tx, + sentry_iter_headers_function_t callback, void *userdata) +{ + sentry__span_iter_headers(tx->inner, callback, userdata); +} diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index f370897859..2a75df1e98 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -1,5 +1,7 @@ -#include "sentry_scope.h" #include "sentry_testsupport.h" + +#include "sentry_scope.h" +#include "sentry_string.h" #include "sentry_tracing.h" #include "sentry_uuid.h" @@ -610,5 +612,99 @@ SENTRY_TEST(drop_unfinished_spans) TEST_CHECK_INT_EQUAL(called_transport, 1); } +static void +forward_headers_to(const char *key, const char *value, void *userdata) +{ + sentry_transaction_context_t *tx_ctx + = (sentry_transaction_context_t *)userdata; + + sentry_transaction_context_update_from_header(tx_ctx, key, value); +} + +SENTRY_TEST(distributed_headers) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 2); + sentry_init(options); + + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_t *tx = sentry_transaction_start(tx_ctx); + + const char *trace_id = sentry_value_as_string( + sentry_value_get_by_key(tx->inner, "trace_id")); + TEST_CHECK(!sentry__string_eq(trace_id, "")); + + const char *span_id + = sentry_value_as_string(sentry_value_get_by_key(tx->inner, "span_id")); + TEST_CHECK(!sentry__string_eq(span_id, "")); + + // check transaction + tx_ctx = sentry_transaction_context_new("distributed!", NULL); + sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); + sentry_transaction_t *dist_tx = sentry_transaction_start(tx_ctx); + + const char *dist_trace_id = sentry_value_as_string( + sentry_value_get_by_key(dist_tx->inner, "trace_id")); + TEST_CHECK_STRING_EQUAL(dist_trace_id, trace_id); + + const char *parent_span_id = sentry_value_as_string( + sentry_value_get_by_key(dist_tx->inner, "parent_span_id")); + TEST_CHECK_STRING_EQUAL(parent_span_id, span_id); + + sentry__transaction_decref(dist_tx); + + // check span + sentry_span_t *child = sentry_transaction_start_child(tx, "honk", "goose"); + + span_id = sentry_value_as_string( + sentry_value_get_by_key(child->inner, "span_id")); + TEST_CHECK(!sentry__string_eq(span_id, "")); + + tx_ctx = sentry_transaction_context_new("distributed!", NULL); + sentry_span_iter_headers(child, forward_headers_to, (void *)tx_ctx); + dist_tx = sentry_transaction_start(tx_ctx); + + dist_trace_id = sentry_value_as_string( + sentry_value_get_by_key(dist_tx->inner, "trace_id")); + TEST_CHECK_STRING_EQUAL(dist_trace_id, trace_id); + + parent_span_id = sentry_value_as_string( + sentry_value_get_by_key(dist_tx->inner, "parent_span_id")); + TEST_CHECK_STRING_EQUAL(parent_span_id, span_id); + + TEST_CHECK(sentry_value_is_true( + sentry_value_get_by_key(dist_tx->inner, "sampled"))); + + sentry__transaction_decref(dist_tx); + sentry__span_free(child); + sentry__transaction_decref(tx); + + // check sampled flag + tx_ctx = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_context_set_sampled(tx_ctx, 0); + tx = sentry_transaction_start(tx_ctx); + + tx_ctx = sentry_transaction_context_new("distributed!", NULL); + sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); + dist_tx = sentry_transaction_start(tx_ctx); + + TEST_CHECK(!sentry_value_is_true( + sentry_value_get_by_key(dist_tx->inner, "sampled"))); + + sentry__transaction_decref(dist_tx); + + // TODO: Check the sampled flag on a child span as well, but I think we + // don't create one if the transaction is not sampled? Well, here is the + // reason why we should! + + sentry__transaction_decref(tx); + + sentry_close(); +} + #undef IS_NULL #undef CHECK_STRING_PROPERTY diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 3460600ae7..7114022d58 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -15,6 +15,7 @@ XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) XX(custom_logger) +XX(distributed_headers) XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) From dc1e86469d8b0b8b578ebd0e0fb7715d4b25d818 Mon Sep 17 00:00:00 2001 From: Mikhail Paulyshka Date: Tue, 18 Jan 2022 15:19:44 +0300 Subject: [PATCH 015/207] fix: Correct CMake `SENTRY_LIBRARY_TYPE` variable check (#662) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3bcc30d83..08522006ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,7 +384,7 @@ if(SENTRY_LINK_PTHREAD) endif() # apply platform libraries to sentry library -if(SENTRY_LIBRARY_TYPE STREQUAL "static") +if(SENTRY_LIBRARY_TYPE STREQUAL "STATIC") target_link_libraries(sentry PUBLIC ${_SENTRY_PLATFORM_LIBS}) else() target_link_libraries(sentry PRIVATE ${_SENTRY_PLATFORM_LIBS}) From 497636c551ee1ccb0a002d8ddde275a2277cec43 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 18 Jan 2022 15:15:32 +0100 Subject: [PATCH 016/207] fix: Read Windows Version from Registry (#623) We were previously relying on the `kernel32.dll` version, as does Crashpad. However that version can lag behind OS releases, which can manifest itself as failing integration tests, as the version does not match the one returned from python `platform.version()`. This tries to use `ntoskrnl.exe` as the kernel version, but falls back to `kernel32.dll` as before in case that does not work. --- src/sentry_os.c | 81 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/src/sentry_os.c b/src/sentry_os.c index 977261eba2..fde7066845 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -1,9 +1,28 @@ #include "sentry_os.h" #include "sentry_string.h" +#include "sentry_utils.h" #ifdef SENTRY_PLATFORM_WINDOWS # include +# define CURRENT_VERSION "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" + +void * +sentry__try_file_version(LPCWSTR filename) +{ + + DWORD size = GetFileVersionInfoSizeW(filename, NULL); + if (!size) { + return NULL; + } + + void *ffibuf = sentry_malloc(size); + if (!GetFileVersionInfoW(filename, 0, size, ffibuf)) { + sentry_free(ffibuf); + return NULL; + } + return ffibuf; +} sentry_value_t sentry__get_os_context(void) @@ -15,15 +34,11 @@ sentry__get_os_context(void) sentry_value_set_by_key(os, "name", sentry_value_new_string("Windows")); - void *ffibuf = NULL; - - DWORD size = GetFileVersionInfoSizeW(L"kernel32.dll", NULL); - if (!size) { - goto fail; + void *ffibuf = sentry__try_file_version(L"ntoskrnl.exe"); + if (!ffibuf) { + ffibuf = sentry__try_file_version(L"kernel32.dll"); } - - ffibuf = sentry_malloc(size); - if (!GetFileVersionInfoW(L"kernel32.dll", 0, size, ffibuf)) { + if (!ffibuf) { goto fail; } @@ -34,17 +49,55 @@ sentry__get_os_context(void) } ffi->dwFileFlags &= ffi->dwFileFlagsMask; + uint32_t major_version = ffi->dwFileVersionMS >> 16; + uint32_t minor_version = ffi->dwFileVersionMS & 0xffff; + uint32_t build_version = ffi->dwFileVersionLS >> 16; + uint32_t ubr = ffi->dwFileVersionLS & 0xffff; + char buf[32]; - snprintf(buf, sizeof(buf), "%u.%u.%u", ffi->dwFileVersionMS >> 16, - ffi->dwFileVersionMS & 0xffff, ffi->dwFileVersionLS >> 16); + snprintf(buf, sizeof(buf), "%u.%u.%u.%lu", major_version, minor_version, + build_version, ubr); + sentry_value_set_by_key(os, "kernel_version", sentry_value_new_string(buf)); - sentry_value_set_by_key(os, "version", sentry_value_new_string(buf)); + sentry_free(ffibuf); - snprintf(buf, sizeof(buf), "%lu", ffi->dwFileVersionLS & 0xffff); + // The `CurrentMajorVersionNumber`, `CurrentMinorVersionNumber` and `UBR` + // are DWORD, while `CurrentBuild` is a SZ (text). - sentry_value_set_by_key(os, "build", sentry_value_new_string(buf)); + uint32_t reg_version = 0; + DWORD buf_size = sizeof(uint32_t); + if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, + "CurrentMajorVersionNumber", RRF_RT_REG_DWORD, NULL, ®_version, + &buf_size) + == ERROR_SUCCESS) { + major_version = reg_version; + } + buf_size = sizeof(uint32_t); + if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, + "CurrentMinorVersionNumber", RRF_RT_REG_DWORD, NULL, ®_version, + &buf_size) + == ERROR_SUCCESS) { + minor_version = reg_version; + } + buf_size = sizeof(buf); + if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild", + RRF_RT_REG_SZ, NULL, buf, &buf_size) + == ERROR_SUCCESS) { + build_version = (uint32_t)sentry__strtod_c(buf, NULL); + } + buf_size = sizeof(uint32_t); + if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "UBR", + RRF_RT_REG_DWORD, NULL, ®_version, &buf_size) + == ERROR_SUCCESS) { + ubr = reg_version; + } - sentry_free(ffibuf); + snprintf(buf, sizeof(buf), "%u.%u.%u", major_version, minor_version, + build_version); + sentry_value_set_by_key(os, "version", sentry_value_new_string(buf)); + + snprintf(buf, sizeof(buf), "%lu", ubr); + sentry_value_set_by_key(os, "build", sentry_value_new_string(buf)); sentry_value_freeze(os); return os; From c4d88817634f912033ad775c86c6e0cf6f93d592 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Wed, 19 Jan 2022 09:50:12 +0100 Subject: [PATCH 017/207] fix: Apply default rate limit (#660) The transport will now fall back to a default 60 second rate limit when it receives a 429 response without additional headers. --- src/sentry_ratelimiter.c | 8 ++++++++ src/sentry_ratelimiter.h | 6 ++++++ src/transports/sentry_transport_curl.c | 5 +++++ src/transports/sentry_transport_winhttp.c | 10 ++++++++++ tests/test_integration_ratelimits.py | 12 ++++++++++++ 5 files changed, 41 insertions(+) diff --git a/src/sentry_ratelimiter.c b/src/sentry_ratelimiter.c index e1dd8c855c..67cddb3e52 100644 --- a/src/sentry_ratelimiter.c +++ b/src/sentry_ratelimiter.c @@ -82,6 +82,14 @@ sentry__rate_limiter_update_from_http_retry_after( return true; } +bool +sentry__rate_limiter_update_from_429(sentry_rate_limiter_t *rl) +{ + rl->disabled_until[SENTRY_RL_CATEGORY_ANY] + = sentry__monotonic_time() + 60 * 1000; + return true; +} + bool sentry__rate_limiter_is_disabled(const sentry_rate_limiter_t *rl, int category) { diff --git a/src/sentry_ratelimiter.h b/src/sentry_ratelimiter.h index 58a666121d..b7d4c55759 100644 --- a/src/sentry_ratelimiter.h +++ b/src/sentry_ratelimiter.h @@ -34,6 +34,12 @@ bool sentry__rate_limiter_update_from_header( bool sentry__rate_limiter_update_from_http_retry_after( sentry_rate_limiter_t *rl, const char *retry_after); +/** + * This will update the rate limiters internal state based on receiving a 429 + * status code. + */ +bool sentry__rate_limiter_update_from_429(sentry_rate_limiter_t *rl); + /** * This will return `true` if the specified `category` is currently rate * limited. diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 7ef2806aee..19ca7c9952 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -185,12 +185,17 @@ sentry__curl_send_task(void *_envelope, void *_state) CURLcode rv = curl_easy_perform(curl); if (rv == CURLE_OK) { + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (info.x_sentry_rate_limits) { sentry__rate_limiter_update_from_header( state->ratelimiter, info.x_sentry_rate_limits); } else if (info.retry_after) { sentry__rate_limiter_update_from_http_retry_after( state->ratelimiter, info.retry_after); + } else if (response_code == 429) { + sentry__rate_limiter_update_from_429(state->ratelimiter); } } else { SENTRY_WARNF( diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index afb6e7c524..5bfdf8171d 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -234,6 +234,10 @@ sentry__winhttp_send_task(void *_envelope, void *_state) // lets just assume we won’t have headers > 2k wchar_t buf[2048]; DWORD buf_size = sizeof(buf); + + DWORD status_code = 0; + DWORD status_code_size = sizeof(status_code); + if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, L"x-sentry-rate-limits", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { @@ -251,6 +255,12 @@ sentry__winhttp_send_task(void *_envelope, void *_state) state->ratelimiter, h); sentry_free(h); } + } else if (WinHttpQueryHeaders(state->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &status_code, + &status_code_size, WINHTTP_NO_HEADER_INDEX) + && status_code == 429) { + sentry__rate_limiter_update_from_429(state->ratelimiter); } } else { SENTRY_DEBUGF( diff --git a/tests/test_integration_ratelimits.py b/tests/test_integration_ratelimits.py index cacb29586d..713a9b6504 100644 --- a/tests/test_integration_ratelimits.py +++ b/tests/test_integration_ratelimits.py @@ -42,3 +42,15 @@ def test_rate_limits(cmake, httpserver): ) run(tmp_path, "sentry_example", ["log", "capture-multiple"], check=True, env=env) assert len(httpserver.log) == 2 + + +def test_only_429(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data( + "OK", 429 + ) + run(tmp_path, "sentry_example", ["log", "capture-multiple"], check=True, env=env) + assert len(httpserver.log) == 1 From 02e0c5f1aad372953e4dd020bea57bfbc9337227 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Thu, 20 Jan 2022 14:03:42 +0100 Subject: [PATCH 018/207] ref: Make API forward-compatible to Sampling Context (NATIVE-457) (#663) --- examples/example.c | 3 ++- include/sentry.h | 11 ++++++--- src/sentry_core.c | 7 +++++- tests/unit/test_tracing.c | 52 ++++++++++++++++++++++++--------------- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/examples/example.c b/examples/example.c index ada2e76bfa..6ee4c2e615 100644 --- a/examples/example.c +++ b/examples/example.c @@ -227,7 +227,8 @@ main(int argc, char **argv) if (has_arg(argc, argv, "unsample-tx")) { sentry_transaction_context_set_sampled(tx_ctx, 0); } - sentry_transaction_t *tx = sentry_transaction_start(tx_ctx); + sentry_transaction_t *tx + = sentry_start_transaction(tx_ctx, sentry_value_new_null()); if (has_arg(argc, argv, "error-status")) { sentry_transaction_set_status( diff --git a/include/sentry.h b/include/sentry.h index dd68c755b8..05c6ca7866 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1265,7 +1265,7 @@ typedef struct sentry_span_s sentry_span_t; /** * Constructs a new Transaction Context. The returned value needs to be passed - * into `sentry_transaction_start` in order to be recorded and sent to sentry. + * into `sentry_start_transaction` in order to be recorded and sent to sentry. * * See * https://docs.sentry.io/platforms/native/enriching-events/transaction-name/ @@ -1351,6 +1351,11 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( * external integration (i.e. a span from a different SDK) or manually * constructed by a user. * + * The second parameter is a custom Sampling Context to be used with a Traces + * Sampler to make a more informed sampling decision. The SDK does not currently + * support a custom Traces Sampler and this parameter is ignored for the time + * being but needs to be provided. + * * Returns a Transaction, which is expected to be manually managed by the * caller. Manual management involves ensuring that `sentry_transaction_finish` * is invoked for the Transaction, and that the caller manually starts and @@ -1373,8 +1378,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( * mention what kind of expectations they carry if they need to mutate or access * the object in a thread-safe way. */ -SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( - sentry_transaction_context_t *tx_cxt); +SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_start_transaction( + sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx); /** * Finishes and sends a Transaction to sentry. The event ID of the Transaction diff --git a/src/sentry_core.c b/src/sentry_core.c index 17de5a8feb..4c9b3b9bdc 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -731,8 +731,13 @@ sentry_set_level(sentry_level_t level) #ifdef SENTRY_PERFORMANCE_MONITORING sentry_transaction_t * -sentry_transaction_start(sentry_transaction_context_t *opaque_tx_cxt) +sentry_start_transaction( + sentry_transaction_context_t *opaque_tx_cxt, sentry_value_t sampling_ctx) { + // Just free this immediately until we implement proper support for + // traces_sampler. + sentry_value_decref(sampling_ctx); + sentry_value_t tx_cxt = opaque_tx_cxt->inner; // If the parent span ID is some empty-ish value, just remove it diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 2a75df1e98..d5e8b5911c 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -137,12 +137,13 @@ SENTRY_TEST(transaction_name_backfill_on_finish) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new(NULL, NULL); - sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx + = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); tx_cxt = sentry_transaction_context_new("", ""); - tx = sentry_transaction_start(tx_cxt); + tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -187,7 +188,8 @@ SENTRY_TEST(basic_function_transport_transaction) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new( "How could you", "Don't capture this."); - sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx + = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); // TODO: `sentry_capture_event` acts as if the event was sent if user // consent was not given @@ -195,14 +197,14 @@ SENTRY_TEST(basic_function_transport_transaction) sentry_user_consent_give(); tx_cxt = sentry_transaction_context_new("honk", "beep"); - tx = sentry_transaction_start(tx_cxt); + tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); sentry_user_consent_revoke(); tx_cxt = sentry_transaction_context_new( "How could you again", "Don't capture this either."); - tx = sentry_transaction_start(tx_cxt); + tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); // TODO: `sentry_capture_event` acts as if the event was sent if user // consent was not given @@ -232,7 +234,8 @@ SENTRY_TEST(transport_sampling_transactions) for (int i = 0; i < 100; i++) { sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("honk", "beep"); - sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx + = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); if (!sentry_uuid_is_nil(&event_id)) { sent_transactions += 1; @@ -275,7 +278,8 @@ SENTRY_TEST(transactions_skip_before_send) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("honk", "beep"); - sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx + = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -310,7 +314,8 @@ SENTRY_TEST(multiple_transactions) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *tx = sentry_transaction_start(tx_cxt); + sentry_transaction_t *tx + = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); sentry_value_t scope_tx = sentry__scope_get_span(); @@ -324,11 +329,11 @@ SENTRY_TEST(multiple_transactions) // Set transaction on scope twice, back-to-back without finishing the first // one tx_cxt = sentry_transaction_context_new("whoa!", NULL); - tx = sentry_transaction_start(tx_cxt); + tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); sentry__transaction_decref(tx); tx_cxt = sentry_transaction_context_new("wowee!", NULL); - tx = sentry_transaction_start(tx_cxt); + tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); scope_tx = sentry__scope_get_span(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wowee!"); @@ -354,7 +359,8 @@ SENTRY_TEST(basic_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_transaction_t *opaque_tx + = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -403,7 +409,8 @@ SENTRY_TEST(spans_on_scope) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_transaction_t *opaque_tx + = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); sentry_set_span(opaque_tx); sentry_span_t *opaque_child @@ -454,7 +461,8 @@ SENTRY_TEST(child_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_transaction_t *opaque_tx + = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -509,7 +517,8 @@ SENTRY_TEST(overflow_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_transaction_t *opaque_tx + = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -583,7 +592,8 @@ SENTRY_TEST(drop_unfinished_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt); + sentry_transaction_t *opaque_tx + = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -632,7 +642,8 @@ SENTRY_TEST(distributed_headers) sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new("wow!", NULL); - sentry_transaction_t *tx = sentry_transaction_start(tx_ctx); + sentry_transaction_t *tx + = sentry_start_transaction(tx_ctx, sentry_value_new_null()); const char *trace_id = sentry_value_as_string( sentry_value_get_by_key(tx->inner, "trace_id")); @@ -645,7 +656,8 @@ SENTRY_TEST(distributed_headers) // check transaction tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); - sentry_transaction_t *dist_tx = sentry_transaction_start(tx_ctx); + sentry_transaction_t *dist_tx + = sentry_start_transaction(tx_ctx, sentry_value_new_null()); const char *dist_trace_id = sentry_value_as_string( sentry_value_get_by_key(dist_tx->inner, "trace_id")); @@ -666,7 +678,7 @@ SENTRY_TEST(distributed_headers) tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_span_iter_headers(child, forward_headers_to, (void *)tx_ctx); - dist_tx = sentry_transaction_start(tx_ctx); + dist_tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); dist_trace_id = sentry_value_as_string( sentry_value_get_by_key(dist_tx->inner, "trace_id")); @@ -686,11 +698,11 @@ SENTRY_TEST(distributed_headers) // check sampled flag tx_ctx = sentry_transaction_context_new("wow!", NULL); sentry_transaction_context_set_sampled(tx_ctx, 0); - tx = sentry_transaction_start(tx_ctx); + tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); - dist_tx = sentry_transaction_start(tx_ctx); + dist_tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); TEST_CHECK(!sentry_value_is_true( sentry_value_get_by_key(dist_tx->inner, "sampled"))); From 8cb33806deda6a5697a157d5caec0e6d7365031f Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Thu, 20 Jan 2022 16:17:57 +0100 Subject: [PATCH 019/207] fix: Match sentry-trace header case-insensitively (#665) --- src/sentry_tracing.c | 8 ++++++-- tests/unit/test_tracing.c | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index a3354790fc..038912526e 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -125,8 +125,12 @@ void sentry_transaction_context_update_from_header( sentry_transaction_context_t *tx_cxt, const char *key, const char *value) { - if (!sentry__string_eq(key, "sentry-trace")) { - return; + // do case-insensitive header key comparison + const char sentry_trace[] = "sentry-trace"; + for (size_t i = 0; i < sizeof(sentry_trace); i++) { + if (tolower(key[i]) != sentry_trace[i]) { + return; + } } // https://develop.sentry.dev/sdk/performance/#header-sentry-trace diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index d5e8b5911c..9beab0afe2 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -640,14 +640,29 @@ SENTRY_TEST(distributed_headers) sentry_options_set_max_spans(options, 2); sentry_init(options); + const char *trace_header + = "2674eb52d5874b13b560236d6c79ce8a-a0f9fdf04f1a63df-1"; + const char *not_expected_header + = "00000000000000000000000000000000-0000000000000000-1"; + const char *expected_trace_id = "2674eb52d5874b13b560236d6c79ce8a"; + sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new("wow!", NULL); + + // check case insensitive headers, and bogus header names + sentry_transaction_context_update_from_header( + tx_ctx, "SeNtry-TrAcE", trace_header); + sentry_transaction_context_update_from_header( + tx_ctx, "nop", not_expected_header); + sentry_transaction_context_update_from_header( + tx_ctx, "sentry-trace-but-a-lot-longer", not_expected_header); + sentry_transaction_t *tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); const char *trace_id = sentry_value_as_string( sentry_value_get_by_key(tx->inner, "trace_id")); - TEST_CHECK(!sentry__string_eq(trace_id, "")); + TEST_CHECK_STRING_EQUAL(trace_id, expected_trace_id); const char *span_id = sentry_value_as_string(sentry_value_get_by_key(tx->inner, "span_id")); From 0fdac103d4f8f02be334d469195b79a757882831 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Fri, 21 Jan 2022 13:05:49 +0100 Subject: [PATCH 020/207] ref(tracing): Revert name change of sentry_transaction_start (#666) --- examples/example.c | 2 +- include/sentry.h | 6 +++--- src/sentry_core.c | 2 +- tests/unit/test_tracing.c | 40 +++++++++++++++++++-------------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/example.c b/examples/example.c index 6ee4c2e615..93065580de 100644 --- a/examples/example.c +++ b/examples/example.c @@ -228,7 +228,7 @@ main(int argc, char **argv) sentry_transaction_context_set_sampled(tx_ctx, 0); } sentry_transaction_t *tx - = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); if (has_arg(argc, argv, "error-status")) { sentry_transaction_set_status( diff --git a/include/sentry.h b/include/sentry.h index 05c6ca7866..52db660843 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1265,7 +1265,7 @@ typedef struct sentry_span_s sentry_span_t; /** * Constructs a new Transaction Context. The returned value needs to be passed - * into `sentry_start_transaction` in order to be recorded and sent to sentry. + * into `sentry_transaction_start` in order to be recorded and sent to sentry. * * See * https://docs.sentry.io/platforms/native/enriching-events/transaction-name/ @@ -1378,7 +1378,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( * mention what kind of expectations they carry if they need to mutate or access * the object in a thread-safe way. */ -SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_start_transaction( +SENTRY_EXPERIMENTAL_API sentry_transaction_t *sentry_transaction_start( sentry_transaction_context_t *tx_cxt, sentry_value_t sampling_ctx); /** @@ -1412,7 +1412,7 @@ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); /** * Starts a new Span. * - * The return value of `sentry_start_transaction` should be passed in as + * The return value of `sentry_transaction_start` should be passed in as * `parent`. * * Both `operation` and `description` can be null, but it is recommended to diff --git a/src/sentry_core.c b/src/sentry_core.c index 4c9b3b9bdc..f5ab25f6ea 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -731,7 +731,7 @@ sentry_set_level(sentry_level_t level) #ifdef SENTRY_PERFORMANCE_MONITORING sentry_transaction_t * -sentry_start_transaction( +sentry_transaction_start( sentry_transaction_context_t *opaque_tx_cxt, sentry_value_t sampling_ctx) { // Just free this immediately until we implement proper support for diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 9beab0afe2..90d89930f2 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -138,12 +138,12 @@ SENTRY_TEST(transaction_name_backfill_on_finish) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new(NULL, NULL); sentry_transaction_t *tx - = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); tx_cxt = sentry_transaction_context_new("", ""); - tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -189,7 +189,7 @@ SENTRY_TEST(basic_function_transport_transaction) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new( "How could you", "Don't capture this."); sentry_transaction_t *tx - = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); // TODO: `sentry_capture_event` acts as if the event was sent if user // consent was not given @@ -197,14 +197,14 @@ SENTRY_TEST(basic_function_transport_transaction) sentry_user_consent_give(); tx_cxt = sentry_transaction_context_new("honk", "beep"); - tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); sentry_user_consent_revoke(); tx_cxt = sentry_transaction_context_new( "How could you again", "Don't capture this either."); - tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); event_id = sentry_transaction_finish(tx); // TODO: `sentry_capture_event` acts as if the event was sent if user // consent was not given @@ -235,7 +235,7 @@ SENTRY_TEST(transport_sampling_transactions) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("honk", "beep"); sentry_transaction_t *tx - = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); if (!sentry_uuid_is_nil(&event_id)) { sent_transactions += 1; @@ -279,7 +279,7 @@ SENTRY_TEST(transactions_skip_before_send) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("honk", "beep"); sentry_transaction_t *tx - = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_uuid_t event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -315,7 +315,7 @@ SENTRY_TEST(multiple_transactions) sentry_transaction_context_t *tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *tx - = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); sentry_value_t scope_tx = sentry__scope_get_span(); @@ -329,11 +329,11 @@ SENTRY_TEST(multiple_transactions) // Set transaction on scope twice, back-to-back without finishing the first // one tx_cxt = sentry_transaction_context_new("whoa!", NULL); - tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); sentry__transaction_decref(tx); tx_cxt = sentry_transaction_context_new("wowee!", NULL); - tx = sentry_start_transaction(tx_cxt, sentry_value_new_null()); + tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); sentry_set_span(tx); scope_tx = sentry__scope_get_span(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wowee!"); @@ -360,7 +360,7 @@ SENTRY_TEST(basic_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx - = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -410,7 +410,7 @@ SENTRY_TEST(spans_on_scope) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx - = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); sentry_set_span(opaque_tx); sentry_span_t *opaque_child @@ -462,7 +462,7 @@ SENTRY_TEST(child_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx - = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -518,7 +518,7 @@ SENTRY_TEST(overflow_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx - = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -593,7 +593,7 @@ SENTRY_TEST(drop_unfinished_spans) sentry_transaction_context_t *opaque_tx_cxt = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx - = sentry_start_transaction(opaque_tx_cxt, sentry_value_new_null()); + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); sentry_value_t tx = opaque_tx->inner; sentry_span_t *opaque_child @@ -658,7 +658,7 @@ SENTRY_TEST(distributed_headers) tx_ctx, "sentry-trace-but-a-lot-longer", not_expected_header); sentry_transaction_t *tx - = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); const char *trace_id = sentry_value_as_string( sentry_value_get_by_key(tx->inner, "trace_id")); @@ -672,7 +672,7 @@ SENTRY_TEST(distributed_headers) tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); sentry_transaction_t *dist_tx - = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); const char *dist_trace_id = sentry_value_as_string( sentry_value_get_by_key(dist_tx->inner, "trace_id")); @@ -693,7 +693,7 @@ SENTRY_TEST(distributed_headers) tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_span_iter_headers(child, forward_headers_to, (void *)tx_ctx); - dist_tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + dist_tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); dist_trace_id = sentry_value_as_string( sentry_value_get_by_key(dist_tx->inner, "trace_id")); @@ -713,11 +713,11 @@ SENTRY_TEST(distributed_headers) // check sampled flag tx_ctx = sentry_transaction_context_new("wow!", NULL); sentry_transaction_context_set_sampled(tx_ctx, 0); - tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); tx_ctx = sentry_transaction_context_new("distributed!", NULL); sentry_transaction_iter_headers(tx, forward_headers_to, (void *)tx_ctx); - dist_tx = sentry_start_transaction(tx_ctx, sentry_value_new_null()); + dist_tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); TEST_CHECK(!sentry_value_is_true( sentry_value_get_by_key(dist_tx->inner, "sampled"))); From 2e4314b6e71a68ba2f14c74d40a57cb766f6243d Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Mon, 24 Jan 2022 12:32:18 +0100 Subject: [PATCH 021/207] feat(tracing): Allow setting a scope on a span (#667) --- include/sentry.h | 36 ++++++++++++++++++++++------ src/sentry_core.c | 49 +++++++++++++++++++++++++++++++-------- src/sentry_scope.c | 28 +++++++++++++++------- src/sentry_scope.h | 18 ++++++++------ src/sentry_tracing.c | 42 ++++++++++++++++++++++++--------- src/sentry_tracing.h | 8 ++++--- tests/unit/test_tracing.c | 28 +++++++++++----------- 7 files changed, 148 insertions(+), 61 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 52db660843..2154d82b24 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1366,8 +1366,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( * * To ensure that any Events or Message Events are associated with this * Transaction while it is active, invoke and pass in the Transaction returned - * by this function to `sentry_set_span`. Further documentation on this can be - * found in `sentry_set_span`'s docstring. + * by this function to `sentry_set_transaction_object`. Further documentation on + * this can be found in `sentry_set_transaction_object`'s docstring. * * Takes ownership of `transaction_context`. A Transaction Context cannot be * modified or re-used after it is used to start a Transaction. @@ -1394,7 +1394,7 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( sentry_transaction_t *tx); /** - * Sets the Span (actually Transaction) so any Events sent while the Transaction + * Sets the Transaction so any Events sent while the Transaction * is active will be associated with the Transaction. * * If the Transaction being passed in is unsampled, it will still be associated @@ -1402,12 +1402,24 @@ SENTRY_EXPERIMENTAL_API sentry_uuid_t sentry_transaction_finish( * missing traces in sentry, see * https://docs.sentry.io/product/sentry-basics/tracing/trace-view/#orphan-traces-and-broken-subtraces * - * This increases the number of references pointing to the transaction. Invoke - * `sentry_transaction_finish` to remove the Span set by this function as well - * as its reference by passing in the same Transaction as the one passed into + * This increases the number of references pointing to the Transaction. Invoke + * `sentry_transaction_finish` to remove the Transaction set by this function as + * well as its reference by passing in the same Transaction as the one passed + * into this function. + */ +SENTRY_EXPERIMENTAL_API void sentry_set_transaction_object( + sentry_transaction_t *tx); + +/** + * Sets the Span so any Events sent while the Span + * is active will be associated with the Span. + * + * This increases the number of references pointing to the Span. Invoke + * `sentry_span_finish` to remove the Span set by this function as well + * as its reference by passing in the same Span as the one passed into * this function. */ -SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); +SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_span_t *span); /** * Starts a new Span. @@ -1429,6 +1441,11 @@ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_transaction_t *tx); * sentry. `sentry_value_null` will be returned if the child Span could not be * created. * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * * This increases the number of references pointing to the Transaction. * * The returned value is not thread-safe. Users are expected to ensure that @@ -1459,6 +1476,11 @@ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( * sentry. `sentry_value_null` will be returned if the child Span could not be * created. * + * To ensure that any Events or Message Events are associated with this + * Span while it is active, invoke and pass in the Span returned + * by this function to `sentry_set_span`. Further documentation on this can be + * found in `sentry_set_span`'s docstring. + * * The returned value is not thread-safe. Users are expected to ensure that * appropriate locking mechanisms are implemented over the Span if it needs * to be mutated across threads. Methods operating on the Span will mention what diff --git a/src/sentry_core.c b/src/sentry_core.c index f5ab25f6ea..712e5c7c6c 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -714,8 +714,8 @@ sentry_set_transaction(const char *transaction) scope->transaction = sentry__string_clone(transaction); #ifdef SENTRY_PERFORMANCE_MONITORING - if (scope->span) { - sentry_transaction_set_name(scope->span, transaction); + if (scope->transaction_object) { + sentry_transaction_set_name(scope->transaction_object, transaction); } #endif } @@ -778,16 +778,16 @@ sentry_transaction_finish(sentry_transaction_t *opaque_tx) sentry_value_t tx = sentry__value_clone(opaque_tx->inner); SENTRY_WITH_SCOPE_MUT (scope) { - if (scope->span) { - sentry_value_t scope_tx = scope->span->inner; + if (scope->transaction_object) { + sentry_value_t scope_tx = scope->transaction_object->inner; const char *tx_id = sentry_value_as_string( sentry_value_get_by_key(tx, "trace_id")); const char *scope_tx_id = sentry_value_as_string( sentry_value_get_by_key(scope_tx, "trace_id")); if (sentry__string_eq(tx_id, scope_tx_id)) { - sentry__transaction_decref(scope->span); - scope->span = NULL; + sentry__transaction_decref(scope->transaction_object); + scope->transaction_object = NULL; } } } @@ -819,7 +819,7 @@ sentry_transaction_finish(sentry_transaction_t *opaque_tx) // TODO: add tracestate sentry_value_t trace_context - = sentry__transaction_get_trace_context(opaque_tx); + = sentry__value_get_trace_context(opaque_tx->inner); sentry_value_t contexts = sentry_value_new_object(); sentry_value_set_by_key(contexts, "trace", trace_context); sentry_value_set_by_key(tx, "contexts", contexts); @@ -843,12 +843,26 @@ sentry_transaction_finish(sentry_transaction_t *opaque_tx) } void -sentry_set_span(sentry_transaction_t *tx) +sentry_set_transaction_object(sentry_transaction_t *tx) { SENTRY_WITH_SCOPE_MUT (scope) { - sentry__transaction_decref(scope->span); + sentry__span_decref(scope->span); + scope->span = NULL; + sentry__transaction_decref(scope->transaction_object); sentry__transaction_incref(tx); - scope->span = tx; + scope->transaction_object = tx; + } +} + +void +sentry_set_span(sentry_span_t *span) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__transaction_decref(scope->transaction_object); + scope->transaction_object = NULL; + sentry__span_decref(scope->span); + sentry__span_incref(span); + scope->span = span; } } @@ -928,6 +942,21 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry_value_t span = sentry__value_clone(opaque_span->inner); + SENTRY_WITH_SCOPE_MUT (scope) { + if (scope->span) { + sentry_value_t scope_span = scope->span->inner; + + const char *span_id = sentry_value_as_string( + sentry_value_get_by_key(span, "trace_id")); + const char *scope_span_id = sentry_value_as_string( + sentry_value_get_by_key(scope_span, "trace_id")); + if (sentry__string_eq(span_id, scope_span_id)) { + sentry__span_decref(scope->span); + scope->span = NULL; + } + } + } + if (!sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))) { SENTRY_DEBUG("span is already finished, aborting span finish"); sentry_value_decref(span); diff --git a/src/sentry_scope.c b/src/sentry_scope.c index b2b7324703..417a1f68f2 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -82,6 +82,7 @@ get_scope(void) g_scope.client_sdk = get_client_sdk(); #ifdef SENTRY_PERFORMANCE_MONITORING + g_scope.transaction_object = NULL; g_scope.span = NULL; #endif @@ -106,7 +107,8 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.client_sdk); #ifdef SENTRY_PERFORMANCE_MONITORING - sentry__transaction_decref(g_scope.span); + sentry__transaction_decref(g_scope.transaction_object); + sentry__span_decref(g_scope.span); #endif } sentry__mutex_unlock(&g_lock); @@ -240,16 +242,24 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } #ifdef SENTRY_PERFORMANCE_MONITORING +sentry_value_t +sentry__get_span_or_transaction(const sentry_scope_t *scope) +{ + if (scope->span) { + return scope->span->inner; + } else if (scope->transaction_object) { + return scope->transaction_object->inner; + } else { + return sentry_value_new_null(); + } +} + # ifdef SENTRY_UNITTEST sentry_value_t -sentry__scope_get_span() +sentry__scope_get_span_or_transaction() { SENTRY_WITH_SCOPE (scope) { - if (!scope->span) { - return sentry_value_new_null(); - } else { - return scope->span->inner; - } + return sentry__get_span_or_transaction(scope); } return sentry_value_new_null(); } @@ -320,8 +330,8 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t contexts = sentry__value_clone(scope->contexts); // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted - sentry_value_t scope_trace - = sentry__transaction_get_trace_context(scope->span); + sentry_value_t scope_trace = sentry__value_get_trace_context( + sentry__get_span_or_transaction(scope)); if (!sentry_value_is_null(scope_trace)) { if (sentry_value_is_null(contexts)) { contexts = sentry_value_new_object(); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 2fe3f5aead..cde4597514 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -21,12 +21,16 @@ typedef struct sentry_scope_s { sentry_value_t client_sdk; #ifdef SENTRY_PERFORMANCE_MONITORING - // Not to be confused with transaction, which is a legacy value. This is - // also known as a transaction, but to maintain consistency with other SDKs - // and to avoid a conflict with the existing transaction field this is named - // span. Whenever possible, `transaction` should pull its value from the - // `name` property nested in this field. - sentry_transaction_t *span; + // The span attached to this scope, if any. + // + // Conceptually, every transaction is a span, so it should be possible to + // attach spans or transactions to a scope. But sentry_span_t and + // sentry_transaction_t are unrelated types in the native SDK, so we need + // two distinct pointers. At most one of them should ever be non-null. + // Whenever possible, `transaction` should pull its value from the + // `name` property nested in transaction_object or span. + sentry_transaction_t *transaction_object; + sentry_span_t *span; #endif } sentry_scope_t; @@ -97,6 +101,6 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, #ifdef SENTRY_PERFORMANCE_MONITORING // this is only used in unit tests #ifdef SENTRY_UNITTEST -sentry_value_t sentry__scope_get_span(); +sentry_value_t sentry__scope_get_span_or_transaction(); #endif #endif diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 038912526e..00b1111b3c 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -205,6 +205,27 @@ sentry__transaction_decref(sentry_transaction_t *tx) }; } +void +sentry__span_incref(sentry_span_t *span) +{ + sentry_value_incref(span->inner); +} + +void +sentry__span_decref(sentry_span_t *span) +{ + if (!span) { + return; + } + + if (sentry_value_refcount(span->inner) <= 1) { + sentry_value_decref(span->inner); + sentry_free(span); + } else { + sentry_value_decref(span->inner); + }; +} + sentry_span_t * sentry__span_new(sentry_transaction_t *tx, sentry_value_t inner) { @@ -280,15 +301,14 @@ sentry__span_free(sentry_span_t *span) } sentry_value_t -sentry__transaction_get_trace_context(sentry_transaction_t *opaque_tx) +sentry__value_get_trace_context(sentry_value_t span) { - if (!opaque_tx || sentry_value_is_null(opaque_tx->inner)) { + if (sentry_value_is_null(span)) { return sentry_value_new_null(); } - sentry_value_t tx = opaque_tx->inner; - if (sentry_value_is_null(sentry_value_get_by_key(tx, "trace_id")) - || sentry_value_is_null(sentry_value_get_by_key(tx, "span_id"))) { + if (sentry_value_is_null(sentry_value_get_by_key(span, "trace_id")) + || sentry_value_is_null(sentry_value_get_by_key(span, "span_id"))) { return sentry_value_new_null(); } @@ -303,12 +323,12 @@ sentry__transaction_get_trace_context(sentry_transaction_t *opaque_tx) } \ } while (0) - PLACE_VALUE("trace_id", tx); - PLACE_VALUE("span_id", tx); - PLACE_VALUE("parent_span_id", tx); - PLACE_VALUE("op", tx); - PLACE_VALUE("description", tx); - PLACE_VALUE("status", tx); + PLACE_VALUE("trace_id", span); + PLACE_VALUE("span_id", span); + PLACE_VALUE("parent_span_id", span); + PLACE_VALUE("op", span); + PLACE_VALUE("description", span); + PLACE_VALUE("status", span); // TODO: freeze this return trace_context; diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index c92fd75010..16c15f77c8 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -33,6 +33,9 @@ sentry_transaction_t *sentry__transaction_new(sentry_value_t inner); void sentry__transaction_incref(sentry_transaction_t *tx); void sentry__transaction_decref(sentry_transaction_t *tx); +void sentry__span_incref(sentry_span_t *span); +void sentry__span_decref(sentry_span_t *span); + sentry_value_t sentry__value_span_new(size_t max_spans, sentry_value_t parent, char *operation, char *description); sentry_span_t *sentry__span_new( @@ -41,10 +44,9 @@ void sentry__span_free(sentry_span_t *span); /** * Returns an object containing tracing information extracted from a - * transaction (/span) which should be included in an event. + * transaction / span which should be included in an event. * See https://develop.sentry.dev/sdk/event-payloads/transaction/#examples */ -sentry_value_t sentry__transaction_get_trace_context( - sentry_transaction_t *span); +sentry_value_t sentry__value_get_trace_context(sentry_value_t span); #endif diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 90d89930f2..07559635a6 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -20,16 +20,16 @@ SENTRY_TEST(basic_tracing_context) sentry_value_t tx = sentry_value_new_object(); opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); sentry_value_set_by_key(tx, "op", sentry_value_new_string("honk.beep")); - TEST_CHECK( - sentry_value_is_null(sentry__transaction_get_trace_context(opaque_tx))); + TEST_CHECK(sentry_value_is_null( + sentry__value_get_trace_context(opaque_tx->inner))); sentry_uuid_t trace_id = sentry_uuid_new_v4(); sentry_value_set_by_key( tx, "trace_id", sentry__value_new_internal_uuid(&trace_id)); sentry__transaction_decref(opaque_tx); opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); - TEST_CHECK( - sentry_value_is_null(sentry__transaction_get_trace_context(opaque_tx))); + TEST_CHECK(sentry_value_is_null( + sentry__value_get_trace_context(opaque_tx->inner))); sentry_uuid_t span_id = sentry_uuid_new_v4(); sentry_value_set_by_key( @@ -38,7 +38,7 @@ SENTRY_TEST(basic_tracing_context) opaque_tx = sentry__transaction_new(sentry__value_clone(tx)); sentry_value_t trace_context - = sentry__transaction_get_trace_context(opaque_tx); + = sentry__value_get_trace_context(opaque_tx->inner); TEST_CHECK(!sentry_value_is_null(trace_context)); TEST_CHECK(!IS_NULL(trace_context, "trace_id")); TEST_CHECK(!IS_NULL(trace_context, "span_id")); @@ -316,13 +316,13 @@ SENTRY_TEST(multiple_transactions) = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); - sentry_set_span(tx); + sentry_set_transaction_object(tx); - sentry_value_t scope_tx = sentry__scope_get_span(); + sentry_value_t scope_tx = sentry__scope_get_span_or_transaction(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wow!"); sentry_uuid_t event_id = sentry_transaction_finish(tx); - scope_tx = sentry__scope_get_span(); + scope_tx = sentry__scope_get_span_or_transaction(); TEST_CHECK(sentry_value_is_null(scope_tx)); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -330,12 +330,12 @@ SENTRY_TEST(multiple_transactions) // one tx_cxt = sentry_transaction_context_new("whoa!", NULL); tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); - sentry_set_span(tx); + sentry_set_transaction_object(tx); sentry__transaction_decref(tx); tx_cxt = sentry_transaction_context_new("wowee!", NULL); tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); - sentry_set_span(tx); - scope_tx = sentry__scope_get_span(); + sentry_set_transaction_object(tx); + scope_tx = sentry__scope_get_span_or_transaction(); CHECK_STRING_PROPERTY(scope_tx, "transaction", "wowee!"); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -411,7 +411,7 @@ SENTRY_TEST(spans_on_scope) = sentry_transaction_context_new("wow!", NULL); sentry_transaction_t *opaque_tx = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); - sentry_set_span(opaque_tx); + sentry_set_transaction_object(opaque_tx); sentry_span_t *opaque_child = sentry_transaction_start_child(opaque_tx, "honk", "goose"); @@ -420,7 +420,7 @@ SENTRY_TEST(spans_on_scope) // Peek into the transaction's span list and make sure everything is // good - sentry_value_t scope_tx = sentry__scope_get_span(); + sentry_value_t scope_tx = sentry__scope_get_span_or_transaction(); const char *trace_id = sentry_value_as_string(sentry_value_get_by_key(scope_tx, "trace_id")); const char *parent_span_id @@ -433,7 +433,7 @@ SENTRY_TEST(spans_on_scope) sentry_span_finish(opaque_child); - scope_tx = sentry__scope_get_span(); + scope_tx = sentry__scope_get_span_or_transaction(); TEST_CHECK(!IS_NULL(scope_tx, "spans")); sentry_value_t spans = sentry_value_get_by_key(scope_tx, "spans"); TEST_CHECK_INT_EQUAL(sentry_value_get_length(spans), 1); From ef36a8734c766f9fd800ea4d521f6adb293ef267 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Mon, 24 Jan 2022 18:12:41 -0500 Subject: [PATCH 022/207] fix(tracing): Set max spans to the default amount when tracing is enabled (#669) --- src/sentry_options.c | 4 ++++ tests/unit/test_tracing.c | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sentry_options.c b/src/sentry_options.c index 59ee5af03f..e74c88f292 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -415,6 +415,10 @@ sentry_options_set_traces_sample_rate( sample_rate = 1.0; } opts->traces_sample_rate = sample_rate; + + if (sample_rate > 0 && opts->max_spans == 0) { + opts->max_spans = SENTRY_SPANS_MAX; + } } /** diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 07559635a6..07dde530db 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -349,7 +349,6 @@ SENTRY_TEST(basic_spans) { sentry_options_t *options = sentry_options_new(); sentry_options_set_traces_sample_rate(options, 1.0); - sentry_options_set_max_spans(options, 3); sentry_init(options); // Starting a child with no active transaction should fail @@ -404,7 +403,6 @@ SENTRY_TEST(spans_on_scope) { sentry_options_t *options = sentry_options_new(); sentry_options_set_traces_sample_rate(options, 1.0); - sentry_options_set_max_spans(options, 3); sentry_init(options); sentry_transaction_context_t *opaque_tx_cxt From 55c07c9c0577b8422ee77232cc5b6b788a9fbcd5 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 25 Jan 2022 16:59:44 +0100 Subject: [PATCH 023/207] feat: Add explicit flush method/hook (NATIVE-111) (#670) Co-authored-by: Sebastian Zivota --- include/sentry.h | 30 ++++++++-- src/sentry_core.c | 10 ++++ src/sentry_sync.c | 72 +++++++++++++++++++++++ src/sentry_sync.h | 6 ++ src/sentry_transport.c | 18 ++++++ src/sentry_transport.h | 7 +++ src/transports/sentry_transport_curl.c | 8 +++ src/transports/sentry_transport_winhttp.c | 8 +++ tests/unit/test_sync.c | 20 +++++++ tests/unit/tests.inc | 1 + 10 files changed, 176 insertions(+), 4 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 2154d82b24..1ae610590d 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -616,13 +616,16 @@ typedef struct sentry_options_s sentry_options_t; * * `startup_func`: This hook will be called by sentry inside of `sentry_init` * and instructs the transport to initialize itself. Failures will bubble up * to `sentry_init`. + * * `flush_func`: Instructs the transport to flush its queue. + * This hook receives a millisecond-resolution `timeout` parameter and should + * return `0` if the transport queue is flushed within the timeout. * * `shutdown_func`: Instructs the transport to flush its queue and shut down. * This hook receives a millisecond-resolution `timeout` parameter and should - * return `true` when the transport was flushed and shut down successfully. - * In case of `false`, sentry will log an error, but continue with freeing the - * transport. + * return `0` if the transport is flushed and shut down successfully. + * In case of a non-zero return value, sentry will log an error, but continue + * with freeing the transport. * * `free_func`: Frees the transports `state`. This hook might be called even - * though `shutdown_func` returned `false` previously. + * though `shutdown_func` returned a failure code previously. * * The transport interface might be extended in the future with hooks to flush * its internal queue without shutting down, and to dump its internal queue to @@ -662,6 +665,16 @@ SENTRY_API void sentry_transport_set_free_func( SENTRY_API void sentry_transport_set_startup_func(sentry_transport_t *transport, int (*startup_func)(const sentry_options_t *options, void *state)); +/** + * Sets the transport flush hook. + * + * This hook will receive a millisecond-resolution timeout. + * It should return `0` if all the pending envelopes are + * sent within the timeout, or `1` if the timeout is hit. + */ +SENTRY_API void sentry_transport_set_flush_func(sentry_transport_t *transport, + int (*flush_func)(uint64_t timeout, void *state)); + /** * Sets the transport shutdown hook. * @@ -1046,6 +1059,15 @@ SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); */ SENTRY_API int sentry_init(sentry_options_t *options); +/** + * Instructs the transport to flush its send queue. + * + * The `timeout` parameter is in milliseconds. + * + * Returns 0 on success, or a non-zero return value in case the timeout is hit. + */ +SENTRY_API int sentry_flush(uint64_t timeout); + /** * Shuts down the sentry client and forces transports to flush out. * diff --git a/src/sentry_core.c b/src/sentry_core.c index 712e5c7c6c..1c0a822c6e 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -200,6 +200,16 @@ sentry_init(sentry_options_t *options) return 1; } +int +sentry_flush(uint64_t timeout) +{ + int rv = 0; + SENTRY_WITH_OPTIONS (options) { + rv = sentry__transport_flush(options->transport, timeout); + } + return rv; +} + int sentry_close(void) { diff --git a/src/sentry_sync.c b/src/sentry_sync.c index b2e566c4b2..a6b6380a15 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -281,6 +281,78 @@ sentry__bgworker_start(sentry_bgworker_t *bgw) return 0; } +typedef struct { + long refcount; + bool was_flushed; + sentry_cond_t signal; + sentry_mutex_t lock; +} sentry_flush_task_t; + +static void +sentry__flush_task(void *task_data, void *UNUSED(state)) +{ + sentry_flush_task_t *flush_task = (sentry_flush_task_t *)task_data; + + sentry__mutex_lock(&flush_task->lock); + flush_task->was_flushed = true; + sentry__cond_wake(&flush_task->signal); + sentry__mutex_unlock(&flush_task->lock); +} + +static void +sentry__flush_task_decref(sentry_flush_task_t *task) +{ + if (sentry__atomic_fetch_and_add(&task->refcount, -1) == 1) { + sentry__mutex_free(&task->lock); + sentry_free(task); + } +} + +int +sentry__bgworker_flush(sentry_bgworker_t *bgw, uint64_t timeout) +{ + if (!sentry__atomic_fetch(&bgw->running)) { + SENTRY_WARN("trying to flush non-running thread"); + return 0; + } + SENTRY_TRACE("flushing background worker thread"); + + sentry_flush_task_t *flush_task + = sentry_malloc(sizeof(sentry_flush_task_t)); + if (!flush_task) { + return 1; + } + memset(flush_task, 0, sizeof(sentry_flush_task_t)); + flush_task->refcount = 2; // this thread + background worker + flush_task->was_flushed = false; + sentry__cond_init(&flush_task->signal); + sentry__mutex_init(&flush_task->lock); + + sentry__mutex_lock(&flush_task->lock); + + /* submit the task that triggers our condvar once it runs */ + sentry__bgworker_submit(bgw, sentry__flush_task, + (void (*)(void *))sentry__flush_task_decref, flush_task); + + uint64_t started = sentry__monotonic_time(); + bool was_flushed = false; + while (true) { + was_flushed = flush_task->was_flushed; + + uint64_t now = sentry__monotonic_time(); + if (was_flushed || (now > started && now - started > timeout)) { + sentry__mutex_unlock(&flush_task->lock); + sentry__flush_task_decref(flush_task); + + // return `0` on success + return !was_flushed; + } + + // this will implicitly release the lock, and re-acquire on wake + sentry__cond_wait_timeout(&flush_task->signal, &flush_task->lock, 250); + } +} + static void shutdown_task(void *task_data, void *UNUSED(state)) { diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 96e353b867..11915536a2 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -394,6 +394,12 @@ void sentry__bgworker_decref(sentry_bgworker_t *bgw); */ int sentry__bgworker_start(sentry_bgworker_t *bgw); +/** + * This will try to flush the background worker thread queue, with a `timeout`. + * Returns 0 on success. + */ +int sentry__bgworker_flush(sentry_bgworker_t *bgw, uint64_t timeout); + /** * This will try to shut down the background worker thread, with a `timeout`. * Returns 0 on success. diff --git a/src/sentry_transport.c b/src/sentry_transport.c index 351ddca0b5..51f8e9b393 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -12,6 +12,7 @@ typedef struct sentry_transport_s { void (*send_envelope_func)(sentry_envelope_t *envelope, void *state); int (*startup_func)(const sentry_options_t *options, void *state); int (*shutdown_func)(uint64_t timeout, void *state); + int (*flush_func)(uint64_t timeout, void *state); void (*free_func)(void *state); size_t (*dump_func)(sentry_run_t *run, void *state); void *state; @@ -57,6 +58,13 @@ sentry_transport_set_shutdown_func(sentry_transport_t *transport, transport->shutdown_func = shutdown_func; } +void +sentry_transport_set_flush_func(sentry_transport_t *transport, + int (*flush_func)(uint64_t timeout, void *state)) +{ + transport->flush_func = flush_func; +} + void sentry__transport_send_envelope( sentry_transport_t *transport, sentry_envelope_t *envelope) @@ -86,6 +94,16 @@ sentry__transport_startup( return 0; } +int +sentry__transport_flush(sentry_transport_t *transport, uint64_t timeout) +{ + if (transport->flush_func && transport->running) { + SENTRY_TRACE("flushing transport"); + return transport->flush_func(timeout, transport->state); + } + return 0; +} + int sentry__transport_shutdown(sentry_transport_t *transport, uint64_t timeout) { diff --git a/src/sentry_transport.h b/src/sentry_transport.h index 2ab786d100..f307881072 100644 --- a/src/sentry_transport.h +++ b/src/sentry_transport.h @@ -31,6 +31,13 @@ void sentry__transport_send_envelope( int sentry__transport_startup( sentry_transport_t *transport, const sentry_options_t *options); +/** + * Instructs the transport to flush its queue. + * + * Returns 0 on success. + */ +int sentry__transport_flush(sentry_transport_t *transport, uint64_t timeout); + /** * Instructs the transport to shut down. * diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 19ca7c9952..31605073f1 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -89,6 +89,13 @@ sentry__curl_transport_start( return sentry__bgworker_start(bgworker); } +static int +sentry__curl_transport_flush(uint64_t timeout, void *transport_state) +{ + sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; + return sentry__bgworker_flush(bgworker, timeout); +} + static int sentry__curl_transport_shutdown(uint64_t timeout, void *transport_state) { @@ -258,6 +265,7 @@ sentry__transport_new_default(void) sentry_transport_set_free_func( transport, (void (*)(void *))sentry__bgworker_decref); sentry_transport_set_startup_func(transport, sentry__curl_transport_start); + sentry_transport_set_flush_func(transport, sentry__curl_transport_flush); sentry_transport_set_shutdown_func( transport, sentry__curl_transport_shutdown); sentry__transport_set_dump_func(transport, sentry__curl_dump_queue); diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 5bfdf8171d..ef9e74b672 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -107,6 +107,13 @@ sentry__winhttp_transport_start( return sentry__bgworker_start(bgworker); } +static int +sentry__winhttp_transport_flush(uint64_t timeout, void *transport_state) +{ + sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; + return sentry__bgworker_shutdown(bgworker, timeout); +} + static int sentry__winhttp_transport_shutdown(uint64_t timeout, void *transport_state) { @@ -332,6 +339,7 @@ sentry__transport_new_default(void) transport, (void (*)(void *))sentry__bgworker_decref); sentry_transport_set_startup_func( transport, sentry__winhttp_transport_start); + sentry_transport_set_flush_func(transport, sentry__winhttp_transport_flush); sentry_transport_set_shutdown_func( transport, sentry__winhttp_transport_shutdown); sentry__transport_set_dump_func(transport, sentry__winhttp_dump_queue); diff --git a/tests/unit/test_sync.c b/tests/unit/test_sync.c index a90a862060..aba8e82908 100644 --- a/tests/unit/test_sync.c +++ b/tests/unit/test_sync.c @@ -131,3 +131,23 @@ SENTRY_TEST(task_queue) // was instructed to shut down TEST_CHECK(executed_after_shutdown); } + +SENTRY_TEST(bgworker_flush) +{ + sentry_bgworker_t *bgw = sentry__bgworker_new(NULL, NULL); + sentry__bgworker_submit(bgw, sleep_task, NULL, NULL); + + sentry__bgworker_start(bgw); + + // first flush times out + int flush = sentry__bgworker_flush(bgw, 500); + TEST_CHECK_INT_EQUAL(flush, 1); + + // second flush succeeds + flush = sentry__bgworker_flush(bgw, 1000); + TEST_CHECK_INT_EQUAL(flush, 0); + + int shutdown = sentry__bgworker_shutdown(bgw, 500); + TEST_CHECK_INT_EQUAL(shutdown, 0); + sentry__bgworker_decref(bgw); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 7114022d58..2f54b3e748 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -9,6 +9,7 @@ XX(basic_http_request_preparation_for_transaction) XX(basic_spans) XX(basic_tracing_context) XX(basic_transaction) +XX(bgworker_flush) XX(buildid_fallback) XX(child_spans) XX(concurrent_init) From 98cefcf4936a46aa43d7b9a569b4a2d7465c35b7 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Tue, 25 Jan 2022 12:56:22 -0500 Subject: [PATCH 024/207] feat(tracing): Always create spans even if they're unsampled (#668) --- src/sentry_core.c | 23 +++++++++-- src/sentry_tracing.c | 18 +-------- tests/unit/test_tracing.c | 82 +++++++++++++++++++++++++++++++++++++-- tests/unit/tests.inc | 1 + 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/src/sentry_core.c b/src/sentry_core.c index 1c0a822c6e..7ab4706145 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -792,9 +792,9 @@ sentry_transaction_finish(sentry_transaction_t *opaque_tx) sentry_value_t scope_tx = scope->transaction_object->inner; const char *tx_id = sentry_value_as_string( - sentry_value_get_by_key(tx, "trace_id")); + sentry_value_get_by_key(tx, "span_id")); const char *scope_tx_id = sentry_value_as_string( - sentry_value_get_by_key(scope_tx, "trace_id")); + sentry_value_get_by_key(scope_tx, "span_id")); if (sentry__string_eq(tx_id, scope_tx_id)) { sentry__transaction_decref(scope->transaction_object); scope->transaction_object = NULL; @@ -943,6 +943,12 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry_value_t root_transaction = opaque_root_transaction->inner; + if (!sentry_value_is_true( + sentry_value_get_by_key(root_transaction, "sampled"))) { + SENTRY_DEBUG("root transaction is unsampled, dropping span"); + goto fail; + } + if (!sentry_value_is_null( sentry_value_get_by_key(root_transaction, "timestamp"))) { SENTRY_DEBUG("span's root transaction is already finished, aborting " @@ -957,9 +963,9 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry_value_t scope_span = scope->span->inner; const char *span_id = sentry_value_as_string( - sentry_value_get_by_key(span, "trace_id")); + sentry_value_get_by_key(span, "span_id")); const char *scope_span_id = sentry_value_as_string( - sentry_value_get_by_key(scope_span, "trace_id")); + sentry_value_get_by_key(scope_span, "span_id")); if (sentry__string_eq(span_id, scope_span_id)) { sentry__span_decref(scope->span); scope->span = NULL; @@ -967,6 +973,15 @@ sentry_span_finish(sentry_span_t *opaque_span) } } + // Note that the current API makes it impossible to set a sampled value + // that's different from the span's root transaction, but let's just be safe + // here. + if (!sentry_value_is_true(sentry_value_get_by_key(span, "sampled"))) { + SENTRY_DEBUG("span is unsampled, dropping span"); + sentry_value_decref(span); + goto fail; + } + if (!sentry_value_is_null(sentry_value_get_by_key(span, "timestamp"))) { SENTRY_DEBUG("span is already finished, aborting span finish"); sentry_value_decref(span); diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 00b1111b3c..d30785f380 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -21,19 +21,13 @@ sentry__value_new_span(sentry_value_t parent, const char *operation) sentry_value_set_by_key(span, "status", sentry_value_new_string("ok")); - // Span creation is currently aggressively pruned prior to this function so - // once we're in here we definitely know that the span and its parent - // transaction are sampled. - // Sampling decisions inherited from traces created in other SDKs should be - // taken care of `continue_from_headers`, spans don't need to worry about - // (inheriting) forced sampling decisions, and transactions cannot be - // children of other transactions, so no inheriting of the sampling field is - // needed. if (!sentry_value_is_null(parent)) { sentry_value_set_by_key(span, "trace_id", sentry_value_get_by_key_owned(parent, "trace_id")); sentry_value_set_by_key(span, "parent_span_id", sentry_value_get_by_key_owned(parent, "span_id")); + sentry_value_set_by_key( + span, "sampled", sentry_value_get_by_key_owned(parent, "sampled")); } return span; @@ -256,13 +250,6 @@ sentry__value_span_new( goto fail; } - // Aggressively discard spans if a transaction is unsampled to avoid - // wasting memory - sentry_value_t sampled = sentry_value_get_by_key(parent, "sampled"); - if (!sentry_value_is_true(sampled)) { - SENTRY_DEBUG("span's parent is unsampled, not creating span"); - goto fail; - } sentry_value_t spans = sentry_value_get_by_key(parent, "spans"); // This only checks that the number of _completed_ spans matches the // number of max spans. This means that the number of in-flight spans @@ -279,7 +266,6 @@ sentry__value_span_new( sentry_value_set_by_key(child, "start_timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); - sentry_value_set_by_key(child, "sampled", sentry_value_new_bool(1)); return child; fail: diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 07dde530db..052cf77956 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -557,6 +557,74 @@ SENTRY_TEST(overflow_spans) sentry_close(); } +SENTRY_TEST(unsampled_spans) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_init(options); + + sentry_transaction_context_t *opaque_tx_cxt + = sentry_transaction_context_new("noisemakers", NULL); + sentry_transaction_context_set_sampled(opaque_tx_cxt, 0); + sentry_transaction_t *opaque_tx + = sentry_transaction_start(opaque_tx_cxt, sentry_value_new_null()); + sentry_value_t tx = opaque_tx->inner; + TEST_CHECK(!sentry_value_is_true(sentry_value_get_by_key(tx, "sampled"))); + + // check that children and grandchildren inherit the sampling decision, + // i.e. it cascades 1+ levels down + sentry_span_t *opaque_child + = sentry_transaction_start_child(opaque_tx, "honk", "goose"); + sentry_value_t child = opaque_child->inner; + TEST_CHECK(!sentry_value_is_null(child)); + TEST_CHECK( + !sentry_value_is_true(sentry_value_get_by_key(child, "sampled"))); + + sentry_span_t *opaque_grandchild + = sentry_span_start_child(opaque_child, "beep", "car"); + sentry_value_t grandchild = opaque_grandchild->inner; + TEST_CHECK(!sentry_value_is_null(grandchild)); + TEST_CHECK( + !sentry_value_is_true(sentry_value_get_by_key(grandchild, "sampled"))); + + // finishing does not add (grand)children to the spans list + sentry_span_finish(opaque_grandchild); + TEST_CHECK( + 0 == sentry_value_get_length(sentry_value_get_by_key(tx, "spans"))); + + sentry_span_finish(opaque_child); + TEST_CHECK( + 0 == sentry_value_get_length(sentry_value_get_by_key(tx, "spans"))); + + // perform the same checks, but with the transaction on the scope + sentry_set_transaction_object(opaque_tx); + + opaque_child = sentry_transaction_start_child(opaque_tx, "toot", "boat"); + child = opaque_child->inner; + TEST_CHECK(!sentry_value_is_null(child)); + TEST_CHECK( + !sentry_value_is_true(sentry_value_get_by_key(child, "sampled"))); + + opaque_grandchild + = sentry_span_start_child(opaque_child, "vroom", "sportscar"); + grandchild = opaque_grandchild->inner; + TEST_CHECK(!sentry_value_is_null(grandchild)); + TEST_CHECK( + !sentry_value_is_true(sentry_value_get_by_key(grandchild, "sampled"))); + + sentry_span_finish(opaque_grandchild); + TEST_CHECK( + 0 == sentry_value_get_length(sentry_value_get_by_key(tx, "spans"))); + + sentry_span_finish(opaque_child); + TEST_CHECK( + 0 == sentry_value_get_length(sentry_value_get_by_key(tx, "spans"))); + + sentry_transaction_finish(opaque_tx); + + sentry_close(); +} + static void check_spans(sentry_envelope_t *envelope, void *data) { @@ -720,12 +788,20 @@ SENTRY_TEST(distributed_headers) TEST_CHECK(!sentry_value_is_true( sentry_value_get_by_key(dist_tx->inner, "sampled"))); + child = sentry_transaction_start_child(tx, "honk", "goose"); + TEST_CHECK(!sentry_value_is_true( + sentry_value_get_by_key(child->inner, "sampled"))); + + tx_ctx = sentry_transaction_context_new("distributed from a child!", NULL); + sentry_span_iter_headers(child, forward_headers_to, (void *)tx_ctx); sentry__transaction_decref(dist_tx); + dist_tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); - // TODO: Check the sampled flag on a child span as well, but I think we - // don't create one if the transaction is not sampled? Well, here is the - // reason why we should! + TEST_CHECK(!sentry_value_is_true( + sentry_value_get_by_key(dist_tx->inner, "sampled"))); + sentry__transaction_decref(dist_tx); + sentry__span_free(child); sentry__transaction_decref(tx); sentry_close(); diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 2f54b3e748..6414d109e5 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -61,6 +61,7 @@ XX(transaction_name_backfill_on_finish) XX(transactions_skip_before_send) XX(transport_sampling_transactions) XX(uninitialized) +XX(unsampled_spans) XX(unwinder) XX(url_parsing_complete) XX(url_parsing_invalid) From c1919370a942c0bf2a7332aa4964428e5f4c0bda Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 27 Jan 2022 09:33:39 +0100 Subject: [PATCH 025/207] test: Add integration test for envelope with transaction (#671) This adds an integration test which inspects the envelope of a transaction sent by the native SDK. NATIVE-407 --- tests/__init__.py | 50 ++++++++++++++++++++++++++-- tests/assertions.py | 43 ++++++++++++++++--------- tests/test_integration_http.py | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 02f6146644..b34e1c2ea5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,6 +5,8 @@ import sys import urllib import pytest +import pprint +import textwrap sourcedir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) @@ -165,6 +167,19 @@ def deserialize( # type: (...) -> Envelope return cls.deserialize_from(io.BytesIO(bytes)) + def print_verbose(self, indent=0): + """Pretty prints the envelope.""" + print(" " * indent + "Envelope:") + indent += 2 + print(" " * indent + "Headers:") + indent += 2 + print(textwrap.indent(pprint.pformat(self.headers), " " * indent)) + indent -= 2 + print(" " * indent + "Items:") + indent += 2 + for item in self.items: + item.print_verbose(indent) + def __repr__(self): # type: (...) -> str return "" % (self.headers, self.items) @@ -180,6 +195,21 @@ def __init__( self.json = json self.bytes = bytes + def print_verbose(self, indent=0): + """Pretty prints the item.""" + print(" " * indent + "Payload:") + indent += 2 + if self.bytes: + payload = self.bytes.encode("utf-8", errors="replace") + if self.json: + payload = pprint.pformat(self.json) + try: + print(textwrap.indent(payload, " " * indent)) + except UnicodeEncodeError: + # Windows CI appears fickle, and we put emojis in the json + payload = payload.encode("ascii", errors="replace").decode() + print(textwrap.indent(payload, " " * indent)) + def __repr__(self): # type: (...) -> str return "" % (self.bytes, self.json) @@ -205,7 +235,11 @@ def __init__( def get_event(self): # type: (...) -> Optional[Event] - if self.headers.get("type") == "event" and self.payload.json is not None: + # Transactions are events with a few extra fields, so return them as well. + if ( + self.headers.get("type") in ["event", "transaction"] + and self.payload.json is not None + ): return self.payload.json return None @@ -220,7 +254,7 @@ def deserialize_from( headers = json.loads(line) length = headers["length"] payload = f.read(length) - if headers.get("type") == "event" or headers.get("type") == "session": + if headers.get("type") in ["event", "session", "transaction"]: rv = cls(headers=headers, payload=PayloadRef(json=json.loads(payload))) else: rv = cls(headers=headers, payload=payload) @@ -234,6 +268,18 @@ def deserialize( # type: (...) -> Optional[Item] return cls.deserialize_from(io.BytesIO(bytes)) + def print_verbose(self, indent=0): + """Pretty prints the item.""" + item_type = self.headers.get("type", "?").capitalize() + print(" " * indent + f"{item_type}:") + indent += 2 + print(" " * indent + "Headers:") + indent += 2 + headers = pprint.pformat(self.headers) + print(textwrap.indent(headers, " " * indent)) + indent -= 2 + self.payload.print_verbose(indent) + def __repr__(self): # type: (...) -> str return "" % ( diff --git a/tests/assertions.py b/tests/assertions.py index 379686f2e6..7f4f061122 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -14,6 +14,11 @@ def matches(actual, expected): return {k: v for (k, v) in actual.items() if k in expected.keys()} == expected +def assert_matches(actual, expected): + """Assert two objects for equality, ignoring extra keys in ``actual``.""" + assert {k: v for (k, v) in actual.items() if k in expected.keys()} == expected + + def assert_session(envelope, extra_assertion=None): session = None for item in envelope: @@ -27,10 +32,15 @@ def assert_session(envelope, extra_assertion=None): "environment": "development", } if extra_assertion: - assert matches(session, extra_assertion) + assert_matches(session, extra_assertion) -def assert_meta(envelope, release="test-example-release", integration=None): +def assert_meta( + envelope, + release="test-example-release", + integration=None, + transaction="test-transaction", +): event = envelope.get_event() expected = { @@ -38,7 +48,7 @@ def assert_meta(envelope, release="test-example-release", integration=None): "environment": "development", "release": release, "user": {"id": 42, "username": "some_name"}, - "transaction": "test-transaction", + "transaction": transaction, "tags": {"expected-tag": "some value"}, "extra": {"extra stuff": "some value", "…unicode key…": "őá…–🤮🚀¿ 한글 테스트"}, } @@ -51,7 +61,7 @@ def assert_meta(envelope, release="test-example-release", integration=None): } if not is_android: if sys.platform == "win32": - assert matches( + assert_matches( event["contexts"]["os"], {"name": "Windows", "version": platform.version()}, ) @@ -62,7 +72,7 @@ def assert_meta(envelope, release="test-example-release", integration=None): version = match.group(1) build = match.group(2) - assert matches( + assert_matches( event["contexts"]["os"], {"name": "Linux", "version": version, "build": build}, ) @@ -72,7 +82,7 @@ def assert_meta(envelope, release="test-example-release", integration=None): version.append("0") version = ".".join(version) - assert matches( + assert_matches( event["contexts"]["os"], { "name": "macOS", @@ -82,9 +92,9 @@ def assert_meta(envelope, release="test-example-release", integration=None): ) assert event["contexts"]["os"]["build"] is not None - assert matches(event, expected) - assert matches(event["sdk"], expected_sdk) - assert matches( + assert_matches(event, expected) + assert_matches(event["sdk"], expected_sdk) + assert_matches( event["contexts"], {"runtime": {"type": "runtime", "name": "testing-runtime"}} ) @@ -92,10 +102,11 @@ def assert_meta(envelope, release="test-example-release", integration=None): assert event["sdk"].get("integrations") is None else: assert event["sdk"]["integrations"] == [integration] - assert any( - "sentry_example" in image["code_file"] - for image in event["debug_meta"]["images"] - ) + if event.get("type") == "event": + assert any( + "sentry_example" in image["code_file"] + for image in event["debug_meta"]["images"] + ) def assert_stacktrace(envelope, inside_exception=False, check_size=True): @@ -155,7 +166,7 @@ def assert_event(envelope): "logger": "my-logger", "message": {"formatted": "Hello World!"}, } - assert matches(event, expected) + assert_matches(event, expected) assert_timestamp(event["timestamp"]) @@ -165,13 +176,13 @@ def assert_exception(envelope): "type": "ParseIntError", "value": "invalid digit found in string", } - assert matches(event["exception"]["values"][0], exception) + assert_matches(event["exception"]["values"][0], exception) assert_timestamp(event["timestamp"]) def assert_crash(envelope): event = envelope.get_event() - assert matches(event, {"level": "fatal"}) + assert_matches(event, {"level": "fatal"}) # depending on the unwinder, we currently don’t get any stack frames from # a `ucontext` assert_stacktrace(envelope, inside_exception=True, check_size=False) diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 1e5f6f46d5..6148bbf073 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1,9 +1,11 @@ +import time import pytest import subprocess import sys import os import time import itertools +import uuid import json from . import make_dsn, check_output, run, Envelope from .conditions import has_http, has_breakpad, has_files @@ -413,3 +415,60 @@ def delayed(req): run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) assert len(httpserver.log) == 10 + + +def test_transaction_only(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) + + httpserver.expect_oneshot_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver), SENTRY_RELEASE="🤮🚀") + + run( + tmp_path, + "sentry_example", + ["log", "capture-transaction"], + check=True, + env=env, + ) + + assert len(httpserver.log) == 1 + output = httpserver.log[0][0].get_data() + envelope = Envelope.deserialize(output) + + # Show what the envelope looks like if the test fails. + envelope.print_verbose() + + # The transaction is overwritten. + assert_meta(envelope, transaction="little.teapot") + + # Extract the one-and-only-item + (event,) = envelope.items + + assert event.headers["type"] == "transaction" + json = event.payload.json + + # See https://develop.sentry.dev/sdk/performance/trace-context/#trace-context + trace_context = json["contexts"]["trace"] + + assert ( + trace_context["op"] == "Short and stout here is my handle and here is my spout" + ) + + assert trace_context["trace_id"] + trace_id = uuid.UUID(hex=trace_context["trace_id"]) + assert trace_id + + # TODO: currently missing + # assert trace_context['public_key'] + + assert trace_context["span_id"] + assert trace_context["status"] == "ok" + + RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" + start_timestamp = time.strptime(json["start_timestamp"], RFC3339_FORMAT) + assert start_timestamp + timestamp = time.strptime(json["timestamp"], RFC3339_FORMAT) + assert timestamp >= start_timestamp From 9b2afa932675d57db31a9b8a370e789af51eee88 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 28 Jan 2022 02:11:37 +0000 Subject: [PATCH 026/207] release: 0.4.14 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ffbe0900..6e60cb2e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 0.4.14 + +### Various fixes & improvements + +- test: Add integration test for envelope with transaction (#671) by @flub +- feat(tracing): Always create spans even if they're unsampled (#668) by @relaxolotl +- feat: Add explicit flush method/hook (NATIVE-111) (#670) by @Swatinem +- fix(tracing): Set max spans to the default amount when tracing is enabled (#669) by @relaxolotl +- feat(tracing): Allow setting a scope on a span (#667) by @loewenheim +- ref(tracing): Revert name change of sentry_transaction_start (#666) by @loewenheim +- fix: Match sentry-trace header case-insensitively (#665) by @Swatinem +- ref: Make API forward-compatible to Sampling Context (NATIVE-457) (#663) by @Swatinem +- fix: Apply default rate limit (#660) by @Swatinem +- fix: Read Windows Version from Registry (#623) by @Swatinem +- fix: Correct CMake `SENTRY_LIBRARY_TYPE` variable check (#662) by @Mixaill +- feat: Implement distributed trace propagation (NATIVE-304) (#657) by @Swatinem +- feat(tracing): Spans now carry pointers to the Transactions they belong to (#656) by @relaxolotl +- feat: basic object merging (#650) by @flub +- feat(tracing): Allow setting custom span status [NATIVE-441] (#648) by @loewenheim +- ref(tracing): Update name of attached transaction in set_transaction [NATIVE-444] (#652) by @loewenheim +- feat(tracing): Introduce structs for performance monitoring constructs (#649) by @relaxolotl +- feat(tracing): Allow transaction renaming [NATIVE-438] (#651) by @loewenheim +- tracing: Add transaction/span tag methods [NATIVE-442] (#626) by @loewenheim +- fix: Revert to mmap-ing modules in the modulefinder (#642) by @Swatinem +- fix: Read section names safely (#641) by @Swatinem +- fix(tracing): Actually set the operation on a transaction (#647) by @relaxolotl +- feat(tracing): Retroactive unit test improvements (#637) by @relaxolotl +- feat(tracing): Basic span support with nesting (#634) by @relaxolotl + +_Plus 12 more_ + ## 0.4.13 **Features** diff --git a/include/sentry.h b/include/sentry.h index 1ae610590d..2e1347873c 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -24,7 +24,7 @@ extern "C" { /* SDK Version */ #define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.13" +#define SENTRY_SDK_VERSION "0.4.14" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 7f4f061122..7ad3e9051a 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -54,9 +54,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.13", + "version": "0.4.14", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.13"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.14"}, ], } if not is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 6148bbf073..d85f34bfca 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.13" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.14" def test_capture_http(cmake, httpserver): From 48d9ac192efd62119a7fbe4a778df14a6a7ef3ab Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Fri, 28 Jan 2022 12:23:43 +0100 Subject: [PATCH 027/207] meta: Manually draft a more detailed Changelog --- CHANGELOG.md | 49 +++++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e60cb2e37..7ccde07474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,34 +2,27 @@ ## 0.4.14 -### Various fixes & improvements - -- test: Add integration test for envelope with transaction (#671) by @flub -- feat(tracing): Always create spans even if they're unsampled (#668) by @relaxolotl -- feat: Add explicit flush method/hook (NATIVE-111) (#670) by @Swatinem -- fix(tracing): Set max spans to the default amount when tracing is enabled (#669) by @relaxolotl -- feat(tracing): Allow setting a scope on a span (#667) by @loewenheim -- ref(tracing): Revert name change of sentry_transaction_start (#666) by @loewenheim -- fix: Match sentry-trace header case-insensitively (#665) by @Swatinem -- ref: Make API forward-compatible to Sampling Context (NATIVE-457) (#663) by @Swatinem -- fix: Apply default rate limit (#660) by @Swatinem -- fix: Read Windows Version from Registry (#623) by @Swatinem -- fix: Correct CMake `SENTRY_LIBRARY_TYPE` variable check (#662) by @Mixaill -- feat: Implement distributed trace propagation (NATIVE-304) (#657) by @Swatinem -- feat(tracing): Spans now carry pointers to the Transactions they belong to (#656) by @relaxolotl -- feat: basic object merging (#650) by @flub -- feat(tracing): Allow setting custom span status [NATIVE-441] (#648) by @loewenheim -- ref(tracing): Update name of attached transaction in set_transaction [NATIVE-444] (#652) by @loewenheim -- feat(tracing): Introduce structs for performance monitoring constructs (#649) by @relaxolotl -- feat(tracing): Allow transaction renaming [NATIVE-438] (#651) by @loewenheim -- tracing: Add transaction/span tag methods [NATIVE-442] (#626) by @loewenheim -- fix: Revert to mmap-ing modules in the modulefinder (#642) by @Swatinem -- fix: Read section names safely (#641) by @Swatinem -- fix(tracing): Actually set the operation on a transaction (#647) by @relaxolotl -- feat(tracing): Retroactive unit test improvements (#637) by @relaxolotl -- feat(tracing): Basic span support with nesting (#634) by @relaxolotl - -_Plus 12 more_ +**Features** + +- The Sentry SDK now has experimental support for performance monitoring. + The performance monitoring API allows manually creating transactions and instrumenting spans, and offers APIs for distributed tracing. + The API is currently disabled by default and needs to be enabled via a compile-time `SENTRY_PERFORMANCE_MONITORING` flag. + For more information, take a look at the more detailed [documentation of performance monitoring](https://docs.sentry.io/platforms/native/performance/). +- Sentry now has an explicit `sentry_flush` method that blocks the calling thread for the given time, waiting for the transport queue to be flushed. Custom transports need to implement a new `flush_hook` for this to work. + +**Fixes** + +- Fix Sentry API deadlocking when the SDK was not initialized (or `sentry_init` failed). +- The rate limit handling of the default transports was updated to match the expected behavior. +- The Windows OS version is now read from the Registry and is more accurate. +- The `SENTRY_LIBRARY_TYPE` CMake option is now correctly honored. +- The Linux Modulefinder was once again improved to increase its memory safety and reliability. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@Mixaill](https://github.com/Mixaill) ## 0.4.13 From f2aedbd9b91e1c70a80544fbf543fe5b472dc2c2 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 31 Jan 2022 17:12:06 +0100 Subject: [PATCH 028/207] ref: Optimize JSON formatting (ISSUE-1379) (#674) --- src/sentry_json.c | 44 +++++++++++++++++++++++++ src/sentry_string.c | 61 ---------------------------------- src/sentry_string.h | 74 ++++++++++++++++++++++++++++++++---------- src/sentry_transport.c | 1 + 4 files changed, 101 insertions(+), 79 deletions(-) diff --git a/src/sentry_json.c b/src/sentry_json.c index 316868256f..cf02c7f1b9 100644 --- a/src/sentry_json.c +++ b/src/sentry_json.c @@ -101,13 +101,49 @@ write_str(sentry_jsonwriter_t *jw, const char *str) sentry__stringbuilder_append(jw->sb, str); } +// The Lookup table and algorithm below are adapted from: +// https://github.com/serde-rs/json/blob/977975ee650829a1f3c232cd5f641a7011bdce1d/src/ser.rs#L2079-L2145 + +// Lookup table of escape sequences. `0` means no need to escape, and `1` means +// that escaping is needed. +static unsigned char needs_escaping[256] = { + // 1 2 3 4 5 6 7 8 9 A B C D E F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F +}; + static void write_json_str(sentry_jsonwriter_t *jw, const char *str) { // using unsigned here because utf-8 is > 127 :-) const unsigned char *ptr = (const unsigned char *)str; write_char(jw, '"'); + + const unsigned char *start = ptr; for (; *ptr; ptr++) { + if (!needs_escaping[*ptr]) { + continue; + } + + size_t len = ptr - start; + if (len) { + sentry__stringbuilder_append_buf(jw->sb, (const char *)start, len); + } + switch (*ptr) { case '\\': write_str(jw, "\\\\"); @@ -142,7 +178,15 @@ write_json_str(sentry_jsonwriter_t *jw, const char *str) write_char(jw, *ptr); } } + + start = ptr + 1; } + + size_t len = ptr - start; + if (len) { + sentry__stringbuilder_append_buf(jw->sb, (const char *)start, len); + } + write_char(jw, '"'); } diff --git a/src/sentry_string.c b/src/sentry_string.c index a1581c752a..f745f6ad78 100644 --- a/src/sentry_string.c +++ b/src/sentry_string.c @@ -39,49 +39,6 @@ sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len) return &sb->buf[sb->len]; } -static int -append(sentry_stringbuilder_t *sb, const char *s, size_t len) -{ - char *buf = sentry__stringbuilder_reserve(sb, len + 1); - if (!buf) { - return 1; - } - memcpy(buf, s, len); - sb->len += len; - - // make sure we're always zero terminated - sb->buf[sb->len] = '\0'; - - return 0; -} - -int -sentry__stringbuilder_append(sentry_stringbuilder_t *sb, const char *s) -{ - return append(sb, s, strlen(s)); -} - -int -sentry__stringbuilder_append_buf( - sentry_stringbuilder_t *sb, const char *s, size_t len) -{ - return append(sb, s, len); -} - -int -sentry__stringbuilder_append_char(sentry_stringbuilder_t *sb, char c) -{ - return append(sb, &c, 1); -} - -int -sentry__stringbuilder_append_char32(sentry_stringbuilder_t *sb, uint32_t c) -{ - char buf[4]; - size_t len = sentry__unichar_to_utf8(c, buf); - return sentry__stringbuilder_append_buf(sb, buf, len); -} - char * sentry_stringbuilder_take_string(sentry_stringbuilder_t *sb) { @@ -121,24 +78,6 @@ sentry__stringbuilder_set_len(sentry_stringbuilder_t *sb, size_t len) sb->len = len; } -char * -sentry__string_clone(const char *str) -{ - return str ? sentry__string_clonen(str, strlen(str)) : NULL; -} - -char * -sentry__string_clonen(const char *str, size_t n) -{ - size_t len = n + 1; - char *rv = sentry_malloc(len); - if (rv) { - memcpy(rv, str, n); - rv[n] = 0; - } - return rv; -} - #ifdef SENTRY_PLATFORM_WINDOWS char * sentry__string_from_wstr(const wchar_t *s) diff --git a/src/sentry_string.h b/src/sentry_string.h index feeff36786..255d55d171 100644 --- a/src/sentry_string.h +++ b/src/sentry_string.h @@ -22,25 +22,55 @@ typedef struct sentry_stringbuilder_s { void sentry__stringbuilder_init(sentry_stringbuilder_t *sb); /** - * Appends a zero terminated string to the builder. + * Resizes the stringbuilder buffer to make sure there is at least `len` bytes + * available at the end, and returns a pointer *to the reservation*. */ -int sentry__stringbuilder_append(sentry_stringbuilder_t *sb, const char *s); +char *sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len); /** * Appends a sized buffer. */ -int sentry__stringbuilder_append_buf( - sentry_stringbuilder_t *sb, const char *s, size_t len); +static inline int +sentry__stringbuilder_append_buf( + sentry_stringbuilder_t *sb, const char *s, size_t len) +{ + size_t needed = sb->len + len + 1; + char *buf = sb->buf; + if (!sb->buf || needed > sb->allocated) { + buf = sentry__stringbuilder_reserve(sb, len + 1); + if (!buf) { + return 1; + } + } else { + buf = buf + sb->len; + } + + memcpy(buf, s, len); + sb->len += len; + + // make sure we're always zero terminated + sb->buf[sb->len] = '\0'; + + return 0; +} /** - * Appends a single character. + * Appends a zero terminated string to the builder. */ -int sentry__stringbuilder_append_char(sentry_stringbuilder_t *sb, char c); +static inline int +sentry__stringbuilder_append(sentry_stringbuilder_t *sb, const char *s) +{ + return sentry__stringbuilder_append_buf(sb, s, strlen(s)); +} /** - * Appends a utf-32 character. + * Appends a single character. */ -int sentry__stringbuilder_append_char32(sentry_stringbuilder_t *sb, uint32_t c); +static inline int +sentry__stringbuilder_append_char(sentry_stringbuilder_t *sb, char c) +{ + return sentry__stringbuilder_append_buf(sb, &c, 1); +} /** * Appends an int64 value. @@ -73,12 +103,6 @@ void sentry__stringbuilder_cleanup(sentry_stringbuilder_t *sb); */ size_t sentry__stringbuilder_len(const sentry_stringbuilder_t *sb); -/** - * Resizes the stringbuilder buffer to make sure there is at least `len` bytes - * available at the end, and returns a pointer *to the reservation*. - */ -char *sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len); - /** * Sets the number of used bytes in the string builder, to be used together with * `sentry__stringbuilder_reserve` to avoid copying from an intermediate buffer. @@ -86,14 +110,28 @@ char *sentry__stringbuilder_reserve(sentry_stringbuilder_t *sb, size_t len); void sentry__stringbuilder_set_len(sentry_stringbuilder_t *sb, size_t len); /** - * Duplicates a zero terminated string. + * Duplicates a zero terminated string with a length limit. */ -char *sentry__string_clone(const char *str); +static inline char * +sentry__string_clonen(const char *str, size_t n) +{ + size_t len = n + 1; + char *rv = (char *)sentry_malloc(len); + if (rv) { + memcpy(rv, str, n); + rv[n] = 0; + } + return rv; +} /** - * Duplicates a zero terminated string with a length limit. + * Duplicates a zero terminated string. */ -char *sentry__string_clonen(const char *str, size_t n); +static inline char * +sentry__string_clone(const char *str) +{ + return str ? sentry__string_clonen(str, strlen(str)) : NULL; +} /** * Converts a string to lowercase. diff --git a/src/sentry_transport.c b/src/sentry_transport.c index 51f8e9b393..4ff909e03b 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -3,6 +3,7 @@ #include "sentry_envelope.h" #include "sentry_options.h" #include "sentry_ratelimiter.h" +#include "sentry_string.h" #define ENVELOPE_MIME "application/x-sentry-envelope" // The headers we use are: `x-sentry-auth`, `content-type`, `content-length` From ac839c27391fde8865dce8c61a94f9f074e284ae Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Thu, 10 Feb 2022 15:48:24 +0100 Subject: [PATCH 029/207] fix: Correctly apply contexts from the scope (#676) --- CHANGELOG.md | 15 +++++++++++---- src/sentry_scope.c | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccde07474..dc53309d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ # Changelog +## Unreleased + +**Fixes**: + +- Fix contexts from the scope not being attached to events correctly. +- Improve performance of event serialization. + ## 0.4.14 -**Features** +**Features**: - The Sentry SDK now has experimental support for performance monitoring. The performance monitoring API allows manually creating transactions and instrumenting spans, and offers APIs for distributed tracing. @@ -10,7 +17,7 @@ For more information, take a look at the more detailed [documentation of performance monitoring](https://docs.sentry.io/platforms/native/performance/). - Sentry now has an explicit `sentry_flush` method that blocks the calling thread for the given time, waiting for the transport queue to be flushed. Custom transports need to implement a new `flush_hook` for this to work. -**Fixes** +**Fixes**: - Fix Sentry API deadlocking when the SDK was not initialized (or `sentry_init` failed). - The rate limit handling of the default transports was updated to match the expected behavior. @@ -26,13 +33,13 @@ Features, fixes and improvements in this release have been contributed by: ## 0.4.13 -**Features** +**Features**: - Add client-side stackwalking on Linux, Windows, and macOS (disabled by default). - CMake: add ability to set solution folder name. - Add AIX support. -**Fixes** +**Fixes**: - CMake: check whether libcurl was already found. - Increment CXX standard version to 14 to allow crashpad to build. diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 417a1f68f2..61e83550f1 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -326,8 +326,9 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry__value_merge_objects(event_extra, scope->extra); } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_value_t contexts = sentry__value_clone(scope->contexts); + +#ifdef SENTRY_PERFORMANCE_MONITORING // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted sentry_value_t scope_trace = sentry__value_get_trace_context( @@ -338,6 +339,7 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, } sentry_value_set_by_key(contexts, "trace", scope_trace); } +#endif // merge contexts sourced from scope into the event sentry_value_t event_contexts = sentry_value_get_by_key(event, "contexts"); @@ -347,7 +349,6 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry__value_merge_objects(event_contexts, contexts); } sentry_value_decref(contexts); -#endif if (mode & SENTRY_SCOPE_BREADCRUMBS) { PLACE_CLONED_VALUE("breadcrumbs", scope->breadcrumbs); From c53b299aeca1cb0a6a2daac73b3ad09822ded34a Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 10 Feb 2022 14:49:07 +0000 Subject: [PATCH 030/207] release: 0.4.15 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc53309d51..0a7a13d4ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.4.15 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 2e1347873c..307b782f2d 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -24,7 +24,7 @@ extern "C" { /* SDK Version */ #define SENTRY_SDK_NAME "sentry.native" -#define SENTRY_SDK_VERSION "0.4.14" +#define SENTRY_SDK_VERSION "0.4.15" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 7ad3e9051a..acba4558ee 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -54,9 +54,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.14", + "version": "0.4.15", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.14"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.15"}, ], } if not is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index d85f34bfca..a1b8367db7 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.14" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.15" def test_capture_http(cmake, httpserver): From a51ccbbdb00cd3042022f7df6d4e352e9ae6c0c3 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Mon, 14 Feb 2022 11:24:18 -0500 Subject: [PATCH 031/207] feat: Include performance monitoring by default in experimental API, remove compile flag for feature (#678) In addition to removing the flag, this also touches several test files. This is because the removal of these flags has created several double imports of sentry.h primarily in the test suite, which causes compilation to fail. --- CHANGELOG.md | 6 ++++++ CMakeLists.txt | 2 -- examples/example.c | 4 ---- include/sentry.h | 11 +++-------- src/CMakeLists.txt | 1 - src/sentry_core.c | 23 +---------------------- src/sentry_core.h | 6 ------ src/sentry_envelope.c | 16 +++------------- src/sentry_envelope.h | 2 -- src/sentry_options.c | 5 ----- src/sentry_options.h | 2 -- src/sentry_scope.c | 18 ++---------------- src/sentry_scope.h | 4 ---- src/sentry_tracing.c | 4 +--- src/sentry_tracing.h | 1 - src/sentry_uuid.c | 10 ++++------ src/sentry_uuid.h | 3 --- src/sentry_value.c | 2 -- src/sentry_value.h | 2 -- tests/unit/CMakeLists.txt | 1 - tests/unit/fuzz.c | 2 -- tests/unit/test_attachments.c | 1 - tests/unit/test_basic.c | 1 - tests/unit/test_concurrency.c | 2 +- tests/unit/test_consent.c | 1 - tests/unit/test_envelopes.c | 1 - tests/unit/test_failures.c | 1 - tests/unit/test_fuzzfailures.c | 2 +- tests/unit/test_logger.c | 1 - tests/unit/test_modulefinder.c | 1 - tests/unit/test_mpack.c | 1 - tests/unit/test_path.c | 1 - tests/unit/test_ratelimiter.c | 1 - tests/unit/test_session.c | 1 - tests/unit/test_slice.c | 1 - tests/unit/test_symbolizer.c | 1 - tests/unit/test_uninit.c | 1 - tests/unit/test_unwinder.c | 1 - tests/unit/test_utils.c | 1 - tests/unit/test_uuid.c | 4 ++-- tests/unit/test_value.c | 1 - 41 files changed, 24 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7a13d4ec..8d8a881854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features**: + +- Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. + ## 0.4.15 **Fixes**: diff --git a/CMakeLists.txt b/CMakeLists.txt index 08522006ce..5bd67acf8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,8 +559,6 @@ if(SENTRY_BUILD_EXAMPLES) add_executable(sentry_example examples/example.c) target_link_libraries(sentry_example PRIVATE sentry) - target_compile_definitions(sentry_example PRIVATE SENTRY_PERFORMANCE_MONITORING) - if(MSVC) target_compile_options(sentry_example PRIVATE $) endif() diff --git a/examples/example.c b/examples/example.c index 93065580de..de086ea2ed 100644 --- a/examples/example.c +++ b/examples/example.c @@ -93,7 +93,6 @@ main(int argc, char **argv) options, sentry_transport_new(print_envelope)); } -#ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { sentry_options_set_traces_sample_rate(options, 1.0); } @@ -101,7 +100,6 @@ main(int argc, char **argv) if (has_arg(argc, argv, "child-spans")) { sentry_options_set_max_spans(options, 5); } -#endif sentry_init(options); @@ -218,7 +216,6 @@ main(int argc, char **argv) sentry_capture_event(event); } -#ifdef SENTRY_PERFORMANCE_MONITORING if (has_arg(argc, argv, "capture-transaction")) { sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new("little.teapot", @@ -253,7 +250,6 @@ main(int argc, char **argv) sentry_transaction_finish(tx); } -#endif // make sure everything flushes sentry_close(); diff --git a/include/sentry.h b/include/sentry.h index 307b782f2d..10b12ae006 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -561,7 +561,6 @@ SENTRY_API void sentry_envelope_free(sentry_envelope_t *envelope); SENTRY_API sentry_value_t sentry_envelope_get_event( const sentry_envelope_t *envelope); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Given an Envelope, returns the embedded Transaction if there is one. * @@ -569,7 +568,6 @@ SENTRY_API sentry_value_t sentry_envelope_get_event( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_envelope_get_transaction( const sentry_envelope_t *envelope); -#endif /** * Serializes the envelope. @@ -1137,9 +1135,9 @@ SENTRY_API sentry_user_consent_t sentry_user_consent_get(void); /** * Sends a sentry event. * - * If SENTRY_PERFORMANCE_MONITORING is enabled, returns a nil UUID if the event - * being passed in is a transaction, and the transaction will not be sent nor - * consumed. `sentry_transaction_finish` should be used to send transactions. + * If returns a nil UUID if the event being passed in is a transaction, and the + * transaction will not be sent nor consumed. `sentry_transaction_finish` should + * be used to send transactions. */ SENTRY_API sentry_uuid_t sentry_capture_event(sentry_value_t event); @@ -1229,7 +1227,6 @@ SENTRY_API void sentry_start_session(void); */ SENTRY_API void sentry_end_session(void); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Sets the maximum number of spans that can be attached to a * transaction. @@ -1708,8 +1705,6 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( sentry_transaction_t *tx, sentry_iter_headers_function_t callback, void *userdata); -#endif - #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22fa09f227..b5161c3fc2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,7 +153,6 @@ if(SENTRY_INTEGRATION_QT) endif() if(SENTRY_BUILD_EXAMPLES) - target_compile_definitions(sentry PRIVATE SENTRY_PERFORMANCE_MONITORING) sentry_target_sources_cwd(sentry sentry_tracing.c sentry_tracing.h diff --git a/src/sentry_core.c b/src/sentry_core.c index 7ab4706145..fd90d71049 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -16,6 +16,7 @@ #include "sentry_session.h" #include "sentry_string.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_transport.h" #include "sentry_value.h" @@ -23,10 +24,6 @@ # include "integrations/sentry_integration_qt.h" #endif -#ifdef SENTRY_PERFORMANCE_MONITORING -# include "sentry_tracing.h" -#endif - static sentry_options_t *g_options = NULL; static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; @@ -367,27 +364,21 @@ event_is_considered_error(sentry_value_t event) return false; } -#ifdef SENTRY_PERFORMANCE_MONITORING bool sentry__event_is_transaction(sentry_value_t event) { sentry_value_t event_type = sentry_value_get_by_key(event, "type"); return sentry__string_eq("transaction", sentry_value_as_string(event_type)); } -#endif sentry_uuid_t sentry_capture_event(sentry_value_t event) { -#ifdef SENTRY_PERFORMANCE_MONITORING if (sentry__event_is_transaction(event)) { return sentry_uuid_nil(); } else { return sentry__capture_event(event); } -#else - return sentry__capture_event(event); -#endif } sentry_uuid_t @@ -401,15 +392,11 @@ sentry__capture_event(sentry_value_t event) SENTRY_WITH_OPTIONS (options) { was_captured = true; -#ifdef SENTRY_PERFORMANCE_MONITORING if (sentry__event_is_transaction(event)) { envelope = sentry__prepare_transaction(options, event, &event_id); } else { envelope = sentry__prepare_event(options, event, &event_id); } -#else - envelope = sentry__prepare_event(options, event, &event_id); -#endif if (envelope) { if (options->session) { sentry_options_t *mut_options = sentry__options_lock(); @@ -439,7 +426,6 @@ sentry__roll_dice(double probability) || ((double)rnd / (double)UINT64_MAX) <= probability; } -#ifdef SENTRY_PERFORMANCE_MONITORING bool sentry__should_send_transaction(sentry_value_t tx_cxt) { @@ -456,7 +442,6 @@ sentry__should_send_transaction(sentry_value_t tx_cxt) } return send; } -#endif sentry_envelope_t * sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, @@ -524,7 +509,6 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, return NULL; } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_envelope_t * sentry__prepare_transaction(const sentry_options_t *options, sentry_value_t transaction, sentry_uuid_t *event_id) @@ -555,7 +539,6 @@ sentry__prepare_transaction(const sentry_options_t *options, sentry_value_decref(transaction); return NULL; } -#endif void sentry_handle_exception(const sentry_ucontext_t *uctx) @@ -723,11 +706,9 @@ sentry_set_transaction(const char *transaction) sentry_free(scope->transaction); scope->transaction = sentry__string_clone(transaction); -#ifdef SENTRY_PERFORMANCE_MONITORING if (scope->transaction_object) { sentry_transaction_set_name(scope->transaction_object, transaction); } -#endif } } @@ -739,7 +720,6 @@ sentry_set_level(sentry_level_t level) } } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_transaction_t * sentry_transaction_start( sentry_transaction_context_t *opaque_tx_cxt, sentry_value_t sampling_ctx) @@ -1019,4 +999,3 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry__span_free(opaque_span); return; } -#endif diff --git a/src/sentry_core.h b/src/sentry_core.h index 90377d5738..18037fbf03 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -29,13 +29,11 @@ */ bool sentry__should_skip_upload(void); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Given a well-formed event, returns whether an event is a transaction or not. * Defaults to false, which will also be returned if the event is malformed. */ bool sentry__event_is_transaction(sentry_value_t event); -#endif /** * Convert the given event into an envelope. This assumes that the event @@ -60,7 +58,6 @@ sentry_envelope_t *sentry__prepare_event(const sentry_options_t *options, */ sentry_uuid_t sentry__capture_event(sentry_value_t event); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Convert the given transaction into an envelope. This assumes that the * event being passed in is a transaction. @@ -78,7 +75,6 @@ sentry_uuid_t sentry__capture_event(sentry_value_t event); */ sentry_envelope_t *sentry__prepare_transaction(const sentry_options_t *options, sentry_value_t transaction, sentry_uuid_t *event_id); -#endif /** * This function will submit the `envelope` to the given `transport`, first @@ -122,9 +118,7 @@ void sentry__options_unlock(void); // these for now are only needed outside of core for tests #ifdef SENTRY_UNITTEST bool sentry__roll_dice(double probability); -# ifdef SENTRY_PERFORMANCE_MONITORING bool sentry__should_send_transaction(sentry_value_t tx_cxt); -# endif #endif #endif diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 4e13669bb4..cfcf5af824 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -201,22 +201,15 @@ sentry_envelope_get_event(const sentry_envelope_t *envelope) } for (size_t i = 0; i < envelope->contents.items.item_count; i++) { -#ifdef SENTRY_PERFORMANCE_MONITORING if (!sentry_value_is_null(envelope->contents.items.items[i].event) && !sentry__event_is_transaction( envelope->contents.items.items[i].event)) { return envelope->contents.items.items[i].event; } -#else - if (!sentry_value_is_null(envelope->contents.items.items[i].event)) { - return envelope->contents.items.items[i].event; - } -#endif } return sentry_value_new_null(); } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_value_t sentry_envelope_get_transaction(const sentry_envelope_t *envelope) { @@ -232,7 +225,6 @@ sentry_envelope_get_transaction(const sentry_envelope_t *envelope) } return sentry_value_new_null(); } -#endif sentry_envelope_item_t * sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) @@ -264,7 +256,6 @@ sentry__envelope_add_event(sentry_envelope_t *envelope, sentry_value_t event) return item; } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_envelope_item_t * sentry__envelope_add_transaction( sentry_envelope_t *envelope, sentry_value_t transaction) @@ -293,17 +284,16 @@ sentry__envelope_add_transaction( sentry_value_incref(event_id); sentry__envelope_set_header(envelope, "event_id", event_id); -# ifdef SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST sentry_value_t now = sentry_value_new_string("2021-12-16T05:53:59.343Z"); -# else +#else sentry_value_t now = sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time())); -# endif +#endif sentry__envelope_set_header(envelope, "sent_at", now); return item; } -#endif sentry_envelope_item_t * sentry__envelope_add_session( diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index e5b11295f9..b5a8f1ab09 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -36,13 +36,11 @@ sentry_uuid_t sentry__envelope_get_event_id(const sentry_envelope_t *envelope); sentry_envelope_item_t *sentry__envelope_add_event( sentry_envelope_t *envelope, sentry_value_t event); -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Add a transaction to this envelope. */ sentry_envelope_item_t *sentry__envelope_add_transaction( sentry_envelope_t *envelope, sentry_value_t transaction); -#endif /** * Add a session to this envelope. diff --git a/src/sentry_options.c b/src/sentry_options.c index e74c88f292..43531118e2 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -51,11 +51,8 @@ sentry_options_new(void) opts->sample_rate = 1.0; opts->refcount = 1; opts->shutdown_timeout = SENTRY_DEFAULT_SHUTDOWN_TIMEOUT; - -#ifdef SENTRY_PERFORMANCE_MONITORING opts->traces_sample_rate = 0.0; opts->max_spans = 0; -#endif return opts; } @@ -378,7 +375,6 @@ sentry_options_set_database_pathw(sentry_options_t *opts, const wchar_t *path) } #endif -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Sets the maximum number of spans that can be attached to a * transaction. @@ -429,4 +425,3 @@ sentry_options_get_traces_sample_rate(sentry_options_t *opts) { return opts->traces_sample_rate; } -#endif diff --git a/src/sentry_options.h b/src/sentry_options.h index ba75e2ee21..1ec3817c7b 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -55,11 +55,9 @@ typedef struct sentry_options_s { sentry_event_function_t before_send_func; void *before_send_data; -#ifdef SENTRY_PERFORMANCE_MONITORING /* Experimentally exposed */ double traces_sample_rate; size_t max_spans; -#endif /* everything from here on down are options which are stored here but not exposed through the options API */ diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 61e83550f1..321b8f41e6 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1,5 +1,4 @@ #include "sentry_scope.h" -#include "sentry.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -8,6 +7,7 @@ #include "sentry_string.h" #include "sentry_symbolizer.h" #include "sentry_sync.h" +#include "sentry_tracing.h" #include "sentry_value.h" #include @@ -20,10 +20,6 @@ # define SENTRY_BACKEND "inproc" #endif -#ifdef SENTRY_PERFORMANCE_MONITORING -# include "sentry_tracing.h" -#endif - static bool g_scope_initialized = false; static sentry_scope_t g_scope = { 0 }; static sentry_mutex_t g_lock = SENTRY__MUTEX_INIT; @@ -80,11 +76,8 @@ get_scope(void) g_scope.breadcrumbs = sentry_value_new_list(); g_scope.level = SENTRY_LEVEL_ERROR; g_scope.client_sdk = get_client_sdk(); - -#ifdef SENTRY_PERFORMANCE_MONITORING g_scope.transaction_object = NULL; g_scope.span = NULL; -#endif g_scope_initialized = true; @@ -105,11 +98,8 @@ sentry__scope_cleanup(void) sentry_value_decref(g_scope.contexts); sentry_value_decref(g_scope.breadcrumbs); sentry_value_decref(g_scope.client_sdk); - -#ifdef SENTRY_PERFORMANCE_MONITORING sentry__transaction_decref(g_scope.transaction_object); sentry__span_decref(g_scope.span); -#endif } sentry__mutex_unlock(&g_lock); } @@ -241,7 +231,6 @@ sentry__symbolize_stacktrace(sentry_value_t stacktrace) } } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_value_t sentry__get_span_or_transaction(const sentry_scope_t *scope) { @@ -254,7 +243,7 @@ sentry__get_span_or_transaction(const sentry_scope_t *scope) } } -# ifdef SENTRY_UNITTEST +#ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction() { @@ -263,7 +252,6 @@ sentry__scope_get_span_or_transaction() } return sentry_value_new_null(); } -# endif #endif void @@ -328,7 +316,6 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, sentry_value_t contexts = sentry__value_clone(scope->contexts); -#ifdef SENTRY_PERFORMANCE_MONITORING // prep contexts sourced from scope; data about transaction on scope needs // to be extracted and inserted sentry_value_t scope_trace = sentry__value_get_trace_context( @@ -339,7 +326,6 @@ sentry__scope_apply_to_event(const sentry_scope_t *scope, } sentry_value_set_by_key(contexts, "trace", scope_trace); } -#endif // merge contexts sourced from scope into the event sentry_value_t event_contexts = sentry_value_get_by_key(event, "contexts"); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index cde4597514..207b1a9957 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -20,7 +20,6 @@ typedef struct sentry_scope_s { sentry_level_t level; sentry_value_t client_sdk; -#ifdef SENTRY_PERFORMANCE_MONITORING // The span attached to this scope, if any. // // Conceptually, every transaction is a span, so it should be possible to @@ -31,7 +30,6 @@ typedef struct sentry_scope_s { // `name` property nested in transaction_object or span. sentry_transaction_t *transaction_object; sentry_span_t *span; -#endif } sentry_scope_t; /** @@ -98,9 +96,7 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, #endif -#ifdef SENTRY_PERFORMANCE_MONITORING // this is only used in unit tests #ifdef SENTRY_UNITTEST sentry_value_t sentry__scope_get_span_or_transaction(); #endif -#endif diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index d30785f380..49b607f1eb 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -1,9 +1,7 @@ -#include "sentry_boot.h" - +#include "sentry_tracing.h" #include "sentry_alloc.h" #include "sentry_logger.h" #include "sentry_string.h" -#include "sentry_tracing.h" #include "sentry_utils.h" #include "sentry_value.h" #include diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index 16c15f77c8..bccc9bc78c 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -1,7 +1,6 @@ #ifndef SENTRY_TRACING_H_INCLUDED #define SENTRY_TRACING_H_INCLUDED -#include "sentry_boot.h" #include "sentry_value.h" /** diff --git a/src/sentry_uuid.c b/src/sentry_uuid.c index aefa4c4f61..ad5e349996 100644 --- a/src/sentry_uuid.c +++ b/src/sentry_uuid.c @@ -102,28 +102,26 @@ sentry_uuid_as_string(const sentry_uuid_t *uuid, char str[37]) #undef B } -#ifdef SENTRY_PERFORMANCE_MONITORING void sentry__internal_uuid_as_string(const sentry_uuid_t *uuid, char str[37]) { -# define B(X) (unsigned char)uuid->bytes[X] +#define B(X) (unsigned char)uuid->bytes[X] snprintf(str, 33, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%" "02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7), B(8), B(9), B(10), B(11), B(12), B(13), B(14), B(15)); -# undef B +#undef B } void sentry__span_uuid_as_string(const sentry_uuid_t *uuid, char str[17]) { -# define B(X) (unsigned char)uuid->bytes[X] +#define B(X) (unsigned char)uuid->bytes[X] snprintf(str, 17, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", B(0), B(1), B(2), B(3), B(4), B(5), B(6), B(7)); -# undef B +#undef B } -#endif #ifdef SENTRY_PLATFORM_WINDOWS sentry_uuid_t diff --git a/src/sentry_uuid.h b/src/sentry_uuid.h index e2e219f86f..c16b943fa9 100644 --- a/src/sentry_uuid.h +++ b/src/sentry_uuid.h @@ -3,7 +3,6 @@ #include "sentry_boot.h" -#ifdef SENTRY_PERFORMANCE_MONITORING /** * Converts a sentry UUID to a string representation used for internal * sentry UUIDs such as event IDs. @@ -22,5 +21,3 @@ void sentry__span_uuid_as_string(const sentry_uuid_t *uuid, char str[17]); */ sentry_uuid_t sentry__uuid_from_native(const GUID *guid); #endif - -#endif diff --git a/src/sentry_value.c b/src/sentry_value.c index cbb367df9d..b8757a538e 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1009,7 +1009,6 @@ sentry__value_new_hexstring(const uint8_t *bytes, size_t len) return sentry__value_new_string_owned(buf); } -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_value_t sentry__value_new_span_uuid(const sentry_uuid_t *uuid) { @@ -1033,7 +1032,6 @@ sentry__value_new_internal_uuid(const sentry_uuid_t *uuid) buf[32] = '\0'; return sentry__value_new_string_owned(buf); } -#endif sentry_value_t sentry__value_new_uuid(const sentry_uuid_t *uuid) diff --git a/src/sentry_value.h b/src/sentry_value.h index 7dfa6eb8c2..437789940b 100644 --- a/src/sentry_value.h +++ b/src/sentry_value.h @@ -118,9 +118,7 @@ typedef struct sentry_jsonwriter_s sentry_jsonwriter_t; void sentry__jsonwriter_write_value( sentry_jsonwriter_t *jw, sentry_value_t value); -#ifdef SENTRY_PERFORMANCE_MONITORING sentry_value_t sentry__value_new_span_uuid(const sentry_uuid_t *uuid); sentry_value_t sentry__value_new_internal_uuid(const sentry_uuid_t *uuid); #endif -#endif diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a2791005e0..db18b0f95c 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -83,7 +83,6 @@ endif() target_compile_definitions(sentry PRIVATE SIZEOF_LONG=${CMAKE_SIZEOF_LONG}) target_compile_definitions(sentry_test_unit PRIVATE SENTRY_UNITTEST) -target_compile_definitions(sentry_test_unit PRIVATE SENTRY_PERFORMANCE_MONITORING) add_test(NAME sentry_test_unit COMMAND sentry_test_unit) diff --git a/tests/unit/fuzz.c b/tests/unit/fuzz.c index 1b79da7e56..b0de75e3e4 100644 --- a/tests/unit/fuzz.c +++ b/tests/unit/fuzz.c @@ -24,8 +24,6 @@ afl-fuzz -i fuzzing-examples -o fuzzing-results -- fuzzing/sentry_fuzz_json @@ # define _CRT_SECURE_NO_WARNINGS #endif -#include "sentry.h" - #include #include diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 4b3a6bbdda..bd4d2d065d 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -2,7 +2,6 @@ #include "sentry_path.h" #include "sentry_string.h" #include "sentry_testsupport.h" -#include typedef struct { uint64_t called; diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 342ef00372..dc85df9263 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -1,6 +1,5 @@ #include "sentry_core.h" #include "sentry_testsupport.h" -#include static void send_envelope_test_basic(const sentry_envelope_t *envelope, void *data) diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index 83f7802403..946081dba5 100644 --- a/tests/unit/test_concurrency.c +++ b/tests/unit/test_concurrency.c @@ -1,6 +1,6 @@ #include "sentry_core.h" #include "sentry_testsupport.h" -#include + #include static void diff --git a/tests/unit/test_consent.c b/tests/unit/test_consent.c index 2d1845fb21..f26af345bd 100644 --- a/tests/unit/test_consent.c +++ b/tests/unit/test_consent.c @@ -1,6 +1,5 @@ #include "sentry_path.h" #include "sentry_testsupport.h" -#include static void init_consenting_sentry(void) diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 2554be1c73..2e6128a9cb 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -3,7 +3,6 @@ #include "sentry_transport.h" #include "sentry_utils.h" #include "sentry_value.h" -#include SENTRY_TEST(basic_http_request_preparation_for_event) { diff --git a/tests/unit/test_failures.c b/tests/unit/test_failures.c index 96483b316c..4d85957153 100644 --- a/tests/unit/test_failures.c +++ b/tests/unit/test_failures.c @@ -1,6 +1,5 @@ #include "sentry_core.h" #include "sentry_testsupport.h" -#include static int transport_startup_fail( diff --git a/tests/unit/test_fuzzfailures.c b/tests/unit/test_fuzzfailures.c index 109873785d..38b4b237d3 100644 --- a/tests/unit/test_fuzzfailures.c +++ b/tests/unit/test_fuzzfailures.c @@ -2,7 +2,7 @@ #include "sentry_path.h" #include "sentry_testsupport.h" #include "sentry_value.h" -#include + #include static void diff --git a/tests/unit/test_logger.c b/tests/unit/test_logger.c index 18948f2bf5..caec1055e7 100644 --- a/tests/unit/test_logger.c +++ b/tests/unit/test_logger.c @@ -1,7 +1,6 @@ #include "sentry_core.h" #include "sentry_logger.h" #include "sentry_testsupport.h" -#include typedef struct { uint64_t called; diff --git a/tests/unit/test_modulefinder.c b/tests/unit/test_modulefinder.c index 90fc31c066..38440406b6 100644 --- a/tests/unit/test_modulefinder.c +++ b/tests/unit/test_modulefinder.c @@ -1,6 +1,5 @@ #include "sentry_path.h" #include "sentry_testsupport.h" -#include #ifdef SENTRY_PLATFORM_LINUX # include "modulefinder/sentry_modulefinder_linux.h" diff --git a/tests/unit/test_mpack.c b/tests/unit/test_mpack.c index 391a3d892c..28b5d3f87b 100644 --- a/tests/unit/test_mpack.c +++ b/tests/unit/test_mpack.c @@ -1,7 +1,6 @@ #include "sentry_path.h" #include "sentry_scope.h" #include "sentry_testsupport.h" -#include SENTRY_TEST(mpack_removed_tags) { diff --git a/tests/unit/test_path.c b/tests/unit/test_path.c index c165b47b8f..3f25d77a52 100644 --- a/tests/unit/test_path.c +++ b/tests/unit/test_path.c @@ -1,7 +1,6 @@ #include "sentry_path.h" #include "sentry_string.h" #include "sentry_testsupport.h" -#include SENTRY_TEST(recursive_paths) { diff --git a/tests/unit/test_ratelimiter.c b/tests/unit/test_ratelimiter.c index c3d596e3ca..26c42afe83 100644 --- a/tests/unit/test_ratelimiter.c +++ b/tests/unit/test_ratelimiter.c @@ -1,7 +1,6 @@ #include "sentry_ratelimiter.h" #include "sentry_testsupport.h" #include "sentry_utils.h" -#include SENTRY_TEST(rate_limit_parsing) { diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index 5864526f1c..3fa4d9766e 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -2,7 +2,6 @@ #include "sentry_session.h" #include "sentry_testsupport.h" #include "sentry_value.h" -#include static void send_envelope(const sentry_envelope_t *envelope, void *data) diff --git a/tests/unit/test_slice.c b/tests/unit/test_slice.c index af149340d6..5052929dc7 100644 --- a/tests/unit/test_slice.c +++ b/tests/unit/test_slice.c @@ -1,6 +1,5 @@ #include "sentry_slice.h" #include "sentry_testsupport.h" -#include SENTRY_TEST(slice) { diff --git a/tests/unit/test_symbolizer.c b/tests/unit/test_symbolizer.c index 9c18fea7cb..0a2f42b456 100644 --- a/tests/unit/test_symbolizer.c +++ b/tests/unit/test_symbolizer.c @@ -1,6 +1,5 @@ #include "sentry_symbolizer.h" #include "sentry_testsupport.h" -#include TEST_VISIBLE void test_function(void) diff --git a/tests/unit/test_uninit.c b/tests/unit/test_uninit.c index 2cd0859d1f..bf9577ce76 100644 --- a/tests/unit/test_uninit.c +++ b/tests/unit/test_uninit.c @@ -1,5 +1,4 @@ #include "sentry_testsupport.h" -#include SENTRY_TEST(uninitialized) { diff --git a/tests/unit/test_unwinder.c b/tests/unit/test_unwinder.c index a5982ff7ce..b3ac88e0e2 100644 --- a/tests/unit/test_unwinder.c +++ b/tests/unit/test_unwinder.c @@ -1,6 +1,5 @@ #include "sentry_symbolizer.h" #include "sentry_testsupport.h" -#include #define MAX_FRAMES 128 diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index bdc17f2aae..a8a6f26bf4 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -2,7 +2,6 @@ #include "sentry_testsupport.h" #include "sentry_utils.h" #include "sentry_value.h" -#include #ifdef SENTRY_PLATFORM_UNIX # include "sentry_unix_pageallocator.h" diff --git a/tests/unit/test_uuid.c b/tests/unit/test_uuid.c index 6be5044f37..2ff38ebd38 100644 --- a/tests/unit/test_uuid.c +++ b/tests/unit/test_uuid.c @@ -1,6 +1,6 @@ #include "sentry_testsupport.h" -#include -#include + +#include "sentry_uuid.h" SENTRY_TEST(uuid_api) { diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 46170b1450..98687b1d37 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -3,7 +3,6 @@ #include "sentry_value.h" #include #include -#include SENTRY_TEST(value_null) { From 4d9668ec7748fcb59bc99e38a9cbff3620c2e155 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 15 Feb 2022 10:05:24 +0100 Subject: [PATCH 032/207] feat: Set special `SENTRY_SDK_NAME` for Android (#677) --- include/sentry.h | 6 +++++- tests/assertions.py | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 10b12ae006..bfd3650535 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -23,7 +23,11 @@ extern "C" { #endif /* SDK Version */ -#define SENTRY_SDK_NAME "sentry.native" +#ifdef __ANDROID__ +# define SENTRY_SDK_NAME "sentry.native.android" +#else +# define SENTRY_SDK_NAME "sentry.native" +#endif #define SENTRY_SDK_VERSION "0.4.15" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION diff --git a/tests/assertions.py b/tests/assertions.py index acba4558ee..bc956e199b 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -59,7 +59,9 @@ def assert_meta( {"name": "github:getsentry/sentry-native", "version": "0.4.15"}, ], } - if not is_android: + if is_android: + expected_sdk["name"] = "sentry.native.android" + else: if sys.platform == "win32": assert_matches( event["contexts"]["os"], From e3c4fc99e3f4f2a3d6f3ec1db786e209b14f6639 Mon Sep 17 00:00:00 2001 From: zhaowq32 Date: Mon, 28 Feb 2022 21:16:26 +0800 Subject: [PATCH 033/207] fix: Compile correct files for iOS breakpad exception_handler (#683) --- external/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 569ccb18cc..24bfe01860 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -91,13 +91,13 @@ set(BREAKPAD_SOURCES_CLIENT_APPLE breakpad/src/client/mac/handler/breakpad_nlist_64.h breakpad/src/client/mac/handler/dynamic_images.cc breakpad/src/client/mac/handler/dynamic_images.h - breakpad/src/client/mac/handler/exception_handler.cc - breakpad/src/client/mac/handler/exception_handler.h breakpad/src/client/mac/handler/minidump_generator.cc breakpad/src/client/mac/handler/minidump_generator.h ) set(BREAKPAD_SOURCES_CLIENT_MAC + breakpad/src/client/mac/handler/exception_handler.cc + breakpad/src/client/mac/handler/exception_handler.h breakpad/src/client/mac/crash_generation/crash_generation_client.cc breakpad/src/client/mac/crash_generation/crash_generation_client.h ) From 47d0156dd012ae2f0e1fac4d972e13ae792ebf66 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 8 Mar 2022 10:24:03 +0100 Subject: [PATCH 034/207] chore: list the libraries required to run tests (#684) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 005c304351..655543465c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,7 @@ Building and testing `sentry-native` currently requires the following tools: - **CMake** and a supported C/C++ compiler, to actually build the code. - **python** and **pytest**, to run integration tests. - **clang-format** and **black**, to format the C/C++ and python code respectively. +- **curl** and **zlib** libraries (e.g. on Ubuntu: libcurl4-openssl-dev, libz-dev) `pytest` and `black` are installed as virtualenv dependencies automatically. From 15a1229824d8192abd5b6e0da79c7572ab0f0ef2 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 9 Mar 2022 12:15:02 +0100 Subject: [PATCH 035/207] feat: SDK name override (#686) --- CHANGELOG.md | 1 + CMakeLists.txt | 7 +++++++ README.md | 6 +++++- include/sentry.h | 10 ++++++---- tests/assertions.py | 4 ++++ tests/test_integration_stdout.py | 22 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8a881854..d3f17eaf66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features**: - Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. +- Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. ## 0.4.15 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd67acf8c..06f6103446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,9 +151,12 @@ if(SENTRY_BACKEND_CRASHPAD AND ANDROID) message(FATAL_ERROR "The Crashpad backend is not currently supported on Android") endif() +set(SENTRY_SDK_NAME "" CACHE STRING "The SDK name to report when sending events.") + message(STATUS "SENTRY_TRANSPORT=${SENTRY_TRANSPORT}") message(STATUS "SENTRY_BACKEND=${SENTRY_BACKEND}") message(STATUS "SENTRY_LIBRARY_TYPE=${SENTRY_LIBRARY_TYPE}") +message(STATUS "SENTRY_SDK_NAME=${SENTRY_SDK_NAME}") if(ANDROID) set(SENTRY_WITH_LIBUNWINDSTACK TRUE) @@ -221,6 +224,10 @@ target_sources(sentry PRIVATE "${PROJECT_SOURCE_DIR}/include/sentry.h") add_library(sentry::sentry ALIAS sentry) add_subdirectory(src) +if (NOT SENTRY_SDK_NAME STREQUAL "") + target_compile_definitions(sentry PRIVATE SENTRY_SDK_NAME="${SENTRY_SDK_NAME}") +endif() + # we do not need this on android, only linux if(LINUX) target_sources(sentry PRIVATE diff --git a/README.md b/README.md index 0261cb3aa3..c072958a24 100644 --- a/README.md +++ b/README.md @@ -264,13 +264,17 @@ Legend: - `SENTRY_FOLDER` (Default: not defined): Sets the sentry-native projects folder name for generators which support project hierarchy (like Microsoft Visual Studio). - To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html) + To use this feature you need to enable hierarchy via [`USE_FOLDERS` property](https://cmake.org/cmake/help/latest/prop_gbl/USE_FOLDERS.html) - `CRASHPAD_ENABLE_STACKTRACE` (Default: OFF): This enables client-side stackwalking when using the crashpad backend. Stack unwinding will happen on the client's machine and the result will be submitted to Sentry attached to the generated minidump. Note that this feature is still experimental. +- `SENTRY_SDK_NAME` (Default: sentry.native or sentry.native.android): + Sets the SDK name that should be included in the reported events. If you're overriding this, make sure to also define + the same value using `target_compile_definitions()` on your own targets that include `sentry.h`. + ### Build Targets - `sentry`: This is the main library and the only default build target. diff --git a/include/sentry.h b/include/sentry.h index bfd3650535..428633ee99 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -23,10 +23,12 @@ extern "C" { #endif /* SDK Version */ -#ifdef __ANDROID__ -# define SENTRY_SDK_NAME "sentry.native.android" -#else -# define SENTRY_SDK_NAME "sentry.native" +#ifndef SENTRY_SDK_NAME +# ifdef __ANDROID__ +# define SENTRY_SDK_NAME "sentry.native.android" +# else +# define SENTRY_SDK_NAME "sentry.native" +# endif #endif #define SENTRY_SDK_VERSION "0.4.15" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION diff --git a/tests/assertions.py b/tests/assertions.py index bc956e199b..9e33e1d427 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -40,6 +40,7 @@ def assert_meta( release="test-example-release", integration=None, transaction="test-transaction", + sdk_override=None, ): event = envelope.get_event() @@ -94,6 +95,9 @@ def assert_meta( ) assert event["contexts"]["os"]["build"] is not None + if sdk_override != None: + expected_sdk["name"] = sdk_override + assert_matches(event, expected) assert_matches(event["sdk"], expected_sdk) assert_matches( diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index 8998fefbac..710dea63a8 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -42,6 +42,28 @@ def test_capture_stdout(cmake): assert_event(envelope) +def test_sdk_name_override(cmake): + sdk_name = "cUsToM.SDK" + tmp_path = cmake( + ["sentry_example"], + { + "SENTRY_BACKEND": "none", + "SENTRY_TRANSPORT": "none", + "SENTRY_SDK_NAME": sdk_name, + }, + ) + + output = check_output( + tmp_path, + "sentry_example", + ["stdout", "capture-event"], + ) + envelope = Envelope.deserialize(output) + + assert_meta(envelope, sdk_override=sdk_name) + assert_event(envelope) + + @pytest.mark.skipif(not has_files, reason="test needs a local filesystem") def test_multi_process(cmake): # NOTE: It would have been nice to do *everything* in a unicode-named From 9eecb1bdf2c7762ba7babe9ea437545a1f759bdc Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 9 Mar 2022 12:30:43 +0100 Subject: [PATCH 036/207] Feat: Crashed last run (#685) --- CHANGELOG.md | 1 + include/sentry.h | 28 +++++++++++++++++ src/sentry_core.c | 22 +++++++++++++ src/sentry_database.c | 35 ++++++++++++++++++++- src/sentry_database.h | 10 ++++++ tests/unit/test_basic.c | 69 +++++++++++++++++++++++++++++++++++++++++ tests/unit/tests.inc | 2 ++ 7 files changed, 166 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f17eaf66..e5adf15724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features**: - Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. +- New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. ## 0.4.15 diff --git a/include/sentry.h b/include/sentry.h index 428633ee99..635eb437e2 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1711,6 +1711,34 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( sentry_transaction_t *tx, sentry_iter_headers_function_t callback, void *userdata); +/** + * Returns whether the application has crashed on the last run. + * + * Notes: + * * The underlying value is set by sentry_init() - it must be called first. + * * Call sentry_clear_crashed_last_run() to reset for the next app run. + * + * Possible return values: + * 1 = the last run was a crash + * 0 = no crash recognized + * -1 = sentry_init() hasn't been called yet + */ +SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(); + +/** + * Clear the status of the "crashed-last-run". You should explicitly call + * this after sentry_init() if you're using sentry_get_crashed_last_run(). + * Otherwise, the same information is reported on any subsequent runs. + * + * Notes: + * * This doesn't change the value of sentry_get_crashed_last_run() yet. + * However, if sentry_init() is called again, the value will change. + * * This may only be called after sentry_init() and before sentry_close(). + * + * Returns 0 on success, 1 on error. + */ +SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(); + #ifdef __cplusplus } #endif diff --git a/src/sentry_core.c b/src/sentry_core.c index fd90d71049..6bcdf8fe13 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -27,6 +27,9 @@ static sentry_options_t *g_options = NULL; static sentry_mutex_t g_options_lock = SENTRY__MUTEX_INIT; +/// see sentry_get_crashed_last_run() for the possible values +static int g_last_crash = -1; + const sentry_options_t * sentry__options_getref(void) { @@ -158,6 +161,7 @@ sentry_init(sentry_options_t *options) last_crash = backend->get_last_crash_func(backend); } + g_last_crash = sentry__has_crash_marker(options); g_options = options; // *after* setting the global options, trigger a scope and consent flush, @@ -999,3 +1003,21 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry__span_free(opaque_span); return; } + +int +sentry_get_crashed_last_run() +{ + return g_last_crash; +} + +int +sentry_clear_crashed_last_run() +{ + bool success = false; + sentry_options_t *options = sentry__options_lock(); + if (options) { + success = sentry__clear_crash_marker(options); + } + sentry__options_unlock(); + return success ? 0 : 1; +} diff --git a/src/sentry_database.c b/src/sentry_database.c index a2c1463580..62a619d8a6 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -235,6 +235,8 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) sentry__capture_envelope(options->transport, session_envelope); } +static const char *g_last_crash_filename = "last_crash"; + bool sentry__write_crash_marker(const sentry_options_t *options) { @@ -244,7 +246,7 @@ sentry__write_crash_marker(const sentry_options_t *options) } sentry_path_t *marker_path - = sentry__path_join_str(options->database_path, "last_crash"); + = sentry__path_join_str(options->database_path, g_last_crash_filename); if (!marker_path) { sentry_free(iso_time); return false; @@ -260,3 +262,34 @@ sentry__write_crash_marker(const sentry_options_t *options) } return !rv; } + +bool +sentry__has_crash_marker(const sentry_options_t *options) +{ + sentry_path_t *marker_path + = sentry__path_join_str(options->database_path, g_last_crash_filename); + if (!marker_path) { + return false; + } + + bool result = sentry__path_is_file(marker_path); + sentry__path_free(marker_path); + return result; +} + +bool +sentry__clear_crash_marker(const sentry_options_t *options) +{ + sentry_path_t *marker_path + = sentry__path_join_str(options->database_path, g_last_crash_filename); + if (!marker_path) { + return false; + } + + int rv = sentry__path_remove(marker_path); + sentry__path_free(marker_path); + if (rv) { + SENTRY_DEBUG("removing the crash timestamp file has failed"); + } + return !rv; +} diff --git a/src/sentry_database.h b/src/sentry_database.h index 086ebab8e2..2d99bedb04 100644 --- a/src/sentry_database.h +++ b/src/sentry_database.h @@ -75,4 +75,14 @@ void sentry__process_old_runs( */ bool sentry__write_crash_marker(const sentry_options_t *options); +/** + * This will check whether the `/last_crash` file exists. + */ +bool sentry__has_crash_marker(const sentry_options_t *options); + +/** + * This will remove the `/last_crash` file. + */ +bool sentry__clear_crash_marker(const sentry_options_t *options); + #endif diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index dc85df9263..ca257f59d6 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -1,5 +1,7 @@ #include "sentry_core.h" +#include "sentry_database.h" #include "sentry_testsupport.h" +#include "sentry_utils.h" static void send_envelope_test_basic(const sentry_envelope_t *envelope, void *data) @@ -96,3 +98,70 @@ SENTRY_TEST(sampling_before_send) // well, its random after all TEST_CHECK(called_beforesend > 50 && called_beforesend < 100); } + +SENTRY_TEST(crash_marker) +{ + sentry_options_t *options = sentry_options_new(); + + // clear returns true, regardless if the file exists + TEST_CHECK(sentry__clear_crash_marker(options)); + + // write should normally be true, even when called multiple times + TEST_CHECK(!sentry__has_crash_marker(options)); + TEST_CHECK(sentry__write_crash_marker(options)); + TEST_CHECK(sentry__has_crash_marker(options)); + TEST_CHECK(sentry__write_crash_marker(options)); + TEST_CHECK(sentry__has_crash_marker(options)); + + TEST_CHECK(sentry__clear_crash_marker(options)); + TEST_CHECK(!sentry__has_crash_marker(options)); + TEST_CHECK(sentry__clear_crash_marker(options)); + + sentry_options_free(options); +} + +SENTRY_TEST(crashed_last_run) +{ + // fails before init() is called + TEST_CHECK_INT_EQUAL(sentry_clear_crashed_last_run(), 1); + + // clear any leftover from previous test runs + sentry_options_t *options = sentry_options_new(); + TEST_CHECK(sentry__clear_crash_marker(options)); + sentry_options_free(options); + + // -1 before sentry_init() + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), -1); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + sentry_close(); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 0); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + // simulate a crash + TEST_CHECK(sentry__write_crash_marker(options)); + + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 1); + + // clear the status and re-init + TEST_CHECK_INT_EQUAL(sentry_clear_crashed_last_run(), 0); + + sentry_close(); + + // no change yet before sentry_init() is called + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 1); + + options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + TEST_CHECK_INT_EQUAL(sentry_init(options), 0); + sentry_close(); + + TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 0); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 6414d109e5..268010e225 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -15,6 +15,8 @@ XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) +XX(crash_marker) +XX(crashed_last_run) XX(custom_logger) XX(distributed_headers) XX(drop_unfinished_spans) From 7d2a4b6c333c5cd8e6d3bef8be1802a0be2aadc6 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Wed, 30 Mar 2022 12:48:26 -0400 Subject: [PATCH 037/207] ref/ci: Update black as well as Windows jobs to unbreak CI (#691, #692) This aggressively bumps black up to a new version to fix our linter jobs in CI. This is fine because the use of Python is exclusively confined to integration tests, which makes the impact of this update fairly small. This change also updates the CI to remove the job running tests on a Windows Server 2016 environment as it is now deprecated and can no longer be used. A job running the same tests on Windows Server 2019 has been created to replace the removed job, as `windows-latest` now uses Windows Server 2022. --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 3 +++ README.md | 4 ++-- tests/requirements.txt | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9971ebcc27..30c1902502 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,8 +75,8 @@ jobs: ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 RUN_ANALYZER: asan,llvm-cov - - name: Windows (VS2017, 32bit) - os: vs2017-win2016 + - name: Windows (VS2019) + os: windows-2019 TEST_X86: 1 - name: Windows (latest) os: windows-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index e5adf15724..facc746b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. +**Fixes**: +- Updated CI as well as list of supported platforms to reflect Windows Server 2016, and therefore MSVC 2017 losing active support. + ## 0.4.15 **Fixes**: diff --git a/README.md b/README.md index c072958a24..35071767b9 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ The SDK currently supports and is tested on the following OS/Compiler variations - 64bit Linux with GCC 9 - 64bit Linux with clang 9 - 32bit Linux with GCC 7 (cross compiled from 64bit host) -- 64bit Windows with MSVC 2019 -- 32bit Windows with MSVC 2017 +- 32bit Windows with MSVC 2019 +- 64bit Windows with MSVC 2022 - macOS Catalina with most recent Compiler toolchain - Android API29 built by NDK21 toolchain - Android API16 built by NDK19 toolchain diff --git a/tests/requirements.txt b/tests/requirements.txt index fcbf849f66..dd2fd18e86 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ -black==21.9b0 +black==22.3.0 pytest==6.2.5 pytest-httpserver==1.0.1 From f6eda12d7180e3801dc3ac9def8c3c61048137c4 Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Wed, 30 Mar 2022 17:59:30 -0400 Subject: [PATCH 038/207] ref: Switch the internal representation of project IDs from ints over to char*s (#690) This tries to future-proof project IDs as they aren't guaranteed to be numbers forever. Project IDs are now treated as opaque strings, and minimal validation is now applied to them to reflect that fact. --- CHANGELOG.md | 3 +++ src/sentry_utils.c | 18 ++++++++---------- src/sentry_utils.h | 2 +- tests/unit/test_utils.c | 30 ++++++++++++++++-------------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index facc746b3c..f8437350e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. +**Internal**: +- Project IDs are now treated as opaque strings instead of integer values. ([#690](https://github.com/getsentry/sentry-native/pull/690)) + **Fixes**: - Updated CI as well as list of supported platforms to reflect Windows Server 2016, and therefore MSVC 2017 losing active support. diff --git a/src/sentry_utils.c b/src/sentry_utils.c index 65f2a2415f..569fa08333 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -218,8 +218,7 @@ sentry__dsn_new(const char *raw_dsn) sentry_url_t url; memset(&url, 0, sizeof(sentry_url_t)); size_t path_len; - char *tmp; - char *end; + char *project_id; sentry_dsn_t *dsn = SENTRY_MAKE(sentry_dsn_t); if (!dsn) { @@ -255,16 +254,14 @@ sentry__dsn_new(const char *raw_dsn) path_len--; } - tmp = strrchr(url.path, '/'); - if (!tmp) { + project_id = strrchr(url.path, '/'); + if (!project_id || strlen(project_id + 1) == 0) { goto exit; } - dsn->project_id = (uint64_t)strtoll(tmp + 1, &end, 10); - if (end != tmp + strlen(tmp)) { - goto exit; - } - *tmp = 0; + dsn->project_id = sentry__string_clone(project_id + 1); + *project_id = 0; + dsn->path = url.path; url.path = NULL; @@ -299,6 +296,7 @@ sentry__dsn_decref(sentry_dsn_t *dsn) sentry_free(dsn->path); sentry_free(dsn->public_key); sentry_free(dsn->secret_key); + sentry_free(dsn->project_id); sentry_free(dsn); } } @@ -329,7 +327,7 @@ init_string_builder_for_url(sentry_stringbuilder_t *sb, const sentry_dsn_t *dsn) sentry__stringbuilder_append_int64(sb, (int64_t)dsn->port); sentry__stringbuilder_append(sb, dsn->path); sentry__stringbuilder_append(sb, "/api/"); - sentry__stringbuilder_append_int64(sb, (int64_t)dsn->project_id); + sentry__stringbuilder_append(sb, dsn->project_id); } char * diff --git a/src/sentry_utils.h b/src/sentry_utils.h index cb79c08f1e..8b19e65c26 100644 --- a/src/sentry_utils.h +++ b/src/sentry_utils.h @@ -49,7 +49,7 @@ typedef struct sentry_dsn_s { char *path; char *secret_key; char *public_key; - uint64_t project_id; + char *project_id; int port; long refcount; bool is_valid; diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index a8a6f26bf4..f280f59f71 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -71,7 +71,7 @@ SENTRY_TEST(url_parsing_invalid) SENTRY_TEST(dsn_parsing_complete) { sentry_dsn_t *dsn = sentry__dsn_new( - "http://username:password@example.com/foo/bar/42?x=y#z"); + "http://username:password@example.com/foo/bar/42%21?x=y#z"); TEST_CHECK(!!dsn); if (!dsn) { return; @@ -83,10 +83,10 @@ SENTRY_TEST(dsn_parsing_complete) TEST_CHECK_STRING_EQUAL(dsn->public_key, "username"); TEST_CHECK_STRING_EQUAL(dsn->secret_key, "password"); TEST_CHECK_STRING_EQUAL(dsn->path, "/foo/bar"); - TEST_CHECK_INT_EQUAL((int)dsn->project_id, 42); + TEST_CHECK_STRING_EQUAL(dsn->project_id, "42%21"); sentry__dsn_decref(dsn); - dsn = sentry__dsn_new("https://username@example.com/42"); + dsn = sentry__dsn_new("https://username@example.com/42%21"); TEST_CHECK(!!dsn); if (!dsn) { return; @@ -97,22 +97,24 @@ SENTRY_TEST(dsn_parsing_complete) TEST_CHECK_STRING_EQUAL(dsn->public_key, "username"); TEST_CHECK(!dsn->secret_key); TEST_CHECK_STRING_EQUAL(dsn->path, ""); - TEST_CHECK_INT_EQUAL((int)dsn->project_id, 42); + TEST_CHECK_STRING_EQUAL(dsn->project_id, "42%21"); sentry__dsn_decref(dsn); -} -SENTRY_TEST(dsn_parsing_invalid) -{ - sentry_dsn_t *dsn - = sentry__dsn_new("http://username:password@example.com/foo/bar?x=y#z"); + dsn = sentry__dsn_new("https://username@example.com/pathone/pathtwo/42%21"); TEST_CHECK(!!dsn); - if (dsn) { - TEST_CHECK(!dsn->is_valid); - sentry__dsn_decref(dsn); + if (!dsn) { + return; } + TEST_CHECK(dsn->is_valid); + TEST_CHECK_STRING_EQUAL(dsn->path, "/pathone/pathtwo"); + TEST_CHECK_STRING_EQUAL(dsn->project_id, "42%21"); + sentry__dsn_decref(dsn); +} - dsn = sentry__dsn_new("=https://foo@bar.ingest.sentry.io/" - "1234567"); +SENTRY_TEST(dsn_parsing_invalid) +{ + sentry_dsn_t *dsn = sentry__dsn_new("=https://foo@bar.ingest.sentry.io/" + "1234567"); TEST_CHECK(!!dsn); if (dsn) { TEST_CHECK(!dsn->is_valid); From b4fd39bde1d902f89eeaeed161efc2749c5adc3f Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 12 Apr 2022 13:44:31 +0200 Subject: [PATCH 039/207] meta: Update break/crashpad to 2022-04-12 (NATIVE-506) (#696) --- CMakeLists.txt | 2 +- external/breakpad | 2 +- external/crashpad | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06f6103446..31861875aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ if(NOT CMAKE_C_STANDARD) endif() if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD 17) endif() include(GNUInstallDirs) diff --git a/external/breakpad b/external/breakpad index e0f523e414..2cbe0387be 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit e0f523e414f88fed3292f152035fd2743ee8a56e +Subproject commit 2cbe0387bea361c15a3a62af71e1d233d4bb1e0d diff --git a/external/crashpad b/external/crashpad index 007c8b5159..be34d6e8b0 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 007c8b51594201f288402721519a7d7a1916e04d +Subproject commit be34d6e8b0b6d3f3845e3a1164771d8e24269ea8 From c5fccbe1e176aa9cc971280741391e68edcda52c Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 12 Apr 2022 14:30:12 +0200 Subject: [PATCH 040/207] meta: Update changelog for release (#697) --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8437350e8..22846573b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,20 @@ - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. **Internal**: + - Project IDs are now treated as opaque strings instead of integer values. ([#690](https://github.com/getsentry/sentry-native/pull/690)) +- Updated Breakpad and Crashpad backends to 2022-04-12. ([#696](https://github.com/getsentry/sentry-native/pull/696)) **Fixes**: + - Updated CI as well as list of supported platforms to reflect Windows Server 2016, and therefore MSVC 2017 losing active support. +- Correctly free Windows Mutexes in Crashpad backend. + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@zhaowq32](https://github.com/zhaowq32) ## 0.4.15 From 9e12f81695bb22a79e141c2e357c4aecf9bc2703 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Wed, 13 Apr 2022 12:46:10 +0200 Subject: [PATCH 041/207] feat: More aggressively prune the Crashpad database (#698) --- CHANGELOG.md | 1 + src/backends/sentry_backend_crashpad.cpp | 18 ++++++++++++++++++ src/sentry_backend.h | 1 + src/sentry_core.c | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22846573b9..8dd450dcd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Removed the `SENTRY_PERFORMANCE_MONITORING` compile flag requirement to access performance monitoring in the Sentry SDK. Performance monitoring is now available to everybody who has opted into the experimental API. - New API to check whether the application has crashed in the previous run: `sentry_get_crashed_last_run()` and `sentry_clear_crashed_last_run()` ([#685](https://github.com/getsentry/sentry-native/pull/685)). - Allow overriding the SDK name at build time - set the `SENTRY_SDK_NAME` CMake cache variable. +- More aggressively prune the Crashpad database. ([#698](https://github.com/getsentry/sentry-native/pull/698)) **Internal**: diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index dcebd9c7e5..c3b20a95f8 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -31,6 +31,7 @@ extern "C" { #include "client/crash_report_database.h" #include "client/crashpad_client.h" #include "client/crashpad_info.h" +#include "client/prune_crash_reports.h" #include "client/settings.h" #if defined(__GNUC__) @@ -426,6 +427,22 @@ sentry__crashpad_backend_last_crash(sentry_backend_t *backend) return crash_time; } +static void +sentry__crashpad_backend_prune_database(sentry_backend_t *backend) +{ + crashpad_state_t *data = (crashpad_state_t *)backend->data; + + // We want to eagerly clean up reports older than 2 days, and limit the + // complete database to a maximum of 8M. That might still be a lot for + // an embedded use-case, but minidumps on desktop can sometimes be quite + // large. + data->db->CleanDatabase(60 * 60 * 24 * 2); + crashpad::BinaryPruneCondition condition(crashpad::BinaryPruneCondition::OR, + new crashpad::DatabaseSizePruneCondition(1024 * 8), + new crashpad::AgePruneCondition(2)); + crashpad::PruneCrashReportDatabase(data->db, &condition); +} + sentry_backend_t * sentry__backend_new(void) { @@ -451,6 +468,7 @@ sentry__backend_new(void) backend->user_consent_changed_func = sentry__crashpad_backend_user_consent_changed; backend->get_last_crash_func = sentry__crashpad_backend_last_crash; + backend->prune_database_func = sentry__crashpad_backend_prune_database; backend->data = data; backend->can_capture_after_shutdown = true; diff --git a/src/sentry_backend.h b/src/sentry_backend.h index bb954272a8..0c69692782 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -25,6 +25,7 @@ struct sentry_backend_s { const sentry_options_t *options); void (*user_consent_changed_func)(sentry_backend_t *); uint64_t (*get_last_crash_func)(sentry_backend_t *); + void (*prune_database_func)(sentry_backend_t *); void *data; bool can_capture_after_shutdown; }; diff --git a/src/sentry_core.c b/src/sentry_core.c index 6bcdf8fe13..b476a66930 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -182,7 +182,11 @@ sentry_init(sentry_options_t *options) // after initializing the transport, we will submit all the unsent envelopes // and handle remaining sessions. + SENTRY_TRACE("processing and pruning old runs"); sentry__process_old_runs(options, last_crash); + if (backend && backend->prune_database_func) { + backend->prune_database_func(backend); + } if (options->auto_session_tracking) { sentry_start_session(); From c85295c81cd37276de092b28187bc491aec963cf Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 13 Apr 2022 10:48:57 +0000 Subject: [PATCH 042/207] release: 0.4.16 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd450dcd3..6465323f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.4.16 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 635eb437e2..c74bcc6685 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.4.15" +#define SENTRY_SDK_VERSION "0.4.16" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 9e33e1d427..ecb3b16930 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.15", + "version": "0.4.16", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.15"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.16"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index a1b8367db7..f93fc5ff5a 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.15" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.16" def test_capture_http(cmake, httpserver): From d05f06059e259d4c09215e8febd1b42faa816018 Mon Sep 17 00:00:00 2001 From: Vitalii Koshura Date: Thu, 21 Apr 2022 19:27:19 +0200 Subject: [PATCH 043/207] Fix library-only build with no examples. (#702) This fixes #701 Signed-off-by: Vitalii Koshura --- src/CMakeLists.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b5161c3fc2..a8175dc3da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,8 @@ sentry_target_sources_cwd(sentry sentry_uuid.h sentry_value.c sentry_value.h + sentry_tracing.c + sentry_tracing.h path/sentry_path.c transports/sentry_disk_transport.c transports/sentry_disk_transport.h @@ -151,10 +153,3 @@ if(SENTRY_INTEGRATION_QT) integrations/sentry_integration_qt.h ) endif() - -if(SENTRY_BUILD_EXAMPLES) - sentry_target_sources_cwd(sentry - sentry_tracing.c - sentry_tracing.h - ) -endif() From c36b04b28130e50dac6b69e9045b4444c5e380ac Mon Sep 17 00:00:00 2001 From: relaxolotl <5597345+relaxolotl@users.noreply.github.com> Date: Thu, 21 Apr 2022 16:11:59 -0400 Subject: [PATCH 044/207] chore: Update changelog (#703) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6465323f7c..25f7fdc07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +**Fixes**: + +- sentry-native now successfully builds when examples aren't included. ([#702](https://github.com/getsentry/sentry-native/pull/702)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@AenBleidd](https://github.com/AenBleidd) + ## 0.4.16 **Features**: From 5500192dda05c82468787b2b0637e9c2688b9aed Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 21 Apr 2022 20:14:41 +0000 Subject: [PATCH 045/207] release: 0.4.17 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f7fdc07f..17d6a93832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.4.17 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index c74bcc6685..6552e42ff0 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.4.16" +#define SENTRY_SDK_VERSION "0.4.17" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index ecb3b16930..1e4120bacd 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.16", + "version": "0.4.17", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.16"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.17"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index f93fc5ff5a..631dae12d9 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.16" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.17" def test_capture_http(cmake, httpserver): From 1367ca61ca0eed067f1287909651a397e0c753f8 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 22 Apr 2022 04:02:58 -0400 Subject: [PATCH 046/207] meta(gha): Deploy action enforce-license-compliance.yml (#704) --- .github/workflows/enforce-license-compliance.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/enforce-license-compliance.yml diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml new file mode 100644 index 0000000000..b331974711 --- /dev/null +++ b/.github/workflows/enforce-license-compliance.yml @@ -0,0 +1,16 @@ +name: Enforce License Compliance + +on: + push: + branches: [master, main, release/*] + pull_request: + branches: [master, main] + +jobs: + enforce-license-compliance: + runs-on: ubuntu-latest + steps: + - name: 'Enforce License Compliance' + uses: getsentry/action-enforce-license-compliance@main + with: + fossa_api_key: ${{ secrets.FOSSA_API_KEY }} From 0c52b8fc1d8640dba360fc09f8a9ed3d0fd76a9a Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Wed, 4 May 2022 13:02:39 -0700 Subject: [PATCH 047/207] chore: Update logo for dark or light theme (#709) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35071767b9..f4a866aa9b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@

- - + + + + + Sentry + -

# Official Sentry SDK for C/C++ From 0a498e3e40d37180464c8fd17058e7330c72eb7a Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 20 May 2022 11:05:40 +0200 Subject: [PATCH 048/207] feat: Allow disabling of the crash-handler backend at runtime. (#717) Co-authored-by: Mischan Toosarani-Hausberger --- CONTRIBUTING.md | 1 + examples/example.c | 4 ++++ include/sentry.h | 24 ++++++++++++++++++++++++ src/sentry_options.c | 7 +++++++ tests/test_integration_crashpad.py | 21 +++++++++++++++++++++ 5 files changed, 57 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 655543465c..9656830275 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,3 +139,4 @@ The example currently supports the following commends: - `capture-multiple`: Captures a number of events. - `sleep`: Introduces a 10 second sleep. - `add-stacktrace`: Adds the current thread stacktrace to the captured event. +- `disable-backend`: Disables the build-configured crash-handler backend. diff --git a/examples/example.c b/examples/example.c index de086ea2ed..0157b8071a 100644 --- a/examples/example.c +++ b/examples/example.c @@ -65,6 +65,10 @@ main(int argc, char **argv) { sentry_options_t *options = sentry_options_new(); + if (has_arg(argc, argv, "disable-backend")) { + sentry_options_set_backend(options, NULL); + } + // this is an example. for real usage, make sure to set this explicitly to // an app specific cache location. sentry_options_set_database_path(options, ".sentry-native"); diff --git a/include/sentry.h b/include/sentry.h index 6552e42ff0..c1b5ade4dc 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -709,6 +709,20 @@ SENTRY_API void sentry_transport_free(sentry_transport_t *transport); SENTRY_API sentry_transport_t *sentry_new_function_transport( void (*func)(const sentry_envelope_t *envelope, void *data), void *data); +/** + * This represents an interface for user-defined backends. + * + * Backends are responsible to handle crashes. They are maintained at runtime + * via various life-cycle hooks from the sentry-core. + * + * At this point none of those interfaces are exposed in the API including + * creation and destruction. The main use-case of the backend in the API at this + * point is to disable it via `sentry_options_set_backend` at runtime before it + * is initialized. + */ +struct sentry_backend_s; +typedef struct sentry_backend_s sentry_backend_t; + /* -- Options APIs -- */ /** @@ -1050,6 +1064,16 @@ SENTRY_API void sentry_options_set_shutdown_timeout( */ SENTRY_API uint64_t sentry_options_get_shutdown_timeout(sentry_options_t *opts); +/** + * Sets a user-defined backend. + * + * Since creation and destruction of backends is not exposed in the API, this + * can only be used to set the backend to `NULL`, which disables the backend in + * the initialization. + */ +SENTRY_API void sentry_options_set_backend( + sentry_options_t *opts, sentry_backend_t *backend); + /* -- Global APIs -- */ /** diff --git a/src/sentry_options.c b/src/sentry_options.c index 43531118e2..7588ad977f 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -425,3 +425,10 @@ sentry_options_get_traces_sample_rate(sentry_options_t *opts) { return opts->traces_sample_rate; } + +void +sentry_options_set_backend(sentry_options_t *opts, sentry_backend_t *backend) +{ + sentry__backend_free(opts->backend); + opts->backend = backend; +} diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 7eb0ca2c2a..cd7363bed9 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -135,3 +135,24 @@ def test_crashpad_dump_inflight(cmake, httpserver): # we trigger 10 normal events, and 1 crash assert len(httpserver.log) >= 11 + + +def test_disable_backend(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + + with httpserver.wait(timeout=5, raise_assertions=False) as waiting: + child = run( + tmp_path, "sentry_example", ["disable-backend", "log", "crash"], env=env + ) + # we crash so process should return non-zero + assert child.returncode + + # crashpad is disabled, and we are only crashing, so we expect the wait to timeout + assert waiting.result is False + + run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) + + # crashpad is disabled, and we are only crashing, so we expect no requests + assert len(httpserver.log) == 0 From 6694b194ac21afd19f4a019283209460e9040445 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 30 May 2022 17:41:07 +0200 Subject: [PATCH 049/207] fix: Only consider completed crash-reports for session status (#719) --- src/backends/sentry_backend_crashpad.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index c3b20a95f8..e65d6f235e 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -409,14 +409,6 @@ sentry__crashpad_backend_last_crash(sentry_backend_t *backend) uint64_t crash_time = 0; std::vector reports; - if (data->db->GetPendingReports(&reports) - == crashpad::CrashReportDatabase::kNoError) { - for (const crashpad::CrashReportDatabase::Report &report : reports) { - report_crash_time(&crash_time, report); - } - } - - reports.clear(); if (data->db->GetCompletedReports(&reports) == crashpad::CrashReportDatabase::kNoError) { for (const crashpad::CrashReportDatabase::Report &report : reports) { From 939dff3f16ec1020193be3619bb3526eb47f0fcc Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 14 Jun 2022 11:02:11 +0200 Subject: [PATCH 050/207] feat: API to add a stack trace to an exception or thread (#723) --- examples/example.c | 3 +-- include/sentry.h | 15 +++++++++++++-- src/backends/sentry_backend_inproc.c | 6 +----- src/sentry_value.c | 10 +++++++--- tests/unit/test_value.c | 18 ++++++++++++++++++ tests/unit/tests.inc | 1 + 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/examples/example.c b/examples/example.c index 0157b8071a..78d91ced5c 100644 --- a/examples/example.c +++ b/examples/example.c @@ -211,8 +211,7 @@ main(int argc, char **argv) sentry_value_t exc = sentry_value_new_exception( "ParseIntError", "invalid digit found in string"); if (has_arg(argc, argv, "add-stacktrace")) { - sentry_value_t stacktrace = sentry_value_new_stacktrace(NULL, 0); - sentry_value_set_by_key(exc, "stacktrace", stacktrace); + sentry_value_set_stacktrace(exc, NULL, 0); } sentry_value_t event = sentry_value_new_event(); sentry_event_add_exception(event, exc); diff --git a/include/sentry.h b/include/sentry.h index c1b5ade4dc..6bbaf3549e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -409,8 +409,8 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( * * See https://develop.sentry.dev/sdk/event-payloads/stacktrace/ * - * The returned object needs to be attached to either an exception - * event, or a thread object. + * The returned object must be attached to either an exception or thread + * object. * * If `ips` is NULL the current stack trace is captured, otherwise `len` * stack trace instruction pointers are attached to the event. @@ -418,6 +418,17 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_stacktrace( void **ips, size_t len); +/** + * Sets the Stack Trace conforming to the Stack Trace Interface in a value. + * + * The value argument must be either an exception or thread object. + * + * If `ips` is NULL the current stack trace is captured, otherwise `len` stack + * trace instruction pointers are attached to the event. + */ +SENTRY_EXPERIMENTAL_API void sentry_value_set_stacktrace( + sentry_value_t value, void **ips, size_t len); + /** * Adds an Exception to an Event value. * diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 2318e27746..9e1a4b8b9b 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -214,11 +214,7 @@ make_signal_event( } SENTRY_TRACEF("captured backtrace with %lu frames", frame_count); - sentry_value_t stacktrace - = sentry_value_new_stacktrace(&backtrace[0], frame_count); - - sentry_value_set_by_key(exc, "stacktrace", stacktrace); - + sentry_value_set_stacktrace(exc, &backtrace[0], frame_count); sentry_event_add_exception(event, exc); return event; diff --git a/src/sentry_value.c b/src/sentry_value.c index b8757a538e..9d78efd9ec 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1200,12 +1200,16 @@ sentry_event_add_thread(sentry_value_t event, sentry_value_t thread) } void -sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +sentry_value_set_stacktrace(sentry_value_t value, void **ips, size_t len) { sentry_value_t stacktrace = sentry_value_new_stacktrace(ips, len); + sentry_value_set_by_key(value, "stacktrace", stacktrace); +} +void +sentry_event_value_add_stacktrace(sentry_value_t event, void **ips, size_t len) +{ sentry_value_t thread = sentry_value_new_object(); - sentry_value_set_by_key(thread, "stacktrace", stacktrace); - + sentry_value_set_stacktrace(thread, ips, len); sentry_event_add_thread(event, thread); } diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 98687b1d37..6a4044c13c 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -546,3 +546,21 @@ SENTRY_TEST(value_collections_leak) TEST_CHECK_INT_EQUAL(sentry_value_refcount(obj), 1); sentry_value_decref(obj); } + +SENTRY_TEST(value_set_stacktrace) +{ + sentry_value_t exc + = sentry_value_new_exception("std::out_of_range", "vector"); + sentry_value_set_stacktrace(exc, NULL, 0); + + sentry_value_t stacktrace = sentry_value_get_by_key(exc, "stacktrace"); + TEST_CHECK(!sentry_value_is_null(stacktrace)); + TEST_CHECK(SENTRY_VALUE_TYPE_OBJECT == sentry_value_get_type(stacktrace)); + + sentry_value_t frames = sentry_value_get_by_key(stacktrace, "frames"); + TEST_CHECK(!sentry_value_is_null(frames)); + TEST_CHECK(SENTRY_VALUE_TYPE_LIST == sentry_value_get_type(frames)); + TEST_CHECK(0 < sentry_value_get_length(frames)); + + sentry_value_decref(exc); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 268010e225..5b46780ea6 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -89,3 +89,4 @@ XX(value_object_merge_nested) XX(value_string) XX(value_unicode) XX(value_wrong_type) +XX(value_set_stacktrace) From 2d53a5a69dfa59b22484e9cef92c847ef9ac5e9f Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Tue, 14 Jun 2022 14:51:26 +0200 Subject: [PATCH 051/207] feat: Capture registers with inproc backend (FEEDBACK-1413) (#714) --- src/backends/sentry_backend_inproc.c | 276 ++++++++++++++++++++++++++- 1 file changed, 274 insertions(+), 2 deletions(-) diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 9e1a4b8b9b..0c5297b325 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -114,7 +114,8 @@ shutdown_inproc_backend(sentry_backend_t *UNUSED(backend)) reset_signal_handlers(); } -#elif defined SENTRY_PLATFORM_WINDOWS +#elif defined(SENTRY_PLATFORM_WINDOWS) + struct signal_slot { DWORD signum; const char *signame; @@ -168,8 +169,273 @@ shutdown_inproc_backend(sentry_backend_t *UNUSED(backend)) SetUnhandledExceptionFilter(current_handler); } } + #endif +sentry_value_t +sentry__registers_from_uctx(const sentry_ucontext_t *uctx) +{ + sentry_value_t registers = sentry_value_new_object(); + +#if defined(SENTRY_PLATFORM_LINUX) + + // just assume the ctx is a bunch of uintpr_t, and index that directly + uintptr_t *ctx = (uintptr_t *)&uctx->user_context->uc_mcontext; + +# define SET_REG(name, num) \ + sentry_value_set_by_key(registers, name, \ + sentry__value_new_addr((uint64_t)(size_t)ctx[num])); + +# if defined(__x86_64__) + + SET_REG("r8", 0); + SET_REG("r9", 1); + SET_REG("r10", 2); + SET_REG("r11", 3); + SET_REG("r12", 4); + SET_REG("r13", 5); + SET_REG("r14", 6); + SET_REG("r15", 7); + SET_REG("rdi", 8); + SET_REG("rsi", 9); + SET_REG("rbp", 10); + SET_REG("rbx", 11); + SET_REG("rdx", 12); + SET_REG("rax", 13); + SET_REG("rcx", 14); + SET_REG("rsp", 15); + SET_REG("rip", 16); + +# elif defined(__i386__) + + // gs, fs, es, ds + SET_REG("edi", 4); + SET_REG("esi", 5); + SET_REG("ebp", 6); + SET_REG("esp", 7); + SET_REG("ebx", 8); + SET_REG("edx", 9); + SET_REG("ecx", 10); + SET_REG("eax", 11); + SET_REG("eip", 14); + SET_REG("eflags", 16); + +# elif defined(__aarch64__) + + // 0 is `fault_address` + SET_REG("x0", 1); + SET_REG("x1", 2); + SET_REG("x2", 3); + SET_REG("x3", 4); + SET_REG("x4", 5); + SET_REG("x5", 6); + SET_REG("x6", 7); + SET_REG("x7", 8); + SET_REG("x8", 9); + SET_REG("x9", 10); + SET_REG("x10", 11); + SET_REG("x11", 12); + SET_REG("x12", 13); + SET_REG("x13", 14); + SET_REG("x14", 15); + SET_REG("x15", 16); + SET_REG("x16", 17); + SET_REG("x17", 18); + SET_REG("x18", 19); + SET_REG("x19", 20); + SET_REG("x20", 21); + SET_REG("x21", 22); + SET_REG("x22", 23); + SET_REG("x23", 24); + SET_REG("x24", 25); + SET_REG("x25", 26); + SET_REG("x26", 27); + SET_REG("x27", 28); + SET_REG("x28", 29); + SET_REG("fp", 30); + SET_REG("lr", 31); + SET_REG("sp", 32); + SET_REG("pc", 33); + +# elif defined(__arm__) + + // trap_no, _error_code, oldmask + SET_REG("r0", 3); + SET_REG("r1", 4); + SET_REG("r2", 5); + SET_REG("r3", 6); + SET_REG("r4", 7); + SET_REG("r5", 8); + SET_REG("r6", 9); + SET_REG("r7", 10); + SET_REG("r8", 11); + SET_REG("r9", 12); + SET_REG("r10", 13); + SET_REG("fp", 14); + SET_REG("ip", 15); + SET_REG("sp", 16); + SET_REG("lr", 17); + SET_REG("pc", 18); + +# endif + +# undef SET_REG + +#elif defined(SENTRY_PLATFORM_DARWIN) + +# define SET_REG(name, prop) \ + sentry_value_set_by_key(registers, name, \ + sentry__value_new_addr((uint64_t)(size_t)thread_state->prop)); + +# if defined(__x86_64__) + + _STRUCT_X86_THREAD_STATE64 *thread_state + = &uctx->user_context->uc_mcontext->__ss; + + SET_REG("rax", __rax); + SET_REG("rbx", __rbx); + SET_REG("rcx", __rcx); + SET_REG("rdx", __rdx); + SET_REG("rdi", __rdi); + SET_REG("rsi", __rsi); + SET_REG("rbp", __rbp); + SET_REG("rsp", __rsp); + SET_REG("r8", __r8); + SET_REG("r9", __r9); + SET_REG("r10", __r10); + SET_REG("r11", __r11); + SET_REG("r12", __r12); + SET_REG("r13", __r13); + SET_REG("r14", __r14); + SET_REG("r15", __r15); + SET_REG("rip", __rip); + +# elif defined(__arm64__) + + _STRUCT_ARM_THREAD_STATE64 *thread_state + = &uctx->user_context->uc_mcontext->__ss; + + SET_REG("x0", __x[0]); + SET_REG("x1", __x[1]); + SET_REG("x2", __x[2]); + SET_REG("x3", __x[3]); + SET_REG("x4", __x[4]); + SET_REG("x5", __x[5]); + SET_REG("x6", __x[6]); + SET_REG("x7", __x[7]); + SET_REG("x8", __x[8]); + SET_REG("x9", __x[9]); + SET_REG("x10", __x[10]); + SET_REG("x11", __x[11]); + SET_REG("x12", __x[12]); + SET_REG("x13", __x[13]); + SET_REG("x14", __x[14]); + SET_REG("x15", __x[15]); + SET_REG("x16", __x[16]); + SET_REG("x17", __x[17]); + SET_REG("x18", __x[18]); + SET_REG("x19", __x[19]); + SET_REG("x20", __x[20]); + SET_REG("x21", __x[21]); + SET_REG("x22", __x[22]); + SET_REG("x23", __x[23]); + SET_REG("x24", __x[24]); + SET_REG("x25", __x[25]); + SET_REG("x26", __x[26]); + SET_REG("x27", __x[27]); + SET_REG("x28", __x[28]); + SET_REG("fp", __fp); + SET_REG("lr", __lr); + SET_REG("sp", __sp); + SET_REG("pc", __pc); + +# elif defined(__arm__) + + _STRUCT_ARM_THREAD_STATE *thread_state + = &uctx->user_context->uc_mcontext->__ss; + + SET_REG("r0", __r[0]); + SET_REG("r1", __r[1]); + SET_REG("r2", __r[2]); + SET_REG("r3", __r[3]); + SET_REG("r4", __r[4]); + SET_REG("r5", __r[5]); + SET_REG("r6", __r[6]); + SET_REG("r7", __r[7]); + SET_REG("r8", __r[8]); + SET_REG("r9", __r[9]); + SET_REG("r10", __r[10]); + SET_REG("fp", __r[11]); + SET_REG("ip", __r[12]); + SET_REG("sp", __sp); + SET_REG("lr", __lr); + SET_REG("pc", __pc); + +# endif + +# undef SET_REG + +#elif defined(SENTRY_PLATFORM_WINDOWS) + PCONTEXT ctx = uctx->exception_ptrs.ContextRecord; + +# define SET_REG(name, prop) \ + sentry_value_set_by_key(registers, name, \ + sentry__value_new_addr((uint64_t)(size_t)ctx->prop)); + +# if defined(_M_AMD64) + + if (ctx->ContextFlags & CONTEXT_INTEGER) { + SET_REG("rax", Rax); + SET_REG("rcx", Rcx); + SET_REG("rdx", Rdx); + SET_REG("rbx", Rbx); + SET_REG("rbp", Rbp); + SET_REG("rsi", Rsi); + SET_REG("rdi", Rdi); + SET_REG("r8", R8); + SET_REG("r9", R9); + SET_REG("r10", R10); + SET_REG("r11", R11); + SET_REG("r12", R12); + SET_REG("r13", R13); + SET_REG("r14", R14); + SET_REG("r15", R15); + } + + if (ctx->ContextFlags & CONTEXT_CONTROL) { + SET_REG("rsp", Rsp); + SET_REG("rip", Rip); + } + +# elif defined(_M_IX86) + + if (ctx->ContextFlags & CONTEXT_INTEGER) { + SET_REG("edi", Edi); + SET_REG("esi", Esi); + SET_REG("ebx", Ebx); + SET_REG("edx", Edx); + SET_REG("ecx", Ecx); + SET_REG("eax", Eax); + } + + if (ctx->ContextFlags & CONTEXT_CONTROL) { + SET_REG("ebp", Ebp); + SET_REG("eip", Eip); + SET_REG("eflags", EFlags); + SET_REG("esp", Esp); + } + +# else + // _ARM64_ +# endif + +# undef SET_REG + +#endif + + return registers; +} + static sentry_value_t make_signal_event( const struct signal_slot *sig_slot, const sentry_ucontext_t *uctx) @@ -214,7 +480,13 @@ make_signal_event( } SENTRY_TRACEF("captured backtrace with %lu frames", frame_count); - sentry_value_set_stacktrace(exc, &backtrace[0], frame_count); + sentry_value_t stacktrace + = sentry_value_new_stacktrace(&backtrace[0], frame_count); + + sentry_value_t registers = sentry__registers_from_uctx(uctx); + sentry_value_set_by_key(stacktrace, "registers", registers); + + sentry_value_set_by_key(exc, "stacktrace", stacktrace); sentry_event_add_exception(event, exc); return event; From b091b76c634ec3e0d9608f3cd8a6cfa70612f260 Mon Sep 17 00:00:00 2001 From: Edwin Date: Wed, 15 Jun 2022 11:36:55 +0200 Subject: [PATCH 052/207] feat: Add run-time version info (#726) --- .gitignore | 1 + include/sentry.h | 15 +++++++++++++++ src/CMakeLists.txt | 1 + src/sentry_info.c | 19 +++++++++++++++++++ tests/unit/CMakeLists.txt | 1 + tests/unit/test_info.c | 16 ++++++++++++++++ tests/unit/tests.inc | 9 ++++++--- 7 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/sentry_info.c create mode 100644 tests/unit/test_info.c diff --git a/.gitignore b/.gitignore index f121c7d220..7e72ac0c9b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ __pycache__ # Devtools CodeChecker +.ccls-cache # Coverage coverage diff --git a/include/sentry.h b/include/sentry.h index 6bbaf3549e..2440da1290 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1774,6 +1774,21 @@ SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(); */ SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(); +/** + * Sentry SDK version. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(); + +/** + * Sentry SDK name. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(); + +/** + * Sentry SDK User-Agent. + */ +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(); + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8175dc3da..1a7ba59490 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ sentry_target_sources_cwd(sentry sentry_database.h sentry_envelope.c sentry_envelope.h + sentry_info.c sentry_json.c sentry_json.h sentry_logger.c diff --git a/src/sentry_info.c b/src/sentry_info.c new file mode 100644 index 0000000000..b2918127b3 --- /dev/null +++ b/src/sentry_info.c @@ -0,0 +1,19 @@ +#include "sentry_boot.h" + +const char * +sentry_sdk_version() +{ + return SENTRY_SDK_VERSION; +} + +const char * +sentry_sdk_name() +{ + return SENTRY_SDK_NAME; +} + +const char * +sentry_sdk_user_agent() +{ + return SENTRY_SDK_USER_AGENT; +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index db18b0f95c..7c9dc6d173 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(sentry_test_unit test_envelopes.c test_failures.c test_fuzzfailures.c + test_info.c test_logger.c test_modulefinder.c test_mpack.c diff --git a/tests/unit/test_info.c b/tests/unit/test_info.c new file mode 100644 index 0000000000..c3c889b769 --- /dev/null +++ b/tests/unit/test_info.c @@ -0,0 +1,16 @@ +#include "sentry_testsupport.h" + +SENTRY_TEST(assert_sdk_version) +{ + TEST_CHECK_STRING_EQUAL(sentry_sdk_version(), SENTRY_SDK_VERSION); +} + +SENTRY_TEST(assert_sdk_name) +{ + TEST_CHECK_STRING_EQUAL(sentry_sdk_name(), SENTRY_SDK_NAME); +} + +SENTRY_TEST(assert_sdk_user_agent) +{ + TEST_CHECK_STRING_EQUAL(sentry_sdk_user_agent(), SENTRY_SDK_USER_AGENT); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 5b46780ea6..f46f330b43 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -1,3 +1,6 @@ +XX(assert_sdk_name) +XX(assert_sdk_user_agent) +XX(assert_sdk_version) XX(background_worker) XX(basic_consent_tracking) XX(basic_function_transport) @@ -15,15 +18,15 @@ XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) -XX(crash_marker) XX(crashed_last_run) +XX(crash_marker) XX(custom_logger) XX(distributed_headers) XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) -XX(dsn_store_url_with_path) XX(dsn_store_url_without_path) +XX(dsn_store_url_with_path) XX(empty_transport) XX(fuzz_json) XX(init_failure) @@ -86,7 +89,7 @@ XX(value_null) XX(value_object) XX(value_object_merge) XX(value_object_merge_nested) +XX(value_set_stacktrace) XX(value_string) XX(value_unicode) XX(value_wrong_type) -XX(value_set_stacktrace) From 3e05e162dd12e263aed0db67762113f2735d76f0 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Fri, 17 Jun 2022 17:15:56 +0200 Subject: [PATCH 053/207] deps: Update Break/Crashpad to 2022-06-14 (#725) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ external/CMakeLists.txt | 15 ++++++++++----- external/breakpad | 2 +- external/crashpad | 2 +- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d6a93832..41f9ab5a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## Unreleased + +**Features**: + +- The crashpad backend now captures thread names. ([#725](https://github.com/getsentry/sentry-native/pull/725)) +- The inproc backend now captures the context registers. ([#714](https://github.com/getsentry/sentry-native/pull/714)) +- A new set of APIs to get the sentry SDK version at runtime. ([#726](https://github.com/getsentry/sentry-native/pull/726)) +- Add more convenient APIs to attach stack traces to exception or thread values. ([#723](https://github.com/getsentry/sentry-native/pull/723)) +- Allow disabling the crash reporting backend at runtime. ([#717](https://github.com/getsentry/sentry-native/pull/717)) + +**Fixes**: + +- Improved heuristics flagging sessions as "crashed". ([#719](https://github.com/getsentry/sentry-native/pull/719)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-06-14. ([#725](https://github.com/getsentry/sentry-native/pull/725)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@olback](https://github.com/olback) + ## 0.4.17 **Fixes**: diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 24bfe01860..ca807f71c8 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -74,6 +74,9 @@ set(BREAKPAD_SOURCES_CLIENT_LINUX breakpad/src/client/linux/minidump_writer/linux_dumper.cc breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc breakpad/src/client/linux/minidump_writer/minidump_writer.cc + breakpad/src/client/linux/minidump_writer/pe_file.cc + breakpad/src/client/linux/minidump_writer/pe_file.h + breakpad/src/client/linux/minidump_writer/pe_structs.h ) set(BREAKPAD_SOURCES_CLIENT_WINDOWS @@ -115,12 +118,12 @@ set(BREAKPAD_SOURCES_CLIENT_IOS breakpad/src/client/mac/handler/ucontext_compat.h ) - add_library(breakpad_client STATIC) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON}) if(LINUX OR ANDROID) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_LINUX} ${BREAKPAD_SOURCES_CLIENT_LINUX}) + if(ANDROID) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_ANDROID}) target_include_directories(breakpad_client PRIVATE breakpad/src/common/android/include) @@ -128,6 +131,7 @@ if(LINUX OR ANDROID) include(CheckFunctionExists) check_function_exists(getcontext HAVE_GETCONTEXT) + if(HAVE_GETCONTEXT) target_compile_definitions(breakpad_client PRIVATE HAVE_GETCONTEXT) else() @@ -141,6 +145,7 @@ if(APPLE) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_APPLE} ${BREAKPAD_SOURCES_CLIENT_APPLE}) + if(NOT IOS) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON_MAC} @@ -167,8 +172,8 @@ endif() # which are being resolved correctly when we add the current directory to # the include directories. A giant hack, yes, but it works target_include_directories(breakpad_client - PRIVATE - "$" - PUBLIC - "$" + PRIVATE + "$" + PUBLIC + "$" ) diff --git a/external/breakpad b/external/breakpad index 2cbe0387be..1fc7929f9e 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 2cbe0387bea361c15a3a62af71e1d233d4bb1e0d +Subproject commit 1fc7929f9ed5073184fe2ba7b52a35d75124ca04 diff --git a/external/crashpad b/external/crashpad index be34d6e8b0..82e2b7db9f 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit be34d6e8b0b6d3f3845e3a1164771d8e24269ea8 +Subproject commit 82e2b7db9fdc99bdd2dee65c9c75f5c1408db3de From ff5bfcf0eb2c47d03eb57a51bdf2e6ad4b8ece10 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 20 Jun 2022 09:01:23 +0000 Subject: [PATCH 054/207] release: 0.4.18 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f9ab5a35..b513858ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.4.18 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 2440da1290..00f236870e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.4.17" +#define SENTRY_SDK_VERSION "0.4.18" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 1e4120bacd..c5d3174cb0 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.17", + "version": "0.4.18", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.17"}, + {"name": "github:getsentry/sentry-native", "version": "0.4.18"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 631dae12d9..78da772db4 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.17" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.18" def test_capture_http(cmake, httpserver): From 6531a262388c67327afb07784762da7343d82c45 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 1 Jul 2022 10:24:29 +0200 Subject: [PATCH 055/207] ref(session): align processing sequence in sentry__capture_event() with docs (#729) processing sequence of sentry__capture_event() to align with the docs/spec (https://develop.sentry.dev/sdk/sessions/#filter-order). --- .github/workflows/ci.yml | 4 +-- CHANGELOG.md | 8 ++++++ include/sentry.h | 20 +++++++++++++++ src/sentry_core.c | 33 ++++++++++++------------ tests/unit/test_basic.c | 54 +++++++++++++++++++++++++++++++++++----- tests/unit/tests.inc | 5 ++-- 6 files changed, 98 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30c1902502..e778a75212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 CMAKE_DEFINES: -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 - - name: macOS (clang 11 + asan + llvm-cov) + - name: macOS (clang 13 + asan + llvm-cov) os: macOs-latest CC: clang CXX: clang++ @@ -136,7 +136,7 @@ jobs: - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} - run: echo "/usr/local/opt/llvm/bin/" >> $GITHUB_PATH + run: echo $(brew --prefix llvm@13)/bin >> $GITHUB_PATH - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b513858ac9..b042fe724a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +**Fixes** + +- Aligned pre-send event processing in `sentry_capture_event()` with the + [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) + ([#729](https://github.com/getsentry/sentry-native/pull/729)) + ## 0.4.18 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 00f236870e..3d33baad2f 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -775,6 +775,13 @@ SENTRY_API void sentry_options_set_transport( * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see * the documentation on SEH (structured exception handling) for more information * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Up to version 0.4.18 the `before_send` callback wasn't invoked in case the + * event sampling discarded an event. In the current implementation the + * `before_send` callback is invoked even if the event sampling discards the + * event, following the cross-SDK session filter order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order */ typedef sentry_value_t (*sentry_event_function_t)( sentry_value_t event, void *hint, void *closure); @@ -801,6 +808,19 @@ SENTRY_API const char *sentry_options_get_dsn(const sentry_options_t *opts); * Sets the sample rate, which should be a double between `0.0` and `1.0`. * Sentry will randomly discard any event that is captured using * `sentry_capture_event` when a sample rate < 1 is set. + * + * The sampling happens at the end of the event processing according to the + * following order: + * + * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * Only items 3. to 6. are currently applicable to sentry-native. This means + * each processing step is executed even if the sampling discards the event + * before sending it to the backend. This is particularly relevant to users of + * the `before_send` callback. + * + * The above is in contrast to versions up to 0.4.18 where the sampling happened + * at the beginning of the processing/filter sequence. */ SENTRY_API void sentry_options_set_sample_rate( sentry_options_t *opts, double sample_rate); diff --git a/src/sentry_core.c b/src/sentry_core.c index b476a66930..e05751c085 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -389,6 +389,14 @@ sentry_capture_event(sentry_value_t event) } } +bool +sentry__roll_dice(double probability) +{ + uint64_t rnd; + return probability >= 1.0 || sentry__getrandom(&rnd, sizeof(rnd)) + || ((double)rnd / (double)UINT64_MAX) <= probability; +} + sentry_uuid_t sentry__capture_event(sentry_value_t event) { @@ -416,8 +424,15 @@ sentry__capture_event(sentry_value_t event) mut_options->session->init = false; sentry__options_unlock(); } - sentry__capture_envelope(options->transport, envelope); - was_sent = true; + + bool should_skip = !sentry__roll_dice(options->sample_rate); + if (should_skip) { + SENTRY_DEBUG("throwing away event due to sample rate"); + sentry_envelope_free(envelope); + } else { + sentry__capture_envelope(options->transport, envelope); + was_sent = true; + } } } if (!was_captured) { @@ -426,14 +441,6 @@ sentry__capture_event(sentry_value_t event) return was_sent ? event_id : sentry_uuid_nil(); } -bool -sentry__roll_dice(double probability) -{ - uint64_t rnd; - return probability >= 1.0 || sentry__getrandom(&rnd, sizeof(rnd)) - || ((double)rnd / (double)UINT64_MAX) <= probability; -} - bool sentry__should_send_transaction(sentry_value_t tx_cxt) { @@ -461,12 +468,6 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__record_errors_on_current_session(1); } - bool should_skip = !sentry__roll_dice(options->sample_rate); - if (should_skip) { - SENTRY_DEBUG("throwing away event due to sample rate"); - goto fail; - } - SENTRY_WITH_SCOPE (scope) { SENTRY_TRACE("merging scope into event"); sentry_scope_mode_t mode = SENTRY_SCOPE_ALL; diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index ca257f59d6..d8a51e4618 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -1,7 +1,6 @@ #include "sentry_core.h" #include "sentry_database.h" #include "sentry_testsupport.h" -#include "sentry_utils.h" static void send_envelope_test_basic(const sentry_envelope_t *envelope, void *data) @@ -63,14 +62,20 @@ SENTRY_TEST(basic_function_transport) TEST_CHECK_INT_EQUAL(called, 2); } +static void +counting_transport_func(const sentry_envelope_t *UNUSED(envelope), void *data) +{ + uint64_t *called = data; + *called += 1; +} + static sentry_value_t before_send(sentry_value_t event, void *UNUSED(hint), void *data) { uint64_t *called = data; *called += 1; - sentry_value_decref(event); - return sentry_value_new_null(); + return event; } SENTRY_TEST(sampling_before_send) @@ -82,7 +87,7 @@ SENTRY_TEST(sampling_before_send) sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); sentry_options_set_transport(options, sentry_new_function_transport( - send_envelope_test_basic, &called_transport)); + counting_transport_func, &called_transport)); sentry_options_set_before_send(options, before_send, &called_beforesend); sentry_options_set_sample_rate(options, 0.75); sentry_init(options); @@ -94,9 +99,46 @@ SENTRY_TEST(sampling_before_send) sentry_close(); + // The behavior here has changed with version 0.4.19: + // the documentation (https://develop.sentry.dev/sdk/sessions/#filter-order) + // requires that the sampling-rate filter for all SDKs is executed last. + // This means the `before_send` callback will be invoked every time and only + // the actual transport will be randomly sampled. + TEST_CHECK(called_transport > 50 && called_transport < 100); + TEST_CHECK_INT_EQUAL(called_beforesend, 100); +} + +static sentry_value_t +discarding_before_send(sentry_value_t event, void *UNUSED(hint), void *data) +{ + uint64_t *called = data; + *called += 1; + + sentry_value_decref(event); + return sentry_value_new_null(); +} + +SENTRY_TEST(discarding_before_send) +{ + uint64_t called_beforesend = 0; + uint64_t called_transport = 0; + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_options_set_transport(options, + sentry_new_function_transport( + counting_transport_func, &called_transport)); + sentry_options_set_before_send( + options, discarding_before_send, &called_beforesend); + sentry_init(options); + + sentry_capture_event( + sentry_value_new_message_event(SENTRY_LEVEL_INFO, NULL, "foo")); + + sentry_close(); + TEST_CHECK_INT_EQUAL(called_transport, 0); - // well, its random after all - TEST_CHECK(called_beforesend > 50 && called_beforesend < 100); + TEST_CHECK_INT_EQUAL(called_beforesend, 1); } SENTRY_TEST(crash_marker) diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index f46f330b43..6f14d67d1b 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -18,15 +18,16 @@ XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) -XX(crashed_last_run) XX(crash_marker) +XX(crashed_last_run) XX(custom_logger) +XX(discarding_before_send) XX(distributed_headers) XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) -XX(dsn_store_url_without_path) XX(dsn_store_url_with_path) +XX(dsn_store_url_without_path) XX(empty_transport) XX(fuzz_json) XX(init_failure) From f32330bf376535e7cbd03eedc063eebbf2e818fe Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Fri, 1 Jul 2022 11:04:55 +0200 Subject: [PATCH 056/207] fix: Make Windows ModuleFinder more resilient to missing Debug Info (#732) Previously the Windows ModuleFinder would early-return in case the loaded library had no debug directory / codeview record. In that case, the image type and code-id would be missing which results in normalization errors in Relay. We now always write the image type, and the code-id as early as possible. --- src/modulefinder/sentry_modulefinder_windows.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/modulefinder/sentry_modulefinder_windows.c b/src/modulefinder/sentry_modulefinder_windows.c index 26e6aa2063..aac7e29824 100644 --- a/src/modulefinder/sentry_modulefinder_windows.c +++ b/src/modulefinder/sentry_modulefinder_windows.c @@ -38,6 +38,12 @@ extract_pdb_info(uintptr_t module_addr, sentry_value_t module) return; } + char id_buf[50]; + snprintf(id_buf, sizeof(id_buf), "%08x%X", + nt_headers->FileHeader.TimeDateStamp, + nt_headers->OptionalHeader.SizeOfImage); + sentry_value_set_by_key(module, "code_id", sentry_value_new_string(id_buf)); + uint32_t relative_addr = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] .VirtualAddress; @@ -63,18 +69,11 @@ extract_pdb_info(uintptr_t module_addr, sentry_value_t module) sentry_uuid_t debug_id_base = sentry__uuid_from_native(&debug_info->pdb_signature); - char id_buf[50]; sentry_uuid_as_string(&debug_id_base, id_buf); id_buf[36] = '-'; snprintf(id_buf + 37, 10, "%x", debug_info->pdb_age); sentry_value_set_by_key( module, "debug_id", sentry_value_new_string(id_buf)); - - snprintf(id_buf, sizeof(id_buf), "%08x%X", - nt_headers->FileHeader.TimeDateStamp, - nt_headers->OptionalHeader.SizeOfImage); - sentry_value_set_by_key(module, "code_id", sentry_value_new_string(id_buf)); - sentry_value_set_by_key(module, "type", sentry_value_new_string("pe")); } static void @@ -97,6 +96,8 @@ load_modules(void) module.modBaseAddr, &vmem_info, sizeof(vmem_info)) && vmem_info.State == MEM_COMMIT) { sentry_value_t rv = sentry_value_new_object(); + sentry_value_set_by_key( + rv, "type", sentry_value_new_string("pe")); sentry_value_set_by_key(rv, "image_addr", sentry__value_new_addr((uint64_t)module.modBaseAddr)); sentry_value_set_by_key(rv, "image_size", From bdd4c3d85d8fc2e47a0c4821cbfa9a21d2f54d8d Mon Sep 17 00:00:00 2001 From: espkk Date: Fri, 1 Jul 2022 15:58:22 +0300 Subject: [PATCH 057/207] feat: invoke on_crash when handling a crash (#724) - don't invoke before_send if on_crash is set - if on_crash returns false crash report will be discarded --- include/sentry.h | 27 ++++++++ src/backends/sentry_backend_breakpad.cpp | 88 +++++++++++++++--------- src/backends/sentry_backend_crashpad.cpp | 64 ++++++++++------- src/backends/sentry_backend_inproc.c | 42 +++++++---- src/sentry_options.c | 8 +++ src/sentry_options.h | 2 + 6 files changed, 160 insertions(+), 71 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index 3d33baad2f..8032a36252 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -86,6 +86,7 @@ extern "C" { #include #include +#include #include /* context type dependencies */ @@ -794,6 +795,32 @@ typedef sentry_value_t (*sentry_event_function_t)( SENTRY_API void sentry_options_set_before_send( sentry_options_t *opts, sentry_event_function_t func, void *data); +/** + * Type of the `on_crash` callback. + * + * Does not work with crashpad on macOS. + * The callback passes a pointer to sentry_ucontext_s structure when exception + * handler is invoked. For breakpad on Linux this pointer is NULL. + * + * If the callback returns false outgoing crash report will be discarded. + * + * This function may be invoked inside of a signal handler and must be safe for + * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. + * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see + * the documentation on SEH (structured exception handling) for more information + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + */ +typedef bool (*sentry_crash_function_t)( + const sentry_ucontext_t *uctx, void *closure); + +/** + * Sets the `on_crash` callback. + * + * See the `sentry_crash_function_t` typedef above for more information. + */ +SENTRY_API void sentry_options_set_on_crash( + sentry_options_t *opts, sentry_crash_function_t func, void *data); + /** * Sets the DSN. */ diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index eaf596d17c..07d64a0bf4 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -40,7 +40,7 @@ extern "C" { static bool sentry__breakpad_backend_callback(const wchar_t *breakpad_dump_path, const wchar_t *minidump_id, void *UNUSED(context), - EXCEPTION_POINTERS *UNUSED(exinfo), MDRawAssertionInfo *UNUSED(assertion), + EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *UNUSED(assertion), bool succeeded) #elif defined(SENTRY_PLATFORM_DARWIN) static bool @@ -95,42 +95,64 @@ sentry__breakpad_backend_callback( SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); - sentry_value_t event = sentry_value_new_event(); - sentry_envelope_t *envelope - = sentry__prepare_event(options, event, NULL); - // the event we just prepared is empty, so no error is recorded for it - sentry__record_errors_on_current_session(1); - sentry_session_t *session = sentry__end_current_session_with_status( - SENTRY_SESSION_STATUS_CRASHED); - sentry__envelope_add_session(envelope, session); - - // the minidump is added as an attachment, with type `event.minidump` - sentry_envelope_item_t *item - = sentry__envelope_add_from_path(envelope, dump_path, "attachment"); - if (item) { - sentry__envelope_item_set_header(item, "attachment_type", - sentry_value_new_string("event.minidump")); - - sentry__envelope_item_set_header(item, "filename", + bool should_handle = true; + + if (options->on_crash_func) { + sentry_ucontext_t *uctx = nullptr; + #ifdef SENTRY_PLATFORM_WINDOWS - sentry__value_new_string_from_wstr( -#else - sentry_value_new_string( + sentry_ucontext_t uctx_data; + uctx_data.exception_ptrs = *exinfo; + uctx = &uctx_data; #endif - sentry__path_filename(dump_path))); + + SENTRY_TRACE("invoking `on_crash` hook"); + should_handle + = options->on_crash_func(uctx, options->on_crash_data); } - // capture the envelope with the disk transport - sentry_transport_t *disk_transport - = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); - sentry__transport_dump_queue(disk_transport, options->run); - sentry_transport_free(disk_transport); - - // now that the envelope was written, we can remove the temporary - // minidump file - sentry__path_remove(dump_path); - sentry__path_free(dump_path); + if (should_handle) { + sentry_value_t event = sentry_value_new_event(); + sentry_envelope_t *envelope + = sentry__prepare_event(options, event, NULL); + // the event we just prepared is empty, + // so no error is recorded for it + sentry__record_errors_on_current_session(1); + sentry_session_t *session = sentry__end_current_session_with_status( + SENTRY_SESSION_STATUS_CRASHED); + sentry__envelope_add_session(envelope, session); + + // the minidump is added as an attachment, + // with type `event.minidump` + sentry_envelope_item_t *item = sentry__envelope_add_from_path( + envelope, dump_path, "attachment"); + if (item) { + sentry__envelope_item_set_header(item, "attachment_type", + sentry_value_new_string("event.minidump")); + + 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(dump_path))); + } + + // capture the envelope with the disk transport + sentry_transport_t *disk_transport + = sentry_new_disk_transport(options->run); + sentry__capture_envelope(disk_transport, envelope); + sentry__transport_dump_queue(disk_transport, options->run); + sentry_transport_free(disk_transport); + + // now that the envelope was written, we can remove the temporary + // minidump file + sentry__path_remove(dump_path); + sentry__path_free(dump_path); + } else { + SENTRY_TRACE("event was discarded by the `on_crash` hook"); + } // after capturing the crash event, try to dump all the in-flight // data of the previous transports diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index e65d6f235e..bf8e55a620 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -122,42 +122,60 @@ sentry__crashpad_backend_flush_scope( #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_WINDOWS) # ifdef SENTRY_PLATFORM_WINDOWS static bool -sentry__crashpad_handler(EXCEPTION_POINTERS *UNUSED(ExceptionInfo)) +sentry__crashpad_handler(EXCEPTION_POINTERS *ExceptionInfo) { # else static bool -sentry__crashpad_handler(int UNUSED(signum), siginfo_t *UNUSED(info), - ucontext_t *UNUSED(user_context)) +sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) { sentry__page_allocator_enable(); sentry__enter_signal_handler(); # endif SENTRY_DEBUG("flushing session and queue before crashpad handler"); + bool should_handle = true; + SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); - sentry_value_t event = sentry_value_new_event(); - if (options->before_send_func) { + if (options->on_crash_func) { + sentry_ucontext_t uctx; +# ifdef SENTRY_PLATFORM_WINDOWS + uctx.exception_ptrs = *ExceptionInfo; +# else + uctx.signum = signum; + uctx.siginfo = info; + uctx.user_context = user_context; +# endif + + SENTRY_TRACE("invoking `on_crash` hook"); + should_handle + = options->on_crash_func(&uctx, options->on_crash_data); + } else if (options->before_send_func) { + sentry_value_t event = sentry_value_new_event(); SENTRY_TRACE("invoking `before_send` hook"); event = options->before_send_func( - event, NULL, options->before_send_data); + event, nullptr, options->before_send_data); + sentry_value_decref(event); } - sentry_value_decref(event); - - sentry__record_errors_on_current_session(1); - sentry_session_t *session = sentry__end_current_session_with_status( - SENTRY_SESSION_STATUS_CRASHED); - if (session) { - sentry_envelope_t *envelope = sentry__envelope_new(); - sentry__envelope_add_session(envelope, session); - - // capture the envelope with the disk transport - sentry_transport_t *disk_transport - = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); - sentry__transport_dump_queue(disk_transport, options->run); - sentry_transport_free(disk_transport); + + if (should_handle) { + sentry__record_errors_on_current_session(1); + sentry_session_t *session = sentry__end_current_session_with_status( + SENTRY_SESSION_STATUS_CRASHED); + if (session) { + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry__envelope_add_session(envelope, session); + + // capture the envelope with the disk transport + sentry_transport_t *disk_transport + = sentry_new_disk_transport(options->run); + sentry__capture_envelope(disk_transport, envelope); + sentry__transport_dump_queue(disk_transport, options->run); + sentry_transport_free(disk_transport); + } + } else { + SENTRY_TRACE("event was discarded by the `on_crash` hook"); } sentry__transport_dump_queue(options->transport, options->run); @@ -167,8 +185,8 @@ sentry__crashpad_handler(int UNUSED(signum), siginfo_t *UNUSED(info), # ifndef SENTRY_PLATFORM_WINDOWS sentry__leave_signal_handler(); # endif - // we did not "handle" the signal, so crashpad should do that. - return false; + // further handling can be skipped via on_crash hook + return !should_handle; } #endif diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 0c5297b325..9e6e9ed7c8 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -526,21 +526,33 @@ handle_ucontext(const sentry_ucontext_t *uctx) SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); - sentry_envelope_t *envelope - = sentry__prepare_event(options, event, NULL); - // TODO(tracing): Revisit when investigating transaction flushing during - // hard crashes. - - sentry_session_t *session = sentry__end_current_session_with_status( - SENTRY_SESSION_STATUS_CRASHED); - sentry__envelope_add_session(envelope, session); - - // capture the envelope with the disk transport - sentry_transport_t *disk_transport - = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); - sentry__transport_dump_queue(disk_transport, options->run); - sentry_transport_free(disk_transport); + bool should_handle = true; + + if (options->on_crash_func) { + SENTRY_TRACE("invoking `on_crash` hook"); + should_handle + = options->on_crash_func(uctx, options->on_crash_data); + } + + if (should_handle) { + sentry_envelope_t *envelope + = sentry__prepare_event(options, event, NULL); + // TODO(tracing): Revisit when investigating transaction flushing + // during hard crashes. + + sentry_session_t *session = sentry__end_current_session_with_status( + SENTRY_SESSION_STATUS_CRASHED); + sentry__envelope_add_session(envelope, session); + + // capture the envelope with the disk transport + sentry_transport_t *disk_transport + = sentry_new_disk_transport(options->run); + sentry__capture_envelope(disk_transport, envelope); + sentry__transport_dump_queue(disk_transport, options->run); + sentry_transport_free(disk_transport); + } else { + SENTRY_TRACE("event was discarded by the `on_crash` hook"); + } // after capturing the crash event, dump all the envelopes to disk sentry__transport_dump_queue(options->transport, options->run); diff --git a/src/sentry_options.c b/src/sentry_options.c index 7588ad977f..b83c304ab7 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -119,6 +119,14 @@ sentry_options_set_before_send( opts->before_send_data = data; } +void +sentry_options_set_on_crash( + sentry_options_t *opts, sentry_crash_function_t func, void *data) +{ + opts->on_crash_func = func; + opts->on_crash_data = data; +} + void sentry_options_set_dsn(sentry_options_t *opts, const char *raw_dsn) { diff --git a/src/sentry_options.h b/src/sentry_options.h index 1ec3817c7b..c6b3c10dc8 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -54,6 +54,8 @@ typedef struct sentry_options_s { sentry_transport_t *transport; sentry_event_function_t before_send_func; void *before_send_data; + sentry_crash_function_t on_crash_func; + void *on_crash_data; /* Experimentally exposed */ double traces_sample_rate; From 6ac1404dc6f46dd6388c3b9f40ca32b585f3ef58 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 27 Jul 2022 17:44:11 +0200 Subject: [PATCH 058/207] ref(on_crash): follow-up to make on_crash releasable (#734) * mentioned in the header docs `before_send` vs `on_crash` behavior * adapted `sentry__prepare_event` to provide a flag for backends on whether `before_send` should be invoked * adapted backends to prevent running `before_send` even if `on_crash` returns true * added `on_crash` vs `before_send` integration tests for `inproc` and `breakpad` (`crashpad` will follow) * changed the signature of `on_crash` to be closer to `before_send` * added remaining CHANGELOG entries for upcoming release --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 19 ++++ CONTRIBUTING.md | 4 + examples/example.c | 66 ++++++++++++ include/sentry.h | 46 +++++++-- src/backends/sentry_backend_breakpad.cpp | 12 ++- src/backends/sentry_backend_crashpad.cpp | 59 +++++++++-- src/backends/sentry_backend_inproc.c | 9 +- src/sentry_core.c | 6 +- src/sentry_core.h | 5 +- tests/assertions.py | 23 ++++- tests/test_integration_crashpad.py | 108 +++++++++++++++++++- tests/test_integration_stdout.py | 124 ++++++++++++++++++----- 13 files changed, 418 insertions(+), 65 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e778a75212..bc9919e747 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,7 +136,7 @@ jobs: - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} - run: echo $(brew --prefix llvm@13)/bin >> $GITHUB_PATH + run: echo $(brew --prefix llvm@14)/bin >> $GITHUB_PATH - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b042fe724a..61f92a51e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,31 @@ ## Unreleased +**Features** + +- Provide `on_crash()` callback to allow clients to act on detected crashes. + Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. + `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use + `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward + compatible for current users of `before_send()` and allows gradual migration to `on_crash()` + ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). + ([#724](https://github.com/getsentry/sentry-native/pull/724), + [#734](https://github.com/getsentry/sentry-native/pull/734)) + **Fixes** +- Make Windows ModuleFinder more resilient to missing Debug Info + ([#732](https://github.com/getsentry/sentry-native/pull/732)) - Aligned pre-send event processing in `sentry_capture_event()` with the [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@espkk](https://github.com/espkk) + ## 0.4.18 **Features**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9656830275..b6e81c408f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -140,3 +140,7 @@ The example currently supports the following commends: - `sleep`: Introduces a 10 second sleep. - `add-stacktrace`: Adds the current thread stacktrace to the captured event. - `disable-backend`: Disables the build-configured crash-handler backend. +- `before-send`: Installs a `before_send()` callback that retains the event. +- `discarding-before-send`: Installs a `before_send()` callback that retains the event. +- `on-crash`: Installs an `on_crash()` callback that retains the crash event. +- `discarding-on-crash`: Installs an `on_crash()` callback that discards the crash event. diff --git a/examples/example.c b/examples/example.c index 78d91ced5c..d5d42cdada 100644 --- a/examples/example.c +++ b/examples/example.c @@ -23,6 +23,54 @@ # define sleep_s(SECONDS) sleep(SECONDS) #endif +static sentry_value_t +before_send_callback(sentry_value_t event, void *hint, void *closure) +{ + (void)hint; + (void)closure; + + // make our mark on the event + sentry_value_set_by_key( + event, "adapted_by", sentry_value_new_string("before_send")); + + // tell the backend to proceed with the event + return event; +} + +static sentry_value_t +discarding_before_send_callback(sentry_value_t event, void *hint, void *closure) +{ + (void)hint; + (void)closure; + + // discard event and signal backend to stop further processing + sentry_value_decref(event); + return sentry_value_new_null(); +} + +static sentry_value_t +discarding_on_crash_callback( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure) +{ + (void)uctx; + (void)closure; + + // discard event and signal backend to stop further processing + sentry_value_decref(event); + return sentry_value_new_null(); +} + +static sentry_value_t +on_crash_callback( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure) +{ + (void)uctx; + (void)closure; + + // tell the backend to retain the event + return event; +} + static void print_envelope(sentry_envelope_t *envelope, void *unused_state) { @@ -105,6 +153,24 @@ main(int argc, char **argv) sentry_options_set_max_spans(options, 5); } + if (has_arg(argc, argv, "before-send")) { + sentry_options_set_before_send(options, before_send_callback, NULL); + } + + if (has_arg(argc, argv, "discarding-before-send")) { + sentry_options_set_before_send( + options, discarding_before_send_callback, NULL); + } + + if (has_arg(argc, argv, "on-crash")) { + sentry_options_set_on_crash(options, on_crash_callback, NULL); + } + + if (has_arg(argc, argv, "discarding-on-crash")) { + sentry_options_set_on_crash( + options, discarding_on_crash_callback, NULL); + } + sentry_init(options); if (!has_arg(argc, argv, "no-setup")) { diff --git a/include/sentry.h b/include/sentry.h index 8032a36252..442e30b72e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -86,7 +86,6 @@ extern "C" { #include #include -#include #include /* context type dependencies */ @@ -771,6 +770,11 @@ SENTRY_API void sentry_options_set_transport( * call `sentry_value_decref` on the provided event, and return a * `sentry_value_new_null()` instead. * + * If you have set an `on_crash` callback (independent of whether it discards or + * retains the event), `before_send` will no longer be invoked for crash-events, + * which allows you to better distinguish between crashes and all other events + * in client-side pre-processing. + * * This function may be invoked inside of a signal handler and must be safe for * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see @@ -798,20 +802,48 @@ SENTRY_API void sentry_options_set_before_send( /** * Type of the `on_crash` callback. * - * Does not work with crashpad on macOS. - * The callback passes a pointer to sentry_ucontext_s structure when exception - * handler is invoked. For breakpad on Linux this pointer is NULL. + * The `on_crash` callback replaces the `before_send` callback for crash events. + * The interface is analogous to `before_send` in that the callback takes + * ownership of the `event`, and should usually return that same event. In case + * the event should be discarded, the callback needs to call + * `sentry_value_decref` on the provided event, and return a + * `sentry_value_new_null()` instead. * - * If the callback returns false outgoing crash report will be discarded. + * Only the `inproc` backend currently fills the passed-in event with useful + * data and processes any modifications to the return value. Since both + * `breakpad` and `crashpad` use minidumps to capture the crash state, the + * passed-in event is empty when using these backends, and they ignore any + * changes to the return value. + * + * If you set this callback in the options, it prevents a concurrently enabled + * `before_send` callback from being invoked in the crash case. This allows for + * better differentiation between crashes and other events and gradual migration + * from existing `before_send` implementations: + * + * - if you have a `before_send` implementation and do not define an `on_crash` + * callback your application will receive both normal and crash events as + * before + * - if you have a `before_send` implementation but only want to handle normal + * events with it, then you can define an `on_crash` callback that returns + * the passed-in event and does nothing else + * - if you are not interested in normal events, but only want to act on + * crashes (within the limits mentioned below), then only define an + * `on_crash` callback with the option to filter (on all backends) or enrich + * (only inproc) the crash event * * This function may be invoked inside of a signal handler and must be safe for * that purpose, see https://man7.org/linux/man-pages/man7/signal-safety.7.html. * On Windows, it may be called from inside of a `UnhandledExceptionFilter`, see * the documentation on SEH (structured exception handling) for more information * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * + * Platform-specific behavior: + * + * - does not work with crashpad on macOS. + * - for breakpad on Linux the `uctx` parameter is always NULL. */ -typedef bool (*sentry_crash_function_t)( - const sentry_ucontext_t *uctx, void *closure); +typedef sentry_value_t (*sentry_crash_function_t)( + const sentry_ucontext_t *uctx, sentry_value_t event, void *closure); /** * Sets the `on_crash` callback. diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index 07d64a0bf4..1c22e43e95 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -91,6 +91,7 @@ sentry__breakpad_backend_callback( #else dump_path = sentry__path_new(descriptor.path()); #endif + sentry_value_t event = sentry_value_new_event(); SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); @@ -107,14 +108,14 @@ sentry__breakpad_backend_callback( #endif SENTRY_TRACE("invoking `on_crash` hook"); - should_handle - = options->on_crash_func(uctx, options->on_crash_data); + sentry_value_t result + = options->on_crash_func(uctx, event, options->on_crash_data); + should_handle = !sentry_value_is_null(result); } if (should_handle) { - sentry_value_t event = sentry_value_new_event(); - sentry_envelope_t *envelope - = sentry__prepare_event(options, event, NULL); + sentry_envelope_t *envelope = sentry__prepare_event( + options, event, nullptr, !options->on_crash_func); // the event we just prepared is empty, // so no error is recorded for it sentry__record_errors_on_current_session(1); @@ -152,6 +153,7 @@ sentry__breakpad_backend_callback( sentry__path_free(dump_path); } else { SENTRY_TRACE("event was discarded by the `on_crash` hook"); + sentry_value_decref(event); } // after capturing the crash event, try to dump all the in-flight diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index bf8e55a620..5bbafe8765 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -43,6 +43,7 @@ extern "C" { extern "C" { #ifdef SENTRY_PLATFORM_LINUX +# include # define SIGNAL_STACK_SIZE 65536 static stack_t g_signal_stack; @@ -133,10 +134,10 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) # endif SENTRY_DEBUG("flushing session and queue before crashpad handler"); - bool should_handle = true; + bool should_dump = true; + sentry_value_t event = sentry_value_new_event(); SENTRY_WITH_OPTIONS (options) { - sentry__write_crash_marker(options); if (options->on_crash_func) { sentry_ucontext_t uctx; @@ -149,17 +150,19 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) # endif SENTRY_TRACE("invoking `on_crash` hook"); - should_handle - = options->on_crash_func(&uctx, options->on_crash_data); + event + = options->on_crash_func(&uctx, event, options->on_crash_data); } else if (options->before_send_func) { - sentry_value_t event = sentry_value_new_event(); SENTRY_TRACE("invoking `before_send` hook"); event = options->before_send_func( event, nullptr, options->before_send_data); - sentry_value_decref(event); } + should_dump = !sentry_value_is_null(event); + sentry_value_decref(event); + + if (should_dump) { + sentry__write_crash_marker(options); - if (should_handle) { sentry__record_errors_on_current_session(1); sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); @@ -175,9 +178,8 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) sentry_transport_free(disk_transport); } } else { - SENTRY_TRACE("event was discarded by the `on_crash` hook"); + SENTRY_TRACE("event was discarded"); } - sentry__transport_dump_queue(options->transport, options->run); } @@ -185,8 +187,43 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) # ifndef SENTRY_PLATFORM_WINDOWS sentry__leave_signal_handler(); # endif - // further handling can be skipped via on_crash hook - return !should_handle; + + // If we __don't__ want a minidump produced by crashpad we need to either + // exit or longjmp at this point. The crashpad client handler which calls + // back here (SetFirstChanceExceptionHandler) does the same if the + // application is not shutdown via the crashpad_handler process. + // + // If we would return `true` here without changing any of the global signal- + // handling state or rectifying the cause of the signal, this would turn + // into a signal-handler/exception-filter loop, because some + // signals/exceptions (like SIGSEGV) are unrecoverable. + // + // Ideally the SetFirstChanceExceptionHandler would accept more than a + // boolean to differentiate between: + // + // * we accept our fate and want a minidump (currently returning `false`) + // * we accept our fate and don't want a minidump (currently not available) + // * we rectified the situation, so crashpads signal-handler can simply + // return, thereby letting the not-rectified signal-cause trigger a + // signal-handler/exception-filter again, which probably leads to us + // (currently returning `true`) + // + // TODO(supervacuus): + // * we need integration tests for more signal/exception types not only + // for unmapped memory access (which is the current crash in example.c). + // * we should adapt the SetFirstChanceExceptionHandler interface in + // crashpad + if (!should_dump) { +# ifdef SENTRY_PLATFORM_WINDOWS + // TerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); + TerminateProcess(GetCurrentProcess(), 1); +# else + _exit(EXIT_FAILURE); +# endif + } + + // we did not "handle" the signal, so crashpad should do that. + return false; } #endif diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 9e6e9ed7c8..32887a0334 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -530,13 +530,13 @@ handle_ucontext(const sentry_ucontext_t *uctx) if (options->on_crash_func) { SENTRY_TRACE("invoking `on_crash` hook"); - should_handle - = options->on_crash_func(uctx, options->on_crash_data); + event = options->on_crash_func(uctx, event, options->on_crash_data); + should_handle = !sentry_value_is_null(event); } if (should_handle) { - sentry_envelope_t *envelope - = sentry__prepare_event(options, event, NULL); + sentry_envelope_t *envelope = sentry__prepare_event( + options, event, NULL, !options->on_crash_func); // TODO(tracing): Revisit when investigating transaction flushing // during hard crashes. @@ -552,6 +552,7 @@ handle_ucontext(const sentry_ucontext_t *uctx) sentry_transport_free(disk_transport); } else { SENTRY_TRACE("event was discarded by the `on_crash` hook"); + sentry_value_decref(event); } // after capturing the crash event, dump all the envelopes to disk diff --git a/src/sentry_core.c b/src/sentry_core.c index e05751c085..1a27fe06c9 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -411,7 +411,7 @@ sentry__capture_event(sentry_value_t event) if (sentry__event_is_transaction(event)) { envelope = sentry__prepare_transaction(options, event, &event_id); } else { - envelope = sentry__prepare_event(options, event, &event_id); + envelope = sentry__prepare_event(options, event, &event_id, true); } if (envelope) { if (options->session) { @@ -460,7 +460,7 @@ sentry__should_send_transaction(sentry_value_t tx_cxt) sentry_envelope_t * sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, - sentry_uuid_t *event_id) + sentry_uuid_t *event_id, bool invoke_before_send) { sentry_envelope_t *envelope = NULL; @@ -477,7 +477,7 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__scope_apply_to_event(scope, options, event, mode); } - if (options->before_send_func) { + if (options->before_send_func && invoke_before_send) { SENTRY_TRACE("invoking `before_send` hook"); event = options->before_send_func(event, NULL, options->before_send_data); diff --git a/src/sentry_core.h b/src/sentry_core.h index 18037fbf03..6ee6c387e7 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -40,9 +40,8 @@ bool sentry__event_is_transaction(sentry_value_t event); * being passed in is not a transaction. * * More specifically, it will do the following things: - * - sample the event, possibly discarding it, * - apply the scope to it, - * - call the before_send hook on it, + * - call the before_send hook on it (if invoke_before_send == true), * - add the event to a new envelope, * - record errors on the current session, * - add any attachments to the envelope as well @@ -51,7 +50,7 @@ bool sentry__event_is_transaction(sentry_value_t event); * `event_id` out-parameter. */ sentry_envelope_t *sentry__prepare_event(const sentry_options_t *options, - sentry_value_t event, sentry_uuid_t *event_id); + sentry_value_t event, sentry_uuid_t *event_id, bool invoke_before_send); /** * Sends a sentry event, regardless of its type. diff --git a/tests/assertions.py b/tests/assertions.py index c5d3174cb0..491f2e31c0 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -1,11 +1,11 @@ import datetime import email import gzip -import sys import platform import re -from .conditions import is_android +import sys +from .conditions import is_android VERSION_RE = re.compile(r"(\d+\.\d+\.\d+)(?:[-\.]?)(.*)") @@ -194,6 +194,25 @@ def assert_crash(envelope): assert_stacktrace(envelope, inside_exception=True, check_size=False) +def assert_crash_timestamp(has_files, tmp_path): + # The crash file should survive a `sentry_init` and should still be there + # even after restarts. + if has_files: + with open("{}/.sentry-native/last_crash".format(tmp_path)) as f: + crash_timestamp = f.read() + assert_timestamp(crash_timestamp) + + +def assert_before_send(envelope): + event = envelope.get_event() + assert_matches(event, {"adapted_by": "before_send"}) + + +def assert_no_before_send(envelope): + event = envelope.get_event() + assert ("adapted_by", "before_send") not in event.items() + + def assert_crashpad_upload(req): multipart = gzip.decompress(req.get_data()) msg = email.message_from_bytes(bytes(str(req.headers), encoding="utf8") + multipart) diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index cd7363bed9..8a5651edb2 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -46,7 +46,7 @@ def test_crashpad_reinstall(cmake, httpserver): assert len(httpserver.log) == 1 -def test_crashpad_crash(cmake, httpserver): +def run_crashpad_dumping_crash(cmake, httpserver, custom_args): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) @@ -57,7 +57,8 @@ def test_crashpad_crash(cmake, httpserver): child = run( tmp_path, "sentry_example", - ["log", "start-session", "attachment", "overflow-breadcrumbs", "crash"], + ["log", "start-session", "attachment", "overflow-breadcrumbs", "crash"] + + custom_args, env=env, ) assert child.returncode # well, its a crash after all @@ -78,9 +79,112 @@ def test_crashpad_crash(cmake, httpserver): else (outputs[1].get_data(), outputs[0]) ) + return session, multipart + + +def run_crashpad_non_dumping_crash(cmake, httpserver, custom_args): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") + + with httpserver.wait(timeout=5, raise_assertions=False) as waiting: + child = run( + tmp_path, + "sentry_example", + [ + "log", + "start-session", + "attachment", + "overflow-breadcrumbs", + "crash", + ] + + custom_args, + env=env, + ) + assert child.returncode # well, its a crash after all + + assert waiting.result is False + + # the session crash heuristic on mac uses timestamps, so make sure we have + # a small delay here + time.sleep(1) + + run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) + + assert len(httpserver.log) == 1 + output = httpserver.log[0][0] + session = output.get_data() + + return session + + +def test_crashpad_crash(cmake, httpserver): + session, multipart = run_crashpad_dumping_crash(cmake, httpserver, []) + envelope = Envelope.deserialize(session) + assert_session(envelope, {"status": "crashed", "errors": 1}) + assert_crashpad_upload(multipart) + +@pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", +) +def test_crashpad_crash_before_send(cmake, httpserver): + session, multipart = run_crashpad_dumping_crash(cmake, httpserver, ["before-send"]) + + envelope = Envelope.deserialize(session) + + assert_session(envelope, {"status": "crashed", "errors": 1}) + assert_crashpad_upload(multipart) + + # TODO(supervacuus): we would expect to see a change coming from the + # before_send() hook, but in contrast to the other backends the crashpad + # backend currently only checks for null-value as a signal not to produce + # a minidump and after this decrefs the event. + + +@pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", +) +def test_crashpad_crash_discarding_before_send(cmake, httpserver): + session = run_crashpad_non_dumping_crash( + cmake, httpserver, ["discarding-before-send"] + ) + + envelope = Envelope.deserialize(session) + + assert_session(envelope, {"status": "abnormal", "errors": 0}) + + +@pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", +) +def test_crashpad_crash_discarding_on_crash(cmake, httpserver): + session = run_crashpad_non_dumping_crash(cmake, httpserver, ["discarding-on-crash"]) + + envelope = Envelope.deserialize(session) + + assert_session(envelope, {"status": "abnormal", "errors": 0}) + + +@pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", +) +def test_crashpad_crash_discarding_before_send_and_on_crash(cmake, httpserver): + session, multipart = run_crashpad_dumping_crash( + cmake, httpserver, ["discarding-before-send", "on-crash"] + ) + + envelope = Envelope.deserialize(session) + + # on_crash() is defined and overrules the discarding before_send() + assert_session(envelope, {"status": "crashed", "errors": 1}) assert_crashpad_upload(multipart) diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index 710dea63a8..ecb2c6f954 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -1,10 +1,11 @@ -import pytest +import os import subprocess import sys -import os import time + +import pytest + from . import check_output, run, Envelope -from .conditions import has_breakpad, has_files from .assertions import ( assert_attachment, assert_meta, @@ -14,8 +15,11 @@ assert_crash, assert_minidump, assert_timestamp, - assert_session, + assert_before_send, + assert_no_before_send, + assert_crash_timestamp, ) +from .conditions import has_breakpad, has_files def test_capture_stdout(cmake): @@ -111,52 +115,118 @@ def test_multi_process(cmake): assert len(runs) == 0 -def test_inproc_crash_stdout(cmake): +def run_crash_stdout_for(backend, cmake, example_args): tmp_path = cmake( ["sentry_example"], - {"SENTRY_BACKEND": "inproc", "SENTRY_TRANSPORT": "none"}, + {"SENTRY_BACKEND": backend, "SENTRY_TRANSPORT": "none"}, ) - child = run(tmp_path, "sentry_example", ["attachment", "crash"]) - assert child.returncode # well, its a crash after all + child = run(tmp_path, "sentry_example", ["attachment", "crash"] + example_args) + assert child.returncode # well, it's a crash after all + + return tmp_path, check_output(tmp_path, "sentry_example", ["stdout", "no-setup"]) + + +def test_inproc_crash_stdout(cmake): + tmp_path, output = run_crash_stdout_for("inproc", cmake, []) - output = check_output(tmp_path, "sentry_example", ["stdout", "no-setup"]) envelope = Envelope.deserialize(output) - # The crash file should survive a `sentry_init` and should still be there - # even after restarts. - if has_files: - with open("{}/.sentry-native/last_crash".format(tmp_path)) as f: - crash_timestamp = f.read() - assert_timestamp(crash_timestamp) + assert_crash_timestamp(has_files, tmp_path) + assert_meta(envelope, integration="inproc") + assert_breadcrumb(envelope) + assert_attachment(envelope) + assert_crash(envelope) + + +def test_inproc_crash_stdout_before_send(cmake): + tmp_path, output = run_crash_stdout_for("inproc", cmake, ["before-send"]) + + envelope = Envelope.deserialize(output) + assert_crash_timestamp(has_files, tmp_path) assert_meta(envelope, integration="inproc") assert_breadcrumb(envelope) assert_attachment(envelope) + assert_crash(envelope) + assert_before_send(envelope) + + +def test_inproc_crash_stdout_discarding_on_crash(cmake): + tmp_path, output = run_crash_stdout_for("inproc", cmake, ["discarding-on-crash"]) + + # since the on_crash() handler discards further processing we expect an empty response + assert len(output) == 0 + + assert_crash_timestamp(has_files, tmp_path) + + +def test_inproc_crash_stdout_before_send_and_on_crash(cmake): + tmp_path, output = run_crash_stdout_for( + "inproc", cmake, ["before-send", "on-crash"] + ) + + # the on_crash() hook retains the event + envelope = Envelope.deserialize(output) + # but we expect no event modification from before_send() since setting on_crash() disables before_send() + assert_no_before_send(envelope) + assert_crash_timestamp(has_files, tmp_path) + assert_meta(envelope, integration="inproc") + assert_breadcrumb(envelope) + assert_attachment(envelope) assert_crash(envelope) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") def test_breakpad_crash_stdout(cmake): - tmp_path = cmake( - ["sentry_example"], - {"SENTRY_BACKEND": "breakpad", "SENTRY_TRANSPORT": "none"}, - ) + tmp_path, output = run_crash_stdout_for("breakpad", cmake, []) - child = run(tmp_path, "sentry_example", ["attachment", "crash"]) - assert child.returncode # well, its a crash after all + envelope = Envelope.deserialize(output) + + assert_crash_timestamp(has_files, tmp_path) + assert_meta(envelope, integration="breakpad") + assert_breadcrumb(envelope) + assert_attachment(envelope) + assert_minidump(envelope) - if has_files: - with open("{}/.sentry-native/last_crash".format(tmp_path)) as f: - crash_timestamp = f.read() - assert_timestamp(crash_timestamp) - output = check_output(tmp_path, "sentry_example", ["stdout", "no-setup"]) +@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") +def test_breakpad_crash_stdout_before_send(cmake): + tmp_path, output = run_crash_stdout_for("breakpad", cmake, ["before-send"]) + envelope = Envelope.deserialize(output) + assert_crash_timestamp(has_files, tmp_path) assert_meta(envelope, integration="breakpad") assert_breadcrumb(envelope) assert_attachment(envelope) - assert_minidump(envelope) + assert_before_send(envelope) + + +@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") +def test_breakpad_crash_stdout_discarding_on_crash(cmake): + tmp_path, output = run_crash_stdout_for("breakpad", cmake, ["discarding-on-crash"]) + + # since the on_crash() handler discards further processing we expect an empty response + assert len(output) == 0 + + assert_crash_timestamp(has_files, tmp_path) + + +@pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") +def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): + tmp_path, output = run_crash_stdout_for( + "breakpad", cmake, ["before-send", "on-crash"] + ) + + # the on_crash() hook retains the event + envelope = Envelope.deserialize(output) + # but we expect no event modification from before_send() since setting on_crash() disables before_send() + assert_no_before_send(envelope) + + assert_crash_timestamp(has_files, tmp_path) + assert_meta(envelope, integration="breakpad") + assert_breadcrumb(envelope) + assert_attachment(envelope) From 2ca30dfd3ef05d89a26744be95b6eb3b1ebee65c Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 28 Jul 2022 12:37:33 +0200 Subject: [PATCH 059/207] ref: Terminate from crashpad handler with sensible return code (#738) --- src/backends/sentry_backend_crashpad.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 5bbafe8765..09002543a7 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -33,6 +33,9 @@ extern "C" { #include "client/crashpad_info.h" #include "client/prune_crash_reports.h" #include "client/settings.h" +#if defined(_MSC_VER) +# include "util/win/termination_codes.h" +#endif #if defined(__GNUC__) # pragma GCC diagnostic pop @@ -215,8 +218,8 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) // crashpad if (!should_dump) { # ifdef SENTRY_PLATFORM_WINDOWS - // TerminateProcess(GetCurrentProcess(), kTerminationCodeCrashNoDump); - TerminateProcess(GetCurrentProcess(), 1); + TerminateProcess(GetCurrentProcess(), + crashpad::TerminationCodes::kTerminationCodeCrashNoDump); # else _exit(EXIT_FAILURE); # endif From 4a414766d2f5ed91c69a9eea619fc501eb2520b5 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 1 Aug 2022 09:54:32 +0200 Subject: [PATCH 060/207] fix: align default value for `environment` payload attribute with... (#739) ...the developer documentation: https://develop.sentry.dev/sdk/event-payloads/#optional-attribute --- src/sentry_options.c | 3 +++ tests/unit/test_session.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/sentry_options.c b/src/sentry_options.c index b83c304ab7..b0a6fe8aa6 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -33,6 +33,9 @@ sentry_options_new(void) opts->release = sentry__string_clone(getenv("SENTRY_RELEASE")); opts->environment = sentry__string_clone(getenv("SENTRY_ENVIRONMENT")); #endif + if (!opts->environment) { + opts->environment = sentry__string_clone("production"); + } opts->max_breadcrumbs = SENTRY_BREADCRUMBS_MAX; opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN; opts->auto_session_tracking = true; diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index 3fa4d9766e..59fa8774cc 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -60,6 +60,12 @@ SENTRY_TEST(session_basics) sentry_options_set_transport( options, sentry_new_function_transport(send_envelope, &called)); sentry_options_set_release(options, "my_release"); + + // the default environment is always `production` if not overwritten by the + // OS environment variable `SENTRY_ENVIRONMENT` + // (see https://develop.sentry.dev/sdk/event-payloads/#optional-attributes) + TEST_CHECK_STRING_EQUAL( + sentry_options_get_environment(options), "production"); sentry_options_set_environment(options, "my_environment"); sentry_init(options); From 666dbc1e80c1b425f3f67acdd1c6323d5e310304 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 1 Aug 2022 12:57:37 +0200 Subject: [PATCH 061/207] chore: update changelog with #739 (#741) --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61f92a51e5..0386a266f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,12 @@ - Make Windows ModuleFinder more resilient to missing Debug Info ([#732](https://github.com/getsentry/sentry-native/pull/732)) -- Aligned pre-send event processing in `sentry_capture_event()` with the +- Aligned pre-send event processing in `sentry_capture_event()` with the [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) +- Align the default value initialization for the `environment` payload attribute with the + [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) + ([#739](https://github.com/getsentry/sentry-native/pull/739)) **Thank you**: From 4890a3011f79be9b9d8c2bf1faa11126acc8a36c Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 1 Aug 2022 12:59:28 +0200 Subject: [PATCH 062/207] fix: Iterate all debug directory entries (#740) Turns out a PE file can have more than one debug directory entry. We now iterate over all of them, trying to find the one that has a valid CodeView record that we can use to get a debug_id from. --- .../sentry_modulefinder_windows.c | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/modulefinder/sentry_modulefinder_windows.c b/src/modulefinder/sentry_modulefinder_windows.c index aac7e29824..31d5449842 100644 --- a/src/modulefinder/sentry_modulefinder_windows.c +++ b/src/modulefinder/sentry_modulefinder_windows.c @@ -44,36 +44,48 @@ extract_pdb_info(uintptr_t module_addr, sentry_value_t module) nt_headers->OptionalHeader.SizeOfImage); sentry_value_set_by_key(module, "code_id", sentry_value_new_string(id_buf)); - uint32_t relative_addr - = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] - .VirtualAddress; + IMAGE_DATA_DIRECTORY debug_entry + = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; + + size_t relative_addr = (size_t)debug_entry.VirtualAddress; if (!relative_addr) { return; } - PIMAGE_DEBUG_DIRECTORY debug_dict - = (PIMAGE_DEBUG_DIRECTORY)(module_addr + relative_addr); - if (!debug_dict || debug_dict->Type != IMAGE_DEBUG_TYPE_CODEVIEW) { + size_t table_size = (size_t)debug_entry.Size; + size_t entry_size = sizeof(IMAGE_DEBUG_DIRECTORY); + if (table_size % entry_size != 0) { return; } - struct CodeViewRecord70 *debug_info - = (struct CodeViewRecord70 *)(module_addr - + debug_dict->AddressOfRawData); - if (!debug_info || debug_info->signature != CV_SIGNATURE) { - return; - } + for (size_t offset = 0; offset < table_size; offset += entry_size) { + PIMAGE_DEBUG_DIRECTORY debug_dict + = (PIMAGE_DEBUG_DIRECTORY)(module_addr + relative_addr + offset); + + if (debug_dict->Type != IMAGE_DEBUG_TYPE_CODEVIEW) { + continue; + } - sentry_value_set_by_key(module, "debug_file", - sentry_value_new_string(debug_info->pdb_filename)); + struct CodeViewRecord70 *debug_info + = (struct CodeViewRecord70 *)(module_addr + + debug_dict->AddressOfRawData); + if (debug_info->signature != CV_SIGNATURE) { + continue; + } - sentry_uuid_t debug_id_base - = sentry__uuid_from_native(&debug_info->pdb_signature); - sentry_uuid_as_string(&debug_id_base, id_buf); - id_buf[36] = '-'; - snprintf(id_buf + 37, 10, "%x", debug_info->pdb_age); - sentry_value_set_by_key( - module, "debug_id", sentry_value_new_string(id_buf)); + sentry_value_set_by_key(module, "debug_file", + sentry_value_new_string(debug_info->pdb_filename)); + + sentry_uuid_t debug_id_base + = sentry__uuid_from_native(&debug_info->pdb_signature); + sentry_uuid_as_string(&debug_id_base, id_buf); + id_buf[36] = '-'; + snprintf(id_buf + 37, 10, "%x", debug_info->pdb_age); + sentry_value_set_by_key( + module, "debug_id", sentry_value_new_string(id_buf)); + + return; + } } static void From b124224363cb3030fe83e359e79d8f75c2ed3ba4 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 1 Aug 2022 17:55:03 +0200 Subject: [PATCH 063/207] ci: Bump compiler and OS versions (#742) - bumps Linux CI host to Ubuntu 22.04 - bumps new gcc to 12, clang to 14 - bumps Android API/NDK to the latest versions (uses x86_64 images) - renames CI jobs, so their names are version-independent (for easier updating) - removes llvm apt repo - replaces codechecker "manual" build with snap package - update changelog with #740. Co-authored-by: Mischan Toosarani-Hausberger --- .github/workflows/ci.yml | 59 ++++++++++++++++++---------------------- CHANGELOG.md | 2 ++ tests/cmake.py | 4 +-- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc9919e747..6b1b904167 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,33 +31,33 @@ jobs: fail-fast: false matrix: include: - - name: Linux (gcc 7, 32-bit) + - name: Linux (old gcc, 32-bit) os: ubuntu-18.04 CC: gcc-7 CXX: g++-7 TEST_X86: 1 - - name: Linux (gcc 10) - os: ubuntu-20.04 - CC: gcc-10 - CXX: g++-10 + - name: Linux (new gcc) + os: ubuntu-22.04 + CC: gcc-12 + CXX: g++-12 # ERROR_ON_WARNINGS: 1 # The GCC analyzer 10.0.1 (as on CI) has an internal compiler error # currently, and is not stable enough to activate. # RUN_ANALYZER: gcc - - name: Linux (clang 11 + asan + llvm-cov) - os: ubuntu-20.04 - CC: clang-11 - CXX: clang++-11 + - name: Linux (clang + asan + llvm-cov) + os: ubuntu-22.04 + CC: clang-14 + CXX: clang++-14 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: asan,llvm-cov - - name: Linux (clang 11 + kcov) - os: ubuntu-20.04 - CC: clang-11 - CXX: clang++-11 + - name: Linux (clang + kcov) + os: ubuntu-22.04 + CC: clang-14 + CXX: clang++-14 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: kcov - - name: Linux (code-checker + valgrind) - os: ubuntu-20.04 + - name: Linux (gcc + code-checker + valgrind) + os: ubuntu-22.04 RUN_ANALYZER: code-checker,valgrind - name: macOS (xcode llvm) os: macOs-latest @@ -68,28 +68,30 @@ jobs: ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 CMAKE_DEFINES: -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 - - name: macOS (clang 13 + asan + llvm-cov) + - name: macOS (clang + asan + llvm-cov) os: macOs-latest CC: clang CXX: clang++ ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 RUN_ANALYZER: asan,llvm-cov - - name: Windows (VS2019) + - name: Windows (old VS, 32-bit) os: windows-2019 TEST_X86: 1 - name: Windows (latest) os: windows-latest # The Android emulator is currently only available on macos, see: # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator - - name: Android (API 16, NDK 20) + - name: Android (old API/NDK) os: macOs-latest ANDROID_API: 16 ANDROID_NDK: 20.1.5948944 - - name: Android (API 30, NDK 22) + ANDROID_ARCH: x86 + - name: Android (new API/NDK) os: macOs-latest - ANDROID_API: 30 - ANDROID_NDK: 22.1.7171670 + ANDROID_API: 32 + ANDROID_NDK: 25.0.8775105 + ANDROID_ARCH: x86_64 name: ${{ matrix.name }} runs-on: ${{ matrix.os }} @@ -100,6 +102,7 @@ jobs: RUN_ANALYZER: ${{ matrix.RUN_ANALYZER }} ANDROID_API: ${{ matrix.ANDROID_API }} ANDROID_NDK: ${{ matrix.ANDROID_NDK }} + ANDROID_ARCH: ${{ matrix.ANDROID_ARCH }} CMAKE_DEFINES: ${{ matrix.CMAKE_DEFINES }} SYSTEM_VERSION_COMPAT: ${{ matrix.SYSTEM_VERSION_COMPAT }} @@ -111,12 +114,9 @@ jobs: - name: Installing Linux Dependencies if: ${{ runner.os == 'Linux' && !env['TEST_X86'] }} - # workaround: https://github.com/actions/virtual-environments/issues/1536#issuecomment-698537248 run: | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add - - sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' -y sudo apt update - sudo apt install cmake clang-11 clang-tools llvm kcov g++-10 valgrind zlib1g-dev libcurl4-openssl-dev + sudo apt install cmake clang-14 clang-tools llvm kcov g++-12 valgrind zlib1g-dev libcurl4-openssl-dev - name: Installing Linux 32-bit Dependencies if: ${{ runner.os == 'Linux' && env['TEST_X86'] }} @@ -127,12 +127,7 @@ jobs: - name: Installing CodeChecker if: ${{ contains(env['RUN_ANALYZER'], 'code-checker') }} - run: | - sudo apt install clang-tidy curl doxygen gcc-multilib libxml2-dev libxslt1-dev python3-dev python3-virtualenv - git clone https://github.com/Ericsson/CodeChecker.git --depth 1 --branch v6.15.0 - cd CodeChecker - BUILD_LOGGER_64_BIT_ONLY=YES BUILD_UI_DIST=NO make standalone_package - echo "$PWD/build/CodeChecker/bin" >> $GITHUB_PATH + run: sudo snap install codechecker --classic - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} @@ -141,7 +136,7 @@ jobs: - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} run: | - export ANDROID_IMAGE="system-images;android-$ANDROID_API;google_apis;x86" + export ANDROID_IMAGE="system-images;android-$ANDROID_API;google_apis;$ANDROID_ARCH" echo "Downloading ndk;$ANDROID_NDK and $ANDROID_IMAGE" echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install \ "ndk;$ANDROID_NDK" "$ANDROID_IMAGE" | \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0386a266f9..4cba29fecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - Align the default value initialization for the `environment` payload attribute with the [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) ([#739](https://github.com/getsentry/sentry-native/pull/739)) +- Iterate all debug directory entries when parsing PE modules for a valid CodeView record + ([#740](https://github.com/getsentry/sentry-native/pull/740)) **Thank you**: diff --git a/tests/cmake.py b/tests/cmake.py index c59ba6db87..a0566023a3 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -175,7 +175,7 @@ def cmake(cwd, targets, options=None): buildcmd.append("--parallel") if "code-checker" in os.environ.get("RUN_ANALYZER", ""): buildcmd = [ - "CodeChecker", + "codechecker", "log", "--output", "compilation.json", @@ -206,7 +206,7 @@ def cmake(cwd, targets, options=None): ] disables = ["--disable={}".format(d) for d in disable] checkcmd = [ - "CodeChecker", + "codechecker", "check", "--jobs", str(os.cpu_count()), From b99ffeba10888cde77e7b8f724ee015f11827412 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 1 Aug 2022 15:59:36 +0000 Subject: [PATCH 064/207] release: 0.5.0 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cba29fecc..192102553f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.0 **Features** diff --git a/include/sentry.h b/include/sentry.h index 442e30b72e..44a0f878f9 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.4.18" +#define SENTRY_SDK_VERSION "0.5.0" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 491f2e31c0..178e837cf5 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.4.18", + "version": "0.5.0", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.4.18"}, + {"name": "github:getsentry/sentry-native", "version": "0.5.0"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 78da772db4..296de6e06a 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.4.18" +auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.0" def test_capture_http(cmake, httpserver): From 87e67ad783a7ec4476b0eb4742bd40fe5a1e2435 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 1 Aug 2022 18:03:31 +0200 Subject: [PATCH 065/207] fix: linter complaint after version bump. --- tests/test_integration_http.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 296de6e06a..042d91a814 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -23,7 +23,9 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") -auth_header = "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.0" +auth_header = ( + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.0" +) def test_capture_http(cmake, httpserver): From b0fab1048643e7eaa89d81c816efd2e31783c4f2 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Thu, 15 Sep 2022 16:34:52 +0200 Subject: [PATCH 066/207] feat: Update Crashpad and register WER handler (#735) Crashpad added a new WER (Windows Error Reporting) handler which needs to be manually registered first in the Windows Registry, and then with the crashpad client. This WER module is able to capture a wider range of crashes that would otherwise bypass the in-process structured exception handling (SEH) mechanism. Co-authored-by: Mischan Toosarani-Hausberger --- .gitignore | 6 +- CHANGELOG.md | 22 ++- CMakeLists.txt | 20 +++ CONTRIBUTING.md | 9 +- README.md | 8 +- examples/example.c | 55 ++++++ external/breakpad | 2 +- external/crashpad | 2 +- include/sentry.h | 9 + src/backends/sentry_backend_crashpad.cpp | 37 +++- tests/conftest.py | 17 +- tests/test_integration_crashpad.py | 206 ++++++++++++++--------- 12 files changed, 299 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 7e72ac0c9b..f3c4c8a7cd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,10 @@ .DS_Store .vs CMakeSettings.json +.idea # CMake builds -/build -/build* -leak-build -unit-build +/*build* # Run files .sentry-native diff --git a/CHANGELOG.md b/CHANGELOG.md index 192102553f..3a0c88ff07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,34 @@ # Changelog +## Unreleased + +**Features**: + +- Crashpad on Windows now supports `fast-fail` crashes via a registered Windows Error Reporting (WER) module. ([#735](https://github.com/getsentry/sentry-native/pull/735)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) + ## 0.5.0 **Features** - Provide `on_crash()` callback to allow clients to act on detected crashes. Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. - `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use + `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward compatible for current users of `before_send()` and allows gradual migration to `on_crash()` - ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). - ([#724](https://github.com/getsentry/sentry-native/pull/724), - [#734](https://github.com/getsentry/sentry-native/pull/734)) + ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). + ([#724](https://github.com/getsentry/sentry-native/pull/724), + [#734](https://github.com/getsentry/sentry-native/pull/734)) **Fixes** -- Make Windows ModuleFinder more resilient to missing Debug Info +- Make Windows ModuleFinder more resilient to missing Debug Info ([#732](https://github.com/getsentry/sentry-native/pull/732)) - Aligned pre-send event processing in `sentry_capture_event()` with the - [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) + [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) - Align the default value initialization for the `environment` payload attribute with the [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31861875aa..21adb7b1cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,6 +436,9 @@ if(SENTRY_BACKEND_CRASHPAD) set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) endif() add_subdirectory(external/crashpad crashpad_build) + if(CRASHPAD_WER_ENABLED) + add_compile_definitions(CRASHPAD_WER_ENABLED) + endif() # set static runtime if enabled if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) @@ -448,6 +451,9 @@ if(SENTRY_BACKEND_CRASHPAD) set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + if(CRASHPAD_WER_ENABLED) + set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() @@ -464,6 +470,9 @@ if(SENTRY_BACKEND_CRASHPAD) set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) + if(CRASHPAD_WER_ENABLED) + set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) + endif() endif() target_link_libraries(sentry PRIVATE @@ -476,9 +485,16 @@ if(SENTRY_BACKEND_CRASHPAD) if(WIN32 AND MSVC) sentry_install(FILES $ DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + if (CRASHPAD_WER_ENABLED) + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + endif() endif() endif() add_dependencies(sentry crashpad::handler) + if(CRASHPAD_WER_ENABLED) + add_dependencies(sentry crashpad::wer) + endif() elseif(SENTRY_BACKEND_BREAKPAD) option(SENTRY_BREAKPAD_SYSTEM "Use system breakpad" OFF) if(SENTRY_BREAKPAD_SYSTEM) @@ -568,6 +584,10 @@ if(SENTRY_BUILD_EXAMPLES) if(MSVC) target_compile_options(sentry_example PRIVATE $) + if(CRASHPAD_WER_ENABLED) + # to test handling SEH by-passing exceptions we need to enable the control flow guard + target_compile_options(sentry_example PRIVATE $) + endif() endif() # set static runtime if enabled diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6e81c408f..61bc6f297e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,7 +122,7 @@ The example can be run manually with a variety of commands to test different scenarios. Additionally, it will use the `SENTRY_DSN` env-variable, and can thus also be used to capture events/crashes directly to sentry. -The example currently supports the following commends: +The example currently supports the following commands: - `capture-event`: Captures an event. - `crash`: Triggers a crash to be captured. @@ -141,6 +141,11 @@ The example currently supports the following commends: - `add-stacktrace`: Adds the current thread stacktrace to the captured event. - `disable-backend`: Disables the build-configured crash-handler backend. - `before-send`: Installs a `before_send()` callback that retains the event. -- `discarding-before-send`: Installs a `before_send()` callback that retains the event. +- `discarding-before-send`: Installs a `before_send()` callback that discards the event. - `on-crash`: Installs an `on_crash()` callback that retains the crash event. - `discarding-on-crash`: Installs an `on_crash()` callback that discards the crash event. + +Only on Windows using crashpad with its WER handler module: + +- `fastfail`: Crashes the application using the `__fastfail` intrinsic directly, thus by-passing SEH. +- `stack-buffer-overrun`: Triggers the Windows Control Flow Guard, which also fast fails and in turn by-passes SEH. diff --git a/README.md b/README.md index f4a866aa9b..77d5da1d21 100644 --- a/README.md +++ b/README.md @@ -315,8 +315,14 @@ Other important configuration options include: - 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 + `before_send` or `on_crash` hook. It will also lose any events that have been queued for sending at time of crash. +- The Crashpad backend on Windows supports fast-fail crashes, which bypass SEH (Structured + Exception Handling) primarily for security reasons. `sentry-native` registers a WER (Windows Error + Reporting) module, which signals the `crashpad_handler` to send a minidump when a fast-fail crash occurs + But since this process bypasses SEH, the application local exception handler is no longer invoked, which + also means that for these kinds of crashes, `before_send` and `on_crash` will not be invoked before + sending the minidump and thus have no effect. ## Development diff --git a/examples/example.c b/examples/example.c index d5d42cdada..ad9e594f94 100644 --- a/examples/example.c +++ b/examples/example.c @@ -93,6 +93,53 @@ has_arg(int argc, char **argv, const char *arg) return false; } +#ifdef CRASHPAD_WER_ENABLED +int +call_rffe_many_times() +{ + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + RaiseFailFastException(NULL, NULL, 0); + return 1; +} + +typedef int (*crash_func)(); + +void +indirect_call(crash_func func) +{ + // This code always generates CFG guards. + func(); +} + +static void +trigger_stack_buffer_overrun() +{ + // Call into the middle of the Crashy function. + crash_func func = (crash_func)((uintptr_t)(call_rffe_many_times) + 16); + __try { + // Generates a STATUS_STACK_BUFFER_OVERRUN exception if CFG triggers. + indirect_call(func); + } __except (EXCEPTION_EXECUTE_HANDLER) { + // CFG fast fail should never be caught. + printf( + "If you see me, then CFG wasn't enabled (compile with /guard:cf)"); + } + // Should only reach here if CFG is disabled. + abort(); +} + +static void +trigger_fastfail_crash() +{ + // this bypasses WINDOWS SEH and will only be caught with the crashpad WER + // module enabled + __fastfail(77); +} + +#endif // CRASHPAD_WER_ENABLED + #ifdef SENTRY_PLATFORM_AIX // AIX has a null page mapped to the bottom of memory, which means null derefs // don't segfault. try dereferencing the top of memory instead; the top nibble @@ -250,6 +297,14 @@ main(int argc, char **argv) if (has_arg(argc, argv, "crash")) { trigger_crash(); } +#ifdef CRASHPAD_WER_ENABLED + if (has_arg(argc, argv, "fastfail")) { + trigger_fastfail_crash(); + } + if (has_arg(argc, argv, "stack-buffer-overrun")) { + trigger_stack_buffer_overrun(); + } +#endif if (has_arg(argc, argv, "assert")) { assert(0); } diff --git a/external/breakpad b/external/breakpad index 1fc7929f9e..caba8b9e85 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 1fc7929f9ed5073184fe2ba7b52a35d75124ca04 +Subproject commit caba8b9e85efa28a137609c1095c5f0d2e906775 diff --git a/external/crashpad b/external/crashpad index 82e2b7db9f..48b24a2fb1 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 82e2b7db9fdc99bdd2dee65c9c75f5c1408db3de +Subproject commit 48b24a2fb12d55688656883fe2eed8ce64709f2c diff --git a/include/sentry.h b/include/sentry.h index 44a0f878f9..87bc0fb33c 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -787,6 +787,11 @@ SENTRY_API void sentry_options_set_transport( * event, following the cross-SDK session filter order: * * https://develop.sentry.dev/sdk/sessions/#filter-order + * + * On Windows the crashpad backend can capture fast-fail crashes which by-pass + * SEH. Since the `before_send` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. */ typedef sentry_value_t (*sentry_event_function_t)( sentry_value_t event, void *hint, void *closure); @@ -841,6 +846,10 @@ SENTRY_API void sentry_options_set_before_send( * * - does not work with crashpad on macOS. * - for breakpad on Linux the `uctx` parameter is always NULL. + * - on Windows the crashpad backend can capture fast-fail crashes which + * by-pass SEH. Since `on_crash` is called by a local exception-handler, it will + * not be invoked when such a crash happened, even though a minidump will be + * sent. */ typedef sentry_value_t (*sentry_crash_function_t)( const sentry_ucontext_t *uctx, sentry_value_t event, void *closure); diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 09002543a7..f9f57fc3ea 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -276,7 +276,6 @@ sentry__crashpad_backend_startup( base::FilePath database(options->database_path->path); base::FilePath handler(absolute_handler_path->path); - sentry__path_free(absolute_handler_path); std::map annotations; std::vector attachments; @@ -320,6 +319,42 @@ sentry__crashpad_backend_startup( annotations, arguments, /* restartable */ true, /* asynchronous_start */ false, attachments); +#ifdef CRASHPAD_WER_ENABLED + sentry_path_t *handler_dir = sentry__path_dir(absolute_handler_path); + sentry_path_t *wer_path = nullptr; + if (handler_dir) { + wer_path = sentry__path_join_str(handler_dir, "crashpad_wer.dll"); + sentry__path_free(handler_dir); + } + + if (wer_path && sentry__path_is_file(wer_path)) { + SENTRY_TRACEF("registering crashpad WER handler " + "\"%" SENTRY_PATH_PRI "\"", + wer_path->path); + + // The WER handler needs to be registered in the registry first. + DWORD dwOne = 1; + LSTATUS reg_res = RegSetKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\Windows Error Reporting\\" + L"RuntimeExceptionHelperModules", + wer_path->path, REG_DWORD, &dwOne, sizeof(DWORD)); + if (reg_res != ERROR_SUCCESS) { + SENTRY_WARN("registering crashpad WER handler in registry failed"); + } else { + std::wstring wer_path_string(wer_path->path); + if (!client.RegisterWerModule(wer_path_string)) { + SENTRY_WARN("registering crashpad WER handler module failed"); + } + } + + sentry__path_free(wer_path); + } else { + SENTRY_WARN("crashpad WER handler module not found"); + } +#endif // CRASHPAD_WER_ENABLED + + sentry__path_free(absolute_handler_path); + if (success) { SENTRY_DEBUG("started crashpad client handler"); } else { diff --git a/tests/conftest.py b/tests/conftest.py index 17c5014315..b2ba2e67ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ def enumerate_unittests(): - regexp = re.compile("XX\((.*?)\)") + regexp = re.compile(r"XX\((.*?)\)") # TODO: actually generate the `tests.inc` file with python curdir = os.path.dirname(os.path.realpath(__file__)) with open(os.path.join(curdir, "unit/tests.inc"), "r") as testsfile: @@ -28,3 +28,18 @@ def cmake(tmp_path_factory): yield cmake.compile cmake.destroy() + + +def pytest_addoption(parser): + parser.addoption( + "--with_crashpad_wer", + action="store_true", + help="Enables tests for the crashpad WER module on Windows", + ) + + +def pytest_runtest_setup(item): + if "with_crashpad_wer" in item.keywords and not item.config.getoption( + "--with_crashpad_wer" + ): + pytest.skip("need --with_crashpad_wer to run this test") diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index 8a5651edb2..fda314da6b 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -1,5 +1,6 @@ import pytest import os +import shutil import sys import time from . import make_dsn, run, Envelope @@ -8,7 +9,6 @@ pytestmark = pytest.mark.skipif(not has_crashpad, reason="tests need crashpad backend") - # Windows and Linux are currently able to flush all the state on crash flushes_state = sys.platform != "darwin" @@ -16,6 +16,9 @@ def test_crashpad_capture(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") run( @@ -32,12 +35,15 @@ def test_crashpad_capture(cmake, httpserver): def test_crashpad_reinstall(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") with httpserver.wait(timeout=10) as waiting: child = run(tmp_path, "sentry_example", ["log", "reinstall", "crash"], env=env) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all assert waiting.result @@ -46,9 +52,36 @@ def test_crashpad_reinstall(cmake, httpserver): assert len(httpserver.log) == 1 -def run_crashpad_dumping_crash(cmake, httpserver, custom_args): +@pytest.mark.skipif( + sys.platform != "win32", + reason="Test covers Windows-specific crashes which can only be covered via the Crashpad WER module", +) +# this test currently can't run on CI because the Windows-image doesn't properly support WER, if you want to run the +# test locally, invoke pytest with the --with_crashpad_wer option which is matched with this marker in the runtest setup +@pytest.mark.with_crashpad_wer +@pytest.mark.parametrize( + "run_args", + [ + # discarding via before-send or on-crash has no consequence for fast-fail crashes because they by-pass SEH and + # thus the crash-handler gets no chance to invoke the FirstChanceHandler which in turn would trigger our hooks. + (["stack-buffer-overrun"]), + (["stack-buffer-overrun", "discarding-before-send"]), + (["stack-buffer-overrun", "discarding-on-crash"]), + (["fastfail"]), + (["fastfail", "discarding-before-send"]), + (["fastfail", "discarding-on-crash"]), + ], +) +def test_crashpad_wer_crash(cmake, httpserver, run_args): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # If we are building on a Windows without WER enabled this test doesn't make sense + if not os.path.exists(tmp_path / "crashpad_wer.dll"): + return + + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") @@ -57,15 +90,14 @@ def run_crashpad_dumping_crash(cmake, httpserver, custom_args): child = run( tmp_path, "sentry_example", - ["log", "start-session", "attachment", "overflow-breadcrumbs", "crash"] - + custom_args, + ["log", "start-session", "attachment", "overflow-breadcrumbs"] + run_args, env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all assert waiting.result - # the session crash heuristic on mac uses timestamps, so make sure we have + # the session crash heuristic on Mac uses timestamps, so make sure we have # a small delay here time.sleep(1) @@ -79,48 +111,70 @@ def run_crashpad_dumping_crash(cmake, httpserver, custom_args): else (outputs[1].get_data(), outputs[0]) ) - return session, multipart + envelope = Envelope.deserialize(session) + + assert_session(envelope, {"status": "crashed", "errors": 1}) + assert_crashpad_upload(multipart) -def run_crashpad_non_dumping_crash(cmake, httpserver, custom_args): +@pytest.mark.parametrize( + "run_args", + [ + # if we crash, we want a dump + ([]), + # if we crash and before-send doesn't discard, we want a dump + pytest.param( + ["before-send"], + marks=pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", + ), + ), + # if on_crash() is non-discarding, a discarding before_send() is overruled, so we get a dump + pytest.param( + ["discarding-before-send", "on-crash"], + marks=pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", + ), + ), + ], +) +def test_crashpad_dumping_crash(cmake, httpserver, run_args): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") - with httpserver.wait(timeout=5, raise_assertions=False) as waiting: + with httpserver.wait(timeout=10) as waiting: child = run( tmp_path, "sentry_example", - [ - "log", - "start-session", - "attachment", - "overflow-breadcrumbs", - "crash", - ] - + custom_args, + ["log", "start-session", "attachment", "overflow-breadcrumbs", "crash"] + + run_args, env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all - assert waiting.result is False + assert waiting.result - # the session crash heuristic on mac uses timestamps, so make sure we have + # the session crash heuristic on Mac uses timestamps, so make sure we have # a small delay here time.sleep(1) run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) - assert len(httpserver.log) == 1 - output = httpserver.log[0][0] - session = output.get_data() - - return session - - -def test_crashpad_crash(cmake, httpserver): - session, multipart = run_crashpad_dumping_crash(cmake, httpserver, []) + assert len(httpserver.log) == 2 + outputs = (httpserver.log[0][0], httpserver.log[1][0]) + session, multipart = ( + (outputs[0].get_data(), outputs[1]) + if b'"type":"session"' in outputs[0].get_data() + else (outputs[1].get_data(), outputs[0]) + ) envelope = Envelope.deserialize(session) @@ -132,60 +186,49 @@ def test_crashpad_crash(cmake, httpserver): sys.platform == "darwin", reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", ) -def test_crashpad_crash_before_send(cmake, httpserver): - session, multipart = run_crashpad_dumping_crash(cmake, httpserver, ["before-send"]) - - envelope = Envelope.deserialize(session) - - assert_session(envelope, {"status": "crashed", "errors": 1}) - assert_crashpad_upload(multipart) - - # TODO(supervacuus): we would expect to see a change coming from the - # before_send() hook, but in contrast to the other backends the crashpad - # backend currently only checks for null-value as a signal not to produce - # a minidump and after this decrefs the event. - - -@pytest.mark.skipif( - sys.platform == "darwin", - reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", +@pytest.mark.parametrize( + "run_args", + [(["discarding-before-send"]), (["discarding-on-crash"])], ) -def test_crashpad_crash_discarding_before_send(cmake, httpserver): - session = run_crashpad_non_dumping_crash( - cmake, httpserver, ["discarding-before-send"] - ) - - envelope = Envelope.deserialize(session) - - assert_session(envelope, {"status": "abnormal", "errors": 0}) +def test_crashpad_non_dumping_crash(cmake, httpserver, run_args): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) -@pytest.mark.skipif( - sys.platform == "darwin", - reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", -) -def test_crashpad_crash_discarding_on_crash(cmake, httpserver): - session = run_crashpad_non_dumping_crash(cmake, httpserver, ["discarding-on-crash"]) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") - envelope = Envelope.deserialize(session) + with httpserver.wait(timeout=5, raise_assertions=False) as waiting: + child = run( + tmp_path, + "sentry_example", + [ + "log", + "start-session", + "attachment", + "overflow-breadcrumbs", + "crash", + ] + + run_args, + env=env, + ) + assert child.returncode # well, it's a crash after all - assert_session(envelope, {"status": "abnormal", "errors": 0}) + assert waiting.result is False + # the session crash heuristic on Mac uses timestamps, so make sure we have + # a small delay here + time.sleep(1) -@pytest.mark.skipif( - sys.platform == "darwin", - reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", -) -def test_crashpad_crash_discarding_before_send_and_on_crash(cmake, httpserver): - session, multipart = run_crashpad_dumping_crash( - cmake, httpserver, ["discarding-before-send", "on-crash"] - ) + run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) + assert len(httpserver.log) == 1 + output = httpserver.log[0][0] + session = output.get_data() envelope = Envelope.deserialize(session) - # on_crash() is defined and overrules the discarding before_send() - assert_session(envelope, {"status": "crashed", "errors": 1}) - assert_crashpad_upload(multipart) + assert_session(envelope, {"status": "abnormal", "errors": 0}) @pytest.mark.skipif( @@ -194,6 +237,9 @@ def test_crashpad_crash_discarding_before_send_and_on_crash(cmake, httpserver): def test_crashpad_crash_after_shutdown(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") @@ -204,11 +250,11 @@ def test_crashpad_crash_after_shutdown(cmake, httpserver): ["log", "crash-after-shutdown"], env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all assert waiting.result - # the session crash heuristic on mac uses timestamps, so make sure we have + # the session crash heuristic on Mac uses timestamps, so make sure we have # a small delay here time.sleep(1) @@ -223,6 +269,9 @@ def test_crashpad_crash_after_shutdown(cmake, httpserver): def test_crashpad_dump_inflight(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data("OK") httpserver.expect_request("/api/123456/envelope/").respond_with_data("OK") @@ -231,7 +280,7 @@ def test_crashpad_dump_inflight(cmake, httpserver): child = run( tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all assert waiting.result @@ -244,6 +293,9 @@ def test_crashpad_dump_inflight(cmake, httpserver): def test_disable_backend(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) + # make sure we are isolated from previous runs + shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) with httpserver.wait(timeout=5, raise_assertions=False) as waiting: From 5962300ea77bc546d293f15c7b091051a8abace6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 21 Sep 2022 16:04:24 +0200 Subject: [PATCH 067/207] ci: add danger workflow (#751) --- .github/workflows/danger.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/danger.yml diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 0000000000..000b75ff3e --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,9 @@ +name: Danger + +on: + pull_request: + types: [opened, synchronize, reopened, edited, ready_for_review] + +jobs: + danger: + uses: getsentry/github-workflows/.github/workflows/danger.yml@v2 From b1fbfe2c2dee6bde7f103f8e54c61e6634d6922c Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Thu, 29 Sep 2022 15:07:21 +0200 Subject: [PATCH 068/207] fix: Be more defensive around transactions (#757) This adds a bunch more NULL-checks for the outer opaque transaction/_ctx and span types. Also removes the `span_free` function which was pretty much duplicated with span_decref. --- CHANGELOG.md | 23 ++------ src/sentry_core.c | 8 ++- src/sentry_tracing.c | 115 +++++++++++++++++++++++--------------- src/sentry_tracing.h | 1 - tests/unit/test_tracing.c | 4 +- 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0c88ff07..7d4b895583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,32 +9,21 @@ **Internal**: - Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) +- Be more defensive around transactions ([#757](https://github.com/getsentry/sentry-native/pull/757)) ## 0.5.0 **Features** - Provide `on_crash()` callback to allow clients to act on detected crashes. - Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. - `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use - `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward - compatible for current users of `before_send()` and allows gradual migration to `on_crash()` - ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). - ([#724](https://github.com/getsentry/sentry-native/pull/724), - [#734](https://github.com/getsentry/sentry-native/pull/734)) + Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward compatible for current users of `before_send()` and allows gradual migration to `on_crash()` ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). ([#724](https://github.com/getsentry/sentry-native/pull/724), [#734](https://github.com/getsentry/sentry-native/pull/734)) **Fixes** -- Make Windows ModuleFinder more resilient to missing Debug Info - ([#732](https://github.com/getsentry/sentry-native/pull/732)) -- Aligned pre-send event processing in `sentry_capture_event()` with the - [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) - ([#729](https://github.com/getsentry/sentry-native/pull/729)) -- Align the default value initialization for the `environment` payload attribute with the - [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) - ([#739](https://github.com/getsentry/sentry-native/pull/739)) -- Iterate all debug directory entries when parsing PE modules for a valid CodeView record - ([#740](https://github.com/getsentry/sentry-native/pull/740)) +- Make Windows ModuleFinder more resilient to missing Debug Info ([#732](https://github.com/getsentry/sentry-native/pull/732)) +- Aligned pre-send event processing in `sentry_capture_event()` with the [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) +- Align the default value initialization for the `environment` payload attribute with the [developer documentation](https://develop.sentry.dev/sdk/event-payloads/#optional-attribute) ([#739](https://github.com/getsentry/sentry-native/pull/739)) +- Iterate all debug directory entries when parsing PE modules for a valid CodeView record ([#740](https://github.com/getsentry/sentry-native/pull/740)) **Thank you**: diff --git a/src/sentry_core.c b/src/sentry_core.c index 1a27fe06c9..669948d2d7 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -737,6 +737,10 @@ sentry_transaction_start( // traces_sampler. sentry_value_decref(sampling_ctx); + if (!opaque_tx_cxt) { + return NULL; + } + sentry_value_t tx_cxt = opaque_tx_cxt->inner; // If the parent span ID is some empty-ish value, just remove it @@ -1001,11 +1005,11 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry_value_set_by_key(root_transaction, "spans", spans); } sentry_value_append(spans, span); - sentry__span_free(opaque_span); + sentry__span_decref(opaque_span); return; fail: - sentry__span_free(opaque_span); + sentry__span_decref(opaque_span); return; } diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 49b607f1eb..9b9139a175 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -55,17 +55,13 @@ sentry_transaction_context_new(const char *name, const char *operation) if (!tx_cxt) { return NULL; } - memset(tx_cxt, 0, sizeof(sentry_transaction_context_t)); + tx_cxt->inner = sentry__value_transaction_context_new(name, operation); - sentry_value_t inner - = sentry__value_transaction_context_new(name, operation); - - if (sentry_value_is_null(inner)) { + if (sentry_value_is_null(tx_cxt->inner)) { + sentry_free(tx_cxt); return NULL; } - tx_cxt->inner = inner; - return tx_cxt; } @@ -87,36 +83,48 @@ void sentry_transaction_context_set_name( sentry_transaction_context_t *tx_cxt, const char *name) { - sentry_value_set_by_key( - tx_cxt->inner, "transaction", sentry_value_new_string(name)); + if (tx_cxt) { + sentry_value_set_by_key( + tx_cxt->inner, "transaction", sentry_value_new_string(name)); + } } void sentry_transaction_context_set_operation( sentry_transaction_context_t *tx_cxt, const char *operation) { - sentry_value_set_by_key( - tx_cxt->inner, "op", sentry_value_new_string(operation)); + if (tx_cxt) { + sentry_value_set_by_key( + tx_cxt->inner, "op", sentry_value_new_string(operation)); + } } void sentry_transaction_context_set_sampled( sentry_transaction_context_t *tx_cxt, int sampled) { - sentry_value_set_by_key( - tx_cxt->inner, "sampled", sentry_value_new_bool(sampled)); + if (tx_cxt) { + sentry_value_set_by_key( + tx_cxt->inner, "sampled", sentry_value_new_bool(sampled)); + } } void sentry_transaction_context_remove_sampled(sentry_transaction_context_t *tx_cxt) { - sentry_value_remove_by_key(tx_cxt->inner, "sampled"); + if (tx_cxt) { + sentry_value_remove_by_key(tx_cxt->inner, "sampled"); + } } void sentry_transaction_context_update_from_header( sentry_transaction_context_t *tx_cxt, const char *key, const char *value) { + if (!tx_cxt) { + return; + } + // do case-insensitive header key comparison const char sentry_trace[] = "sentry-trace"; for (size_t i = 0; i < sizeof(sentry_trace); i++) { @@ -169,7 +177,6 @@ sentry__transaction_new(sentry_value_t inner) if (!tx) { return NULL; } - memset(tx, 0, sizeof(sentry_transaction_t)); tx->inner = inner; @@ -179,7 +186,9 @@ sentry__transaction_new(sentry_value_t inner) void sentry__transaction_incref(sentry_transaction_t *tx) { - sentry_value_incref(tx->inner); + if (tx) { + sentry_value_incref(tx->inner); + } } void @@ -200,7 +209,9 @@ sentry__transaction_decref(sentry_transaction_t *tx) void sentry__span_incref(sentry_span_t *span) { - sentry_value_incref(span->inner); + if (span) { + sentry_value_incref(span->inner); + } } void @@ -212,6 +223,7 @@ sentry__span_decref(sentry_span_t *span) if (sentry_value_refcount(span->inner) <= 1) { sentry_value_decref(span->inner); + sentry__transaction_decref(span->transaction); sentry_free(span); } else { sentry_value_decref(span->inner); @@ -229,7 +241,6 @@ sentry__span_new(sentry_transaction_t *tx, sentry_value_t inner) if (!span) { return NULL; } - memset(span, 0, sizeof(sentry_span_t)); span->inner = inner; @@ -270,20 +281,6 @@ sentry__value_span_new( return sentry_value_new_null(); } -// TODO: for now, don't allow multiple references to spans. this should be -// revisited when sentry_transaction_t stores a list of sentry_span_t's -// instead of a list of sentry_value_t's. -void -sentry__span_free(sentry_span_t *span) -{ - if (!span) { - return; - } - sentry_value_decref(span->inner); - sentry__transaction_decref(span->transaction); - sentry_free(span); -} - sentry_value_t sentry__value_get_trace_context(sentry_value_t span) { @@ -323,8 +320,10 @@ sentry__value_get_trace_context(sentry_value_t span) void sentry_transaction_set_name(sentry_transaction_t *tx, const char *name) { - sentry_value_set_by_key( - tx->inner, "transaction", sentry_value_new_string(name)); + if (tx) { + sentry_value_set_by_key( + tx->inner, "transaction", sentry_value_new_string(name)); + } } static void @@ -348,13 +347,17 @@ void sentry_transaction_set_tag( sentry_transaction_t *tx, const char *tag, const char *value) { - set_tag(tx->inner, tag, value); + if (tx) { + set_tag(tx->inner, tag, value); + } } void sentry_span_set_tag(sentry_span_t *span, const char *tag, const char *value) { - set_tag(span->inner, tag, value); + if (span) { + set_tag(span->inner, tag, value); + } } static void @@ -369,13 +372,17 @@ remove_tag(sentry_value_t item, const char *tag) void sentry_transaction_remove_tag(sentry_transaction_t *tx, const char *tag) { - remove_tag(tx->inner, tag); + if (tx) { + remove_tag(tx->inner, tag); + } } void sentry_span_remove_tag(sentry_span_t *span, const char *tag) { - remove_tag(span->inner, tag); + if (span) { + remove_tag(span->inner, tag); + } } static void @@ -393,13 +400,17 @@ void sentry_transaction_set_data( sentry_transaction_t *tx, const char *key, sentry_value_t value) { - set_data(tx->inner, key, value); + if (tx) { + set_data(tx->inner, key, value); + } } void sentry_span_set_data(sentry_span_t *span, const char *key, sentry_value_t value) { - set_data(span->inner, key, value); + if (span) { + set_data(span->inner, key, value); + } } static void @@ -414,13 +425,17 @@ remove_data(sentry_value_t item, const char *key) void sentry_transaction_remove_data(sentry_transaction_t *tx, const char *key) { - remove_data(tx->inner, key); + if (tx) { + remove_data(tx->inner, key); + } } void sentry_span_remove_data(sentry_span_t *span, const char *key) { - remove_data(span->inner, key); + if (span) { + remove_data(span->inner, key); + } } sentry_value_t @@ -475,14 +490,18 @@ set_status(sentry_value_t item, sentry_span_status_t status) void sentry_span_set_status(sentry_span_t *span, sentry_span_status_t status) { - set_status(span->inner, status); + if (span) { + set_status(span->inner, status); + } } void sentry_transaction_set_status( sentry_transaction_t *tx, sentry_span_status_t status) { - set_status(tx->inner, status); + if (tx) { + set_status(tx->inner, status); + } } static void @@ -509,12 +528,16 @@ void sentry_span_iter_headers(sentry_span_t *span, sentry_iter_headers_function_t callback, void *userdata) { - sentry__span_iter_headers(span->inner, callback, userdata); + if (span) { + sentry__span_iter_headers(span->inner, callback, userdata); + } } void sentry_transaction_iter_headers(sentry_transaction_t *tx, sentry_iter_headers_function_t callback, void *userdata) { - sentry__span_iter_headers(tx->inner, callback, userdata); + if (tx) { + sentry__span_iter_headers(tx->inner, callback, userdata); + } } diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index bccc9bc78c..e04e5ee2fd 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -39,7 +39,6 @@ sentry_value_t sentry__value_span_new(size_t max_spans, sentry_value_t parent, char *operation, char *description); sentry_span_t *sentry__span_new( sentry_transaction_t *parent_tx, sentry_value_t inner); -void sentry__span_free(sentry_span_t *span); /** * Returns an object containing tracing information extracted from a diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index 052cf77956..a960273e03 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -773,7 +773,7 @@ SENTRY_TEST(distributed_headers) sentry_value_get_by_key(dist_tx->inner, "sampled"))); sentry__transaction_decref(dist_tx); - sentry__span_free(child); + sentry__span_decref(child); sentry__transaction_decref(tx); // check sampled flag @@ -801,7 +801,7 @@ SENTRY_TEST(distributed_headers) sentry_value_get_by_key(dist_tx->inner, "sampled"))); sentry__transaction_decref(dist_tx); - sentry__span_free(child); + sentry__span_decref(child); sentry__transaction_decref(tx); sentry_close(); From 40c35354b1d778411bbfcef53ec25be53aa29f18 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 7 Oct 2022 08:51:46 +0200 Subject: [PATCH 069/207] chore: update libunwindstack-ndk submodule (#759) * chore: update libunwindstack-ndk submodule synced with commit 235e2604e2fa3c1f8f7a68c5e285f9622e741e64 of upstream repo: https://android.googlesource.com/platform/system/unwinding here: https://github.com/getsentry/libunwindstack-ndk/pull/6 * Update changelog --- CHANGELOG.md | 1 + external/libunwindstack-ndk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d4b895583..85958ec8c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ **Internal**: +- Updated libunwindstack-ndk submodule to 2022-09-16 ([#759](https://github.com/getsentry/sentry-native/pull/759)) - Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) - Be more defensive around transactions ([#757](https://github.com/getsentry/sentry-native/pull/757)) diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index a4c27d48de..5a3cf38dba 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit a4c27d48deff95fe922fe9733ef5c1339bdbf4fb +Subproject commit 5a3cf38dbaa9603dc0943fa0fb1ad69d52799af0 From 366193f453f179919aa315a599085298899a8418 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 17 Oct 2022 11:18:50 +0200 Subject: [PATCH 070/207] fix: define a timeout for android sim start (#764) It happens regularly that the android simulator doesn't start (for whatever reason). But by default, that obsolete step runs until the default job timeout of 360 minutes is reached. When starting successfully, this job step never reaches 6 minutes, so 10 minutes should give it more than enough time (and would still be within the bounds of our typical CI run end-to-end of ~15 mins). --- .github/workflows/ci.yml | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b1b904167..bbf88890c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,6 +145,7 @@ jobs: - name: Starting Android Simulator if: ${{ env['ANDROID_API'] }} run: bash scripts/start-android.sh + timeout-minutes: 10 - name: Test shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 85958ec8c0..d6aa63ea55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Updated libunwindstack-ndk submodule to 2022-09-16 ([#759](https://github.com/getsentry/sentry-native/pull/759)) - Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) - Be more defensive around transactions ([#757](https://github.com/getsentry/sentry-native/pull/757)) +- Added a CI timeout for the Android simulator start. ([#764](https://github.com/getsentry/sentry-native/pull/764)) ## 0.5.0 From 736dfc512ffa82a4ca6e5b8ccfe4bc81ad80cd2f Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 17 Oct 2022 11:19:19 +0200 Subject: [PATCH 071/207] fix: Do not shut down winhttp transport on flush (#763) --- CHANGELOG.md | 10 +++++++--- src/transports/sentry_transport_winhttp.c | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6aa63ea55..a651674b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,21 +6,25 @@ - Crashpad on Windows now supports `fast-fail` crashes via a registered Windows Error Reporting (WER) module. ([#735](https://github.com/getsentry/sentry-native/pull/735)) +**Fixes**: + +- Fix "flush" implementation of winhttp transport. ([#763](https://github.com/getsentry/sentry-native/pull/763)) + **Internal**: -- Updated libunwindstack-ndk submodule to 2022-09-16 ([#759](https://github.com/getsentry/sentry-native/pull/759)) +- Updated libunwindstack-ndk submodule to 2022-09-16. ([#759](https://github.com/getsentry/sentry-native/pull/759)) - Updated Breakpad and Crashpad backends to 2022-09-14. ([#735](https://github.com/getsentry/sentry-native/pull/735)) - Be more defensive around transactions ([#757](https://github.com/getsentry/sentry-native/pull/757)) - Added a CI timeout for the Android simulator start. ([#764](https://github.com/getsentry/sentry-native/pull/764)) ## 0.5.0 -**Features** +**Features**: - Provide `on_crash()` callback to allow clients to act on detected crashes. Users often inquired about distinguishing between crashes and "normal" events in the `before_send()` hook. `on_crash()` can be considered a replacement for `before_send()` for crash events, where the goal is to use `before_send()` only for normal events, while `on_crash()` is only invoked for crashes. This change is backward compatible for current users of `before_send()` and allows gradual migration to `on_crash()` ([see the docs for details](https://docs.sentry.io/platforms/native/configuration/filtering/)). ([#724](https://github.com/getsentry/sentry-native/pull/724), [#734](https://github.com/getsentry/sentry-native/pull/734)) -**Fixes** +**Fixes**: - Make Windows ModuleFinder more resilient to missing Debug Info ([#732](https://github.com/getsentry/sentry-native/pull/732)) - Aligned pre-send event processing in `sentry_capture_event()` with the [cross-SDK session filter order](https://develop.sentry.dev/sdk/sessions/#filter-order) ([#729](https://github.com/getsentry/sentry-native/pull/729)) diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index ef9e74b672..9d509f23d8 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -111,7 +111,7 @@ static int sentry__winhttp_transport_flush(uint64_t timeout, void *transport_state) { sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_shutdown(bgworker, timeout); + return sentry__bgworker_flush(bgworker, timeout); } static int From 1a4c7c15f086644d1838c44209c8213a8cc3b497 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 17 Oct 2022 09:24:26 +0000 Subject: [PATCH 072/207] release: 0.5.1 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a651674b34..ebfa562414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.1 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 87bc0fb33c..4dc8b4cada 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.5.0" +#define SENTRY_SDK_VERSION "0.5.1" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 178e837cf5..a59e5c9309 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.5.0", + "version": "0.5.1", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.5.0"}, + {"name": "github:getsentry/sentry-native", "version": "0.5.1"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 042d91a814..94d031d2f2 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.0" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.1" ) From 1b7c8e7269a91f972fe37207e9a8d75d1da81d36 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 19 Oct 2022 15:28:02 +0200 Subject: [PATCH 073/207] chore: update crashpad + breakpad 2022-10-17 (#765) * chore: update crashpad + breakpad 2022-10-17 * Update changelog --- CHANGELOG.md | 6 ++++++ external/breakpad | 2 +- external/crashpad | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfa562414..0416203a4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-10-17. ([#765](https://github.com/getsentry/sentry-native/pull/765)) + ## 0.5.1 **Features**: diff --git a/external/breakpad b/external/breakpad index caba8b9e85..e406f0bcaa 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit caba8b9e85efa28a137609c1095c5f0d2e906775 +Subproject commit e406f0bcaa5139dc9e997e239e964177610bd483 diff --git a/external/crashpad b/external/crashpad index 48b24a2fb1..a42e249453 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 48b24a2fb12d55688656883fe2eed8ce64709f2c +Subproject commit a42e2494531c055c0de429d735beb11a3a662369 From fbab203d62e7e62e823d1984e2db2597dca64fbb Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 19 Oct 2022 17:05:43 +0200 Subject: [PATCH 074/207] chore: Update changelog with community contributions (#766) --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0416203a4b..4d4e36761a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,19 @@ ## Unreleased +**Fixes**: + +- Fix build when CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION is undefined. ([crashpad#73](https://github.com/getsentry/crashpad/pull/73)) + **Internal**: - Updated Breakpad and Crashpad backends to 2022-10-17. ([#765](https://github.com/getsentry/sentry-native/pull/765)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@AenBleidd](https://github.com/AenBleidd) ## 0.5.1 From 28be51f5e3acb01327b1164206d3145464577670 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 19 Oct 2022 15:06:36 +0000 Subject: [PATCH 075/207] release: 0.5.2 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d4e36761a..18b8241947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.2 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 4dc8b4cada..af94c7849e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.5.1" +#define SENTRY_SDK_VERSION "0.5.2" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index a59e5c9309..04133f742c 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.5.1", + "version": "0.5.2", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.5.1"}, + {"name": "github:getsentry/sentry-native", "version": "0.5.2"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 94d031d2f2..8173905ef6 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.1" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.2" ) From e5eeb797c77b95b9d5165c8f71da845250ef6a73 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 21 Oct 2022 10:54:25 +0200 Subject: [PATCH 076/207] ci: Update actions to get rid of deprecated... (#767) ...node.js runner versions: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/ It might also be relevant for the deprecation of the `set-output` command: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ which we also don't invoke directly but via our actions. * specify python-version for setup-python * Update changelog * Update .github/workflows/ci.yml Co-authored-by: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Co-authored-by: Ivan Dlugos <6349682+vaind@users.noreply.github.com> --- .github/workflows/ci.yml | 17 ++++++++++------- CHANGELOG.md | 6 ++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbf88890c4..ef7104bb71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,14 +12,14 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: make style build-ios: name: Xcode Build for iOS runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: "recursive" - run: | @@ -107,10 +107,13 @@ jobs: SYSTEM_VERSION_COMPAT: ${{ matrix.SYSTEM_VERSION_COMPAT }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' - name: Installing Linux Dependencies if: ${{ runner.os == 'Linux' && !env['TEST_X86'] }} @@ -157,7 +160,7 @@ jobs: - name: "Upload to codecov.io" if: ${{ contains(env['RUN_ANALYZER'], 'cov') }} - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # pin@v3 with: directory: coverage @@ -169,7 +172,7 @@ jobs: # run on master or the release branch if: ${{ needs.test.result == 'success' && github.event_name == 'push' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive @@ -179,7 +182,7 @@ jobs: zip -r sentry-native.zip . - name: Upload source artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ${{ github.sha }} # When downloading artifacts, they are double-zipped: diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b8241947..3eb691f671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Internal**: + +- CI: update github actions to upgrade deprecated node runners. ([#767](https://github.com/getsentry/sentry-native/pull/767)) + ## 0.5.2 **Fixes**: From d380374c32d58f75b16758217338ec9cc9aa5b9e Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 24 Oct 2022 20:08:29 +0200 Subject: [PATCH 077/207] ci: upgrade ubuntu for old gcc job (#768) --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef7104bb71..3afc36bb55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: matrix: include: - name: Linux (old gcc, 32-bit) - os: ubuntu-18.04 + os: ubuntu-20.04 CC: gcc-7 CXX: g++-7 TEST_X86: 1 @@ -126,7 +126,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install cmake gcc-multilib g++-multilib zlib1g-dev:i386 libssl-dev:i386 libcurl4-openssl-dev:i386 + sudo apt install cmake gcc-7-multilib g++-7-multilib zlib1g-dev:i386 libssl-dev:i386 libcurl4-openssl-dev:i386 - name: Installing CodeChecker if: ${{ contains(env['RUN_ANALYZER'], 'code-checker') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eb691f671..825fc92297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ **Internal**: -- CI: update github actions to upgrade deprecated node runners. ([#767](https://github.com/getsentry/sentry-native/pull/767)) +- CI: updated github actions to upgrade deprecated node runners. ([#767](https://github.com/getsentry/sentry-native/pull/767)) +- CI: upgraded Ubuntu to 20.04 for "old gcc" (v7) job due to deprecation. ([#768](https://github.com/getsentry/sentry-native/pull/768)) ## 0.5.2 From 4b546acca7f4c4713b638faa7521450a6fc7914c Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 7 Dec 2022 12:00:09 +0100 Subject: [PATCH 078/207] Search code-id in ".note" ELF sections (#775) --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 4 + src/modulefinder/sentry_modulefinder_linux.c | 207 +++++++++++-------- 3 files changed, 129 insertions(+), 88 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3afc36bb55..ca414278f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,16 +60,16 @@ jobs: os: ubuntu-22.04 RUN_ANALYZER: code-checker,valgrind - name: macOS (xcode llvm) - os: macOs-latest + os: macOs-11 ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 - name: macOS (xcode llvm + universal) - os: macOs-latest + os: macOs-11 ERROR_ON_WARNINGS: 1 SYSTEM_VERSION_COMPAT: 0 CMAKE_DEFINES: -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 - name: macOS (clang + asan + llvm-cov) - os: macOs-latest + os: macOs-11 CC: clang CXX: clang++ ERROR_ON_WARNINGS: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 825fc92297..a5d9c913d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Fixes** + +- Linux module-finder now also searches for code-id in ".note" ELF sections ([#775](https://github.com/getsentry/sentry-native/pull/775)) + **Internal**: - CI: updated github actions to upgrade deprecated node runners. ([#767](https://github.com/getsentry/sentry-native/pull/767)) diff --git a/src/modulefinder/sentry_modulefinder_linux.c b/src/modulefinder/sentry_modulefinder_linux.c index b37722de42..1d459c12f0 100644 --- a/src/modulefinder/sentry_modulefinder_linux.c +++ b/src/modulefinder/sentry_modulefinder_linux.c @@ -292,7 +292,7 @@ get_code_id_from_notes( } static const uint8_t * -get_code_id_from_elf(const sentry_module_t *module, size_t *size_out) +get_code_id_from_program_header(const sentry_module_t *module, size_t *size_out) { *size_out = 0; @@ -350,68 +350,90 @@ get_code_id_from_elf(const sentry_module_t *module, size_t *size_out) return NULL; } -static sentry_uuid_t -get_code_id_from_text_fallback(const sentry_module_t *module) -{ - const uint8_t *text = NULL; - size_t text_size = 0; - - // iterate over all the program headers, for 32/64 bit separately - unsigned char e_ident[EI_NIDENT]; - ENSURE(sentry__module_read_safely(e_ident, module, 0, EI_NIDENT)); - if (e_ident[EI_CLASS] == ELFCLASS64) { - Elf64_Ehdr elf; - ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf64_Ehdr))); +#define ELF_SECTION_ITER(INNER) \ + unsigned char e_ident[EI_NIDENT]; \ + ENSURE(sentry__module_read_safely(e_ident, module, 0, EI_NIDENT)); \ + if (e_ident[EI_CLASS] == ELFCLASS64) { \ + Elf64_Ehdr elf; \ + ENSURE( \ + sentry__module_read_safely(&elf, module, 0, sizeof(Elf64_Ehdr))); \ + \ + Elf64_Shdr strheader; \ + ENSURE(sentry__module_read_safely(&strheader, module, \ + elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, \ + sizeof(Elf64_Shdr))); \ + \ + for (int i = 0; i < elf.e_shnum; i++) { \ + Elf64_Shdr header; \ + ENSURE(sentry__module_read_safely(&header, module, \ + elf.e_shoff + elf.e_shentsize * i, sizeof(Elf64_Shdr))); \ + \ + char name[6]; \ + ENSURE(sentry__module_read_safely(name, module, \ + strheader.sh_offset + header.sh_name, sizeof(name))); \ + name[5] = '\0'; \ + \ + INNER \ + } \ + } else { \ + Elf32_Ehdr elf; \ + ENSURE( \ + sentry__module_read_safely(&elf, module, 0, sizeof(Elf32_Ehdr))); \ + \ + Elf32_Shdr strheader; \ + ENSURE(sentry__module_read_safely(&strheader, module, \ + elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, \ + sizeof(Elf32_Shdr))); \ + \ + for (int i = 0; i < elf.e_shnum; i++) { \ + Elf32_Shdr header; \ + ENSURE(sentry__module_read_safely(&header, module, \ + elf.e_shoff + elf.e_shentsize * i, sizeof(Elf32_Shdr))); \ + \ + char name[6]; \ + ENSURE(sentry__module_read_safely(name, module, \ + strheader.sh_offset + header.sh_name, sizeof(name))); \ + name[5] = '\0'; \ + \ + INNER \ + } \ + } - Elf64_Shdr strheader; - ENSURE(sentry__module_read_safely(&strheader, module, - elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, - sizeof(Elf64_Shdr))); +static const uint8_t * +get_code_id_from_note_section(const sentry_module_t *module, size_t *size_out) +{ + *size_out = 0; - for (int i = 0; i < elf.e_shnum; i++) { - Elf64_Shdr header; - ENSURE(sentry__module_read_safely(&header, module, - elf.e_shoff + elf.e_shentsize * i, sizeof(Elf64_Shdr))); - - char name[6]; - ENSURE(sentry__module_read_safely(name, module, - strheader.sh_offset + header.sh_name, sizeof(name))); - name[5] = '\0'; - if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { - text = sentry__module_get_addr( - module, header.sh_offset, header.sh_size); - ENSURE(text); - text_size = header.sh_size; - break; + ELF_SECTION_ITER( + if (header.sh_type == SHT_NOTE && strcmp(name, ".note") == 0) { + void *segment_addr = sentry__module_get_addr( + module, header.sh_offset, header.sh_size); + ENSURE(segment_addr); + const uint8_t *code_id = get_code_id_from_notes(header.sh_addralign, + segment_addr, + (void *)((uintptr_t)segment_addr + header.sh_size), size_out); + if (code_id) { + return code_id; } - } - } else { - Elf32_Ehdr elf; - ENSURE(sentry__module_read_safely(&elf, module, 0, sizeof(Elf32_Ehdr))); + }) +fail: + return NULL; +} - Elf32_Shdr strheader; - ENSURE(sentry__module_read_safely(&strheader, module, - elf.e_shoff + elf.e_shentsize * elf.e_shstrndx, - sizeof(Elf32_Shdr))); +static sentry_uuid_t +get_code_id_from_text_section(const sentry_module_t *module) +{ + const uint8_t *text = NULL; + size_t text_size = 0; - for (int i = 0; i < elf.e_shnum; i++) { - Elf32_Shdr header; - ENSURE(sentry__module_read_safely(&header, module, - elf.e_shoff + elf.e_shentsize * i, sizeof(Elf32_Shdr))); - - char name[6]; - ENSURE(sentry__module_read_safely(name, module, - strheader.sh_offset + header.sh_name, sizeof(name))); - name[5] = '\0'; - if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { - text = sentry__module_get_addr( - module, header.sh_offset, header.sh_size); - ENSURE(text); - text_size = header.sh_size; - break; - } - } - } + ELF_SECTION_ITER( + if (header.sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) { + text = sentry__module_get_addr( + module, header.sh_offset, header.sh_size); + ENSURE(text); + text_size = header.sh_size; + break; + }) sentry_uuid_t uuid = sentry_uuid_nil(); @@ -427,28 +449,43 @@ get_code_id_from_text_fallback(const sentry_module_t *module) return sentry_uuid_nil(); } +#undef ELF_SECTION_ITER + bool sentry__procmaps_read_ids_from_elf( sentry_value_t value, const sentry_module_t *module) { - // and try to get the debug id from the elf headers of the loaded - // modules + // try to get the debug id from the elf headers of the loaded modules size_t code_id_size; - const uint8_t *code_id = get_code_id_from_elf(module, &code_id_size); + const uint8_t *code_id + = get_code_id_from_program_header(module, &code_id_size); sentry_uuid_t uuid = sentry_uuid_nil(); + if (code_id) { sentry_value_set_by_key(value, "code_id", sentry__value_new_hexstring(code_id, code_id_size)); memcpy(uuid.bytes, code_id, MIN(code_id_size, 16)); } else { - uuid = get_code_id_from_text_fallback(module); + // no code-id found, try the ".note.gnu.build-id" section + code_id = get_code_id_from_note_section(module, &code_id_size); + if (code_id) { + sentry_value_set_by_key(value, "code_id", + sentry__value_new_hexstring(code_id, code_id_size)); + + memcpy(uuid.bytes, code_id, MIN(code_id_size, 16)); + } else { + // We were not able to locate the code-id, so fall back to + // hashing the first page of the ".text" (program code) + // section. + uuid = get_code_id_from_text_section(module); + } } // the usage of these is described here: // https://getsentry.github.io/symbolicator/advanced/symbol-server-compatibility/#identifiers - // in particular, the debug_id is a `little-endian GUID`, so we have to do - // appropriate byte-flipping + // in particular, the debug_id is a `little-endian GUID`, so we have + // to do appropriate byte-flipping char *uuid_bytes = uuid.bytes; uint32_t *a = (uint32_t *)uuid_bytes; *a = htonl(*a); @@ -480,14 +517,15 @@ sentry__procmaps_module_to_value(const sentry_module_t *module) sentry_value_set_by_key( mod_val, "image_size", sentry_value_new_int32(module_size)); - // At least on the android API-16, x86 simulator, the linker apparently - // does not load the complete file into memory. Or at least, the section - // headers which are located at the end of the file are not loaded, and - // we would be poking into invalid memory. To be safe, we mmap the - // complete file from disk, so we have the on-disk layout, and are - // independent of how the runtime linker would load or re-order any - // sections. The exception here is the linux-gate, which is not an - // actual file on disk, so we actually poke at its memory. + // At least on the android API-16, x86 simulator, the linker + // apparently does not load the complete file into memory. Or at + // least, the section headers which are located at the end of the + // file are not loaded, and we would be poking into invalid memory. + // To be safe, we mmap the complete file from disk, so we have the + // on-disk layout, and are independent of how the runtime linker + // would load or re-order any sections. The exception here is the + // linux-gate, which is not an actual file on disk, so we actually + // poke at its memory. if (sentry__slice_eq(module->file, LINUX_GATE)) { sentry__procmaps_read_ids_from_elf(mod_val, module); } else { @@ -617,8 +655,9 @@ load_modules(sentry_value_t modules) uint64_t linux_vdso = get_linux_vdso(); - // we have multiple memory maps per file, and we need to merge their offsets - // based on the filename. Luckily, the maps are ordered by filename, so yay + // we have multiple memory maps per file, and we need to merge their + // offsets based on the filename. Luckily, the maps are ordered by + // filename, so yay sentry_module_t last_module; memset(&last_module, 0, sizeof(sentry_module_t)); while (true) { @@ -649,19 +688,17 @@ load_modules(sentry_value_t modules) } if (is_valid_elf_header((void *)(size_t)module.start)) { - // On android, we sometimes have multiple mappings for the same - // inode at the same offset, such as this, excuse the auto-format - // here: - // 737b5570d000-737b5570e000 r--p 00000000 07:70 34 - // /apex/com.android.runtime/lib64/bionic/libdl.so - // 737b5570e000-737b5570f000 r-xp 00000000 07:70 34 - // /apex/com.android.runtime/lib64/bionic/libdl.so - // 737b5570f000-737b55710000 r--p 00000000 07:70 34 - // /apex/com.android.runtime/lib64/bionic/libdl.so + // clang-format off + // On android, we sometimes have multiple mappings for the + // same inode at the same offset, such as this: + // 737b5570d000-737b5570e000 r--p 00000000 07:70 34 /apex/com.android.runtime/lib64/bionic/libdl.so + // 737b5570e000-737b5570f000 r-xp 00000000 07:70 34 /apex/com.android.runtime/lib64/bionic/libdl.so + // 737b5570f000-737b55710000 r--p 00000000 07:70 34 /apex/com.android.runtime/lib64/bionic/libdl.so + // clang-format on if (!is_duplicated_mapping(&last_module, &module)) { - // try to append the module based on the mappings that we have - // found so far + // try to append the module based on the mappings that + // we have found so far try_append_module(modules, &last_module); // start a new module based on the current mapping From a6e1ee3d4a988cf63d100620743ace060b539927 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 7 Dec 2022 11:01:30 +0000 Subject: [PATCH 079/207] release: 0.5.3 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d9c913d9..b4ab1f272d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.3 **Fixes** diff --git a/include/sentry.h b/include/sentry.h index af94c7849e..b4d098f9aa 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.5.2" +#define SENTRY_SDK_VERSION "0.5.3" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 04133f742c..f86c8df182 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.5.2", + "version": "0.5.3", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.5.2"}, + {"name": "github:getsentry/sentry-native", "version": "0.5.3"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 8173905ef6..7889e0e84c 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.2" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.3" ) From bf4c56ae59ccf13e81cf4fdd7bbd82753e3d13d9 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 9 Dec 2022 10:41:40 +0100 Subject: [PATCH 080/207] Cover code-id from ELF section with test case (#776) --- tests/fixtures/without-buildid-phdr.so | Bin 0 -> 15552 bytes tests/unit/test_modulefinder.c | 102 ++++++++++--------------- tests/unit/tests.inc | 6 +- 3 files changed, 45 insertions(+), 63 deletions(-) create mode 100755 tests/fixtures/without-buildid-phdr.so diff --git a/tests/fixtures/without-buildid-phdr.so b/tests/fixtures/without-buildid-phdr.so new file mode 100755 index 0000000000000000000000000000000000000000..82dd6a498286adc70e09913bdee3c162afe146f5 GIT binary patch literal 15552 zcmeHOUu+ab7@xaqp&+HGpcRNL#0N-ZK@ccv;rjoYv`|Y)(1_dX-L^e*f10~JZHb9Z z5Cb8YKJuiAG0KYxh8KJ>U=4-`67)%<4~+>4B*qkt1WfGteY@Z9y0^U&^uZW1liYs) zznS^X?C#v%%sU-Do#9YOp+wX^#gehg(?rE^X}2&$qN+*N(X~-M>H5~pn-*|`Vgyx^ zm+PoyZ;^xaFUbT7JIA>x4RHw20a1hcSq}5f>&mjvJJ?L5TyM z6o1eZ~sxliA^bM3-M7yDj+l>5N=@i=pR=+`FVxv<_I{orj^SmeU}Gpg7)Up4)+ zi|9K^k9vtMm&3$8rk1H&0_0LyENdi_%UT5|o_8!uS=|G@R?^PfBdLO8=LdS*(z&cX z5Fbk0o~^pYN{q*?;Z!!BP93wAV;3BiP7Ni@LeAW+x_Y`>+pHbt4s#c3CA_$2j8{m# zN)+Q;T$@_PXeM1N&10E|m~S)UcqE{o5`AOT3xq%9nf;!g$HuGKlW+R;JccNGH3Av|jetf#BcKt`2xtT}0vdt;2Liu0JpE_v_%C&_)3rZrQz~}ik`pdn zj~)NIZrW{N>`e1L)}N{Ujmh4-&Kh$4L|m7ZW*Rn3vg5Q+^1tf(@A`_#mbuue+1TY@ z_s1^Zjl@D%V&Bd=t0_TI64aGuh8s4t&&P9nCYw)?SrvC4jva6AXEt`~hEpFq-8@V} zX-$-_Hxs2&=}PTu#DEU_4VMA1LzFQ;Ydzf-e9i1-sx7_f%AQ|u|_y{ueTc%;0swO-^|*{AxH$k^YyPGo$yTq`p6x$<{WF0lVGSIV(G zD4SN@J?11g%4SvUK|U$=X%p^ZYZANZogqn>kOr%kzTf&W~|tN*O{(?8E{ zdw>IdZFc38V!tP{h?4EUBiP3ttP%TzyV}}bFgCa36Qf2?Dq9>k_L@7*?OUJQ;xWOn z3OPc*Dm3{w4~sQI`TAm5SE;X6z_C)lL;?Rw{ZfT-tklz}%KWR; zFDsvesMIf4n6H(3nmvYJn z_7D^DwFmbP@8b_u!6zem@ccoZ3Dq9rs3e{&26DZa4d`3QDwQ!zX zAbqF`{+C?+!tuRIdc{vA!)3~spNQi;!STB3>KBgt9O=XSlnmfsLmf8A7A(dh`fZEo zyGg$(n1p1T^i|JSB=SzdDbjA8gqlBAVL2Hq!3Qe}%CeFg;dn?XWrywDaK)&d8M&a@J@(o8&Ve?XMhY>FsVKEggTcI${zKYwsr&9S=chSI@!L zmLBV1XXl}g0c)V8wWos;a=rIIQ&Q#l$paiQiAMd0O5F1%$}CJ|ocIt?C-2cwaM_$= znGsTY>~Tk# zE^MYfDv*yR$rF<2>0$4Af~Pd`Oe#TjjS;i8t3vw^DhX zgY`y~betRUjZzWl7}@-VbNEY)1YV;c9`937tk=+f_<{2IDPHqH#(NjkkjjGX7qxqy ziEW6#C>4R86Gzk!{Lud_*||?x*Oi}d@|?r^QPOy42O$vUN#u{mdkgfs&zD(ATh<;U z&L59;0VvkD2;jc@%OnXLkNzuz1TKbqb`*_}H#l!O$(^Huej^U!@CDh} z1@Tx1&1~bSGK`);SHy#UNjiVLii+!Yk7AyQc0v3%WbnshUDYV@s3#Z?`5H0ogZ3Ly z&tfbRFK~EHbp4J#tP}UL9`Qz5<1vW$)AHQ&`9>vSlK}q&**4P${Q)lg{ReILg{$%B cJ?RZpB*uWK_>gF|_@CuS Date: Mon, 12 Dec 2022 11:42:20 +0100 Subject: [PATCH 081/207] fix: better error messages in `sentry_transport_curl` (#777) Up to now, we only printed the numeric error code returned by `curl_easy_perform()`, which isn't sensationally helpful to our users. We now also print the contents of a `CURLOPT_ERRORBUFFER` and fall back to `curl_easy_strerror()` in case the error buffer is empty. --- CHANGELOG.md | 8 +++++++- src/transports/sentry_transport_curl.c | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ab1f272d..a6069eb612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Changelog +## Unreleased + +**Fixes**: + +- Better error messages in `sentry_transport_curl` ([#777](https://github.com/getsentry/sentry-native/pull/777)) + ## 0.5.3 -**Fixes** +**Fixes**: - Linux module-finder now also searches for code-id in ".note" ELF sections ([#775](https://github.com/getsentry/sentry-native/pull/775)) diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 31605073f1..9f9f29a5db 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -176,6 +176,10 @@ sentry__curl_send_task(void *_envelope, void *_state) curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)req->body_len); curl_easy_setopt(curl, CURLOPT_USERAGENT, SENTRY_SDK_USER_AGENT); + char error_buf[CURL_ERROR_SIZE]; + error_buf[0] = 0; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf); + struct header_info info; info.retry_after = NULL; info.x_sentry_rate_limits = NULL; @@ -205,8 +209,17 @@ sentry__curl_send_task(void *_envelope, void *_state) sentry__rate_limiter_update_from_429(state->ratelimiter); } } else { - SENTRY_WARNF( - "sending via `curl_easy_perform` failed with code `%d`", (int)rv); + size_t len = strlen(error_buf); + if (len) { + if (error_buf[len - 1] == '\n') { + error_buf[len - 1] = 0; + } + SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", + (int)rv, error_buf); + } else { + SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", + (int)rv, curl_easy_strerror(rv)); + } } curl_slist_free_all(headers); From ed0a62d4e9222918d15a342ba5647561826b23ed Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 16 Dec 2022 14:22:42 +0100 Subject: [PATCH 082/207] Update pads 2022-12-12 (#778) --- CHANGELOG.md | 6 +++++- external/breakpad | 2 +- external/crashpad | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6069eb612..d7fd7d29fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ **Fixes**: -- Better error messages in `sentry_transport_curl` ([#777](https://github.com/getsentry/sentry-native/pull/777)) +- Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) + +**Internal**: + +- Updated Breakpad and Crashpad backends to 2022-12-12. ([#778](https://github.com/getsentry/sentry-native/pull/778)) ## 0.5.3 diff --git a/external/breakpad b/external/breakpad index e406f0bcaa..53abd9137b 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit e406f0bcaa5139dc9e997e239e964177610bd483 +Subproject commit 53abd9137b7f8439a2174169042dd6b30721dcf3 diff --git a/external/crashpad b/external/crashpad index a42e249453..b670429f08 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit a42e2494531c055c0de429d735beb11a3a662369 +Subproject commit b670429f08535f9409c681cc79e72904e27a0080 From 1f27ce5d0216b0c3d4989fa5ca527fe02c39e9cc Mon Sep 17 00:00:00 2001 From: cnicolaescu <121427183+cnicolaescu@users.noreply.github.com> Date: Mon, 9 Jan 2023 09:33:19 +0100 Subject: [PATCH 083/207] fix: increase curl headers buffer size to 512 (#784) --- CHANGELOG.md | 4 +++- src/transports/sentry_transport_curl.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7fd7d29fd..3f80752244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) +- Increased curl headers buffer size to 512 (in `sentry_transport_curl`). ([#784](https://github.com/getsentry/sentry-native/pull/784)) **Internal**: @@ -30,7 +31,7 @@ **Internal**: - Updated Breakpad and Crashpad backends to 2022-10-17. ([#765](https://github.com/getsentry/sentry-native/pull/765)) - + **Thank you**: Features, fixes and improvements in this release have been contributed by: @@ -650,6 +651,7 @@ See [#220](https://github.com/getsentry/sentry-native/issues/220) for details. This function now takes a pointer to the new `sentry_transport_t` type. Migrating from the old API can be done by wrapping with `sentry_new_function_transport`, like this: + ```c sentry_options_set_transport( options, sentry_new_function_transport(send_envelope_func, &closure_data)); diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 9f9f29a5db..05efc6d0e5 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -150,7 +150,7 @@ sentry__curl_send_task(void *_envelope, void *_state) struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "expect:"); for (size_t i = 0; i < req->headers_len; i++) { - char buf[255]; + char buf[512]; size_t written = (size_t)snprintf(buf, sizeof(buf), "%s:%s", req->headers[i].key, req->headers[i].value); if (written >= sizeof(buf)) { From 57701f8d7f797f5feaf33761de24f43f1f625f4c Mon Sep 17 00:00:00 2001 From: cnicolaescu <121427183+cnicolaescu@users.noreply.github.com> Date: Mon, 9 Jan 2023 09:35:12 +0100 Subject: [PATCH 084/207] fix: avoid race condition when starting bgw thread (#785) * fix: avoid race condition when starting bgw thread - specifically on Windows: the `sentry_thread_spawn` macro is defined as follows: ``` # define sentry__thread_spawn(ThreadId, Func, Data) \ (*ThreadId = CreateThread(NULL, 0, Func, Data, 0, NULL), \ *ThreadId == INVALID_HANDLE_VALUE ? 1 : 0) ``` We can see that `ThreadId` is initialized with the value returned by `CreateThread`, but the new thread might be running before `CreateThread` returns. In this change a new function `sentry__thread_get_current_threadid` is added, which returns the current thread id in a reliable way. * Updated CHANGELOG.md Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 1 + src/sentry_sync.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f80752244..20ae0d873c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) +- Fix sporadic crash on Windows due to race condition when initializing background-worker thread-id. ([#785](https://github.com/getsentry/sentry-native/pull/785)) - Increased curl headers buffer size to 512 (in `sentry_transport_curl`). ([#784](https://github.com/getsentry/sentry-native/pull/784)) **Internal**: diff --git a/src/sentry_sync.c b/src/sentry_sync.c index a6b6380a15..e81d776c20 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -20,6 +20,12 @@ typedef struct { } THREADNAME_INFO; # pragma pack(pop) +sentry_threadid_t +sentry__thread_get_current_threadid() +{ + return GetCurrentThread(); +} + int sentry__thread_setname(sentry_threadid_t thread_id, const char *thread_name) { @@ -61,6 +67,12 @@ sentry__thread_setname(sentry_threadid_t thread_id, const char *thread_name) return 0; } #else +sentry_threadid_t +sentry__thread_get_current_threadid() +{ + return pthread_self(); +} + int sentry__thread_setname(sentry_threadid_t thread_id, const char *thread_name) { @@ -218,7 +230,12 @@ worker_thread(void *data) // should be called inside thread itself because of MSVC issues and mac // https://randomascii.wordpress.com/2015/10/26/thread-naming-in-windows-time-for-something-better/ - if (sentry__thread_setname(bgw->thread_id, bgw->thread_name)) { + // Additionally, `bgw->thread_id` cannot be used reliably because it is + // subject to initialization race condition: current thread might be running + // before `bgw->thread_id` is initialized in the thread that started the + // background worker. + if (sentry__thread_setname( + sentry__thread_get_current_threadid(), bgw->thread_name)) { SENTRY_WARN("failed to set background worker thread name"); } From 4d8627e87285ead6045bcddbf70cc51183df7904 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 26 Jan 2023 15:08:55 +0100 Subject: [PATCH 085/207] fix: open the file lock on "UNIX" with O_RDRW (#791) For some reason, the file lock on UNIX was always opened using O_RDONLY. This doesn't make sense in combination with O_TRUNC, since this is a write operation. On some systems (customer-scenario was AWS EFS) this will lead to an EBADF if we try to flock that descriptor. The docs consider this combination undefined. This change also gets rid of the AIX compile conditional, since this should work on all systems now. I also added a warning to the run-initialization in case we get an error when trying to lock. macOS-11 CI runner needed to be updated to llvm15: https://github.com/actions/runner-images/blob/macOS-11/20230117.2/images/macos/macos-11-Readme.md --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + src/path/sentry_path_unix.c | 10 +--------- src/sentry_database.c | 16 ++++++++++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca414278f9..00909a062f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,7 +134,7 @@ jobs: - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} - run: echo $(brew --prefix llvm@14)/bin >> $GITHUB_PATH + run: echo $(brew --prefix llvm@15)/bin >> $GITHUB_PATH - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 20ae0d873c..78b9c82691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ **Fixes**: +- Open the database file-lock on "UNIX" with `O_RDRW` ([#791](https://github.com/getsentry/sentry-native/pull/791)) - Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) - Fix sporadic crash on Windows due to race condition when initializing background-worker thread-id. ([#785](https://github.com/getsentry/sentry-native/pull/785)) - Increased curl headers buffer size to 512 (in `sentry_transport_curl`). ([#784](https://github.com/getsentry/sentry-native/pull/784)) diff --git a/src/path/sentry_path_unix.c b/src/path/sentry_path_unix.c index 041a23b41b..d75a0eb8f1 100644 --- a/src/path/sentry_path_unix.c +++ b/src/path/sentry_path_unix.c @@ -54,15 +54,7 @@ sentry__filelock_try_lock(sentry_filelock_t *lock) { lock->is_locked = false; - const int oflags = -#ifdef SENTRY_PLATFORM_AIX - // Under AIX, O_TRUNC can only be set if it can be written to, and - // flock (well, fcntl) will return EBADF if the fd is not read-write. - O_RDWR | O_CREAT | O_TRUNC; -#else - O_RDONLY | O_CREAT | O_TRUNC; -#endif - int fd = open(lock->path->path, oflags, + int fd = open(lock->path->path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd < 0) { return false; diff --git a/src/sentry_database.c b/src/sentry_database.c index 62a619d8a6..d9874fcdb0 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -4,6 +4,7 @@ #include "sentry_json.h" #include "sentry_options.h" #include "sentry_session.h" +#include #include sentry_run_t * @@ -49,13 +50,20 @@ sentry__run_new(const sentry_path_t *database_path) run->run_path = run_path; run->session_path = session_path; run->lock = sentry__filelock_new(lock_path); - if (!run->lock || !sentry__filelock_try_lock(run->lock)) { - sentry__run_free(run); - return NULL; + if (!run->lock) { + goto error; + } + if (!sentry__filelock_try_lock(run->lock)) { + SENTRY_WARNF("failed to lock file \"%s\" (%s)", lock_path->path, + strerror(errno)); + goto error; } - sentry__path_create_dir_all(run->run_path); return run; + +error: + sentry__run_free(run); + return NULL; } void From 938b6dda4d49649e02544cd19f757ec9fdcd9eb1 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 26 Jan 2023 16:03:32 +0100 Subject: [PATCH 086/207] Cleanup pre-release changelog (#793) --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78b9c82691..94fc9cad1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,21 @@ **Fixes**: -- Open the database file-lock on "UNIX" with `O_RDRW` ([#791](https://github.com/getsentry/sentry-native/pull/791)) - Better error messages in `sentry_transport_curl`. ([#777](https://github.com/getsentry/sentry-native/pull/777)) -- Fix sporadic crash on Windows due to race condition when initializing background-worker thread-id. ([#785](https://github.com/getsentry/sentry-native/pull/785)) - Increased curl headers buffer size to 512 (in `sentry_transport_curl`). ([#784](https://github.com/getsentry/sentry-native/pull/784)) +- Fix sporadic crash on Windows due to race condition when initializing background-worker thread-id. ([#785](https://github.com/getsentry/sentry-native/pull/785)) +- Open the database file-lock on "UNIX" with `O_RDRW` ([#791](https://github.com/getsentry/sentry-native/pull/791)) **Internal**: - Updated Breakpad and Crashpad backends to 2022-12-12. ([#778](https://github.com/getsentry/sentry-native/pull/778)) +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@cnicolaescu](https://github.com/cnicolaescu) + ## 0.5.3 **Fixes**: From 63699d75a77a689abd8016c52577e799f3dbad4f Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 26 Jan 2023 15:04:31 +0000 Subject: [PATCH 087/207] release: 0.5.4 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fc9cad1d..9ca80e7440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.5.4 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index b4d098f9aa..3e5f16a878 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.5.3" +#define SENTRY_SDK_VERSION "0.5.4" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index f86c8df182..9ecc88fd21 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.5.3", + "version": "0.5.4", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.5.3"}, + {"name": "github:getsentry/sentry-native", "version": "0.5.4"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 7889e0e84c..ebf69be4f3 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.3" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.4" ) From 19df84a7b437a9dbe2dabd2a451ac2cf508e00dd Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 31 Jan 2023 14:03:58 +0100 Subject: [PATCH 088/207] fix: explicit breakpad C++17 dependency (#800) Breakpad now uses features from C++17: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3954471 We should make this explicit via the `breakpad_client` target requirements. This change only creates a lower bound because, for compilers that support only parts of C++17, the build will not necessarily complain. For instance, `breakpad` uses `static_assert()` without a second message parameter, which was added with https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3928.pdf, which not all compilers will support that accept the C++17 standard (e.g., GCC only supports it starting from version 6). The issue was initially raised here: https://github.com/getsentry/sentry-native/discussions/798#discussioncomment-4826165 --- external/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index ca807f71c8..362808ea60 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -119,6 +119,8 @@ set(BREAKPAD_SOURCES_CLIENT_IOS ) add_library(breakpad_client STATIC) +set_property(TARGET breakpad_client PROPERTY CXX_STANDARD 17) +set_property(TARGET breakpad_client PROPERTY CXX_STANDARD_REQUIRED On) target_sources(breakpad_client PRIVATE ${BREAKPAD_SOURCES_COMMON}) if(LINUX OR ANDROID) From a11a10739130d777b3529287414ddec550c52cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lup=C4=8D=C3=ADk?= Date: Thu, 2 Feb 2023 13:41:00 +0100 Subject: [PATCH 089/207] feat: Allow ending a session with a different status code (#801) --- CHANGELOG.md | 6 ++++++ include/sentry.h | 35 +++++++++++++++++++++++++---------- src/sentry_session.c | 26 ++++++++++++++++++++++---- src/sentry_session.h | 7 ------- tests/unit/test_session.c | 7 +++++-- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca80e7440..d77bf17dc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features**: + +- A session may be ended with a different status code. ([#801](https://github.com/getsentry/sentry-native/pull/801)) + ## 0.5.4 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 3e5f16a878..e7610ea1fd 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1346,16 +1346,6 @@ SENTRY_API void sentry_set_transaction(const char *transaction); */ SENTRY_API void sentry_set_level(sentry_level_t level); -/** - * Starts a new session. - */ -SENTRY_API void sentry_start_session(void); - -/** - * Ends a session. - */ -SENTRY_API void sentry_end_session(void); - /** * Sets the maximum number of spans that can be attached to a * transaction. @@ -1384,6 +1374,31 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_traces_sample_rate( SENTRY_EXPERIMENTAL_API double sentry_options_get_traces_sample_rate( sentry_options_t *opts); +/* -- Session APIs -- */ + +typedef enum { + SENTRY_SESSION_STATUS_OK, + SENTRY_SESSION_STATUS_CRASHED, + SENTRY_SESSION_STATUS_ABNORMAL, + SENTRY_SESSION_STATUS_EXITED, +} sentry_session_status_t; + +/** + * Starts a new session. + */ +SENTRY_API void sentry_start_session(void); + +/** + * Ends a session. + */ +SENTRY_API void sentry_end_session(void); + +/** + * Ends a session with an explicit `status` code. + */ +SENTRY_EXPERIMENTAL_API void sentry_end_session_with_status( + sentry_session_status_t status); + /* -- Performance Monitoring/Tracing APIs -- */ /** diff --git a/src/sentry_session.c b/src/sentry_session.c index 1aa7dc7963..e782d385f1 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -265,6 +265,17 @@ sentry__end_current_session_with_status(sentry_session_status_t status) return session; } +static void +sentry__capture_session(sentry_session_t *session) +{ + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry__envelope_add_session(envelope, session); + + SENTRY_WITH_OPTIONS (options) { + sentry__capture_envelope(options->transport, envelope); + } +} + void sentry_end_session(void) { @@ -273,13 +284,20 @@ sentry_end_session(void) return; } - sentry_envelope_t *envelope = sentry__envelope_new(); - sentry__envelope_add_session(envelope, session); + sentry__capture_session(session); sentry__session_free(session); +} - SENTRY_WITH_OPTIONS (options) { - sentry__capture_envelope(options->transport, envelope); +void +sentry_end_session_with_status(sentry_session_status_t status) +{ + sentry_session_t *session = sentry__end_current_session_with_status(status); + if (!session) { + return; } + + sentry__capture_session(session); + sentry__session_free(session); } void diff --git a/src/sentry_session.h b/src/sentry_session.h index 5ba036cbd5..2a18edb4c0 100644 --- a/src/sentry_session.h +++ b/src/sentry_session.h @@ -8,13 +8,6 @@ struct sentry_jsonwriter_s; -typedef enum { - SENTRY_SESSION_STATUS_OK, - SENTRY_SESSION_STATUS_CRASHED, - SENTRY_SESSION_STATUS_ABNORMAL, - SENTRY_SESSION_STATUS_EXITED, -} sentry_session_status_t; - /** * This represents a session, with the number of errors, a status and other * metadata. diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index 59fa8774cc..fbe586a02a 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -26,7 +26,7 @@ send_envelope(const sentry_envelope_t *envelope, void *data) SENTRY_VALUE_TYPE_STRING); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(session, "status")), - "exited"); + *called == 2 ? "crashed" : "exited"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(session, "did")), *called == 1 ? "foo@blabla.invalid" : "swatinem"); @@ -83,9 +83,12 @@ SENTRY_TEST(session_basics) user, "username", sentry_value_new_string("swatinem")); sentry_set_user(user); + sentry_end_session_with_status(SENTRY_SESSION_STATUS_CRASHED); + sentry_start_session(); + sentry_close(); - TEST_CHECK_INT_EQUAL(called, 2); + TEST_CHECK_INT_EQUAL(called, 3); } typedef struct { From 3d75dc620427251a4b6ed1d76b89fb624730030d Mon Sep 17 00:00:00 2001 From: pastdue <30942300+past-due@users.noreply.github.com> Date: Sat, 4 Feb 2023 04:03:40 -0500 Subject: [PATCH 090/207] Fixes for mingw builds (+ ARM64) (#794) --- CHANGELOG.md | 4 ++++ src/backends/sentry_backend_crashpad.cpp | 2 +- src/sentry_sync.h | 2 +- src/unwinder/sentry_unwinder_dbghelp.c | 20 ++++++++++++++++++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d77bf17dc5..ddc624d934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - A session may be ended with a different status code. ([#801](https://github.com/getsentry/sentry-native/pull/801)) +**Fixes**: + +- Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794)) + ## 0.5.4 **Fixes**: diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index f9f57fc3ea..c44672341e 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -33,7 +33,7 @@ extern "C" { #include "client/crashpad_info.h" #include "client/prune_crash_reports.h" #include "client/settings.h" -#if defined(_MSC_VER) +#if defined(_WIN32) # include "util/win/termination_codes.h" #endif diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 11915536a2..0741c30702 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -13,7 +13,7 @@ # define THREAD_FUNCTION_API #endif -#if defined(__MINGW32__) && !defined(__MINGW64__) +#if defined(__MINGW32__) && !defined(__MINGW64__) && !defined(__clang__) # define UNSIGNED_MINGW unsigned #else # define UNSIGNED_MINGW diff --git a/src/unwinder/sentry_unwinder_dbghelp.c b/src/unwinder/sentry_unwinder_dbghelp.c index fd6ec3b725..7d22728dae 100644 --- a/src/unwinder/sentry_unwinder_dbghelp.c +++ b/src/unwinder/sentry_unwinder_dbghelp.c @@ -43,16 +43,32 @@ sentry__unwind_stack_dbghelp( memset(&stack_frame, 0, sizeof(stack_frame)); size_t size = 0; -#if defined(_WIN64) +#if defined(_M_X64) int machine_type = IMAGE_FILE_MACHINE_AMD64; stack_frame.AddrPC.Offset = ctx.Rip; stack_frame.AddrFrame.Offset = ctx.Rbp; stack_frame.AddrStack.Offset = ctx.Rsp; -#else +#elif defined(_M_IX86) int machine_type = IMAGE_FILE_MACHINE_I386; stack_frame.AddrPC.Offset = ctx.Eip; stack_frame.AddrFrame.Offset = ctx.Ebp; stack_frame.AddrStack.Offset = ctx.Esp; +#elif defined(_M_ARM64) + int machine_type = IMAGE_FILE_MACHINE_ARM64; + stack_frame.AddrPC.Offset = ctx.Pc; +# if defined(NONAMELESSUNION) + stack_frame.AddrFrame.Offset = ctx.DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp; +# else + stack_frame.AddrFrame.Offset = ctx.Fp; +# endif + stack_frame.AddrStack.Offset = ctx.Sp; +#elif defined(_M_ARM) + int machine_type = IMAGE_FILE_MACHINE_ARM; + stack_frame.AddrPC.Offset = ctx.Pc; + stack_frame.AddrFrame.Offset = ctx.R11; + stack_frame.AddrStack.Offset = ctx.Sp; +#else +# error "Platform not supported!" #endif stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrFrame.Mode = AddrModeFlat; From 596260cf5f14ca209ee26159cec3cdc789bee72a Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 7 Feb 2023 12:22:30 +0100 Subject: [PATCH 091/207] chore: sync with recent crashpad changes (#803) - Switch Crashpad transport on Linux to use libcurl ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#75](https://github.com/getsentry/crashpad/pull/75), [crashpad#79](https://github.com/getsentry/crashpad/pull/79)) - Avoid accidentally mutating CONTEXT when client-side stack walking in Crashpad ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#77](https://github.com/getsentry/crashpad/pull/77)) - Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794), [crashpad#78](https://github.com/getsentry/crashpad/pull/78)) - Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) --- CHANGELOG.md | 16 +++++++++++++++- external/crashpad | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc624d934..d25527e7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,21 @@ **Fixes**: -- Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794)) +- Switch Crashpad transport on Linux to use libcurl ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#75](https://github.com/getsentry/crashpad/pull/75), [crashpad#79](https://github.com/getsentry/crashpad/pull/79)) +- Avoid accidentally mutating CONTEXT when client-side stack walking in Crashpad ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#77](https://github.com/getsentry/crashpad/pull/77)) +- Fix various mingw compilation issues ([#794](https://github.com/getsentry/sentry-native/pull/794), [crashpad#78](https://github.com/getsentry/crashpad/pull/78)) + +**Internal**: + +- Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@BogdanLivadariu](https://github.com/BogdanLivadariu) +- [@ShawnCZek](https://github.com/ShawnCZek) +- [@past-due](https://github.com/past-due) ## 0.5.4 diff --git a/external/crashpad b/external/crashpad index b670429f08..ec99257868 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit b670429f08535f9409c681cc79e72904e27a0080 +Subproject commit ec992578688b4c51c1856d08731cf7dcf10e446a From 41487cae9690e589cdf752845be3fecdec6af347 Mon Sep 17 00:00:00 2001 From: pastdue <30942300+past-due@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:59:17 -0500 Subject: [PATCH 092/207] ci: Add llvm-mingw (#797) --- .github/workflows/ci.yml | 14 +++++++++ CHANGELOG.md | 1 + scripts/install-llvm-mingw.ps1 | 54 ++++++++++++++++++++++++++++++++++ tests/cmake.py | 2 +- 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100755 scripts/install-llvm-mingw.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00909a062f..fa8c1bb9a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,11 @@ jobs: TEST_X86: 1 - name: Windows (latest) os: windows-latest + - name: LLVM-Mingw + os: windows-latest + TEST_MINGW: 1 + MINGW_PKG_PREFIX: x86_64-w64-mingw32 + MINGW_ASM_MASM_COMPILER: llvm-ml;-m64 # The Android emulator is currently only available on macos, see: # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator - name: Android (old API/NDK) @@ -98,6 +103,7 @@ jobs: env: TEST_X86: ${{ matrix.TEST_X86 }} + TEST_MINGW: ${{ matrix.TEST_MINGW }} ERROR_ON_WARNINGS: ${{ matrix.ERROR_ON_WARNINGS }} RUN_ANALYZER: ${{ matrix.RUN_ANALYZER }} ANDROID_API: ${{ matrix.ANDROID_API }} @@ -135,6 +141,14 @@ jobs: - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} run: echo $(brew --prefix llvm@15)/bin >> $GITHUB_PATH + + - name: Installing LLVM-MINGW Dependencies + if: ${{ runner.os == 'Windows' && env['TEST_MINGW'] }} + shell: powershell + env: + MINGW_PKG_PREFIX: ${{ matrix.MINGW_PKG_PREFIX }} + MINGW_ASM_MASM_COMPILER: ${{ matrix.MINGW_ASM_MASM_COMPILER }} + run: . "scripts\install-llvm-mingw.ps1" - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d25527e7c8..dbbff5ccd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ **Internal**: - Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) +- CI: Updated GitHub Actions to test on LLVM-mingw. ([#797](https://github.com/getsentry/sentry-native/pull/797)) **Thank you**: diff --git a/scripts/install-llvm-mingw.ps1 b/scripts/install-llvm-mingw.ps1 new file mode 100755 index 0000000000..a81acaf4db --- /dev/null +++ b/scripts/install-llvm-mingw.ps1 @@ -0,0 +1,54 @@ +Start-Sleep -Milliseconds 1 # See: https://stackoverflow.com/a/49859001 + +$LLVM_MINGW_RELEASE = "20220906"; +$LLVM_MINGW_PKG = "llvm-mingw-${LLVM_MINGW_RELEASE}-ucrt-x86_64" +$LLVM_MINGW_DL_URL = "https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_RELEASE}/${LLVM_MINGW_PKG}.zip" +$LLVM_MINGW_DL_SHA512 = "3c724dd0663558c7247d2cdde196b37dc54e49fb8c4065aef0274d69d92d2d023440505fe6f23e83476d56f4a39c105d551f998a4342e823a2d2705d7a73fe7c" +$DL_BASEDIR = "$env:GITHUB_WORKSPACE\dl" +$LLVM_MINGW_DL_PATH = "${DL_BASEDIR}\llvm-mingw.zip" +if (!(Test-Path -Path "$DL_BASEDIR")) { New-Item -ItemType Directory -Force -Path "$DL_BASEDIR" } + +# Download LLVM-mingw +$CurlArguments = '-s', '-Lf', '-o', "${LLVM_MINGW_DL_PATH}", "${LLVM_MINGW_DL_URL}" +& curl.exe @CurlArguments +$dl_zip_hash = Get-FileHash -LiteralPath "${LLVM_MINGW_DL_PATH}" -Algorithm SHA512 +if ($dl_zip_hash.Hash -eq $LLVM_MINGW_DL_SHA512) { + Write-Host "Successfully downloaded LLVM-mingw .zip" +} Else { + Write-Error "The downloaded LLVM-mingw zip hash '$($dl_zip_hash.Hash)' does not match the expected hash: '$LLVM_MINGW_DL_SHA512'" +} + +# Extract LLVM-mingw +Write-Host "Extracting LLVM-mingw..." +$LLVM_MINGW_INSTALL_PATH = "$env:GITHUB_WORKSPACE\buildtools\llvm-mingw" +New-Item -ItemType Directory -Force -Path "${LLVM_MINGW_INSTALL_PATH}" +Expand-Archive -LiteralPath "${LLVM_MINGW_DL_PATH}" -DestinationPath "${LLVM_MINGW_INSTALL_PATH}" +# Export the LLVM-mingw install path +$LLVM_MINGW_INSTALL_PATH = "${LLVM_MINGW_INSTALL_PATH}\${LLVM_MINGW_PKG}" +echo "LLVM_MINGW_INSTALL_PATH=${LLVM_MINGW_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +# Prepend bin path to the system PATH +echo "Path to LLVM-mingw bin folder: ${LLVM_MINGW_INSTALL_PATH}\bin" +echo "${LLVM_MINGW_INSTALL_PATH}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + +# Download ninja-build +$NINJA_DL_URL = "https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-win.zip" +$NINJA_DL_SHA512 = "a700e794c32eb67b9f87040db7f1ba3a8e891636696fc54d416b01661c2421ff46fa517c97fd904adacdf8e621df3e68ea380105b909ae8b6651a78ae7eb3199" +$NINJA_DL_PATH = "${DL_BASEDIR}\ninja-win.zip" +$CurlArguments = '-s', '-Lf', '-o', "${NINJA_DL_PATH}", "${NINJA_DL_URL}" +& curl.exe @CurlArguments +$ninja_zip_hash = Get-FileHash -LiteralPath "${NINJA_DL_PATH}" -Algorithm SHA512 +if ($ninja_zip_hash.Hash -eq $NINJA_DL_SHA512) { + Write-Host "Successfully downloaded Ninja-Build .zip" +} Else { + Write-Error "The downloaded Ninja-build zip hash '$($ninja_zip_hash.Hash)' does not match the expected hash: '$NINJA_DL_SHA512'" +} + +Write-Host "Extracting Ninja-Build..." +$NINJA_INSTALL_PATH = "$env:GITHUB_WORKSPACE\buildtools\ninja" +New-Item -ItemType Directory -Force -Path "${NINJA_INSTALL_PATH}" +Expand-Archive -LiteralPath "${NINJA_DL_PATH}" -DestinationPath "${NINJA_INSTALL_PATH}" +# Export the NINJA executable path +echo "NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + +# Add CMAKE_DEFINES +echo "CMAKE_DEFINES=-DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER} -GNinja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/tests/cmake.py b/tests/cmake.py index a0566023a3..35c48a8b57 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -143,7 +143,7 @@ def cmake(cwd, targets, options=None): cflags = [] if os.environ.get("ERROR_ON_WARNINGS"): cflags.append("-Werror") - if sys.platform == "win32": + if sys.platform == "win32" and not os.environ.get("TEST_MINGW"): # MP = object level parallelism, WX = warnings as errors cpus = os.cpu_count() cflags.append("/WX /MP{}".format(cpus)) From d82af0ad569dc2424cd0a44842ef99f60ebe5432 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 8 Feb 2023 10:32:33 +0100 Subject: [PATCH 093/207] fix: use void for all empty parameter-lists in C functions (#804) This is one of many aspects where C and C++ standards deviate: An empty parameter list in C++ is equivalent to a void parameter list. But in C, an empty parameter list meant: you could give any number of types as parameters. With C11, this "feature" became obsolete: empty parameter lists are no longer standard-conforming C. Since some of those are in our API surface, we should fix this. --- include/sentry.h | 10 +++++----- src/sentry_core.c | 4 ++-- src/sentry_info.c | 6 +++--- src/sentry_scope.c | 4 ++-- src/sentry_scope.h | 4 ++-- src/sentry_sync.c | 2 +- src/sentry_utils.c | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index e7610ea1fd..41e86fac57 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1861,7 +1861,7 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_iter_headers( * 0 = no crash recognized * -1 = sentry_init() hasn't been called yet */ -SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(); +SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(void); /** * Clear the status of the "crashed-last-run". You should explicitly call @@ -1875,22 +1875,22 @@ SENTRY_EXPERIMENTAL_API int sentry_get_crashed_last_run(); * * Returns 0 on success, 1 on error. */ -SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(); +SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(void); /** * Sentry SDK version. */ -SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(); +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(void); /** * Sentry SDK name. */ -SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(); +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(void); /** * Sentry SDK User-Agent. */ -SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(); +SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(void); #ifdef __cplusplus } diff --git a/src/sentry_core.c b/src/sentry_core.c index 669948d2d7..b10d6065df 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1014,13 +1014,13 @@ sentry_span_finish(sentry_span_t *opaque_span) } int -sentry_get_crashed_last_run() +sentry_get_crashed_last_run(void) { return g_last_crash; } int -sentry_clear_crashed_last_run() +sentry_clear_crashed_last_run(void) { bool success = false; sentry_options_t *options = sentry__options_lock(); diff --git a/src/sentry_info.c b/src/sentry_info.c index b2918127b3..ca2f6387a4 100644 --- a/src/sentry_info.c +++ b/src/sentry_info.c @@ -1,19 +1,19 @@ #include "sentry_boot.h" const char * -sentry_sdk_version() +sentry_sdk_version(void) { return SENTRY_SDK_VERSION; } const char * -sentry_sdk_name() +sentry_sdk_name(void) { return SENTRY_SDK_NAME; } const char * -sentry_sdk_user_agent() +sentry_sdk_user_agent(void) { return SENTRY_SDK_USER_AGENT; } diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 321b8f41e6..0201a673bb 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -118,7 +118,7 @@ sentry__scope_unlock(void) } void -sentry__scope_flush_unlock() +sentry__scope_flush_unlock(void) { sentry__scope_unlock(); SENTRY_WITH_OPTIONS (options) { @@ -245,7 +245,7 @@ sentry__get_span_or_transaction(const sentry_scope_t *scope) #ifdef SENTRY_UNITTEST sentry_value_t -sentry__scope_get_span_or_transaction() +sentry__scope_get_span_or_transaction(void) { SENTRY_WITH_SCOPE (scope) { return sentry__get_span_or_transaction(scope); diff --git a/src/sentry_scope.h b/src/sentry_scope.h index 207b1a9957..5b2ba5fe6e 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -68,7 +68,7 @@ void sentry__scope_cleanup(void); * This function must be called while holding the scope lock, and it will be * unlocked internally. */ -void sentry__scope_flush_unlock(); +void sentry__scope_flush_unlock(void); /** * This will merge the requested data which is in the given `scope` to the given @@ -98,5 +98,5 @@ void sentry__scope_apply_to_event(const sentry_scope_t *scope, // this is only used in unit tests #ifdef SENTRY_UNITTEST -sentry_value_t sentry__scope_get_span_or_transaction(); +sentry_value_t sentry__scope_get_span_or_transaction(void); #endif diff --git a/src/sentry_sync.c b/src/sentry_sync.c index e81d776c20..3e4c1940b6 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -68,7 +68,7 @@ sentry__thread_setname(sentry_threadid_t thread_id, const char *thread_name) } #else sentry_threadid_t -sentry__thread_get_current_threadid() +sentry__thread_get_current_threadid(void) { return pthread_self(); } diff --git a/src/sentry_utils.c b/src/sentry_utils.c index 569fa08333..dbafea8113 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -480,7 +480,7 @@ sentry__iso8601_to_msec(const char *iso) // to ensure the C locale is also used there. #if !defined(SENTRY_PLATFORM_ANDROID) && !defined(SENTRY_PLATFORM_IOS) static sentry__locale_t -c_locale() +c_locale(void) { static long c_locale_initialized = 0; static sentry__locale_t c_locale; From 4cbeefccf84ca54d57f9cdc8bddb01c7a2fbf3c3 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 13 Feb 2023 14:20:04 +0100 Subject: [PATCH 094/207] chore: update breakpad 2023-02-08 (#805) --- CHANGELOG.md | 1 + external/CMakeLists.txt | 4 ++++ external/breakpad | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbbff5ccd2..303f67e4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) - CI: Updated GitHub Actions to test on LLVM-mingw. ([#797](https://github.com/getsentry/sentry-native/pull/797)) +- Updated Breakpad backend to 2023-02-08. ([#805](https://github.com/getsentry/sentry-native/pull/805), [breakpad#34](https://github.com/getsentry/breakpad/pull/34)) **Thank you**: diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 362808ea60..fb2917f4e9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -20,6 +20,10 @@ set(BREAKPAD_SOURCES_COMMON_LINUX breakpad/src/common/linux/linux_libc_support.cc breakpad/src/common/linux/memory_mapped_file.cc breakpad/src/common/linux/safe_readlink.cc + breakpad/src/common/linux/scoped_pipe.cc + breakpad/src/common/linux/scoped_pipe.h + breakpad/src/common/linux/scoped_tmpfile.cc + breakpad/src/common/linux/scoped_tmpfile.h ) set(BREAKPAD_SOURCES_COMMON_LINUX_GETCONTEXT diff --git a/external/breakpad b/external/breakpad index 53abd9137b..0878ec17ca 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 53abd9137b7f8439a2174169042dd6b30721dcf3 +Subproject commit 0878ec17caa6f07e0a70f2c4e930ef5f887d147f From 80831d452e9407537a1dba8b25c226c36f0fa7c0 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 13 Feb 2023 14:22:11 +0100 Subject: [PATCH 095/207] fix: Add a linker script to enforce exported symbols (#363) Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 23 +++++++++++++---------- src/exports.map | 4 ++++ tests/unit/CMakeLists.txt | 1 + 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 src/exports.map diff --git a/CHANGELOG.md b/CHANGELOG.md index 303f67e4c9..e5e843326b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Breaking changes**: + +- When built as a shared library for Android or Linux, the Native SDK limits the export of symbols to the `sentry_`-prefix. The option `SENTRY_EXPORT_SYMBOLS` is no longer available and the linker settings are constrained to the Native SDK and no longer `PUBLIC` to parent projects. ([#363](https://github.com/getsentry/sentry-native/pull/363)) + **Features**: - A session may be ended with a different status code. ([#801](https://github.com/getsentry/sentry-native/pull/801)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21adb7b1cc..67331dd4c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -334,16 +334,6 @@ target_include_directories(sentry "$" ) -# The modulefinder and symbolizer need these two settings, and they are exported -# as `PUBLIC`, so libraries that depend on sentry get these too: -# `-E`: To have all symbols in the dynamic symbol table. -# `--build-id`: To have a build-id in the ELF object. -# FIXME: cmake 3.13 introduced target_link_options -option(SENTRY_EXPORT_SYMBOLS "Export symbols for modulefinder and symbolizer" ON) -if(SENTRY_EXPORT_SYMBOLS) - target_link_libraries(sentry PUBLIC - "$<$,$>:-Wl,-E,--build-id=sha1>") -endif() #respect CMAKE_SYSTEM_VERSION if(WIN32) @@ -601,3 +591,16 @@ if(SENTRY_BUILD_EXAMPLES) add_test(NAME sentry_example COMMAND sentry_example) endif() + +# Limit the exported symbols when sentry is built as a shared library to those with a "sentry_" prefix: +# - we do this at the end of the file as to not affect subdirectories reading target_link_libraries from the parent. +# - we do this as PRIVATE since our version script does not make sense in any other project that adds us. +# +# Used linker parameters: +# `--build-id`: To have a build-id in the ELF object. +# `--version-script`: version script either hides "foreign" symbols or defers them as unknown ("U") to system libraries. +# FIXME: cmake 3.13 introduced target_link_options (blocked by Android) +if(SENTRY_BUILD_SHARED_LIBS) + target_link_libraries(sentry PRIVATE + "$<$,$>:-Wl,--build-id=sha1,--version-script=${PROJECT_SOURCE_DIR}/src/exports.map>") +endif() \ No newline at end of file diff --git a/src/exports.map b/src/exports.map new file mode 100644 index 0000000000..a361e42517 --- /dev/null +++ b/src/exports.map @@ -0,0 +1,4 @@ +{ + global: sentry_*; + local: *; +}; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7c9dc6d173..93acedf270 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -54,6 +54,7 @@ target_link_libraries(sentry_test_unit PRIVATE ${SENTRY_LINK_LIBRARIES} ${SENTRY_INTERFACE_LINK_LIBRARIES} "$<$:rt>" + "$<$,$>:-Wl,-E,--build-id=sha1>" ) if(MINGW) From c9b03a6a3039e282d5f721e8a4fb6cd8ca9be01e Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 13 Feb 2023 17:05:56 +0100 Subject: [PATCH 096/207] chore: update libunwindstack 2023-02-09 (#807) --- CHANGELOG.md | 1 + external/libunwindstack-ndk | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e843326b..e16043551b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Updated Crashpad backend to 2023-02-07. ([#803](https://github.com/getsentry/sentry-native/pull/803), [crashpad#80](https://github.com/getsentry/crashpad/pull/80)) - CI: Updated GitHub Actions to test on LLVM-mingw. ([#797](https://github.com/getsentry/sentry-native/pull/797)) - Updated Breakpad backend to 2023-02-08. ([#805](https://github.com/getsentry/sentry-native/pull/805), [breakpad#34](https://github.com/getsentry/breakpad/pull/34)) +- Updated libunwindstack to 2023-02-09. ([#807](https://github.com/getsentry/sentry-native/pull/807), [libunwindstack-ndk#7](https://github.com/getsentry/libunwindstack-ndk/pull/7)) **Thank you**: diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index 5a3cf38dba..1929f7b601 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit 5a3cf38dbaa9603dc0943fa0fb1ad69d52799af0 +Subproject commit 1929f7b601797fc8b2cac092d563b31d01d46a76 From 22b9b937272952be0e344c0d6c442878982e8caa Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 13 Feb 2023 17:21:14 +0000 Subject: [PATCH 097/207] release: 0.6.0 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e16043551b..a1cde9759a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.0 **Breaking changes**: diff --git a/include/sentry.h b/include/sentry.h index 41e86fac57..b5381642da 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.5.4" +#define SENTRY_SDK_VERSION "0.6.0" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 9ecc88fd21..934858cea9 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.5.4", + "version": "0.6.0", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.5.4"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.0"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index ebf69be4f3..b1a3f22102 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.5.4" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.0" ) From 7abe8d1b2657110c0d10eb035827c7ebe3a0406c Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 28 Feb 2023 18:44:15 +0100 Subject: [PATCH 098/207] fix: Remove OpenSSL dependency for the crashpad backend on Linux (#812) --- CHANGELOG.md | 6 ++++++ external/crashpad | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1cde9759a..3aee25d33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Fixes**: + +- Remove OpenSSL as direct dependency for the crashpad backend on Linux. ([#812](https://github.com/getsentry/sentry-native/pull/812), [crashpad#81](https://github.com/getsentry/crashpad/pull/81)) + ## 0.6.0 **Breaking changes**: diff --git a/external/crashpad b/external/crashpad index ec99257868..2237d97ee2 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit ec992578688b4c51c1856d08731cf7dcf10e446a +Subproject commit 2237d97ee2c38c930c07001e660be57324f69a37 From 1f14ef01a46fe276de5c5b74e248f927455288eb Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 6 Mar 2023 11:46:10 +0100 Subject: [PATCH 099/207] fix: check for libcurl version and feature AsynchDNS (#813) --- CHANGELOG.md | 1 + CMakeLists.txt | 2 +- src/sentry_utils.c | 17 +++++++++++++++ src/sentry_utils.h | 16 ++++++++++++++ src/transports/sentry_transport_curl.c | 27 +++++++++++++++++++++++- tests/unit/test_utils.c | 29 ++++++++++++++++++++++++++ tests/unit/tests.inc | 5 +++-- 7 files changed, 93 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aee25d33a..fad40d9aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Remove OpenSSL as direct dependency for the crashpad backend on Linux. ([#812](https://github.com/getsentry/sentry-native/pull/812), [crashpad#81](https://github.com/getsentry/crashpad/pull/81)) +- Check `libcurl` for feature `AsynchDNS` at compile- and runtime. ([#813](https://github.com/getsentry/sentry-native/pull/813)) ## 0.6.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 67331dd4c0..831e8e1009 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,7 +274,7 @@ endif() if(SENTRY_TRANSPORT_CURL) if(NOT CURL_FOUND) # Some other lib might bring libcurl already - find_package(CURL REQUIRED) + find_package(CURL REQUIRED COMPONENTS AsynchDNS) endif() if(TARGET CURL::libcurl) # Only available in cmake 3.12+ diff --git a/src/sentry_utils.c b/src/sentry_utils.c index dbafea8113..2319fc9889 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -529,3 +529,20 @@ sentry__snprintf_c(char *buf, size_t buf_size, const char *fmt, ...) va_end(args); return rv; } + +bool +sentry__check_min_version(sentry_version_t actual, sentry_version_t expected) +{ + if (actual.major < expected.major) { + return false; + } + if (actual.major == expected.major && actual.minor < expected.minor) { + return false; + } + if (actual.major == expected.major && actual.minor == expected.minor + && actual.patch < expected.patch) { + return false; + } + + return true; +} diff --git a/src/sentry_utils.h b/src/sentry_utils.h index 8b19e65c26..703b53c3f2 100644 --- a/src/sentry_utils.h +++ b/src/sentry_utils.h @@ -198,4 +198,20 @@ double sentry__strtod_c(const char *ptr, char **endptr); */ int sentry__snprintf_c(char *buf, size_t buf_size, const char *fmt, ...); +/** + * Represents a version of a software artifact. + */ +typedef struct { + unsigned int major; + unsigned int minor; + unsigned int patch; +} sentry_version_t; + +/** + * Checks whether `actual` is the same or a later version than `expected`. + * Returns `true` if that is the case. + */ +bool sentry__check_min_version( + sentry_version_t actual, sentry_version_t expected); + #endif diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 05efc6d0e5..cb30d432cf 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -11,7 +11,6 @@ #include #include -#include #include typedef struct curl_transport_state_s { @@ -67,6 +66,32 @@ sentry__curl_transport_start( SENTRY_WARNF("`curl_global_init` failed with code `%d`", (int)rv); return 1; } + + curl_version_info_data *version_data + = curl_version_info(CURLVERSION_NOW); + + if (!version_data) { + SENTRY_WARN("Failed to retrieve `curl_version_info`"); + return 1; + } + + sentry_version_t curl_version = { + .major = (version_data->version_num >> 16) & 0xff, + .minor = (version_data->version_num >> 8) & 0xff, + .patch = version_data->version_num & 0xff, + }; + + if (!sentry__check_min_version( + curl_version, (sentry_version_t) { 7, 10, 7 })) { + SENTRY_WARNF("`libcurl` is at unsupported version `%u.%u.%u`", + curl_version.major, curl_version.minor, curl_version.patch); + return 1; + } + + if ((version_data->features & CURL_VERSION_ASYNCHDNS) == 0) { + SENTRY_WARN("`libcurl` was not compiled with feature `AsynchDNS`"); + return 1; + } } sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index f280f59f71..855f59d3d7 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -221,3 +221,32 @@ SENTRY_TEST(os) sentry_value_decref(os); } + +SENTRY_TEST(check_version) +{ + TEST_CHECK(sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 11, .patch = 7 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 10, .patch = 8 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(sentry__check_min_version( + (sentry_version_t) { .major = 8, .minor = 9, .patch = 7 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 11, .patch = 6 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + + TEST_CHECK(!sentry__check_min_version( + (sentry_version_t) { .major = 6, .minor = 10, .patch = 7 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(!sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 9, .patch = 7 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); + TEST_CHECK(!sentry__check_min_version( + (sentry_version_t) { .major = 7, .minor = 10, .patch = 6 }, + (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); +} \ No newline at end of file diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 0a7d222326..c60d465545 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -14,20 +14,21 @@ XX(basic_tracing_context) XX(basic_transaction) XX(bgworker_flush) XX(build_id_parser) +XX(check_version) XX(child_spans) XX(concurrent_init) XX(concurrent_uninit) XX(count_sampled_events) -XX(crashed_last_run) XX(crash_marker) +XX(crashed_last_run) XX(custom_logger) XX(discarding_before_send) XX(distributed_headers) XX(drop_unfinished_spans) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) -XX(dsn_store_url_without_path) XX(dsn_store_url_with_path) +XX(dsn_store_url_without_path) XX(empty_transport) XX(fuzz_json) XX(init_failure) From 903c17ae20888679caab1871cc74de577509452e Mon Sep 17 00:00:00 2001 From: Cyriuz Date: Mon, 13 Mar 2023 14:55:26 +0100 Subject: [PATCH 100/207] Allow setting CRASHPAD_WER_ENABLED when using system crashpad (#816) --- CHANGELOG.md | 1 + CMakeLists.txt | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fad40d9aeb..578b01de68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Remove OpenSSL as direct dependency for the crashpad backend on Linux. ([#812](https://github.com/getsentry/sentry-native/pull/812), [crashpad#81](https://github.com/getsentry/crashpad/pull/81)) - Check `libcurl` for feature `AsynchDNS` at compile- and runtime. ([#813](https://github.com/getsentry/sentry-native/pull/813)) +- Allow setting `CRASHPAD_WER_ENABLED` when using system crashpad. ([#816](https://github.com/getsentry/sentry-native/pull/816)) ## 0.6.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 831e8e1009..aeea09f81e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,8 +426,9 @@ if(SENTRY_BACKEND_CRASHPAD) set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) endif() add_subdirectory(external/crashpad crashpad_build) + if(CRASHPAD_WER_ENABLED) - add_compile_definitions(CRASHPAD_WER_ENABLED) + add_dependencies(sentry crashpad::wer) endif() # set static runtime if enabled @@ -482,8 +483,9 @@ if(SENTRY_BACKEND_CRASHPAD) endif() endif() add_dependencies(sentry crashpad::handler) + if(CRASHPAD_WER_ENABLED) - add_dependencies(sentry crashpad::wer) + add_compile_definitions(CRASHPAD_WER_ENABLED) endif() elseif(SENTRY_BACKEND_BREAKPAD) option(SENTRY_BREAKPAD_SYSTEM "Use system breakpad" OFF) From 694f055132625db33bd919c86773df3843c4cafc Mon Sep 17 00:00:00 2001 From: Martin Delille Date: Sat, 25 Mar 2023 21:45:32 +0100 Subject: [PATCH 101/207] Add conan, nix and vcpkg badge (#795) * Add conan badge * Update README.md Co-authored-by: Mischan Toosarani-Hausberger * Use shields for conan and vcpkg * Update changelog --------- Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 4 ++++ README.md | 2 ++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 578b01de68..06ebfcdae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - Check `libcurl` for feature `AsynchDNS` at compile- and runtime. ([#813](https://github.com/getsentry/sentry-native/pull/813)) - Allow setting `CRASHPAD_WER_ENABLED` when using system crashpad. ([#816](https://github.com/getsentry/sentry-native/pull/816)) +**Docs**: + +- Add badges for conan, nix and vcpkg package-repos to README. ([#795](https://github.com/getsentry/sentry-native/pull/795)) + ## 0.6.0 **Breaking changes**: diff --git a/README.md b/README.md index 77d5da1d21..a05faf493f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Conan Center](https://shields.io/conan/v/sentry-native)](https://conan.io/center/sentry-native) [![nixpkgs unstable](https://repology.org/badge/version-for-repo/nix_unstable/sentry-native.svg)](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/libraries/sentry-native/default.nix) [![vcpkg](https://shields.io/vcpkg/v/sentry-native)](https://vcpkg.link/ports/sentry-native) +

From 81f813083bfddad5348c2bffbc170bf0498125d3 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 28 Mar 2023 11:47:45 +0200 Subject: [PATCH 102/207] Pre-release changelog update (#825) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ebfcdae7..660c958939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ - Add badges for conan, nix and vcpkg package-repos to README. ([#795](https://github.com/getsentry/sentry-native/pull/795)) +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@Cyriuz](https://github.com/Cyriuz) +- [@MartinDelille](https://github.com/MartinDelille) + ## 0.6.0 **Breaking changes**: From 4410f906df4a9b2736ce4259294289857412dc98 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 28 Mar 2023 09:48:45 +0000 Subject: [PATCH 103/207] release: 0.6.1 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660c958939..69c253cc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.1 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index b5381642da..088f81dffc 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.0" +#define SENTRY_SDK_VERSION "0.6.1" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 934858cea9..89fe1eec98 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.0", + "version": "0.6.1", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.0"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.1"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index b1a3f22102..2bca41e6d9 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.0" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.1" ) From 436627a13f5073c33b61ff74e4653698fc3f9184 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 28 Mar 2023 12:53:33 +0200 Subject: [PATCH 104/207] fix: register with_crashpad_wer marker to get rid of the pytest warning (#826) --- tests/conftest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index b2ba2e67ad..c2f1383c78 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,3 +43,10 @@ def pytest_runtest_setup(item): "--with_crashpad_wer" ): pytest.skip("need --with_crashpad_wer to run this test") + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "with_crashpad_wer: mark test to only run when WER testing is enabled", + ) From e97930a2ea10f2db263c5fece6a5d548f67f5613 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 25 Apr 2023 15:55:17 +0200 Subject: [PATCH 105/207] feat: extend API with ptr/len-string interfaces (#827) --- .github/workflows/ci.yml | 5 +- CHANGELOG.md | 6 + Makefile | 6 + include/sentry.h | 104 +++++++- src/path/sentry_path_unix.c | 10 +- src/path/sentry_path_windows.c | 54 +++- src/sentry_core.c | 130 +++++++++- src/sentry_envelope.c | 22 +- src/sentry_json.c | 6 +- src/sentry_options.c | 113 +++++++- src/sentry_path.h | 2 + src/sentry_slice.c | 2 +- src/sentry_string.h | 26 +- src/sentry_sync.h | 5 +- src/sentry_tracing.c | 242 ++++++++++++++--- src/sentry_tracing.h | 6 +- src/sentry_utils.c | 32 ++- src/sentry_utils.h | 1 + src/sentry_uuid.c | 10 +- src/sentry_value.c | 156 +++++++++-- src/transports/sentry_transport_curl.c | 3 +- src/transports/sentry_transport_winhttp.c | 2 +- tests/assertions.py | 4 +- tests/leaks.txt | 1 + tests/requirements.txt | 6 +- tests/test_integration_http.py | 31 ++- tests/unit/sentry_testsupport.h | 7 + tests/unit/test_attachments.c | 4 +- tests/unit/test_basic.c | 11 +- tests/unit/test_envelopes.c | 70 ++++- tests/unit/test_path.c | 20 ++ tests/unit/test_session.c | 6 +- tests/unit/test_tracing.c | 303 +++++++++++++++++++++- tests/unit/test_utils.c | 34 ++- tests/unit/test_value.c | 222 +++++++++++++++- tests/unit/tests.inc | 31 +++ 36 files changed, 1529 insertions(+), 164 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa8c1bb9a8..a5d03e81af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: make style + - run: | + sudo apt update + sudo apt install clang-format-15 + make style-15 build-ios: name: Xcode Build for iOS diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c253cc5a..952f1c4977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features**: + +- Extend API with ptr/len-string interfaces. ([#827](https://github.com/getsentry/sentry-native/pull/827)) + ## 0.6.1 **Fixes**: diff --git a/Makefile b/Makefile index 6925df596c..a936b143fd 100644 --- a/Makefile +++ b/Makefile @@ -83,3 +83,9 @@ style: setup-venv @.venv/bin/python ./scripts/check-clang-format.py -r examples include src tests/unit @.venv/bin/black --diff --check tests .PHONY: style + +# TODO: workaround for clang-format 15+ where local formatting breaks with clang-format-14 based style checks on CI +style-15: setup-venv + @.venv/bin/python ./scripts/check-clang-format.py --clang-format-executable /usr/bin/clang-format-15 -r examples include src tests/unit + @.venv/bin/black --diff --check tests +.PHONY: style-15 diff --git a/include/sentry.h b/include/sentry.h index 088f81dffc..32cecb63a2 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -207,6 +207,8 @@ SENTRY_API sentry_value_t sentry_value_new_bool(int value); * Creates a new null terminated string. */ SENTRY_API sentry_value_t sentry_value_new_string(const char *value); +SENTRY_API sentry_value_t sentry_value_new_string_n( + const char *value, size_t value_len); /** * Creates a new list value. @@ -232,10 +234,15 @@ SENTRY_API sentry_value_type_t sentry_value_get_type(sentry_value_t value); SENTRY_API int sentry_value_set_by_key( sentry_value_t value, const char *k, sentry_value_t v); +SENTRY_API int sentry_value_set_by_key_n( + sentry_value_t value, const char *k, size_t k_len, sentry_value_t v); + /** * This removes a value from the map by key. */ SENTRY_API int sentry_value_remove_by_key(sentry_value_t value, const char *k); +SENTRY_API int sentry_value_remove_by_key_n( + sentry_value_t value, const char *k, size_t k_len); /** * Appends a value to a list. @@ -268,6 +275,8 @@ SENTRY_API int sentry_value_remove_by_index(sentry_value_t value, size_t index); */ SENTRY_API sentry_value_t sentry_value_get_by_key( sentry_value_t value, const char *k); +SENTRY_API sentry_value_t sentry_value_get_by_key_n( + sentry_value_t value, const char *k, size_t k_len); /** * Looks up a value in a map by key. If missing a null value is returned. @@ -278,6 +287,8 @@ SENTRY_API sentry_value_t sentry_value_get_by_key( */ SENTRY_API sentry_value_t sentry_value_get_by_key_owned( sentry_value_t value, const char *k); +SENTRY_API sentry_value_t sentry_value_get_by_key_owned_n( + sentry_value_t value, const char *k, size_t k_len); /** * Looks up a value in a list by index. If missing a null value is returned. @@ -365,6 +376,8 @@ SENTRY_API sentry_value_t sentry_value_new_event(void); */ SENTRY_API sentry_value_t sentry_value_new_message_event( sentry_level_t level, const char *logger, const char *text); +SENTRY_API sentry_value_t sentry_value_new_message_event_n(sentry_level_t level, + const char *logger, size_t logger_len, const char *text, size_t text_len); /** * Creates a new Breadcrumb with a specific type and message. @@ -375,6 +388,8 @@ SENTRY_API sentry_value_t sentry_value_new_message_event( */ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( const char *type, const char *message); +SENTRY_API sentry_value_t sentry_value_new_breadcrumb_n( + const char *type, size_t type_len, const char *message, size_t message_len); /** * Creates a new Exception value. @@ -390,6 +405,8 @@ SENTRY_API sentry_value_t sentry_value_new_breadcrumb( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( const char *type, const char *value); +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception_n( + const char *type, size_t type_len, const char *value, size_t value_len); /** * Creates a new Thread value. @@ -403,6 +420,8 @@ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_exception( */ SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread( uint64_t id, const char *name); +SENTRY_EXPERIMENTAL_API sentry_value_t sentry_value_new_thread_n( + uint64_t id, const char *name, size_t name_len); /** * Creates a new Stack Trace conforming to the Stack Trace Interface. @@ -534,6 +553,8 @@ SENTRY_API sentry_uuid_t sentry_uuid_new_v4(void); * Parses a uuid from a string. */ SENTRY_API sentry_uuid_t sentry_uuid_from_string(const char *str); +SENTRY_API sentry_uuid_t sentry_uuid_from_string_n( + const char *str, size_t str_len); /** * Creates a uuid from bytes. @@ -603,6 +624,8 @@ SENTRY_API char *sentry_envelope_serialize( */ SENTRY_API int sentry_envelope_write_to_file( const sentry_envelope_t *envelope, const char *path); +SENTRY_API int sentry_envelope_write_to_file_n( + const sentry_envelope_t *envelope, const char *path, size_t path_len); /** * The Sentry Client Options. @@ -866,6 +889,8 @@ SENTRY_API void sentry_options_set_on_crash( * Sets the DSN. */ SENTRY_API void sentry_options_set_dsn(sentry_options_t *opts, const char *dsn); +SENTRY_API void sentry_options_set_dsn_n( + sentry_options_t *opts, const char *dsn, size_t dsn_len); /** * Gets the DSN. @@ -903,6 +928,8 @@ SENTRY_API double sentry_options_get_sample_rate(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_release( sentry_options_t *opts, const char *release); +SENTRY_API void sentry_options_set_release_n( + sentry_options_t *opts, const char *release, size_t release_len); /** * Gets the release. @@ -914,6 +941,8 @@ SENTRY_API const char *sentry_options_get_release(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_environment( sentry_options_t *opts, const char *environment); +SENTRY_API void sentry_options_set_environment_n( + sentry_options_t *opts, const char *environment, size_t environment_len); /** * Gets the environment. @@ -926,6 +955,8 @@ SENTRY_API const char *sentry_options_get_environment( */ SENTRY_API void sentry_options_set_dist( sentry_options_t *opts, const char *dist); +SENTRY_API void sentry_options_set_dist_n( + sentry_options_t *opts, const char *dist, size_t dist_len); /** * Gets the dist. @@ -939,6 +970,8 @@ SENTRY_API const char *sentry_options_get_dist(const sentry_options_t *opts); */ SENTRY_API void sentry_options_set_http_proxy( sentry_options_t *opts, const char *proxy); +SENTRY_API void sentry_options_set_http_proxy_n( + sentry_options_t *opts, const char *proxy, size_t proxy_len); /** * Returns the configured http proxy. @@ -952,6 +985,8 @@ SENTRY_API const char *sentry_options_get_http_proxy( */ SENTRY_API void sentry_options_set_ca_certs( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_ca_certs_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Returns the configured path for ca certificates. @@ -964,6 +999,8 @@ SENTRY_API const char *sentry_options_get_ca_certs( */ SENTRY_API void sentry_options_set_transport_thread_name( sentry_options_t *opts, const char *name); +SENTRY_API void sentry_options_set_transport_thread_name_n( + sentry_options_t *opts, const char *name, size_t name_len); /** * Returns the configured http transport thread name. @@ -1069,6 +1106,8 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces( */ SENTRY_API void sentry_options_add_attachment( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_add_attachment_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Sets the path to the crashpad handler if the crashpad backend is used. @@ -1086,6 +1125,8 @@ SENTRY_API void sentry_options_add_attachment( */ SENTRY_API void sentry_options_set_handler_path( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_handler_path_n( + sentry_options_t *opts, const char *path, size_t path_len); /** * Sets the path to the Sentry Database Directory. @@ -1118,6 +1159,8 @@ SENTRY_API void sentry_options_set_handler_path( */ SENTRY_API void sentry_options_set_database_path( sentry_options_t *opts, const char *path); +SENTRY_API void sentry_options_set_database_path_n( + sentry_options_t *opts, const char *path, size_t path_len); #ifdef SENTRY_PLATFORM_WINDOWS /** @@ -1125,18 +1168,24 @@ SENTRY_API void sentry_options_set_database_path( */ SENTRY_API void sentry_options_add_attachmentw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_add_attachmentw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); /** * Wide char version of `sentry_options_set_handler_path`. */ SENTRY_API void sentry_options_set_handler_pathw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_set_handler_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); /** * Wide char version of `sentry_options_set_database_path`. */ SENTRY_API void sentry_options_set_database_pathw( sentry_options_t *opts, const wchar_t *path); +SENTRY_API void sentry_options_set_database_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len); #endif /** @@ -1297,31 +1346,40 @@ SENTRY_API void sentry_remove_user(void); * Sets a tag. */ SENTRY_API void sentry_set_tag(const char *key, const char *value); +SENTRY_API void sentry_set_tag_n( + const char *key, size_t key_len, const char *value, size_t value_len); /** * Removes the tag with the specified key. */ SENTRY_API void sentry_remove_tag(const char *key); +SENTRY_API void sentry_remove_tag_n(const char *key, size_t key_len); /** * Sets extra information. */ SENTRY_API void sentry_set_extra(const char *key, sentry_value_t value); +SENTRY_API void sentry_set_extra_n( + const char *key, size_t key_len, sentry_value_t value); /** * Removes the extra with the specified key. */ SENTRY_API void sentry_remove_extra(const char *key); +SENTRY_API void sentry_remove_extra_n(const char *key, size_t key_len); /** * Sets a context object. */ SENTRY_API void sentry_set_context(const char *key, sentry_value_t value); +SENTRY_API void sentry_set_context_n( + const char *key, size_t key_len, sentry_value_t value); /** * Removes the context object with the specified key. */ SENTRY_API void sentry_remove_context(const char *key); +SENTRY_API void sentry_remove_context_n(const char *key, size_t key_len); /** * Sets the event fingerprint. @@ -1330,6 +1388,8 @@ SENTRY_API void sentry_remove_context(const char *key); * trailing `NULL`. */ SENTRY_API void sentry_set_fingerprint(const char *fingerprint, ...); +SENTRY_API void sentry_set_fingerprint_n( + const char *fingerprint, size_t fingerprint_len, ...); /** * Removes the fingerprint. @@ -1340,6 +1400,8 @@ SENTRY_API void sentry_remove_fingerprint(void); * Sets the transaction. */ SENTRY_API void sentry_set_transaction(const char *transaction); +SENTRY_API void sentry_set_transaction_n( + const char *transaction, size_t transaction_len); /** * Sets the event level. @@ -1448,6 +1510,9 @@ typedef struct sentry_span_s sentry_span_t; */ SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * sentry_transaction_context_new(const char *name, const char *operation); +SENTRY_EXPERIMENTAL_API sentry_transaction_context_t * +sentry_transaction_context_new_n(const char *name, size_t name_len, + const char *operation, size_t operation_len); /** * Sets the `name` on a Transaction Context, which will be used in the @@ -1458,6 +1523,8 @@ sentry_transaction_context_new(const char *name, const char *operation); */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( sentry_transaction_context_t *tx_cxt, const char *name); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name_n( + sentry_transaction_context_t *tx_cxt, const char *name, size_t name_len); /** * Sets the `operation` on a Transaction Context, which will be used in the @@ -1471,6 +1538,9 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_name( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation( sentry_transaction_context_t *tx_cxt, const char *operation); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_set_operation_n( + sentry_transaction_context_t *tx_cxt, const char *operation, + size_t operation_len); /** * Sets the `sampled` field on a Transaction Context, which will be used in the @@ -1508,6 +1578,9 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_remove_sampled( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header( sentry_transaction_context_t *tx_cxt, const char *key, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_context_update_from_header_n( + sentry_transaction_context_t *tx_cxt, const char *key, size_t key_len, + const char *value, size_t value_len); /** * Starts a new Transaction based on the provided context, restored from an @@ -1618,7 +1691,11 @@ SENTRY_EXPERIMENTAL_API void sentry_set_span(sentry_span_t *span); * in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( - sentry_transaction_t *parent, char *operation, char *description); + sentry_transaction_t *parent, const char *operation, + const char *description); +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child_n( + sentry_transaction_t *parent, const char *operation, size_t operation_len, + const char *description, size_t description_len); /** * Starts a new Span. @@ -1651,7 +1728,10 @@ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_transaction_start_child( * in a thread-safe way. */ SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child( - sentry_span_t *parent, char *operation, char *description); + sentry_span_t *parent, const char *operation, const char *description); +SENTRY_EXPERIMENTAL_API sentry_span_t *sentry_span_start_child_n( + sentry_span_t *parent, const char *operation, size_t operation_len, + const char *description, size_t description_len); /** * Finishes a Span. @@ -1675,6 +1755,9 @@ SENTRY_EXPERIMENTAL_API void sentry_span_finish(sentry_span_t *span); */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( sentry_transaction_t *transaction, const char *tag, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag_n( + sentry_transaction_t *transaction, const char *tag, size_t tag_len, + const char *value, size_t value_len); /** * Removes a tag from a Transaction. @@ -1684,6 +1767,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_tag( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( sentry_transaction_t *transaction, const char *tag); +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag_n( + sentry_transaction_t *transaction, const char *tag, size_t tag_len); /** * Sets the given key in a Transaction's "data" section to the given value. @@ -1693,6 +1778,9 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_tag( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( sentry_transaction_t *transaction, const char *key, sentry_value_t value); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data_n( + sentry_transaction_t *transaction, const char *key, size_t key_len, + sentry_value_t value); /** * Removes a key from a Transaction's "data" section. @@ -1702,6 +1790,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_data( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( sentry_transaction_t *transaction, const char *key); +SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data_n( + sentry_transaction_t *transaction, const char *key, size_t key_len); /** * Sets a tag on a Span to the given string value. @@ -1713,6 +1803,8 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_remove_data( */ SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( sentry_span_t *span, const char *tag, const char *value); +SENTRY_EXPERIMENTAL_API void sentry_span_set_tag_n(sentry_span_t *span, + const char *tag, size_t tag_len, const char *value, size_t value_len); /** * Removes a tag from a Span. @@ -1722,6 +1814,8 @@ SENTRY_EXPERIMENTAL_API void sentry_span_set_tag( */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( sentry_span_t *span, const char *tag); +SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag_n( + sentry_span_t *span, const char *tag, size_t tag_len); /** * Sets the given key in a Span's "data" section to the given value. @@ -1731,6 +1825,8 @@ SENTRY_EXPERIMENTAL_API void sentry_span_remove_tag( */ SENTRY_EXPERIMENTAL_API void sentry_span_set_data( sentry_span_t *span, const char *key, sentry_value_t value); +SENTRY_EXPERIMENTAL_API void sentry_span_set_data_n( + sentry_span_t *span, const char *key, size_t key_len, sentry_value_t value); /** * Removes a key from a Span's "data" section. @@ -1740,6 +1836,8 @@ SENTRY_EXPERIMENTAL_API void sentry_span_set_data( */ SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( sentry_span_t *span, const char *key); +SENTRY_EXPERIMENTAL_API void sentry_span_remove_data_n( + sentry_span_t *span, const char *key, size_t key_len); /** * Sets a Transaction's name. @@ -1749,6 +1847,8 @@ SENTRY_EXPERIMENTAL_API void sentry_span_remove_data( */ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( sentry_transaction_t *transaction, const char *name); +SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name_n( + sentry_transaction_t *transaction, const char *name, size_t name_len); /** * The status of a Span or Transaction. diff --git a/src/path/sentry_path_unix.c b/src/path/sentry_path_unix.c index d75a0eb8f1..298913996b 100644 --- a/src/path/sentry_path_unix.c +++ b/src/path/sentry_path_unix.c @@ -169,9 +169,9 @@ sentry__path_dir(const sentry_path_t *path) } sentry_path_t * -sentry__path_from_str(const char *s) +sentry__path_from_str_n(const char *s, size_t s_len) { - char *path = sentry__string_clone(s); + char *path = sentry__string_clone_n(s, s_len); if (!path) { return NULL; } @@ -179,6 +179,12 @@ sentry__path_from_str(const char *s) return sentry__path_from_str_owned(path); } +sentry_path_t * +sentry__path_from_str(const char *s) +{ + return s ? sentry__path_from_str_n(s, strlen(s)) : NULL; +} + sentry_path_t * sentry__path_from_str_owned(char *s) { diff --git a/src/path/sentry_path_windows.c b/src/path/sentry_path_windows.c index ea2a7097e7..b953af9a6b 100644 --- a/src/path/sentry_path_windows.c +++ b/src/path/sentry_path_windows.c @@ -135,16 +135,28 @@ sentry__path_dir(const sentry_path_t *path) } sentry_path_t * -sentry__path_from_wstr(const wchar_t *s) +sentry__path_from_wstr_n(const wchar_t *s, size_t s_len) { - size_t len = wcslen(s) + 1; - sentry_path_t *rv = path_with_len(len); + if (!s) { + return NULL; + } + sentry_path_t *rv = path_with_len(s_len + 1); if (rv) { - memcpy(rv->path, s, len * sizeof(wchar_t)); + memcpy(rv->path, s, s_len * sizeof(wchar_t)); + rv->path[s_len] = 0; } return rv; } +sentry_path_t * +sentry__path_from_wstr(const wchar_t *s) +{ + if (!s) { + return NULL; + } + return sentry__path_from_wstr_n(s, wcslen(s)); +} + sentry_path_t * sentry__path_join_wstr(const sentry_path_t *base, const wchar_t *other) { @@ -189,20 +201,42 @@ sentry__path_join_wstr(const sentry_path_t *base, const wchar_t *other) } sentry_path_t * -sentry__path_from_str(const char *s) +sentry__path_from_str_n(const char *s, size_t s_len) { - size_t len = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0); + if (!s) { + return NULL; + } sentry_path_t *rv = SENTRY_MAKE(sentry_path_t); if (!rv) { return NULL; } - rv->path = sentry_malloc(sizeof(wchar_t) * len); + size_t src_size = sizeof(char) * s_len; + size_t dst_size = sizeof(wchar_t) * (s_len + 1); + rv->path = sentry_malloc(dst_size); if (!rv->path) { - sentry_free(rv); - return NULL; + goto error; + } + int conv_len = MultiByteToWideChar( + CP_ACP, 0, s, (int)src_size, rv->path, (int)s_len); + if (conv_len == 0) { + goto error; } - MultiByteToWideChar(CP_ACP, 0, s, -1, rv->path, (int)len); + rv->path[conv_len] = 0; return rv; + +error: + sentry_free(rv); + return NULL; +} + +sentry_path_t * +sentry__path_from_str(const char *s) +{ + if (!s) { + return NULL; + } + + return sentry__path_from_str_n(s, strlen(s)); } sentry_path_t * diff --git a/src/sentry_core.c b/src/sentry_core.c index b10d6065df..8cbb39a031 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1,10 +1,8 @@ #include "sentry_boot.h" #include -#include #include -#include "sentry_alloc.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -640,6 +638,16 @@ sentry_set_tag(const char *key, const char *value) } } +void +sentry_set_tag_n( + const char *key, size_t key_len, const char *value, size_t value_len) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_set_by_key_n(scope->tags, key, key_len, + sentry_value_new_string_n(value, value_len)); + } +} + void sentry_remove_tag(const char *key) { @@ -648,6 +656,14 @@ sentry_remove_tag(const char *key) } } +void +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); + } +} + void sentry_set_extra(const char *key, sentry_value_t value) { @@ -656,6 +672,14 @@ sentry_set_extra(const char *key, sentry_value_t value) } } +void +sentry_set_extra_n(const char *key, size_t key_len, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_set_by_key_n(scope->extra, key, key_len, value); + } +} + void sentry_remove_extra(const char *key) { @@ -664,6 +688,14 @@ sentry_remove_extra(const char *key) } } +void +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); + } +} + void sentry_set_context(const char *key, sentry_value_t value) { @@ -672,6 +704,14 @@ sentry_set_context(const char *key, sentry_value_t value) } } +void +sentry_set_context_n(const char *key, size_t key_len, sentry_value_t value) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_set_by_key_n(scope->contexts, key, key_len, value); + } +} + void sentry_remove_context(const char *key) { @@ -680,6 +720,33 @@ sentry_remove_context(const char *key) } } +void +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); + } +} + +void +sentry_set_fingerprint_n(const char *fingerprint, size_t fingerprint_len, ...) +{ + sentry_value_t fingerprint_value = sentry_value_new_list(); + + va_list va; + va_start(va, fingerprint_len); + for (; fingerprint; fingerprint = va_arg(va, const char *)) { + sentry_value_append(fingerprint_value, + sentry_value_new_string_n(fingerprint, fingerprint_len)); + } + va_end(va); + + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_value_decref(scope->fingerprint); + scope->fingerprint = fingerprint_value; + } +} + void sentry_set_fingerprint(const char *fingerprint, ...) { @@ -696,7 +763,7 @@ sentry_set_fingerprint(const char *fingerprint, ...) SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->fingerprint); scope->fingerprint = fingerprint_value; - }; + } } void @@ -705,7 +772,7 @@ sentry_remove_fingerprint(void) SENTRY_WITH_SCOPE_MUT (scope) { sentry_value_decref(scope->fingerprint); scope->fingerprint = sentry_value_new_null(); - }; + } } void @@ -721,6 +788,21 @@ sentry_set_transaction(const char *transaction) } } +void +sentry_set_transaction_n(const char *transaction, size_t transaction_len) +{ + SENTRY_WITH_SCOPE_MUT (scope) { + sentry_free(scope->transaction); + scope->transaction + = sentry__string_clone_n(transaction, transaction_len); + + if (scope->transaction_object) { + sentry_transaction_set_name_n( + scope->transaction_object, transaction, transaction_len); + } + } +} + void sentry_set_level(sentry_level_t level) { @@ -870,8 +952,9 @@ sentry_set_span(sentry_span_t *span) } sentry_span_t * -sentry_transaction_start_child( - sentry_transaction_t *opaque_parent, char *operation, char *description) +sentry_transaction_start_child_n(sentry_transaction_t *opaque_parent, + const char *operation, size_t operation_len, const char *description, + size_t description_len) { if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { SENTRY_DEBUG("no transaction available to create a child under"); @@ -886,14 +969,25 @@ sentry_transaction_start_child( max_spans = options->max_spans; } - sentry_value_t span - = sentry__value_span_new(max_spans, parent, operation, description); + sentry_value_t span = sentry__value_span_new_n(max_spans, parent, + (sentry_slice_t) { operation, operation_len }, + (sentry_slice_t) { description, description_len }); return sentry__span_new(opaque_parent, span); } sentry_span_t * -sentry_span_start_child( - sentry_span_t *opaque_parent, char *operation, char *description) +sentry_transaction_start_child(sentry_transaction_t *opaque_parent, + const char *operation, const char *description) +{ + const size_t operation_len = operation ? strlen(operation) : 0; + const size_t description_len = description ? strlen(description) : 0; + return sentry_transaction_start_child_n( + opaque_parent, operation, operation_len, description, description_len); +} + +sentry_span_t * +sentry_span_start_child_n(sentry_span_t *opaque_parent, const char *operation, + size_t operation_len, const char *description, size_t description_len) { if (!opaque_parent || sentry_value_is_null(opaque_parent->inner)) { SENTRY_DEBUG("no parent span available to create a child span under"); @@ -912,12 +1006,23 @@ sentry_span_start_child( max_spans = options->max_spans; } - sentry_value_t span - = sentry__value_span_new(max_spans, parent, operation, description); + sentry_value_t span = sentry__value_span_new_n(max_spans, parent, + (sentry_slice_t) { operation, operation_len }, + (sentry_slice_t) { description, description_len }); return sentry__span_new(opaque_parent->transaction, span); } +sentry_span_t * +sentry_span_start_child(sentry_span_t *opaque_parent, const char *operation, + const char *description) +{ + size_t operation_len = operation ? strlen(operation) : 0; + size_t description_len = description ? strlen(description) : 0; + return sentry_span_start_child_n( + opaque_parent, operation, operation_len, description, description_len); +} + void sentry_span_finish(sentry_span_t *opaque_span) { @@ -1010,7 +1115,6 @@ sentry_span_finish(sentry_span_t *opaque_span) fail: sentry__span_decref(opaque_span); - return; } int diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index cfcf5af824..4b68a27871 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -322,7 +322,7 @@ sentry__envelope_add_from_buffer(sentry_envelope_t *envelope, const char *buf, // NOTE: function will check for the clone of `buf` internally and free it // on error return envelope_add_from_owned_buffer( - envelope, sentry__string_clonen(buf, buf_len), buf_len, type); + envelope, sentry__string_clone_n(buf, buf_len), buf_len, type); } sentry_envelope_item_t * @@ -459,10 +459,13 @@ sentry_envelope_write_to_path( } int -sentry_envelope_write_to_file( - const sentry_envelope_t *envelope, const char *path) +sentry_envelope_write_to_file_n( + const sentry_envelope_t *envelope, const char *path, size_t path_len) { - sentry_path_t *path_obj = sentry__path_from_str(path); + if (!envelope || !path) { + return 1; + } + sentry_path_t *path_obj = sentry__path_from_str_n(path, path_len); int rv = sentry_envelope_write_to_path(envelope, path_obj); @@ -471,6 +474,17 @@ sentry_envelope_write_to_file( return rv; } +int +sentry_envelope_write_to_file( + const sentry_envelope_t *envelope, const char *path) +{ + if (!envelope || !path) { + return 1; + } + + return sentry_envelope_write_to_file_n(envelope, path, strlen(path)); +} + #ifdef SENTRY_UNITTEST size_t sentry__envelope_get_item_count(const sentry_envelope_t *envelope) diff --git a/src/sentry_json.c b/src/sentry_json.c index cf02c7f1b9..f83cd1886d 100644 --- a/src/sentry_json.c +++ b/src/sentry_json.c @@ -471,8 +471,8 @@ tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, break; } case JSMN_STRING: { - char *string - = sentry__string_clonen(buf + root->start, root->end - root->start); + char *string = sentry__string_clone_n_unchecked( + buf + root->start, root->end - root->start); if (decode_string_inplace(string)) { rv = sentry__value_new_string_owned(string); } else { @@ -492,7 +492,7 @@ tokens_to_value(jsmntok_t *tokens, size_t token_count, const char *buf, sentry_value_t child; NESTED_PARSE(&child); - char *key = sentry__string_clonen( + char *key = sentry__string_clone_n_unchecked( buf + token->start, token->end - token->start); if (decode_string_inplace(key)) { sentry_value_set_by_key(rv, key, child); diff --git a/src/sentry_options.c b/src/sentry_options.c index b0a6fe8aa6..4814bfefd2 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -130,6 +130,14 @@ sentry_options_set_on_crash( opts->on_crash_data = data; } +void +sentry_options_set_dsn_n( + sentry_options_t *opts, const char *raw_dsn, size_t raw_dsn_len) +{ + sentry__dsn_decref(opts->dsn); + opts->dsn = sentry__dsn_new_n(raw_dsn, raw_dsn_len); +} + void sentry_options_set_dsn(sentry_options_t *opts, const char *raw_dsn) { @@ -160,6 +168,14 @@ sentry_options_get_sample_rate(const sentry_options_t *opts) return opts->sample_rate; } +void +sentry_options_set_release_n( + sentry_options_t *opts, const char *release, size_t release_len) +{ + sentry_free(opts->release); + opts->release = sentry__string_clone_n(release, release_len); +} + void sentry_options_set_release(sentry_options_t *opts, const char *release) { @@ -173,6 +189,14 @@ sentry_options_get_release(const sentry_options_t *opts) return opts->release; } +void +sentry_options_set_environment_n( + sentry_options_t *opts, const char *environment, size_t environment_len) +{ + sentry_free(opts->environment); + opts->environment = sentry__string_clone_n(environment, environment_len); +} + void sentry_options_set_environment(sentry_options_t *opts, const char *environment) { @@ -186,6 +210,14 @@ sentry_options_get_environment(const sentry_options_t *opts) return opts->environment; } +void +sentry_options_set_dist_n( + sentry_options_t *opts, const char *dist, size_t dist_len) +{ + sentry_free(opts->dist); + opts->dist = sentry__string_clone_n(dist, dist_len); +} + void sentry_options_set_dist(sentry_options_t *opts, const char *dist) { @@ -199,6 +231,14 @@ sentry_options_get_dist(const sentry_options_t *opts) return opts->dist; } +void +sentry_options_set_http_proxy_n( + sentry_options_t *opts, const char *proxy, size_t proxy_len) +{ + sentry_free(opts->http_proxy); + opts->http_proxy = sentry__string_clone_n(proxy, proxy_len); +} + void sentry_options_set_http_proxy(sentry_options_t *opts, const char *proxy) { @@ -219,6 +259,14 @@ sentry_options_set_ca_certs(sentry_options_t *opts, const char *path) opts->ca_certs = sentry__string_clone(path); } +void +sentry_options_set_ca_certs_n( + sentry_options_t *opts, const char *path, size_t path_len) +{ + sentry_free(opts->ca_certs); + opts->ca_certs = sentry__string_clone_n(path, path_len); +} + const char * sentry_options_get_ca_certs(const sentry_options_t *opts) { @@ -233,6 +281,14 @@ sentry_options_set_transport_thread_name( opts->transport_thread_name = sentry__string_clone(name); } +void +sentry_options_set_transport_thread_name_n( + sentry_options_t *opts, const char *name, size_t name_len) +{ + sentry_free(opts->transport_thread_name); + opts->transport_thread_name = sentry__string_clone_n(name, name_len); +} + const char * sentry_options_get_transport_thread_name(const sentry_options_t *opts) { @@ -350,6 +406,13 @@ sentry_options_add_attachment(sentry_options_t *opts, const char *path) add_attachment(opts, sentry__path_from_str(path)); } +void +sentry_options_add_attachment_n( + sentry_options_t *opts, const char *path, size_t path_len) +{ + add_attachment(opts, sentry__path_from_str_n(path, path_len)); +} + void sentry_options_set_handler_path(sentry_options_t *opts, const char *path) { @@ -357,6 +420,14 @@ sentry_options_set_handler_path(sentry_options_t *opts, const char *path) opts->handler_path = sentry__path_from_str(path); } +void +sentry_options_set_handler_path_n( + sentry_options_t *opts, const char *path, size_t path_len) +{ + sentry__path_free(opts->handler_path); + opts->handler_path = sentry__path_from_str_n(path, path_len); +} + void sentry_options_set_database_path(sentry_options_t *opts, const char *path) { @@ -364,25 +435,57 @@ sentry_options_set_database_path(sentry_options_t *opts, const char *path) opts->database_path = sentry__path_from_str(path); } +void +sentry_options_set_database_path_n( + sentry_options_t *opts, const char *path, size_t path_len) +{ + sentry__path_free(opts->database_path); + opts->database_path = sentry__path_from_str_n(path, path_len); +} + #ifdef SENTRY_PLATFORM_WINDOWS +void +sentry_options_add_attachmentw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len) +{ + add_attachment(opts, sentry__path_from_wstr_n(path, path_len)); +} + void sentry_options_add_attachmentw(sentry_options_t *opts, const wchar_t *path) { - add_attachment(opts, sentry__path_from_wstr(path)); + size_t path_len = path ? wcslen(path) : 0; + sentry_options_add_attachmentw_n(opts, path, path_len); } void -sentry_options_set_handler_pathw(sentry_options_t *opts, const wchar_t *path) +sentry_options_set_handler_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len) { sentry__path_free(opts->handler_path); - opts->handler_path = sentry__path_from_wstr(path); + opts->handler_path = sentry__path_from_wstr_n(path, path_len); } void -sentry_options_set_database_pathw(sentry_options_t *opts, const wchar_t *path) +sentry_options_set_handler_pathw(sentry_options_t *opts, const wchar_t *path) +{ + size_t path_len = path ? wcslen(path) : 0; + sentry_options_set_handler_pathw_n(opts, path, path_len); +} + +void +sentry_options_set_database_pathw_n( + sentry_options_t *opts, const wchar_t *path, size_t path_len) { sentry__path_free(opts->database_path); - opts->database_path = sentry__path_from_wstr(path); + opts->database_path = sentry__path_from_wstr_n(path, path_len); +} + +void +sentry_options_set_database_pathw(sentry_options_t *opts, const wchar_t *path) +{ + size_t path_len = path ? wcslen(path) : 0; + sentry_options_set_database_pathw_n(opts, path, path_len); } #endif diff --git a/src/sentry_path.h b/src/sentry_path.h index 8cedd1d213..02e42d5fe0 100644 --- a/src/sentry_path.h +++ b/src/sentry_path.h @@ -53,6 +53,7 @@ sentry_path_t *sentry__path_dir(const sentry_path_t *path); * Create a new path from the given string. */ sentry_path_t *sentry__path_from_str(const char *s); +sentry_path_t *sentry__path_from_str_n(const char *s, size_t s_len); /** * Create a new path from the given string. @@ -205,6 +206,7 @@ void sentry__filelock_free(sentry_filelock_t *lock); * Create a new path from a Wide String. */ sentry_path_t *sentry__path_from_wstr(const wchar_t *s); +sentry_path_t *sentry__path_from_wstr_n(const wchar_t *s, size_t s_len); /** * Create another path by appending a new path segment. diff --git a/src/sentry_slice.c b/src/sentry_slice.c index 79894b7ded..0f68311b22 100644 --- a/src/sentry_slice.c +++ b/src/sentry_slice.c @@ -15,7 +15,7 @@ sentry__slice_from_str(const char *str) char * sentry__slice_to_owned(sentry_slice_t slice) { - return sentry__string_clonen(slice.ptr, slice.len); + return sentry__string_clone_n_unchecked(slice.ptr, slice.len); } bool diff --git a/src/sentry_string.h b/src/sentry_string.h index 255d55d171..95e1e4f614 100644 --- a/src/sentry_string.h +++ b/src/sentry_string.h @@ -110,10 +110,11 @@ size_t sentry__stringbuilder_len(const sentry_stringbuilder_t *sb); void sentry__stringbuilder_set_len(sentry_stringbuilder_t *sb, size_t len); /** - * Duplicates a zero terminated string with a length limit. + * Duplicates a zero terminated string with a length limit. Does not check + * if `str` is NULL. */ static inline char * -sentry__string_clonen(const char *str, size_t n) +sentry__string_clone_n_unchecked(const char *str, size_t n) { size_t len = n + 1; char *rv = (char *)sentry_malloc(len); @@ -124,13 +125,32 @@ sentry__string_clonen(const char *str, size_t n) return rv; } +/** + * Duplicates a ptr/len string into a zero terminated string. + */ +static inline char * +sentry__string_clone_n(const char *str, size_t n) +{ + return str ? sentry__string_clone_n_unchecked(str, n) : NULL; +} + /** * Duplicates a zero terminated string. */ static inline char * sentry__string_clone(const char *str) { - return str ? sentry__string_clonen(str, strlen(str)) : NULL; + return str ? sentry__string_clone_n_unchecked(str, strlen(str)) : NULL; +} + +static inline char * +sentry__string_clone_max_n(const char *str, size_t str_len, size_t max_len) +{ + if (!str) { + return NULL; + } + size_t min_len = str_len < max_len ? str_len : max_len; + return sentry__string_clone_n_unchecked(str, min_len); } /** diff --git a/src/sentry_sync.h b/src/sentry_sync.h index 0741c30702..73531235b5 100644 --- a/src/sentry_sync.h +++ b/src/sentry_sync.h @@ -167,7 +167,10 @@ typedef HANDLE sentry_threadid_t; typedef struct sentry__winmutex_s sentry_mutex_t; # define SENTRY__MUTEX_INIT \ { \ - INIT_ONCE_STATIC_INIT, { 0 } \ + INIT_ONCE_STATIC_INIT, \ + { \ + 0 \ + } \ } # define sentry__mutex_init(Lock) sentry__winmutex_init(Lock) # define sentry__mutex_lock(Lock) sentry__winmutex_lock(Lock) diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index 9b9139a175..b32c30496a 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -1,17 +1,20 @@ #include "sentry_tracing.h" +#include "sentry.h" #include "sentry_alloc.h" #include "sentry_logger.h" +#include "sentry_slice.h" #include "sentry_string.h" #include "sentry_utils.h" #include "sentry_value.h" #include sentry_value_t -sentry__value_new_span(sentry_value_t parent, const char *operation) +sentry__value_new_span_n(sentry_value_t parent, sentry_slice_t operation) { sentry_value_t span = sentry_value_new_object(); - sentry_value_set_by_key(span, "op", sentry_value_new_string(operation)); + sentry_value_set_by_key( + span, "op", sentry_value_new_string_n(operation.ptr, operation.len)); sentry_uuid_t span_id = sentry_uuid_new_v4(); sentry_value_set_by_key( @@ -32,30 +35,34 @@ sentry__value_new_span(sentry_value_t parent, const char *operation) } sentry_value_t -sentry__value_transaction_context_new(const char *name, const char *operation) +sentry__value_transaction_context_new_n( + sentry_slice_t name, sentry_slice_t operation) { sentry_value_t transaction_context - = sentry__value_new_span(sentry_value_new_null(), operation); + = sentry__value_new_span_n(sentry_value_new_null(), operation); sentry_uuid_t trace_id = sentry_uuid_new_v4(); sentry_value_set_by_key(transaction_context, "trace_id", sentry__value_new_internal_uuid(&trace_id)); - sentry_value_set_by_key( - transaction_context, "transaction", sentry_value_new_string(name)); + sentry_value_set_by_key(transaction_context, "transaction", + sentry_value_new_string_n(name.ptr, name.len)); return transaction_context; } sentry_transaction_context_t * -sentry_transaction_context_new(const char *name, const char *operation) +sentry_transaction_context_new_n(const char *name, size_t name_len, + const char *operation, size_t operation_len) { sentry_transaction_context_t *tx_cxt = SENTRY_MAKE(sentry_transaction_context_t); if (!tx_cxt) { return NULL; } - tx_cxt->inner = sentry__value_transaction_context_new(name, operation); + tx_cxt->inner = sentry__value_transaction_context_new_n( + (sentry_slice_t) { name, name_len }, + (sentry_slice_t) { operation, operation_len }); if (sentry_value_is_null(tx_cxt->inner)) { sentry_free(tx_cxt); @@ -65,6 +72,16 @@ sentry_transaction_context_new(const char *name, const char *operation) return tx_cxt; } +sentry_transaction_context_t * +sentry_transaction_context_new(const char *name, const char *operation) +{ + size_t name_len = name ? strlen(name) : 0; + size_t operation_len = operation ? strlen(operation) : 0; + + return sentry_transaction_context_new_n( + name, name_len, operation, operation_len); +} + void sentry__transaction_context_free(sentry_transaction_context_t *tx_cxt) { @@ -76,7 +93,7 @@ sentry__transaction_context_free(sentry_transaction_context_t *tx_cxt) sentry_free(tx_cxt); } else { sentry_value_decref(tx_cxt->inner); - }; + } } void @@ -89,6 +106,16 @@ sentry_transaction_context_set_name( } } +void +sentry_transaction_context_set_name_n( + sentry_transaction_context_t *tx_cxt, const char *name, size_t name_len) +{ + if (tx_cxt) { + sentry_value_set_by_key(tx_cxt->inner, "transaction", + sentry_value_new_string_n(name, name_len)); + } +} + void sentry_transaction_context_set_operation( sentry_transaction_context_t *tx_cxt, const char *operation) @@ -99,6 +126,16 @@ sentry_transaction_context_set_operation( } } +void +sentry_transaction_context_set_operation_n(sentry_transaction_context_t *tx_cxt, + const char *operation, size_t operation_len) +{ + if (tx_cxt) { + sentry_value_set_by_key(tx_cxt->inner, "op", + sentry_value_new_string_n(operation, operation_len)); + } +} + void sentry_transaction_context_set_sampled( sentry_transaction_context_t *tx_cxt, int sampled) @@ -118,8 +155,9 @@ sentry_transaction_context_remove_sampled(sentry_transaction_context_t *tx_cxt) } void -sentry_transaction_context_update_from_header( - sentry_transaction_context_t *tx_cxt, const char *key, const char *value) +sentry_transaction_context_update_from_header_n( + sentry_transaction_context_t *tx_cxt, const char *key, size_t key_len, + const char *value, size_t value_len) { if (!tx_cxt) { return; @@ -127,7 +165,11 @@ sentry_transaction_context_update_from_header( // do case-insensitive header key comparison const char sentry_trace[] = "sentry-trace"; - for (size_t i = 0; i < sizeof(sentry_trace); i++) { + const size_t sentry_trace_len = sizeof(sentry_trace) - 1; + if (key_len != sentry_trace_len) { + return; + } + for (size_t i = 0; i < sentry_trace_len; i++) { if (tolower(key[i]) != sentry_trace[i]) { return; } @@ -136,7 +178,7 @@ sentry_transaction_context_update_from_header( // https://develop.sentry.dev/sdk/performance/#header-sentry-trace // sentry-trace = traceid-spanid(-sampled)? const char *trace_id_start = value; - const char *trace_id_end = strchr(trace_id_start, '-'); + const char *trace_id_end = memchr(trace_id_start, '-', value_len); if (!trace_id_end) { return; } @@ -144,7 +186,7 @@ sentry_transaction_context_update_from_header( sentry_value_t inner = tx_cxt->inner; char *s - = sentry__string_clonen(trace_id_start, trace_id_end - trace_id_start); + = sentry__string_clone_n(trace_id_start, trace_id_end - trace_id_start); sentry_value_t trace_id = sentry__value_new_string_owned(s); sentry_value_set_by_key(inner, "trace_id", trace_id); @@ -158,7 +200,7 @@ sentry_transaction_context_update_from_header( } // else: we have a sampled flag - s = sentry__string_clonen(span_id_start, span_id_end - span_id_start); + s = sentry__string_clone_n(span_id_start, span_id_end - span_id_start); sentry_value_t parent_span_id = sentry__value_new_string_owned(s); sentry_value_set_by_key(inner, "parent_span_id", parent_span_id); @@ -166,6 +208,17 @@ sentry_transaction_context_update_from_header( sentry_value_set_by_key(inner, "sampled", sentry_value_new_bool(sampled)); } +void +sentry_transaction_context_update_from_header( + sentry_transaction_context_t *tx_cxt, const char *key, const char *value) +{ + size_t key_len = key ? strlen(key) : 0; + size_t value_len = value ? strlen(value) : 0; + + sentry_transaction_context_update_from_header_n( + tx_cxt, key, key_len, value, value_len); +} + sentry_transaction_t * sentry__transaction_new(sentry_value_t inner) { @@ -203,7 +256,7 @@ sentry__transaction_decref(sentry_transaction_t *tx) sentry_free(tx); } else { sentry_value_decref(tx->inner); - }; + } } void @@ -227,7 +280,7 @@ sentry__span_decref(sentry_span_t *span) sentry_free(span); } else { sentry_value_decref(span->inner); - }; + } } sentry_span_t * @@ -251,8 +304,8 @@ sentry__span_new(sentry_transaction_t *tx, sentry_value_t inner) } sentry_value_t -sentry__value_span_new( - size_t max_spans, sentry_value_t parent, char *operation, char *description) +sentry__value_span_new_n(size_t max_spans, sentry_value_t parent, + sentry_slice_t operation, sentry_slice_t description) { if (!sentry_value_is_null(sentry_value_get_by_key(parent, "timestamp"))) { SENTRY_DEBUG("span's parent is already finished, not creating span"); @@ -269,9 +322,9 @@ sentry__value_span_new( goto fail; } - sentry_value_t child = sentry__value_new_span(parent, operation); - sentry_value_set_by_key( - child, "description", sentry_value_new_string(description)); + sentry_value_t child = sentry__value_new_span_n(parent, operation); + sentry_value_set_by_key(child, "description", + sentry_value_new_string_n(description.ptr, description.len)); sentry_value_set_by_key(child, "start_timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); @@ -281,6 +334,17 @@ sentry__value_span_new( return sentry_value_new_null(); } +sentry_value_t +sentry__value_span_new(size_t max_spans, sentry_value_t parent, + const char *operation, const char *description) +{ + const size_t operation_len = operation ? strlen(operation) : 0; + const size_t description_len = description ? strlen(description) : 0; + return sentry__value_span_new_n(max_spans, parent, + (sentry_slice_t) { operation, operation_len }, + (sentry_slice_t) { description, description_len }); +} + sentry_value_t sentry__value_get_trace_context(sentry_value_t span) { @@ -326,21 +390,37 @@ sentry_transaction_set_name(sentry_transaction_t *tx, const char *name) } } +void +sentry_transaction_set_name_n( + sentry_transaction_t *tx, const char *name, size_t name_len) +{ + if (tx) { + sentry_value_set_by_key(tx->inner, "transaction", + sentry_value_new_string_n(name, name_len)); + } +} + static void -set_tag(sentry_value_t item, const char *tag, const char *value) +set_tag_n(sentry_value_t item, sentry_slice_t tag, sentry_slice_t value) { sentry_value_t tags = sentry_value_get_by_key(item, "tags"); if (sentry_value_is_null(tags)) { tags = sentry_value_new_object(); sentry_value_set_by_key(item, "tags", tags); } + char *s = sentry__string_clone_max_n(value.ptr, value.len, 200); + sentry_value_t tag_value + = s ? sentry__value_new_string_owned(s) : sentry_value_new_null(); + sentry_value_set_by_key_n(tags, tag.ptr, tag.len, tag_value); +} - char *s = sentry__string_clonen(value, 200); - if (s) { - sentry_value_set_by_key(tags, tag, sentry__value_new_string_owned(s)); - } else { - sentry_value_set_by_key(tags, tag, sentry_value_new_null()); - } +static void +set_tag(sentry_value_t item, const char *tag, const char *value) +{ + const size_t tag_len = tag ? strlen(tag) : 0; + const size_t value_len = value ? strlen(value) : 0; + set_tag_n(item, (sentry_slice_t) { tag, tag_len }, + (sentry_slice_t) { value, value_len }); } void @@ -352,6 +432,16 @@ sentry_transaction_set_tag( } } +void +sentry_transaction_set_tag_n(sentry_transaction_t *tx, const char *tag, + size_t tag_len, const char *value, size_t value_len) +{ + if (tx) { + set_tag_n(tx->inner, (sentry_slice_t) { tag, tag_len }, + (sentry_slice_t) { value, value_len }); + } +} + void sentry_span_set_tag(sentry_span_t *span, const char *tag, const char *value) { @@ -360,6 +450,16 @@ sentry_span_set_tag(sentry_span_t *span, const char *tag, const char *value) } } +void +sentry_span_set_tag_n(sentry_span_t *span, const char *tag, size_t tag_len, + const char *value, size_t value_len) +{ + if (span) { + set_tag_n(span->inner, (sentry_slice_t) { tag, tag_len }, + (sentry_slice_t) { value, value_len }); + } +} + static void remove_tag(sentry_value_t item, const char *tag) { @@ -369,6 +469,15 @@ remove_tag(sentry_value_t item, const char *tag) } } +static void +remove_tag_n(sentry_value_t item, const char *tag, size_t tag_len) +{ + sentry_value_t tags = sentry_value_get_by_key(item, "tags"); + if (!sentry_value_is_null(tags)) { + sentry_value_remove_by_key_n(tags, tag, tag_len); + } +} + void sentry_transaction_remove_tag(sentry_transaction_t *tx, const char *tag) { @@ -377,6 +486,15 @@ sentry_transaction_remove_tag(sentry_transaction_t *tx, const char *tag) } } +void +sentry_transaction_remove_tag_n( + sentry_transaction_t *tx, const char *tag, size_t tag_len) +{ + if (tx) { + remove_tag_n(tx->inner, tag, tag_len); + } +} + void sentry_span_remove_tag(sentry_span_t *span, const char *tag) { @@ -385,6 +503,14 @@ sentry_span_remove_tag(sentry_span_t *span, const char *tag) } } +void +sentry_span_remove_tag_n(sentry_span_t *span, const char *tag, size_t tag_len) +{ + if (span) { + remove_tag_n(span->inner, tag, tag_len); + } +} + static void set_data(sentry_value_t item, const char *key, sentry_value_t value) { @@ -396,6 +522,18 @@ set_data(sentry_value_t item, const char *key, sentry_value_t value) sentry_value_set_by_key(data, key, value); } +static void +set_data_n( + sentry_value_t item, const char *key, size_t key_len, sentry_value_t value) +{ + sentry_value_t data = sentry_value_get_by_key(item, "data"); + if (sentry_value_is_null(data)) { + data = sentry_value_new_object(); + sentry_value_set_by_key(item, "data", data); + } + sentry_value_set_by_key_n(data, key, key_len, value); +} + void sentry_transaction_set_data( sentry_transaction_t *tx, const char *key, sentry_value_t value) @@ -405,6 +543,15 @@ sentry_transaction_set_data( } } +void +sentry_transaction_set_data_n(sentry_transaction_t *tx, const char *key, + size_t key_len, sentry_value_t value) +{ + if (tx) { + set_data_n(tx->inner, key, key_len, value); + } +} + void sentry_span_set_data(sentry_span_t *span, const char *key, sentry_value_t value) { @@ -413,6 +560,15 @@ sentry_span_set_data(sentry_span_t *span, const char *key, sentry_value_t value) } } +void +sentry_span_set_data_n( + sentry_span_t *span, const char *key, size_t key_len, sentry_value_t value) +{ + if (span) { + set_data_n(span->inner, key, key_len, value); + } +} + static void remove_data(sentry_value_t item, const char *key) { @@ -422,6 +578,15 @@ remove_data(sentry_value_t item, const char *key) } } +static void +remove_data_n(sentry_value_t item, const char *key, size_t key_len) +{ + sentry_value_t data = sentry_value_get_by_key(item, "data"); + if (!sentry_value_is_null(data)) { + sentry_value_remove_by_key_n(data, key, key_len); + } +} + void sentry_transaction_remove_data(sentry_transaction_t *tx, const char *key) { @@ -430,6 +595,15 @@ sentry_transaction_remove_data(sentry_transaction_t *tx, const char *key) } } +void +sentry_transaction_remove_data_n( + sentry_transaction_t *tx, const char *key, size_t key_len) +{ + if (tx) { + remove_data_n(tx->inner, key, key_len); + } +} + void sentry_span_remove_data(sentry_span_t *span, const char *key) { @@ -438,6 +612,14 @@ sentry_span_remove_data(sentry_span_t *span, const char *key) } } +void +sentry_span_remove_data_n(sentry_span_t *span, const char *key, size_t key_len) +{ + if (span) { + remove_data_n(span->inner, key, key_len); + } +} + sentry_value_t sentry_status_to_string(sentry_span_status_t status) { @@ -481,7 +663,7 @@ sentry_status_to_string(sentry_span_status_t status) } } -void +static void set_status(sentry_value_t item, sentry_span_status_t status) { sentry_value_set_by_key(item, "status", sentry_status_to_string(status)); diff --git a/src/sentry_tracing.h b/src/sentry_tracing.h index e04e5ee2fd..d312b1cd41 100644 --- a/src/sentry_tracing.h +++ b/src/sentry_tracing.h @@ -1,6 +1,7 @@ #ifndef SENTRY_TRACING_H_INCLUDED #define SENTRY_TRACING_H_INCLUDED +#include "sentry_slice.h" #include "sentry_value.h" /** @@ -36,7 +37,10 @@ void sentry__span_incref(sentry_span_t *span); void sentry__span_decref(sentry_span_t *span); sentry_value_t sentry__value_span_new(size_t max_spans, sentry_value_t parent, - char *operation, char *description); + const char *operation, const char *description); +sentry_value_t sentry__value_span_new_n(size_t max_spans, sentry_value_t parent, + sentry_slice_t operation, sentry_slice_t description); + sentry_span_t *sentry__span_new( sentry_transaction_t *parent_tx, sentry_value_t inner); diff --git a/src/sentry_utils.c b/src/sentry_utils.c index 2319fc9889..bb80a98f0a 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -4,7 +4,6 @@ #include "sentry_boot.h" #include "sentry_alloc.h" -#include "sentry_core.h" #include "sentry_string.h" #include "sentry_sync.h" #include "sentry_utils.h" @@ -64,7 +63,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) if (!tmp) { goto error; } - url_out->scheme = sentry__string_clonen(ptr, tmp - ptr); + url_out->scheme = sentry__string_clone_n_unchecked(ptr, tmp - ptr); if (!url_out->scheme || !is_scheme_valid(url_out->scheme)) { goto error; @@ -97,13 +96,14 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) tmp = ptr; if (has_username) { SKIP_WHILE_NOT2(tmp, '@', ':'); - url_out->username = sentry__string_clonen(ptr, tmp - ptr); + url_out->username = sentry__string_clone_n_unchecked(ptr, tmp - ptr); ptr = tmp; if (*ptr == ':') { ptr++; tmp = ptr; SKIP_WHILE_NOT(tmp, '@'); - url_out->password = sentry__string_clonen(ptr, tmp - ptr); + url_out->password + = sentry__string_clone_n_unchecked(ptr, tmp - ptr); ptr = tmp; } if (*ptr != '@') { @@ -126,7 +126,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) tmp++; } - url_out->host = sentry__string_clonen(ptr, tmp - ptr); + url_out->host = sentry__string_clone_n_unchecked(ptr, tmp - ptr); /* port */ ptr = tmp; @@ -134,7 +134,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) ptr++; tmp = ptr; SKIP_WHILE_NOT(tmp, '/'); - aux_buf = sentry__string_clonen(ptr, tmp - ptr); + aux_buf = sentry__string_clone_n_unchecked(ptr, tmp - ptr); char *end; url_out->port = (int)strtol(aux_buf, &end, 10); if (end != aux_buf + strlen(aux_buf)) { @@ -157,7 +157,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) /* path */ tmp = ptr; SKIP_WHILE_NOT2(tmp, '#', '?'); - url_out->path = sentry__string_clonen(ptr, tmp - ptr); + url_out->path = sentry__string_clone_n_unchecked(ptr, tmp - ptr); ptr = tmp; /* query */ @@ -165,7 +165,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) ptr++; tmp = ptr; SKIP_WHILE_NOT(tmp, '#'); - url_out->query = sentry__string_clonen(ptr, tmp - ptr); + url_out->query = sentry__string_clone_n_unchecked(ptr, tmp - ptr); ptr = tmp; } @@ -174,7 +174,7 @@ sentry__url_parse(sentry_url_t *url_out, const char *url) ptr++; tmp = ptr; SKIP_WHILE_NOT(tmp, 0); - url_out->fragment = sentry__string_clonen(ptr, tmp - ptr); + url_out->fragment = sentry__string_clone_n_unchecked(ptr, tmp - ptr); } if (url_out->port == 0) { @@ -213,7 +213,7 @@ sentry__url_cleanup(sentry_url_t *url) } sentry_dsn_t * -sentry__dsn_new(const char *raw_dsn) +sentry__dsn_new_n(const char *raw_dsn, size_t raw_dsn_len) { sentry_url_t url; memset(&url, 0, sizeof(sentry_url_t)); @@ -227,7 +227,7 @@ sentry__dsn_new(const char *raw_dsn) memset(dsn, 0, sizeof(sentry_dsn_t)); dsn->refcount = 1; - dsn->raw = sentry__string_clone(raw_dsn); + dsn->raw = sentry__string_clone_n(raw_dsn, raw_dsn_len); if (!dsn->raw || !dsn->raw[0] || sentry__url_parse(&url, dsn->raw) != 0) { goto exit; } @@ -274,6 +274,16 @@ sentry__dsn_new(const char *raw_dsn) return dsn; } +sentry_dsn_t * +sentry__dsn_new(const char *raw_dsn) +{ + if (!raw_dsn) { + return NULL; + } + + return sentry__dsn_new_n(raw_dsn, strlen(raw_dsn)); +} + sentry_dsn_t * sentry__dsn_incref(sentry_dsn_t *dsn) { diff --git a/src/sentry_utils.h b/src/sentry_utils.h index 703b53c3f2..6f96eea647 100644 --- a/src/sentry_utils.h +++ b/src/sentry_utils.h @@ -63,6 +63,7 @@ typedef struct sentry_dsn_s { * DSN has been successfully parsed. */ sentry_dsn_t *sentry__dsn_new(const char *dsn); +sentry_dsn_t *sentry__dsn_new_n(const char *dsn, size_t raw_dsn_len); /** * Increases the reference-count of the DSN. diff --git a/src/sentry_uuid.c b/src/sentry_uuid.c index ad5e349996..bd1ee66dae 100644 --- a/src/sentry_uuid.c +++ b/src/sentry_uuid.c @@ -25,13 +25,13 @@ sentry_uuid_new_v4(void) } sentry_uuid_t -sentry_uuid_from_string(const char *str) +sentry_uuid_from_string_n(const char *str, size_t str_len) { sentry_uuid_t rv; memset(&rv, 0, sizeof(rv)); size_t i = 0; - size_t len = strlen(str); + size_t len = str_len; size_t pos = 0; bool is_nibble = true; char nibble = 0; @@ -65,6 +65,12 @@ sentry_uuid_from_string(const char *str) return rv; } +sentry_uuid_t +sentry_uuid_from_string(const char *str) +{ + return str ? sentry_uuid_from_string_n(str, strlen(str)) + : sentry_uuid_nil(); +} sentry_uuid_t sentry_uuid_from_bytes(const char bytes[16]) { diff --git a/src/sentry_value.c b/src/sentry_value.c index 9d78efd9ec..e46eb79f5e 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -5,7 +5,6 @@ #include #include #include -#include #if defined(_MSC_VER) # pragma warning(push) @@ -21,6 +20,7 @@ #include "sentry_alloc.h" #include "sentry_core.h" #include "sentry_json.h" +#include "sentry_slice.h" #include "sentry_string.h" #include "sentry_sync.h" #include "sentry_utils.h" @@ -320,15 +320,22 @@ sentry_value_new_bool(int value) } sentry_value_t -sentry_value_new_string(const char *value) +sentry_value_new_string_n(const char *value, size_t value_len) { - char *s = sentry__string_clone(value); + char *s = sentry__string_clone_n(value, value_len); if (!s) { return sentry_value_new_null(); } return sentry__value_new_string_owned(s); } +sentry_value_t +sentry_value_new_string(const char *value) +{ + return value ? sentry_value_new_string_n(value, strlen(value)) + : sentry_value_new_null(); +} + sentry_value_t sentry_value_new_list(void) { @@ -440,8 +447,13 @@ sentry_value_get_type(sentry_value_t value) } int -sentry_value_set_by_key(sentry_value_t value, const char *k, sentry_value_t v) +sentry_value_set_by_key_n( + sentry_value_t value, const char *k, size_t k_len, sentry_value_t v) { + if (!k) { + goto fail; + } + sentry_slice_t k_slice = { k, k_len }; thing_t *thing = value_as_unfrozen_thing(value); if (!thing || thing_get_type(thing) != THING_TYPE_OBJECT) { goto fail; @@ -449,7 +461,7 @@ sentry_value_set_by_key(sentry_value_t value, const char *k, sentry_value_t v) obj_t *o = thing->payload._ptr; for (size_t i = 0; i < o->len; i++) { obj_pair_t *pair = &o->pairs[i]; - if (sentry__string_eq(pair->k, k)) { + if (sentry__slice_eqs(k_slice, pair->k)) { sentry_value_decref(pair->v); pair->v = v; return 0; @@ -462,7 +474,7 @@ sentry_value_set_by_key(sentry_value_t value, const char *k, sentry_value_t v) } obj_pair_t pair; - pair.k = sentry__string_clone(k); + pair.k = sentry__slice_to_owned(k_slice); if (!pair.k) { goto fail; } @@ -476,8 +488,23 @@ sentry_value_set_by_key(sentry_value_t value, const char *k, sentry_value_t v) } int -sentry_value_remove_by_key(sentry_value_t value, const char *k) +sentry_value_set_by_key(sentry_value_t value, const char *k, sentry_value_t v) { + if (k) { + return sentry_value_set_by_key_n(value, k, strlen(k), v); + } + + sentry_value_decref(v); + return 1; +} + +int +sentry_value_remove_by_key_n(sentry_value_t value, const char *k, size_t k_len) +{ + if (!k) { + return 1; + } + sentry_slice_t k_slice = { k, k_len }; thing_t *thing = value_as_unfrozen_thing(value); if (!thing || thing_get_type(thing) != THING_TYPE_OBJECT) { return 1; @@ -485,7 +512,7 @@ sentry_value_remove_by_key(sentry_value_t value, const char *k) obj_t *o = thing->payload._ptr; for (size_t i = 0; i < o->len; i++) { obj_pair_t *pair = &o->pairs[i]; - if (sentry__string_eq(pair->k, k)) { + if (sentry__slice_eqs(k_slice, pair->k)) { sentry_free(pair->k); sentry_value_decref(pair->v); memmove(o->pairs + i, o->pairs + i + 1, @@ -497,6 +524,16 @@ sentry_value_remove_by_key(sentry_value_t value, const char *k) return 1; } +int +sentry_value_remove_by_key(sentry_value_t value, const char *k) +{ + if (k) { + return sentry_value_remove_by_key_n(value, k, strlen(k)); + } + + return 1; +} + int sentry_value_append(sentry_value_t value, sentry_value_t v) { @@ -684,14 +721,17 @@ sentry_value_remove_by_index(sentry_value_t value, size_t index) } sentry_value_t -sentry_value_get_by_key(sentry_value_t value, const char *k) +sentry_value_get_by_key_n(sentry_value_t value, const char *k, size_t k_len) { + if (!k) { + return sentry_value_new_null(); + } const thing_t *thing = value_as_thing(value); if (thing && thing_get_type(thing) == THING_TYPE_OBJECT) { obj_t *o = thing->payload._ptr; for (size_t i = 0; i < o->len; i++) { obj_pair_t *pair = &o->pairs[i]; - if (sentry__string_eq(pair->k, k)) { + if (sentry__slice_eqs((sentry_slice_t) { k, k_len }, pair->k)) { return pair->v; } } @@ -699,10 +739,30 @@ sentry_value_get_by_key(sentry_value_t value, const char *k) return sentry_value_new_null(); } +sentry_value_t +sentry_value_get_by_key(sentry_value_t value, const char *k) +{ + const size_t k_len = k ? strlen(k) : 0; + return sentry_value_get_by_key_n(value, k, k_len); +} + +sentry_value_t +sentry_value_get_by_key_owned_n( + sentry_value_t value, const char *k, size_t k_len) +{ + sentry_value_t rv = sentry_value_get_by_key_n(value, k, k_len); + sentry_value_incref(rv); + return rv; +} + sentry_value_t sentry_value_get_by_key_owned(sentry_value_t value, const char *k) { - sentry_value_t rv = sentry_value_get_by_key(value, k); + if (k) { + return sentry_value_get_by_key_owned_n(value, k, strlen(k)); + } + + sentry_value_t rv = sentry_value_new_null(); sentry_value_incref(rv); return rv; } @@ -1069,52 +1129,90 @@ sentry_value_new_event(void) } sentry_value_t -sentry_value_new_message_event( - sentry_level_t level, const char *logger, const char *text) +sentry_value_new_message_event_n(sentry_level_t level, const char *logger, + size_t logger_len, const char *text, size_t text_len) { sentry_value_t rv = sentry_value_new_event(); sentry_value_set_by_key(rv, "level", sentry__value_new_level(level)); if (logger) { - sentry_value_set_by_key(rv, "logger", sentry_value_new_string(logger)); + sentry_value_set_by_key( + rv, "logger", sentry_value_new_string_n(logger, logger_len)); } if (text) { sentry_value_t container = sentry_value_new_object(); sentry_value_set_by_key( - container, "formatted", sentry_value_new_string(text)); + container, "formatted", sentry_value_new_string_n(text, text_len)); sentry_value_set_by_key(rv, "message", container); } return rv; } sentry_value_t -sentry_value_new_breadcrumb(const char *type, const char *message) +sentry_value_new_message_event( + sentry_level_t level, const char *logger, const char *text) { - sentry_value_t rv = sentry_value_new_object(); - sentry_value_set_by_key(rv, "timestamp", + size_t logger_len = logger ? strlen(logger) : 0; + size_t text_len = text ? strlen(text) : 0; + return sentry_value_new_message_event_n( + level, logger, logger_len, text, text_len); +} + +static void +timestamp_value(sentry_value_t value) +{ + sentry_value_set_by_key(value, "timestamp", sentry__value_new_string_owned( sentry__msec_time_to_iso8601(sentry__msec_time()))); +} + +sentry_value_t +sentry_value_new_breadcrumb_n( + const char *type, size_t type_len, const char *message, size_t message_len) +{ + sentry_value_t rv = sentry_value_new_object(); + timestamp_value(rv); if (type) { - sentry_value_set_by_key(rv, "type", sentry_value_new_string(type)); + sentry_value_set_by_key( + rv, "type", sentry_value_new_string_n(type, type_len)); } if (message) { sentry_value_set_by_key( - rv, "message", sentry_value_new_string(message)); + rv, "message", sentry_value_new_string_n(message, message_len)); } return rv; } sentry_value_t -sentry_value_new_exception(const char *type, const char *value) +sentry_value_new_breadcrumb(const char *type, const char *message) +{ + const size_t type_len = type ? strlen(type) : 0; + const size_t message_len = message ? strlen(message) : 0; + return sentry_value_new_breadcrumb_n(type, type_len, message, message_len); +} + +sentry_value_t +sentry_value_new_exception_n( + const char *type, size_t type_len, const char *value, size_t value_len) { sentry_value_t exc = sentry_value_new_object(); - sentry_value_set_by_key(exc, "type", sentry_value_new_string(type)); - sentry_value_set_by_key(exc, "value", sentry_value_new_string(value)); + sentry_value_set_by_key( + exc, "type", sentry_value_new_string_n(type, type_len)); + sentry_value_set_by_key( + exc, "value", sentry_value_new_string_n(value, value_len)); return exc; } sentry_value_t -sentry_value_new_thread(uint64_t id, const char *name) +sentry_value_new_exception(const char *type, const char *value) +{ + const size_t type_len = type ? strlen(type) : 0; + const size_t value_len = value ? strlen(value) : 0; + return sentry_value_new_exception_n(type, type_len, value, value_len); +} + +sentry_value_t +sentry_value_new_thread_n(uint64_t id, const char *name, size_t name_len) { sentry_value_t thread = sentry_value_new_object(); @@ -1128,12 +1226,20 @@ sentry_value_new_thread(uint64_t id, const char *name) } if (name) { - sentry_value_set_by_key(thread, "name", sentry_value_new_string(name)); + sentry_value_set_by_key( + thread, "name", sentry_value_new_string_n(name, name_len)); } return thread; } +sentry_value_t +sentry_value_new_thread(uint64_t id, const char *name) +{ + const size_t name_len = name ? strlen(name) : 0; + return sentry_value_new_thread_n(id, name, name_len); +} + sentry_value_t sentry_value_new_stacktrace(void **ips, size_t len) { diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index cb30d432cf..b9f52c8813 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -47,6 +47,7 @@ sentry__curl_bgworker_state_free(void *_state) curl_bgworker_state_t *state = _state; if (state->curl_handle) { curl_easy_cleanup(state->curl_handle); + curl_global_cleanup(); } sentry__dsn_decref(state->dsn); sentry__rate_limiter_free(state->ratelimiter); @@ -140,7 +141,7 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { size_t bytes = size * nitems; struct header_info *info = userdata; - char *header = sentry__string_clonen(buffer, bytes); + char *header = sentry__string_clone_n(buffer, bytes); if (!header) { return bytes; } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 9d509f23d8..93848c3452 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -74,7 +74,7 @@ sentry__winhttp_transport_start( const char *ptr = opts->http_proxy + 7; const char *slash = strchr(ptr, '/'); if (slash) { - char *copy = sentry__string_clonen(ptr, slash - ptr); + char *copy = sentry__string_clone_n(ptr, slash - ptr); state->proxy = sentry__string_to_wstr(copy); sentry_free(copy); } else { diff --git a/tests/assertions.py b/tests/assertions.py index 89fe1eec98..6bae5f60d6 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -7,7 +7,7 @@ from .conditions import is_android -VERSION_RE = re.compile(r"(\d+\.\d+\.\d+)(?:[-\.]?)(.*)") +VERSION_RE = re.compile(r"(\d+\.\d+\.\d+)[-.]?(.*)") def matches(actual, expected): @@ -95,7 +95,7 @@ def assert_meta( ) assert event["contexts"]["os"]["build"] is not None - if sdk_override != None: + if sdk_override is not None: expected_sdk["name"] = sdk_override assert_matches(event, expected) diff --git a/tests/leaks.txt b/tests/leaks.txt index 2ba3e9dc8b..4ca4e4b684 100644 --- a/tests/leaks.txt +++ b/tests/leaks.txt @@ -3,3 +3,4 @@ # Adding a manual `[paths release]` "fixes" the `crashpad_handler` leak, but leads to a use-after-free in sentry. # https://github.com/getsentry/crashpad/blob/9cd1a4dadb51b31665f5e50c5ffc25bb9d10571a/client/crash_report_database_mac.mm#L705 leak:contentsOfDirectoryAtPath +leak:SCDynamicStoreCopyProxiesWithOptions diff --git a/tests/requirements.txt b/tests/requirements.txt index dd2fd18e86..4d181eb9d0 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ -black==22.3.0 -pytest==6.2.5 -pytest-httpserver==1.0.1 +black==23.3.0 +pytest==7.2.2 +pytest-httpserver==1.0.6 diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 2bca41e6d9..400b639bdf 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1,13 +1,10 @@ -import time import pytest -import subprocess -import sys import os import time import itertools import uuid import json -from . import make_dsn, check_output, run, Envelope +from . import make_dsn, run, Envelope from .conditions import has_http, has_breakpad, has_files from .assertions import ( assert_attachment, @@ -217,7 +214,7 @@ def test_inproc_crash_http(cmake, httpserver): ["log", "start-session", "attachment", "crash"], env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all run( tmp_path, @@ -254,7 +251,7 @@ def test_inproc_reinstall(cmake, httpserver): ["log", "reinstall", "crash"], env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all run( tmp_path, @@ -279,8 +276,7 @@ def test_inproc_dump_inflight(cmake, httpserver): child = run( tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env ) - assert child.returncode # well, its a crash after all - + assert child.returncode # well, it's a crash after all run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) # we trigger 10 normal events, and 1 crash @@ -303,7 +299,7 @@ def test_breakpad_crash_http(cmake, httpserver): ["log", "start-session", "attachment", "crash"], env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all run( tmp_path, @@ -341,7 +337,7 @@ def test_breakpad_reinstall(cmake, httpserver): ["log", "reinstall", "crash"], env=env, ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all run( tmp_path, @@ -367,7 +363,7 @@ def test_breakpad_dump_inflight(cmake, httpserver): child = run( tmp_path, "sentry_example", ["log", "capture-multiple", "crash"], env=env ) - assert child.returncode # well, its a crash after all + assert child.returncode # well, it's a crash after all run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) @@ -405,6 +401,7 @@ def delayed(req): env=env, check=True, ) + assert child.returncode == 0 httpserver.clear_all_handlers() httpserver.clear_log() @@ -419,6 +416,9 @@ def delayed(req): assert len(httpserver.log) == 10 +RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" + + def test_transaction_only(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) @@ -450,10 +450,10 @@ def test_transaction_only(cmake, httpserver): (event,) = envelope.items assert event.headers["type"] == "transaction" - json = event.payload.json + payload = event.payload.json # See https://develop.sentry.dev/sdk/performance/trace-context/#trace-context - trace_context = json["contexts"]["trace"] + trace_context = payload["contexts"]["trace"] assert ( trace_context["op"] == "Short and stout here is my handle and here is my spout" @@ -469,8 +469,7 @@ def test_transaction_only(cmake, httpserver): assert trace_context["span_id"] assert trace_context["status"] == "ok" - RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" - start_timestamp = time.strptime(json["start_timestamp"], RFC3339_FORMAT) + start_timestamp = time.strptime(payload["start_timestamp"], RFC3339_FORMAT) assert start_timestamp - timestamp = time.strptime(json["timestamp"], RFC3339_FORMAT) + timestamp = time.strptime(payload["timestamp"], RFC3339_FORMAT) assert timestamp >= start_timestamp diff --git a/tests/unit/sentry_testsupport.h b/tests/unit/sentry_testsupport.h index 81927db1b7..bd6f112a76 100644 --- a/tests/unit/sentry_testsupport.h +++ b/tests/unit/sentry_testsupport.h @@ -23,6 +23,13 @@ TEST_MSG("Received: %s", Val); \ } while (0) +#define TEST_CHECK_WSTRING_EQUAL(Val, ReferenceVal) \ + do { \ + TEST_CHECK(wcscmp(Val, ReferenceVal) == 0); \ + TEST_MSG("Expected: %s", ReferenceVal); \ + TEST_MSG("Received: %s", Val); \ + } while (0) + #define TEST_CHECK_JSON_VALUE(Val, ReferenceJson) \ do { \ char *json = sentry_value_to_json(Val); \ diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index bd4d2d065d..62dcf0af72 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -35,7 +35,8 @@ SENTRY_TEST(lazy_attachments) sentry_options_set_transport(options, sentry_new_function_transport( send_envelope_test_attachments, &testdata)); - sentry_options_set_release(options, "prod"); + char rel[] = { 't', 'e', 's', 't' }; + sentry_options_set_release_n(options, rel, sizeof(rel)); sentry_options_add_attachment(options, PREFIX ".existing-file-attachment"); sentry_options_add_attachment( @@ -53,6 +54,7 @@ SENTRY_TEST(lazy_attachments) char *serialized = sentry_stringbuilder_take_string(&testdata.serialized_envelope); + TEST_CHECK(strstr(serialized, "\"release\":\"test\"") != NULL); TEST_CHECK(strstr(serialized, "{\"type\":\"attachment\",\"length\":3," "\"filename\":\".existing-file-attachment\"}\n" diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index d8a51e4618..fc508bbd20 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -176,14 +176,19 @@ SENTRY_TEST(crashed_last_run) TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), -1); options = sentry_options_new(); - sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + const char *dsn_str = "https://foo@sentry.invalid/42"; + const char dsn[] = { 'h', 't', 't', 'p', 's', ':', '/', '/', 'f', 'o', 'o', + '@', 's', 'e', 'n', 't', 'r', 'y', '.', 'i', 'n', 'v', 'a', 'l', 'i', + 'd', '/', '4', '2' }; + sentry_options_set_dsn_n(options, dsn, sizeof(dsn)); + TEST_CHECK_STRING_EQUAL(sentry_options_get_dsn(options), dsn_str); TEST_CHECK_INT_EQUAL(sentry_init(options), 0); sentry_close(); TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 0); options = sentry_options_new(); - sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_options_set_dsn_n(options, dsn, sizeof(dsn)); // simulate a crash TEST_CHECK(sentry__write_crash_marker(options)); @@ -201,7 +206,7 @@ SENTRY_TEST(crashed_last_run) TEST_CHECK_INT_EQUAL(sentry_get_crashed_last_run(), 1); options = sentry_options_new(); - sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + sentry_options_set_dsn_n(options, dsn, sizeof(dsn)); TEST_CHECK_INT_EQUAL(sentry_init(options), 0); sentry_close(); diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 2e6128a9cb..e0032706cd 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -1,9 +1,21 @@ #include "sentry_envelope.h" +#include "sentry_path.h" #include "sentry_testsupport.h" #include "sentry_transport.h" #include "sentry_utils.h" #include "sentry_value.h" +static char *const SERIALIZED_ENVELOPE_STR + = "{\"dsn\":\"https://foo@sentry.invalid/42\"," + "\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" + "{\"type\":\"event\",\"length\":71}\n" + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"some-" + "context\":null}\n" + "{\"type\":\"minidump\",\"length\":4}\n" + "MDMP\n" + "{\"type\":\"attachment\",\"length\":12}\n" + "Hello World!"; + SENTRY_TEST(basic_http_request_preparation_for_event) { sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.invalid/42"); @@ -124,7 +136,8 @@ SENTRY_TEST(basic_http_request_preparation_for_minidump) sentry__dsn_decref(dsn); } -SENTRY_TEST(serialize_envelope) +sentry_envelope_t * +create_test_envelope() { sentry_options_t *options = sentry_options_new(); sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); @@ -145,25 +158,60 @@ SENTRY_TEST(serialize_envelope) char msg[] = "Hello World!"; sentry__envelope_add_from_buffer( envelope, msg, sizeof(msg) - 1, "attachment"); + return envelope; +} + +SENTRY_TEST(serialize_envelope) +{ + sentry_envelope_t *envelope = create_test_envelope(); sentry_stringbuilder_t sb; sentry__stringbuilder_init(&sb); sentry__envelope_serialize_into_stringbuilder(envelope, &sb); char *str = sentry__stringbuilder_into_string(&sb); - TEST_CHECK_STRING_EQUAL(str, - "{\"dsn\":\"https://foo@sentry.invalid/42\"," - "\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" - "{\"type\":\"event\",\"length\":71}\n" - "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"some-" - "context\":null}\n" - "{\"type\":\"minidump\",\"length\":4}\n" - "MDMP\n" - "{\"type\":\"attachment\",\"length\":12}\n" - "Hello World!"); + TEST_CHECK_STRING_EQUAL(str, SERIALIZED_ENVELOPE_STR); sentry_envelope_free(envelope); sentry_free(str); sentry_close(); } + +SENTRY_TEST(basic_write_envelope_to_file) +{ + sentry_envelope_t *envelope = create_test_envelope(); + const char *test_file_str = "sentry_test_envelope"; + sentry_path_t *test_file_path = sentry__path_from_str(test_file_str); + int rv = sentry_envelope_write_to_file(envelope, test_file_str); + TEST_CHECK_INT_EQUAL(rv, 0); + TEST_ASSERT(sentry__path_is_file(test_file_path)); + + size_t test_file_size; + char *test_file_content + = sentry__path_read_to_buffer(test_file_path, &test_file_size); + TEST_CHECK_INT_EQUAL(test_file_size, strlen(SERIALIZED_ENVELOPE_STR)); + TEST_CHECK_STRING_EQUAL(test_file_content, SERIALIZED_ENVELOPE_STR); + + sentry_free(test_file_content); + sentry__path_remove(test_file_path); + sentry__path_free(test_file_path); + sentry_envelope_free(envelope); + sentry_close(); +} + +SENTRY_TEST(write_envelope_to_file_null) +{ + sentry_envelope_t *empty_envelope = sentry__envelope_new(); + + TEST_CHECK_INT_EQUAL( + sentry_envelope_write_to_file(NULL, "irrelevant/path"), 1); + TEST_CHECK_INT_EQUAL( + sentry_envelope_write_to_file(empty_envelope, NULL), 1); + TEST_CHECK_INT_EQUAL( + sentry_envelope_write_to_file_n(NULL, "irrelevant/path", 0), 1); + TEST_CHECK_INT_EQUAL( + sentry_envelope_write_to_file_n(empty_envelope, NULL, 0), 1); + + sentry_envelope_free(empty_envelope); +} diff --git a/tests/unit/test_path.c b/tests/unit/test_path.c index 3f25d77a52..8424d1f0b3 100644 --- a/tests/unit/test_path.c +++ b/tests/unit/test_path.c @@ -58,6 +58,26 @@ SENTRY_TEST(path_joining_unix) #endif } +SENTRY_TEST(path_from_str_null) +{ + TEST_CHECK(NULL == sentry__path_from_str(NULL)); + TEST_CHECK(NULL == sentry__path_from_str_n(NULL, 0)); + TEST_CHECK(NULL == sentry__path_from_str_n(NULL, 10)); +} + +SENTRY_TEST(path_from_str_n_wo_null_termination) +{ + // provide non-null-terminated path string with buffer character at the end. + char path_str[] = { 't', 'e', 's', 't', 'X' }; + sentry_path_t *test_path = sentry__path_from_str_n(path_str, 4); +#ifdef SENTRY_PLATFORM_WINDOWS + TEST_CHECK_WSTRING_EQUAL(test_path->path, L"test"); +#else + TEST_CHECK_STRING_EQUAL(test_path->path, "test"); +#endif + sentry__path_free(test_path); +} + SENTRY_TEST(path_joining_windows) { #ifndef SENTRY_PLATFORM_WINDOWS diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index fbe586a02a..ebaaed389b 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -47,7 +47,7 @@ send_envelope(const sentry_envelope_t *envelope, void *data) "my_release"); TEST_CHECK_STRING_EQUAL( sentry_value_as_string(sentry_value_get_by_key(attrs, "environment")), - "my_environment"); + "test"); sentry_value_decref(session); } @@ -67,6 +67,10 @@ SENTRY_TEST(session_basics) TEST_CHECK_STRING_EQUAL( sentry_options_get_environment(options), "production"); sentry_options_set_environment(options, "my_environment"); + TEST_CHECK_STRING_EQUAL( + sentry_options_get_environment(options), "my_environment"); + char env[] = { 't', 'e', 's', 't' }; + sentry_options_set_environment_n(options, env, sizeof(env)); sentry_init(options); // a session was already started by automatic session tracking diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index a960273e03..bdac0921fd 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -94,9 +94,19 @@ SENTRY_TEST(basic_transaction) sentry_transaction_context_set_name(opaque_tx_cxt, ""); CHECK_STRING_PROPERTY(tx_cxt, "transaction", ""); + char txn_ctx_name[] = { 'h', 'o', 'n', 'k', '.', 'b', 'e', 'e', 'p' }; + sentry_transaction_context_set_name_n( + opaque_tx_cxt, txn_ctx_name, sizeof(txn_ctx_name)); + CHECK_STRING_PROPERTY(tx_cxt, "transaction", "honk.beep"); + sentry_transaction_context_set_operation(opaque_tx_cxt, ""); CHECK_STRING_PROPERTY(tx_cxt, "op", ""); + char txn_ctx_op[] = { 'b', 'e', 'e', 'p', 'b', 'e', 'e', 'p' }; + sentry_transaction_context_set_operation_n( + opaque_tx_cxt, txn_ctx_op, sizeof(txn_ctx_op)); + CHECK_STRING_PROPERTY(tx_cxt, "op", "beepbeep"); + sentry_transaction_context_set_sampled(opaque_tx_cxt, 1); TEST_CHECK( sentry_value_is_true(sentry_value_get_by_key(tx_cxt, "sampled")) @@ -195,9 +205,13 @@ SENTRY_TEST(basic_function_transport_transaction) // consent was not given TEST_CHECK(!sentry_uuid_is_nil(&event_id)); sentry_user_consent_give(); - - tx_cxt = sentry_transaction_context_new("honk", "beep"); + char name[] = { 'h', 'o', 'n', 'k' }; + char op[] = { 'b', 'e', 'e', 'p' }; + tx_cxt + = sentry_transaction_context_new_n(name, sizeof(name), op, sizeof(op)); tx = sentry_transaction_start(tx_cxt, sentry_value_new_null()); + CHECK_STRING_PROPERTY(tx->inner, "transaction", "honk"); + CHECK_STRING_PROPERTY(tx->inner, "op", "beep"); event_id = sentry_transaction_finish(tx); TEST_CHECK(!sentry_uuid_is_nil(&event_id)); @@ -697,6 +711,43 @@ forward_headers_to(const char *key, const char *value, void *userdata) sentry_transaction_context_update_from_header(tx_ctx, key, value); } +SENTRY_TEST(update_from_header_null_ctx) +{ + sentry_transaction_context_update_from_header( + NULL, "irrelevant-key", "irrelevant-value"); +} + +SENTRY_TEST(update_from_header_no_sampled_flag) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_set_dsn(options, "https://foo@sentry.invalid/42"); + + sentry_options_set_traces_sample_rate(options, 1.0); + sentry_options_set_max_spans(options, 2); + sentry_init(options); + + sentry_transaction_context_update_from_header( + NULL, "irrelevant-key", "irrelevant-value"); + const char *trace_header + = "2674eb52d5874b13b560236d6c79ce8a-a0f9fdf04f1a63df"; + sentry_transaction_context_t *tx_ctx + = sentry_transaction_context_new("wow!", NULL); + sentry_transaction_context_update_from_header( + tx_ctx, "sentry-trace", trace_header); + sentry_transaction_t *tx + = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + + CHECK_STRING_PROPERTY( + tx->inner, "trace_id", "2674eb52d5874b13b560236d6c79ce8a"); + CHECK_STRING_PROPERTY(tx->inner, "parent_span_id", "a0f9fdf04f1a63df"); + sentry_value_t sampled = sentry_value_get_by_key(tx->inner, "sampled"); + TEST_CHECK(sentry_value_get_type(sampled) == SENTRY_VALUE_TYPE_BOOL); + TEST_CHECK(sentry_value_is_true(sampled)); + + sentry__transaction_decref(tx); + sentry_close(); +} + SENTRY_TEST(distributed_headers) { sentry_options_t *options = sentry_options_new(); @@ -715,9 +766,14 @@ SENTRY_TEST(distributed_headers) sentry_transaction_context_t *tx_ctx = sentry_transaction_context_new("wow!", NULL); - // check case insensitive headers, and bogus header names + // check case-insensitive headers, and bogus header names sentry_transaction_context_update_from_header( tx_ctx, "SeNtry-TrAcE", trace_header); + sentry_transaction_context_update_from_header( + tx_ctx, "sentry_trace", not_expected_header); + sentry_transaction_context_update_from_header( + tx_ctx, NULL, not_expected_header); + sentry_transaction_context_update_from_header(tx_ctx, "sentry-trace", NULL); sentry_transaction_context_update_from_header( tx_ctx, "nop", not_expected_header); sentry_transaction_context_update_from_header( @@ -807,5 +863,246 @@ SENTRY_TEST(distributed_headers) sentry_close(); } +void +check_after_set(sentry_value_t inner, const char *inner_key, + const char *item_key, const char *expected) +{ + sentry_value_t inner_tags = sentry_value_get_by_key(inner, inner_key); + TEST_CHECK_INT_EQUAL(1, sentry_value_get_length(inner_tags)); + TEST_CHECK( + sentry_value_get_type(sentry_value_get_by_key(inner_tags, item_key)) + == SENTRY_VALUE_TYPE_STRING); + CHECK_STRING_PROPERTY(inner_tags, item_key, expected); +} + +void +check_after_remove( + sentry_value_t inner, const char *inner_key, const char *item_key) +{ + sentry_value_t inner_tags = sentry_value_get_by_key(inner, inner_key); + TEST_CHECK_INT_EQUAL(0, sentry_value_get_length(inner_tags)); + TEST_CHECK(IS_NULL(inner_tags, item_key)); +} + +SENTRY_TEST(txn_tagging) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + + sentry_transaction_set_tag(txn, "os.name", "Linux"); + check_after_set(txn->inner, "tags", "os.name", "Linux"); + + sentry_transaction_remove_tag(txn, "os.name"); + check_after_remove(txn->inner, "tags", "os.name"); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(span_tagging) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_span_t *span = sentry__span_new(txn, sentry_value_new_object()); + + sentry_span_set_tag(span, "os.name", "Linux"); + check_after_set(span->inner, "tags", "os.name", "Linux"); + + sentry_span_remove_tag(span, "os.name"); + check_after_remove(span->inner, "tags", "os.name"); + + sentry__span_decref(span); + sentry__transaction_decref(txn); +} + +SENTRY_TEST(txn_tagging_n) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + + char tag[] = { 'o', 's', '.', 'n', 'a', 'm', 'e' }; + char tag_val[] = { 'L', 'i', 'n', 'u', 'x' }; + sentry_transaction_set_tag_n( + txn, tag, sizeof(tag), tag_val, sizeof(tag_val)); + check_after_set(txn->inner, "tags", "os.name", "Linux"); + + sentry_transaction_remove_tag_n(txn, tag, sizeof(tag)); + check_after_remove(txn->inner, "tags", "os.name"); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(span_tagging_n) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_span_t *span = sentry__span_new(txn, sentry_value_new_object()); + + char tag[] = { 'o', 's', '.', 'n', 'a', 'm', 'e' }; + char tag_val[] = { 'L', 'i', 'n', 'u', 'x' }; + sentry_span_set_tag_n(span, tag, sizeof(tag), tag_val, sizeof(tag_val)); + check_after_set(span->inner, "tags", "os.name", "Linux"); + + sentry_span_remove_tag_n(span, tag, sizeof(tag)); + check_after_remove(span->inner, "tags", "os.name"); + + sentry__span_decref(span); + sentry__transaction_decref(txn); +} + +SENTRY_TEST(txn_name) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + + char *txn_name = "the_txn"; + sentry_transaction_set_name(txn, txn_name); + sentry_value_t txn_name_value + = sentry_value_get_by_key(txn->inner, "transaction"); + TEST_CHECK( + sentry_value_get_type(txn_name_value) == SENTRY_VALUE_TYPE_STRING); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(txn_name_value), txn_name); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(txn_data) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + + sentry_transaction_set_data( + txn, "os.name", sentry_value_new_string("Linux")); + check_after_set(txn->inner, "data", "os.name", "Linux"); + + sentry_transaction_remove_data(txn, "os.name"); + check_after_remove(txn->inner, "data", "os.name"); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(span_data) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_span_t *span = sentry__span_new(txn, sentry_value_new_object()); + + sentry_span_set_data(span, "os.name", sentry_value_new_string("Linux")); + check_after_set(span->inner, "data", "os.name", "Linux"); + + sentry_span_remove_data(span, "os.name"); + check_after_remove(span->inner, "data", "os.name"); + + sentry__span_decref(span); + sentry__transaction_decref(txn); +} + +SENTRY_TEST(txn_name_n) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + char txn_name[] = { 't', 'h', 'e', '_', 't', 'x', 'n' }; + sentry_transaction_set_name_n(txn, txn_name, sizeof(txn_name)); + + sentry_value_t txn_name_value + = sentry_value_get_by_key(txn->inner, "transaction"); + TEST_CHECK( + sentry_value_get_type(txn_name_value) == SENTRY_VALUE_TYPE_STRING); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(txn_name_value), "the_txn"); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(txn_data_n) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + + char data_k[] = { 'o', 's', '.', 'n', 'a', 'm', 'e' }; + char data_v[] = { 'L', 'i', 'n', 'u', 'x' }; + sentry_value_t data_value + = sentry_value_new_string_n(data_v, sizeof(data_v)); + sentry_transaction_set_data_n(txn, data_k, sizeof(data_k), data_value); + check_after_set(txn->inner, "data", "os.name", "Linux"); + + sentry_transaction_remove_data_n(txn, data_k, sizeof(data_k)); + check_after_remove(txn->inner, "data", "os.name"); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(span_data_n) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_span_t *span = sentry__span_new(txn, sentry_value_new_object()); + + char data_k[] = { 'o', 's', '.', 'n', 'a', 'm', 'e' }; + char data_v[] = { 'L', 'i', 'n', 'u', 'x' }; + sentry_value_t data_value + = sentry_value_new_string_n(data_v, sizeof(data_v)); + sentry_span_set_data_n(span, data_k, sizeof(data_k), data_value); + check_after_set(span->inner, "data", "os.name", "Linux"); + + sentry_span_remove_data_n(span, data_k, sizeof(data_k)); + check_after_remove(span->inner, "data", "os.name"); + + sentry__span_decref(span); + sentry__transaction_decref(txn); +} + +SENTRY_TEST(sentry__value_span_new_requires_unfinished_parent) +{ + sentry_value_t parent = sentry_value_new_object(); + // timestamps are typically iso8601 strings, but this is irrelevant to + // `sentry__value_span_new` which just wants `timestamp` to not be null. + sentry_value_set_by_key(parent, "timestamp", sentry_value_new_object()); + sentry_value_t inner_span = sentry__value_span_new(0, parent, NULL, NULL); + TEST_CHECK(sentry_value_is_null(inner_span)); + + sentry_value_decref(parent); +} + +SENTRY_TEST(set_tag_allows_null_tag_and_value) +{ + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_transaction_set_tag(txn, NULL, NULL); + sentry_value_t tags = sentry_value_get_by_key(txn->inner, "tags"); + TEST_CHECK(!sentry_value_is_null(tags)); + TEST_CHECK(sentry_value_get_type(tags) == SENTRY_VALUE_TYPE_OBJECT); + TEST_CHECK(sentry_value_get_length(tags) == 0); + + sentry_transaction_set_tag(txn, "os.name", NULL); + tags = sentry_value_get_by_key(txn->inner, "tags"); + TEST_CHECK(!sentry_value_is_null(tags)); + TEST_CHECK(sentry_value_get_type(tags) == SENTRY_VALUE_TYPE_OBJECT); + TEST_CHECK(sentry_value_get_length(tags) == 1); + TEST_CHECK(IS_NULL(tags, "os.name")); + + sentry__transaction_decref(txn); +} + +SENTRY_TEST(set_tag_cuts_value_at_length_200) +{ + const char test_value[] + = "012345678901234567890123456789012345678901234567890123456789" + "012345678901234567890123456789012345678901234567890123456789" + "012345678901234567890123456789012345678901234567890123456789" + "012345678901234567890123456789012345678901234567890123456789"; + + sentry_transaction_t *txn + = sentry__transaction_new(sentry_value_new_object()); + sentry_transaction_set_tag(txn, "cut-off", test_value); + sentry_value_t tags = sentry_value_get_by_key(txn->inner, "tags"); + TEST_CHECK(!sentry_value_is_null(tags)); + TEST_CHECK(sentry_value_get_type(tags) == SENTRY_VALUE_TYPE_OBJECT); + TEST_CHECK(sentry_value_get_length(tags) == 1); + TEST_CHECK_INT_EQUAL(strlen(sentry_value_as_string( + sentry_value_get_by_key(tags, "cut-off"))), + 200); + + sentry__transaction_decref(txn); +} + #undef IS_NULL #undef CHECK_STRING_PROPERTY diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index 855f59d3d7..57f1ffb4ce 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -249,4 +249,36 @@ SENTRY_TEST(check_version) TEST_CHECK(!sentry__check_min_version( (sentry_version_t) { .major = 7, .minor = 10, .patch = 6 }, (sentry_version_t) { .major = 7, .minor = 10, .patch = 7 })); -} \ No newline at end of file +} + +SENTRY_TEST(dsn_without_url_scheme_is_invalid) +{ + sentry_dsn_t *dsn = sentry__dsn_new("//without-scheme-separator"); + TEST_CHECK(dsn->is_valid == false); + sentry__dsn_decref(dsn); +} + +SENTRY_TEST(dsn_with_non_http_scheme_is_invalid) +{ + sentry_dsn_t *dsn = sentry__dsn_new("ftp://ftp-server/"); + TEST_CHECK(dsn->is_valid == false); + sentry__dsn_decref(dsn); +} + +SENTRY_TEST(dsn_without_project_id_is_invalid) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.io/"); + TEST_CHECK(dsn->is_valid == false); + sentry__dsn_decref(dsn); +} + +SENTRY_TEST(dsn_with_ending_forward_slash_will_be_cleaned) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.io/42/43/44////"); + + TEST_CHECK_STRING_EQUAL(dsn->path, "/42/43"); + TEST_CHECK_STRING_EQUAL(dsn->project_id, "44"); + TEST_CHECK(dsn->is_valid == true); + + sentry__dsn_decref(dsn); +} diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 6a4044c13c..92e195d580 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -102,16 +102,31 @@ SENTRY_TEST(value_string) sentry_value_decref(val); } +SENTRY_TEST(value_string_n) +{ + sentry_value_t val = sentry_value_new_string_n(NULL, 0); + TEST_CHECK(sentry_value_is_null(val)); + TEST_CHECK(sentry_value_get_type(val) == SENTRY_VALUE_TYPE_NULL); + TEST_CHECK(sentry_value_is_true(val) == false); + sentry_value_decref(val); + + char non_null_term_empty_str[] = { 'h', 'e', 'l', 'l', 'o' }; + val = sentry_value_new_string_n( + non_null_term_empty_str, sizeof(non_null_term_empty_str)); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(val), "hello"); + TEST_CHECK(sentry_value_get_type(val) == SENTRY_VALUE_TYPE_STRING); + TEST_CHECK(sentry_value_is_true(val) == true); + sentry_value_decref(val); +} + SENTRY_TEST(value_unicode) { // https://xkcd.com/1813/ :-) - sentry_value_t val - = sentry_value_new_string("őá…–🤮🚀¿ 한글 테스트 \a\v"); - TEST_CHECK_STRING_EQUAL(sentry_value_as_string(val), - "őá…–🤮🚀¿ 한글 테스트 \a\v"); + sentry_value_t val = sentry_value_new_string("őá…–🤮🚀¿ 한글 테스트 \a\v"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(val), "őá…–🤮🚀¿ 한글 테스트 \a\v"); // json does not need to escape unicode, except for control characters - TEST_CHECK_JSON_VALUE( - val, "\"őá…–🤮🚀¿ 한글 테스트 \\u0007\\u000b\""); + TEST_CHECK_JSON_VALUE(val, "\"őá…–🤮🚀¿ 한글 테스트 \\u0007\\u000b\""); sentry_value_decref(val); char zalgo[] = "z̴̢̈͜ä̴̺̟́ͅl̸̛̦͎̺͂̃̚͝g̷̦̲͊͋̄̌͝o̸͇̞̪͙̞͌̇̀̓̏͜"; val = sentry_value_new_string(zalgo); @@ -161,7 +176,7 @@ SENTRY_TEST(value_list) sentry_value_decref(val); val = sentry_value_new_list(); - for (uint32_t i = 1; i <= 10; i++) { + for (int32_t i = 1; i <= 10; i++) { sentry_value_append(val, sentry_value_new_int32(i)); } sentry__value_append_bounded(val, sentry_value_new_int32(1010), 5); @@ -547,6 +562,89 @@ SENTRY_TEST(value_collections_leak) sentry_value_decref(obj); } +SENTRY_TEST(value_set_by_null_key) +{ + sentry_value_t value = sentry_value_new_object(); + sentry_value_t payload = sentry_value_new_object(); + + TEST_CHECK(sentry_value_refcount(payload) == 1); + TEST_CHECK_INT_EQUAL(1, sentry_value_set_by_key(value, NULL, payload)); + TEST_CHECK(sentry_value_get_length(value) == 0); + + payload = sentry_value_new_object(); + TEST_CHECK(sentry_value_refcount(payload) == 1); + TEST_CHECK_INT_EQUAL(1, sentry_value_set_by_key_n(value, NULL, 0, payload)); + TEST_CHECK(sentry_value_get_length(value) == 0); + + payload = sentry_value_new_object(); + TEST_CHECK(sentry_value_refcount(payload) == 1); + TEST_CHECK_INT_EQUAL( + 1, sentry_value_set_by_key_n(value, NULL, 10, payload)); + TEST_CHECK(sentry_value_get_length(value) == 0); + + sentry_value_decref(value); +} + +SENTRY_TEST(value_remove_by_null_key) +{ + sentry_value_t value = sentry_value_new_object(); + + TEST_CHECK_INT_EQUAL(0, + sentry_value_set_by_key(value, "some_key", sentry_value_new_object())); + TEST_CHECK(sentry_value_get_length(value) == 1); + + TEST_CHECK_INT_EQUAL(1, sentry_value_remove_by_key(value, NULL)); + TEST_CHECK_INT_EQUAL(1, sentry_value_get_length(value)); + TEST_CHECK_INT_EQUAL(1, sentry_value_remove_by_key_n(value, NULL, 0)); + TEST_CHECK_INT_EQUAL(1, sentry_value_get_length(value)); + TEST_CHECK_INT_EQUAL(1, sentry_value_remove_by_key_n(value, NULL, 10)); + TEST_CHECK_INT_EQUAL(1, sentry_value_get_length(value)); + + sentry_value_decref(value); +} + +SENTRY_TEST(value_get_by_null_key) +{ + sentry_value_t value = sentry_value_new_object(); + + const char *some_key = "some_key"; + TEST_CHECK_INT_EQUAL( + 0, sentry_value_set_by_key(value, some_key, sentry_value_new_object())); + TEST_CHECK(sentry_value_get_length(value) == 1); + + sentry_value_t rv = sentry_value_get_by_key(value, NULL); + TEST_CHECK(sentry_value_is_null(rv)); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + + rv = sentry_value_get_by_key_owned(value, NULL); + TEST_CHECK(sentry_value_is_null(rv)); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + sentry_value_decref(rv); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + + rv = sentry_value_get_by_key_owned(value, some_key); + TEST_CHECK(!sentry_value_is_null(rv)); + TEST_CHECK_INT_EQUAL(2, sentry_value_refcount(rv)); + sentry_value_decref(rv); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + + // if `k_len` != any length of keys stored in the object this won't + // segfault because the `sentry_slice_t` equality check already fails due to + // the length-inequality and never reaches `memcmp()`. + TEST_CHECK(sentry_value_is_null(sentry_value_get_by_key_n(value, NULL, 0))); + // If `k_len' == any key-length, we'd segfault without a NULL-check. + TEST_CHECK(sentry_value_is_null( + sentry_value_get_by_key_n(value, NULL, strlen(some_key)))); + + rv = sentry_value_get_by_key_owned_n(value, NULL, strlen(some_key)); + TEST_CHECK(sentry_value_is_null(rv)); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + sentry_value_decref(rv); + TEST_CHECK_INT_EQUAL(1, sentry_value_refcount(rv)); + + sentry_value_decref(value); +} + SENTRY_TEST(value_set_stacktrace) { sentry_value_t exc @@ -564,3 +662,113 @@ SENTRY_TEST(value_set_stacktrace) sentry_value_decref(exc); } + +SENTRY_TEST(message_with_null_text_is_valid) +{ + sentry_value_t message_event = sentry_value_new_message_event( + SENTRY_LEVEL_WARNING, "some-logger", NULL); + + TEST_CHECK(!sentry_value_is_null(message_event)); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + message_event, "logger")), + "some-logger"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(message_event, "level")), + "warning"); + + sentry_value_decref(message_event); +} + +SENTRY_TEST(breadcrumb_without_type_or_message_still_valid) +{ + sentry_value_t breadcrumb = sentry_value_new_breadcrumb(NULL, NULL); + TEST_CHECK(!sentry_value_is_null(breadcrumb)); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(breadcrumb, "timestamp"))); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(breadcrumb, "type"))); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(breadcrumb, "message"))); + sentry_value_decref(breadcrumb); + + char *const test_type = "navigation"; + breadcrumb = sentry_value_new_breadcrumb(test_type, NULL); + TEST_CHECK(!sentry_value_is_null(breadcrumb)); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(breadcrumb, "timestamp"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(breadcrumb, "type")), + test_type); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(breadcrumb, "message"))); + sentry_value_decref(breadcrumb); + + char *const test_message = "a fork in the road, take it"; + breadcrumb = sentry_value_new_breadcrumb(NULL, test_message); + TEST_CHECK(!sentry_value_is_null(breadcrumb)); + TEST_CHECK(!sentry_value_is_null( + sentry_value_get_by_key(breadcrumb, "timestamp"))); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(breadcrumb, "type"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(breadcrumb, "message")), + test_message); + sentry_value_decref(breadcrumb); +} + +SENTRY_TEST(exception_without_type_or_value_still_valid) +{ + sentry_value_t exception = sentry_value_new_exception(NULL, NULL); + TEST_CHECK(!sentry_value_is_null(exception)); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(exception, "type"))); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(exception, "value"))); + sentry_value_decref(exception); + + char *const test_type = "EXC_BAD_ACCESS / KERN_INVALID_ADDRESS / 0x61"; + exception = sentry_value_new_exception(test_type, NULL); + TEST_CHECK(!sentry_value_is_null(exception)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(exception, "type")), + test_type); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(exception, "value"))); + sentry_value_decref(exception); + + char *const test_value + = "Fatal Error: EXC_BAD_ACCESS / KERN_INVALID_ADDRESS / 0x61"; + exception = sentry_value_new_exception(NULL, test_value); + TEST_CHECK(!sentry_value_is_null(exception)); + TEST_CHECK( + sentry_value_is_null(sentry_value_get_by_key(exception, "type"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(exception, "value")), + test_value); + sentry_value_decref(exception); +} + +SENTRY_TEST(thread_without_name_still_valid) +{ + sentry_value_t thread = sentry_value_new_thread(0xFF00FF00FF00FF00, NULL); + TEST_CHECK(!sentry_value_is_null(thread)); + TEST_CHECK(!sentry_value_is_null(sentry_value_get_by_key(thread, "id"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(thread, "id")), + "18374966859414961920"); + TEST_CHECK(sentry_value_is_null(sentry_value_get_by_key(thread, "name"))); + sentry_value_decref(thread); + + char *const test_name = "worker"; + thread = sentry_value_new_thread(0xAA00AA00AA00AA00, test_name); + TEST_CHECK(!sentry_value_is_null(thread)); + TEST_CHECK(!sentry_value_is_null(sentry_value_get_by_key(thread, "id"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(thread, "id")), + "12249977906276641280"); + TEST_CHECK(!sentry_value_is_null(sentry_value_get_by_key(thread, "name"))); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(thread, "name")), + test_name); + sentry_value_decref(thread); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index c60d465545..94b1cbca5f 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -12,7 +12,9 @@ XX(basic_http_request_preparation_for_transaction) XX(basic_spans) XX(basic_tracing_context) XX(basic_transaction) +XX(basic_write_envelope_to_file) XX(bgworker_flush) +XX(breadcrumb_without_type_or_message_still_valid) XX(build_id_parser) XX(check_version) XX(child_spans) @@ -29,7 +31,12 @@ XX(dsn_parsing_complete) XX(dsn_parsing_invalid) XX(dsn_store_url_with_path) XX(dsn_store_url_without_path) +XX(dsn_with_ending_forward_slash_will_be_cleaned) +XX(dsn_with_non_http_scheme_is_invalid) +XX(dsn_without_project_id_is_invalid) +XX(dsn_without_url_scheme_is_invalid) XX(empty_transport) +XX(exception_without_type_or_value_still_valid) XX(fuzz_json) XX(init_failure) XX(internal_uuid_api) @@ -37,6 +44,7 @@ XX(invalid_dsn) XX(invalid_proxy) XX(iso_time) XX(lazy_attachments) +XX(message_with_null_text_is_valid) XX(module_addr) XX(module_finder) XX(mpack_newlines) @@ -49,6 +57,8 @@ XX(page_allocator) XX(path_basics) XX(path_current_exe) XX(path_directory) +XX(path_from_str_n_wo_null_termination) +XX(path_from_str_null) XX(path_joining_unix) XX(path_joining_windows) XX(path_relative_filename) @@ -58,18 +68,34 @@ XX(recursive_paths) XX(sampling_before_send) XX(sampling_decision) XX(sampling_transaction) +XX(sentry__value_span_new_requires_unfinished_parent) XX(serialize_envelope) XX(session_basics) +XX(set_tag_allows_null_tag_and_value) +XX(set_tag_cuts_value_at_length_200) XX(slice) +XX(span_data) +XX(span_data_n) +XX(span_tagging) +XX(span_tagging_n) XX(spans_on_scope) XX(symbolizer) XX(task_queue) +XX(thread_without_name_still_valid) XX(transaction_name_backfill_on_finish) XX(transactions_skip_before_send) XX(transport_sampling_transactions) +XX(txn_data) +XX(txn_data_n) +XX(txn_name) +XX(txn_name_n) +XX(txn_tagging) +XX(txn_tagging_n) XX(uninitialized) XX(unsampled_spans) XX(unwinder) +XX(update_from_header_no_sampled_flag) +XX(update_from_header_null_ctx) XX(url_parsing_complete) XX(url_parsing_invalid) XX(url_parsing_partial) @@ -79,6 +105,7 @@ XX(value_bool) XX(value_collections_leak) XX(value_double) XX(value_freezing) +XX(value_get_by_null_key) XX(value_int32) XX(value_json_deeply_nested) XX(value_json_escaping) @@ -91,7 +118,11 @@ XX(value_null) XX(value_object) XX(value_object_merge) XX(value_object_merge_nested) +XX(value_remove_by_null_key) +XX(value_set_by_null_key) XX(value_set_stacktrace) XX(value_string) +XX(value_string_n) XX(value_unicode) XX(value_wrong_type) +XX(write_envelope_to_file_null) From daf2d1c9b7aebbc470f717c12133fdbbbb0f7cd7 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Fri, 28 Apr 2023 09:02:03 +0200 Subject: [PATCH 106/207] Allow setting sdk_name at runtime (#834) --- CHANGELOG.md | 1 + include/sentry.h | 32 ++++++++++++- src/backends/sentry_backend_crashpad.cpp | 3 +- src/sentry_options.c | 48 +++++++++++++++++++ src/sentry_options.h | 2 + src/sentry_scope.c | 11 ++++- src/sentry_transport.c | 5 +- src/sentry_transport.h | 2 +- src/sentry_utils.c | 21 ++++++--- src/sentry_utils.h | 6 ++- src/transports/sentry_transport_curl.c | 5 +- src/transports/sentry_transport_winhttp.c | 7 ++- tests/unit/CMakeLists.txt | 1 + tests/unit/test_envelopes.c | 8 ++-- tests/unit/test_options.c | 54 ++++++++++++++++++++++ tests/unit/test_utils.c | 56 ++++++++++++++++++++++- tests/unit/tests.inc | 8 ++++ 17 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 tests/unit/test_options.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 952f1c4977..cf7d681b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Features**: - Extend API with ptr/len-string interfaces. ([#827](https://github.com/getsentry/sentry-native/pull/827)) +- Allow setting sdk_name at runtime ([#834](https://github.com/getsentry/sentry-native/pull/834)) ## 0.6.1 diff --git a/include/sentry.h b/include/sentry.h index 32cecb63a2..17760e24cc 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1008,6 +1008,32 @@ SENTRY_API void sentry_options_set_transport_thread_name_n( SENTRY_API const char *sentry_options_get_transport_thread_name( const sentry_options_t *opts); +/* + * Configures the name of the sentry SDK. Returns 0 on success. + */ +SENTRY_API int sentry_options_set_sdk_name( + sentry_options_t *opts, const char *sdk_name); + +/* + * Configures the name of the sentry SDK. Returns 0 on success. + */ +SENTRY_API int sentry_options_set_sdk_name_n( + sentry_options_t *opts, const char *sdk_name, size_t sdk_name_len); + +/** + * Returns the configured sentry SDK name. Unless overwritten this defaults to + * SENTRY_SDK_NAME. + */ +SENTRY_API const char *sentry_options_get_sdk_name( + const sentry_options_t *opts); + +/** + * Returns the user agent. Unless overwritten this defaults to + * "SENTRY_SDK_NAME / SENTRY_SDK_VERSION". + */ +SENTRY_API const char *sentry_options_get_user_agent( + const sentry_options_t *opts); + /** * Enables or disables debug printing mode. */ @@ -1983,12 +2009,14 @@ SENTRY_EXPERIMENTAL_API int sentry_clear_crashed_last_run(void); SENTRY_EXPERIMENTAL_API const char *sentry_sdk_version(void); /** - * Sentry SDK name. + * Sentry SDK name set during build time. + * Deprecated: Please use sentry_options_get_sdk_name instead. */ SENTRY_EXPERIMENTAL_API const char *sentry_sdk_name(void); /** - * Sentry SDK User-Agent. + * Sentry SDK User-Agent set during build time. + * Deprecated: Please use sentry_options_get_user_agent instead. */ SENTRY_EXPERIMENTAL_API const char *sentry_sdk_user_agent(void); diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index c44672341e..381779296c 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -311,7 +311,8 @@ sentry__crashpad_backend_startup( data->db = crashpad::CrashReportDatabase::Initialize(database).release(); crashpad::CrashpadClient client; - char *minidump_url = sentry__dsn_get_minidump_url(options->dsn); + char *minidump_url + = sentry__dsn_get_minidump_url(options->dsn, options->user_agent); SENTRY_TRACEF("using minidump url \"%s\"", minidump_url); std::string url = minidump_url ? std::string(minidump_url) : std::string(); sentry_free(minidump_url); diff --git a/src/sentry_options.c b/src/sentry_options.c index 4814bfefd2..014ec491f4 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -36,6 +36,7 @@ sentry_options_new(void) if (!opts->environment) { opts->environment = sentry__string_clone("production"); } + sentry_options_set_sdk_name(opts, SENTRY_SDK_NAME); opts->max_breadcrumbs = SENTRY_BREADCRUMBS_MAX; opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN; opts->auto_session_tracking = true; @@ -84,6 +85,8 @@ sentry_options_free(sentry_options_t *opts) } sentry__dsn_decref(opts->dsn); sentry_free(opts->release); + sentry_free(opts->sdk_name); + sentry_free(opts->user_agent); sentry_free(opts->environment); sentry_free(opts->dist); sentry_free(opts->http_proxy); @@ -295,6 +298,51 @@ sentry_options_get_transport_thread_name(const sentry_options_t *opts) return opts->transport_thread_name; } +int +sentry_options_set_sdk_name(sentry_options_t *opts, const char *sdk_name) +{ + if (!opts || !sdk_name) { + return 1; + } + const size_t sdk_name_len = strlen(sdk_name); + return sentry_options_set_sdk_name_n(opts, sdk_name, sdk_name_len); +} + +int +sentry_options_set_sdk_name_n( + sentry_options_t *opts, const char *sdk_name, size_t sdk_name_len) +{ + if (!opts || !sdk_name) { + return 1; + } + + sentry_free(opts->sdk_name); + opts->sdk_name = sentry__string_clone_n(sdk_name, sdk_name_len); + + sentry_stringbuilder_t sb; + sentry__stringbuilder_init(&sb); + sentry__stringbuilder_append(&sb, opts->sdk_name); + sentry__stringbuilder_append(&sb, "/"); + sentry__stringbuilder_append(&sb, SENTRY_SDK_VERSION); + + sentry_free(opts->user_agent); + opts->user_agent = sentry__stringbuilder_into_string(&sb); + + return 0; +} + +const char * +sentry_options_get_sdk_name(const sentry_options_t *opts) +{ + return opts->sdk_name; +} + +const char * +sentry_options_get_user_agent(const sentry_options_t *opts) +{ + return opts->user_agent; +} + void sentry_options_set_debug(sentry_options_t *opts, int debug) { diff --git a/src/sentry_options.h b/src/sentry_options.h index c6b3c10dc8..060b17f708 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -38,6 +38,8 @@ typedef struct sentry_options_s { char *http_proxy; char *ca_certs; char *transport_thread_name; + char *sdk_name; + char *user_agent; sentry_path_t *database_path; sentry_path_t *handler_path; sentry_logger_t logger; diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 0201a673bb..a98a583ba3 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -29,8 +29,15 @@ get_client_sdk(void) { sentry_value_t client_sdk = sentry_value_new_object(); - sentry_value_t name = sentry_value_new_string(SENTRY_SDK_NAME); - sentry_value_set_by_key(client_sdk, "name", name); + SENTRY_WITH_OPTIONS (options) { + sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); + sentry_value_set_by_key(client_sdk, "name", sdk_name); + } + // in case the SDK is not initialized yet, fallback to build-time value + if (sentry_value_is_null(sentry_value_get_by_key(client_sdk, "name"))) { + sentry_value_t sdk_name = sentry_value_new_string(SENTRY_SDK_NAME); + sentry_value_set_by_key(client_sdk, "name", sdk_name); + } sentry_value_t version = sentry_value_new_string(SENTRY_SDK_VERSION); sentry_value_set_by_key(client_sdk, "version", version); diff --git a/src/sentry_transport.c b/src/sentry_transport.c index 4ff909e03b..ea62c56204 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -150,7 +150,8 @@ sentry_transport_free(sentry_transport_t *transport) sentry_prepared_http_request_t * sentry__prepare_http_request(sentry_envelope_t *envelope, - const sentry_dsn_t *dsn, const sentry_rate_limiter_t *rl) + const sentry_dsn_t *dsn, const sentry_rate_limiter_t *rl, + const char *user_agent) { if (!dsn || !dsn->is_valid) { return NULL; @@ -189,7 +190,7 @@ sentry__prepare_http_request(sentry_envelope_t *envelope, sentry_prepared_http_header_t *h; h = &req->headers[req->headers_len++]; h->key = "x-sentry-auth"; - h->value = sentry__dsn_get_auth_header(dsn); + h->value = sentry__dsn_get_auth_header(dsn, user_agent); h = &req->headers[req->headers_len++]; h->key = "content-type"; diff --git a/src/sentry_transport.h b/src/sentry_transport.h index f307881072..e29dc09f5b 100644 --- a/src/sentry_transport.h +++ b/src/sentry_transport.h @@ -82,7 +82,7 @@ typedef struct sentry_prepared_http_request_s { */ sentry_prepared_http_request_t *sentry__prepare_http_request( sentry_envelope_t *envelope, const sentry_dsn_t *dsn, - const sentry_rate_limiter_t *rl); + const sentry_rate_limiter_t *rl, const char *user_agent); /** * Free a previously allocated HTTP request. diff --git a/src/sentry_utils.c b/src/sentry_utils.c index bb80a98f0a..45f32081e5 100644 --- a/src/sentry_utils.c +++ b/src/sentry_utils.c @@ -312,7 +312,7 @@ sentry__dsn_decref(sentry_dsn_t *dsn) } char * -sentry__dsn_get_auth_header(const sentry_dsn_t *dsn) +sentry__dsn_get_auth_header(const sentry_dsn_t *dsn, const char *user_agent) { if (!dsn || !dsn->is_valid) { return NULL; @@ -321,8 +321,14 @@ sentry__dsn_get_auth_header(const sentry_dsn_t *dsn) sentry__stringbuilder_init(&sb); sentry__stringbuilder_append(&sb, "Sentry sentry_key="); sentry__stringbuilder_append(&sb, dsn->public_key); - sentry__stringbuilder_append( - &sb, ", sentry_version=7, sentry_client=" SENTRY_SDK_USER_AGENT); + sentry__stringbuilder_append(&sb, ", sentry_version=7"); + + sentry__stringbuilder_append(&sb, ", sentry_client="); + if (user_agent) { + sentry__stringbuilder_append(&sb, user_agent); + } else { + sentry__stringbuilder_append(&sb, SENTRY_SDK_USER_AGENT); + } return sentry__stringbuilder_into_string(&sb); } @@ -353,15 +359,16 @@ sentry__dsn_get_envelope_url(const sentry_dsn_t *dsn) } char * -sentry__dsn_get_minidump_url(const sentry_dsn_t *dsn) +sentry__dsn_get_minidump_url(const sentry_dsn_t *dsn, const char *user_agent) { - if (!dsn || !dsn->is_valid) { + if (!dsn || !dsn->is_valid || !user_agent) { return NULL; } sentry_stringbuilder_t sb; init_string_builder_for_url(&sb, dsn); - sentry__stringbuilder_append( - &sb, "/minidump/?sentry_client=" SENTRY_SDK_USER_AGENT "&sentry_key="); + sentry__stringbuilder_append(&sb, "/minidump/?sentry_client="); + sentry__stringbuilder_append(&sb, user_agent); + sentry__stringbuilder_append(&sb, "&sentry_key="); sentry__stringbuilder_append(&sb, dsn->public_key); return sentry__stringbuilder_into_string(&sb); } diff --git a/src/sentry_utils.h b/src/sentry_utils.h index 6f96eea647..b5b8e9d3e1 100644 --- a/src/sentry_utils.h +++ b/src/sentry_utils.h @@ -80,7 +80,8 @@ void sentry__dsn_decref(sentry_dsn_t *dsn); * described here: * https://docs.sentry.io/development/sdk-dev/overview/#authentication */ -char *sentry__dsn_get_auth_header(const sentry_dsn_t *dsn); +char *sentry__dsn_get_auth_header( + const sentry_dsn_t *dsn, const char *user_agent); /** * Returns the envelope endpoint url used for normal uploads as a newly @@ -92,7 +93,8 @@ char *sentry__dsn_get_envelope_url(const sentry_dsn_t *dsn); * Returns the minidump endpoint url used for uploads done by the out-of-process * crashpad backend as a newly allocated string. */ -char *sentry__dsn_get_minidump_url(const sentry_dsn_t *dsn); +char *sentry__dsn_get_minidump_url( + const sentry_dsn_t *dsn, const char *user_agent); /** * Returns the number of milliseconds since the unix epoch. diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index b9f52c8813..11f87e5d2f 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -16,6 +16,7 @@ typedef struct curl_transport_state_s { sentry_dsn_t *dsn; CURL *curl_handle; + char *user_agent; char *http_proxy; char *ca_certs; sentry_rate_limiter_t *ratelimiter; @@ -52,6 +53,7 @@ sentry__curl_bgworker_state_free(void *_state) sentry__dsn_decref(state->dsn); sentry__rate_limiter_free(state->ratelimiter); sentry_free(state->ca_certs); + sentry_free(state->user_agent); sentry_free(state->http_proxy); sentry_free(state); } @@ -100,6 +102,7 @@ sentry__curl_transport_start( state->dsn = sentry__dsn_incref(options->dsn); state->http_proxy = sentry__string_clone(options->http_proxy); + state->user_agent = sentry__string_clone(options->user_agent); state->ca_certs = sentry__string_clone(options->ca_certs); state->curl_handle = curl_easy_init(); state->debug = options->debug; @@ -168,7 +171,7 @@ sentry__curl_send_task(void *_envelope, void *_state) curl_bgworker_state_t *state = (curl_bgworker_state_t *)_state; sentry_prepared_http_request_t *req = sentry__prepare_http_request( - envelope, state->dsn, state->ratelimiter); + envelope, state->dsn, state->ratelimiter, state->user_agent); if (!req) { return; } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 93848c3452..ffb114eb9a 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -63,7 +63,7 @@ sentry__winhttp_transport_start( winhttp_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); state->dsn = sentry__dsn_incref(opts->dsn); - state->user_agent = sentry__string_to_wstr(SENTRY_SDK_USER_AGENT); + state->user_agent = sentry__string_to_wstr(opts->user_agent); state->debug = opts->debug; sentry__bgworker_setname(bgworker, opts->transport_thread_name); @@ -152,9 +152,11 @@ sentry__winhttp_send_task(void *_envelope, void *_state) uint64_t started = sentry__monotonic_time(); + char *user_agent = sentry__string_from_wstr(state->user_agent); sentry_prepared_http_request_t *req = sentry__prepare_http_request( - envelope, state->dsn, state->ratelimiter); + envelope, state->dsn, state->ratelimiter, user_agent); if (!req) { + sentry_free(user_agent); return; } @@ -283,6 +285,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) state->request = NULL; WinHttpCloseHandle(request); } + sentry_free(user_agent); sentry_free(url); sentry_free(headers); sentry__prepared_http_request_free(req); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 93acedf270..840c855619 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(sentry_test_unit test_logger.c test_modulefinder.c test_mpack.c + test_options.c test_path.c test_ratelimiter.c test_sampling.c diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index e0032706cd..7be59db4a5 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -29,7 +29,7 @@ SENTRY_TEST(basic_http_request_preparation_for_event) sentry__envelope_add_event(envelope, event); sentry_prepared_http_request_t *req - = sentry__prepare_http_request(envelope, dsn, NULL); + = sentry__prepare_http_request(envelope, dsn, NULL, NULL); TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); @@ -58,7 +58,7 @@ SENTRY_TEST(basic_http_request_preparation_for_transaction) sentry__envelope_add_transaction(envelope, transaction); sentry_prepared_http_request_t *req - = sentry__prepare_http_request(envelope, dsn, NULL); + = sentry__prepare_http_request(envelope, dsn, NULL, NULL); TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); @@ -91,7 +91,7 @@ SENTRY_TEST(basic_http_request_preparation_for_event_with_attachment) envelope, msg, sizeof(msg) - 1, "attachment"); sentry_prepared_http_request_t *req - = sentry__prepare_http_request(envelope, dsn, NULL); + = sentry__prepare_http_request(envelope, dsn, NULL, NULL); TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); @@ -120,7 +120,7 @@ SENTRY_TEST(basic_http_request_preparation_for_minidump) envelope, msg, sizeof(msg) - 1, "attachment"); sentry_prepared_http_request_t *req - = sentry__prepare_http_request(envelope, dsn, NULL); + = sentry__prepare_http_request(envelope, dsn, NULL, NULL); TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); diff --git a/tests/unit/test_options.c b/tests/unit/test_options.c new file mode 100644 index 0000000000..529b914725 --- /dev/null +++ b/tests/unit/test_options.c @@ -0,0 +1,54 @@ +#include "sentry_options.h" +#include "sentry_testsupport.h" + +SENTRY_TEST(options_sdk_name_defaults) +{ + sentry_options_t *options = sentry_options_new(); + // when nothing is set + + // then both sdk name and user agent should default to the build time + // directives + TEST_CHECK_STRING_EQUAL( + sentry_options_get_sdk_name(options), SENTRY_SDK_NAME); + TEST_CHECK_STRING_EQUAL( + sentry_options_get_user_agent(options), SENTRY_SDK_USER_AGENT); + + sentry_options_free(options); +} + +SENTRY_TEST(options_sdk_name_custom) +{ + sentry_options_t *options = sentry_options_new(); + + // when the sdk name is set to a custom string + const int result + = sentry_options_set_sdk_name(options, "sentry.native.android.flutter"); + + // both the sdk_name and user_agent should reflect this change + TEST_CHECK_INT_EQUAL(result, 0); + TEST_CHECK_STRING_EQUAL( + sentry_options_get_sdk_name(options), "sentry.native.android.flutter"); + + TEST_CHECK_STRING_EQUAL(sentry_options_get_user_agent(options), + "sentry.native.android.flutter/" SENTRY_SDK_VERSION); + + sentry_options_free(options); +} + +SENTRY_TEST(options_sdk_name_invalid) +{ + sentry_options_t *options = sentry_options_new(); + + // when the sdk name is set to an invalid value + const char *sdk_name = NULL; + const int result = sentry_options_set_sdk_name(options, sdk_name); + + // then the value should should be ignored + TEST_CHECK_INT_EQUAL(result, 1); + TEST_CHECK_STRING_EQUAL( + sentry_options_get_sdk_name(options), SENTRY_SDK_NAME); + TEST_CHECK_STRING_EQUAL( + sentry_options_get_user_agent(options), SENTRY_SDK_USER_AGENT); + + sentry_options_free(options); +} diff --git a/tests/unit/test_utils.c b/tests/unit/test_utils.c index 57f1ffb4ce..2323034154 100644 --- a/tests/unit/test_utils.c +++ b/tests/unit/test_utils.c @@ -152,7 +152,7 @@ SENTRY_TEST(dsn_store_url_with_path) TEST_CHECK_STRING_EQUAL( url, "http://example.com:80/foo/bar/api/42/envelope/"); sentry_free(url); - url = sentry__dsn_get_minidump_url(dsn); + url = sentry__dsn_get_minidump_url(dsn, SENTRY_SDK_USER_AGENT); TEST_CHECK_STRING_EQUAL(url, "http://example.com:80/foo/bar/api/42/minidump/" "?sentry_client=" SENTRY_SDK_USER_AGENT "&sentry_key=username"); @@ -168,7 +168,7 @@ SENTRY_TEST(dsn_store_url_without_path) url = sentry__dsn_get_envelope_url(dsn); TEST_CHECK_STRING_EQUAL(url, "http://example.com:80/api/42/envelope/"); sentry_free(url); - url = sentry__dsn_get_minidump_url(dsn); + url = sentry__dsn_get_minidump_url(dsn, SENTRY_SDK_USER_AGENT); TEST_CHECK_STRING_EQUAL(url, "http://example.com:80/api/42/minidump/" "?sentry_client=" SENTRY_SDK_USER_AGENT "&sentry_key=username"); @@ -176,6 +176,18 @@ SENTRY_TEST(dsn_store_url_without_path) sentry__dsn_decref(dsn); } +SENTRY_TEST(dsn_store_url_custom_agent) +{ + sentry_dsn_t *dsn + = sentry__dsn_new("http://username:password@example.com/42?x=y#z"); + char *url = sentry__dsn_get_minidump_url(dsn, "custom_user_agent"); + TEST_CHECK_STRING_EQUAL(url, + "http://example.com:80/api/42/minidump/" + "?sentry_client=custom_user_agent&sentry_key=username"); + sentry_free(url); + sentry__dsn_decref(dsn); +} + SENTRY_TEST(page_allocator) { #ifndef SENTRY_PLATFORM_UNIX @@ -282,3 +294,43 @@ SENTRY_TEST(dsn_with_ending_forward_slash_will_be_cleaned) sentry__dsn_decref(dsn); } + +SENTRY_TEST(dsn_auth_header_no_user_agent) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://key@sentry.io/42"); + char *auth_header = sentry__dsn_get_auth_header(dsn, NULL); + TEST_CHECK_STRING_EQUAL(auth_header, + "Sentry sentry_key=key, sentry_version=7, " + "sentry_client=" SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION); + + sentry_free(auth_header); + sentry__dsn_decref(dsn); +} + +SENTRY_TEST(dsn_auth_header_custom_user_agent) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://key@sentry.io/42"); + char *auth_header = sentry__dsn_get_auth_header(dsn, "user_agent"); + TEST_CHECK_STRING_EQUAL(auth_header, + "Sentry sentry_key=key, sentry_version=7, " + "sentry_client=user_agent"); + + sentry_free(auth_header); + sentry__dsn_decref(dsn); +} + +SENTRY_TEST(dsn_auth_header_null_dsn) +{ + char *auth_header = sentry__dsn_get_auth_header(NULL, NULL); + TEST_CHECK(!auth_header); +} + +SENTRY_TEST(dsn_auth_header_invalid_dsn) +{ + sentry_dsn_t *dsn = sentry__dsn_new("whatever"); + char *auth_header = sentry__dsn_get_auth_header(dsn, NULL); + TEST_CHECK(!auth_header); + + sentry_free(auth_header); + sentry__dsn_decref(dsn); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 94b1cbca5f..1770edfc11 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -27,8 +27,13 @@ XX(custom_logger) XX(discarding_before_send) XX(distributed_headers) XX(drop_unfinished_spans) +XX(dsn_auth_header_custom_user_agent) +XX(dsn_auth_header_invalid_dsn) +XX(dsn_auth_header_no_user_agent) +XX(dsn_auth_header_null_dsn) XX(dsn_parsing_complete) XX(dsn_parsing_invalid) +XX(dsn_store_url_custom_agent) XX(dsn_store_url_with_path) XX(dsn_store_url_without_path) XX(dsn_with_ending_forward_slash_will_be_cleaned) @@ -51,6 +56,9 @@ XX(mpack_newlines) XX(mpack_removed_tags) XX(multiple_inits) XX(multiple_transactions) +XX(options_sdk_name_custom) +XX(options_sdk_name_defaults) +XX(options_sdk_name_invalid) XX(os) XX(overflow_spans) XX(page_allocator) From b1f034bf06f6714771a9d8a7e2d24d64afb7963d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 2 May 2023 10:15:00 +0000 Subject: [PATCH 107/207] release: 0.6.2 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7d681b22..c3e3c2fdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.2 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 17760e24cc..e89d96a212 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.1" +#define SENTRY_SDK_VERSION "0.6.2" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 6bae5f60d6..ee64989080 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.1", + "version": "0.6.2", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.1"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.2"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 400b639bdf..f5aa7aef7c 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -21,7 +21,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.1" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.2" ) From 1fb06c277a7e02d373a1f40a6190f9f56a3fd0a0 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 4 May 2023 19:53:44 +0200 Subject: [PATCH 108/207] chore: update crashpad 2023-05-03 (#837) --- CHANGELOG.md | 6 ++++++ external/crashpad | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e3c2fdaf..19601e70ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +# Unreleased + +**Internal**: + +- Updated Crashpad backend to 2023-05-03. ([#837](https://github.com/getsentry/sentry-native/pull/837), [crashpad#82](https://github.com/getsentry/crashpad/pull/82)) + ## 0.6.2 **Features**: diff --git a/external/crashpad b/external/crashpad index 2237d97ee2..1904ae1436 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 2237d97ee2c38c930c07001e660be57324f69a37 +Subproject commit 1904ae14360bb3115e098950733a4fd3da93b25a From d30e96ddf6ab3f5276044dcf79cab01fcaa96a95 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 8 May 2023 17:27:39 +0200 Subject: [PATCH 109/207] chore: update breakpad 2023-05-03 (#836) --- CHANGELOG.md | 3 ++- external/CMakeLists.txt | 2 ++ external/breakpad | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19601e70ed..bc17e38b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog -# Unreleased +## Unreleased **Internal**: +- Updated Breakpad backend to 2023-05-03. ([#836](https://github.com/getsentry/sentry-native/pull/836), [breakpad#35](https://github.com/getsentry/breakpad/pull/35)) - Updated Crashpad backend to 2023-05-03. ([#837](https://github.com/getsentry/sentry-native/pull/837), [crashpad#82](https://github.com/getsentry/crashpad/pull/82)) ## 0.6.2 diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index fb2917f4e9..9f3a298699 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -40,6 +40,8 @@ set(BREAKPAD_SOURCES_COMMON_WINDOWS ) set(BREAKPAD_SOURCES_COMMON_APPLE + breakpad/src/common/mac/arch_utilities.cc + breakpad/src/common/mac/arch_utilities.h breakpad/src/common/mac/file_id.cc breakpad/src/common/mac/file_id.h breakpad/src/common/mac/macho_id.cc diff --git a/external/breakpad b/external/breakpad index 0878ec17ca..250c6bc334 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 0878ec17caa6f07e0a70f2c4e930ef5f887d147f +Subproject commit 250c6bc334f2783efc2d8c029b3873cf109e0e49 From 601149883a4cae9bef92383cb5b0a58c9f5517ab Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 9 May 2023 16:23:09 +0200 Subject: [PATCH 110/207] feat: disable PC adjustment in the backend for libunwindstack (#839) --- CHANGELOG.md | 4 ++++ src/backends/sentry_backend_inproc.c | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc17e38b3a..08340275b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Features**: + +- disable PC adjustment in the backend for libunwindstack ([#839](https://github.com/getsentry/sentry-native/pull/839)) + **Internal**: - Updated Breakpad backend to 2023-05-03. ([#836](https://github.com/getsentry/sentry-native/pull/836), [breakpad#35](https://github.com/getsentry/breakpad/pull/35)) diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 32887a0334..4d1b9e4d5e 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -486,6 +486,14 @@ make_signal_event( sentry_value_t registers = sentry__registers_from_uctx(uctx); sentry_value_set_by_key(stacktrace, "registers", registers); +#ifdef SENTRY_WITH_UNWINDER_LIBUNWINDSTACK + // libunwindstack already adjusts the PC according to `GetPcAdjustment()` + // https://github.com/getsentry/libunwindstack-ndk/blob/1929f7b601797fc8b2cac092d563b31d01d46a76/Regs.cpp#L187 + // so there is no need to adjust the PC in the backend processing. + sentry_value_set_by_key(stacktrace, "instruction_addr_adjustment", + sentry_value_new_string("none")); +#endif + sentry_value_set_by_key(exc, "stacktrace", stacktrace); sentry_event_add_exception(event, exc); From 6c76f5845491e6b79711a34715523fc2b800f614 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 16 May 2023 07:52:53 +0200 Subject: [PATCH 111/207] feat: inspect/enrich event in crashpad backend (#843) --- CHANGELOG.md | 3 +- src/backends/sentry_backend_crashpad.cpp | 108 +++++++++++++---------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08340275b7..bb0c31af4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ **Features**: -- disable PC adjustment in the backend for libunwindstack ([#839](https://github.com/getsentry/sentry-native/pull/839)) +- Disable PC adjustment in the backend for libunwindstack ([#839](https://github.com/getsentry/sentry-native/pull/839)) +- Crashpad backend allows inspection and enrichment of the crash event in the on_crash/before_send hooks ([#843](https://github.com/getsentry/sentry-native/pull/843)) **Internal**: diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 381779296c..ddb93f2caa 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -78,12 +78,13 @@ typedef struct { sentry_path_t *breadcrumb1_path; sentry_path_t *breadcrumb2_path; size_t num_breadcrumbs; + sentry_value_t crash_event; } crashpad_state_t; static void -sentry__crashpad_backend_user_consent_changed(sentry_backend_t *backend) +crashpad_backend_user_consent_changed(sentry_backend_t *backend) { - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); if (!data->db || !data->db->GetSettings()) { return; } @@ -91,18 +92,23 @@ sentry__crashpad_backend_user_consent_changed(sentry_backend_t *backend) } static void -sentry__crashpad_backend_flush_scope( +crashpad_backend_flush_scope( sentry_backend_t *backend, const sentry_options_t *options) { - const crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); if (!data->event_path) { return; } // This here is an empty object that we copy the scope into. // Even though the API is specific to `event`, an `event` has a few default - // properties that we do not want here. - sentry_value_t event = sentry_value_new_object(); + // properties that we do not want here. But in case of a crash we use the + // crash-event filled in the crash-handler and on_crash/before_send + // respectively. + sentry_value_t event = sentry_value_is_null(data->crash_event) + ? sentry_value_new_object() + : data->crash_event; + SENTRY_WITH_SCOPE (scope) { // we want the scope without any modules or breadcrumbs sentry__scope_apply_to_event(scope, options, event, SENTRY_SCOPE_NONE); @@ -138,9 +144,11 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) SENTRY_DEBUG("flushing session and queue before crashpad handler"); bool should_dump = true; - sentry_value_t event = sentry_value_new_event(); SENTRY_WITH_OPTIONS (options) { + auto *data = static_cast(options->backend->data); + sentry_value_decref(data->crash_event); + data->crash_event = sentry_value_new_event(); if (options->on_crash_func) { sentry_ucontext_t uctx; @@ -153,17 +161,18 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) # endif SENTRY_TRACE("invoking `on_crash` hook"); - event - = options->on_crash_func(&uctx, event, options->on_crash_data); + data->crash_event = options->on_crash_func( + &uctx, data->crash_event, options->on_crash_data); } else if (options->before_send_func) { SENTRY_TRACE("invoking `before_send` hook"); - event = options->before_send_func( - event, nullptr, options->before_send_data); + data->crash_event = options->before_send_func( + data->crash_event, nullptr, options->before_send_data); } - should_dump = !sentry_value_is_null(event); - sentry_value_decref(event); + should_dump = !sentry_value_is_null(data->crash_event); if (should_dump) { + crashpad_backend_flush_scope(options->backend, options); + sentry__write_crash_marker(options); sentry__record_errors_on_current_session(1); @@ -231,10 +240,10 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) #endif static int -sentry__crashpad_backend_startup( +crashpad_backend_startup( sentry_backend_t *backend, const sentry_options_t *options) { - sentry_path_t *owned_handler_path = NULL; + sentry_path_t *owned_handler_path = nullptr; sentry_path_t *handler_path = options->handler_path; if (!handler_path) { sentry_path_t *current_exe = sentry__path_current_exe(); @@ -272,7 +281,7 @@ sentry__crashpad_backend_startup( "\"%" SENTRY_PATH_PRI "\"", absolute_handler_path->path); sentry_path_t *current_run_folder = options->run->run_path; - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); base::FilePath database(options->database_path->path); base::FilePath handler(absolute_handler_path->path); @@ -283,7 +292,7 @@ sentry__crashpad_backend_startup( // register attachments for (sentry_attachment_t *attachment = options->attachments; attachment; attachment = attachment->next) { - attachments.push_back(base::FilePath(attachment->path->path)); + attachments.emplace_back(attachment->path->path); } // and add the serialized event, and two rotating breadcrumb files @@ -299,12 +308,12 @@ sentry__crashpad_backend_startup( sentry__path_touch(data->breadcrumb1_path); sentry__path_touch(data->breadcrumb2_path); - attachments.push_back(base::FilePath(data->event_path->path)); - attachments.push_back(base::FilePath(data->breadcrumb1_path->path)); - attachments.push_back(base::FilePath(data->breadcrumb2_path->path)); + attachments.insert(attachments.end(), + { base::FilePath(data->event_path->path), + base::FilePath(data->breadcrumb1_path->path), + base::FilePath(data->breadcrumb2_path->path) }); - std::vector arguments; - arguments.push_back("--no-rate-limit"); + std::vector arguments { "--no-rate-limit" }; // Initialize database first, flushing the consent later on as part of // `sentry_init` will persist the upload flag. @@ -395,7 +404,7 @@ sentry__crashpad_backend_startup( } static void -sentry__crashpad_backend_shutdown(sentry_backend_t *backend) +crashpad_backend_shutdown(sentry_backend_t *backend) { #ifdef SENTRY_PLATFORM_LINUX // restore signal handlers to their default state @@ -406,7 +415,7 @@ sentry__crashpad_backend_shutdown(sentry_backend_t *backend) } #endif - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); delete data->db; data->db = nullptr; @@ -419,10 +428,10 @@ sentry__crashpad_backend_shutdown(sentry_backend_t *backend) } static void -sentry__crashpad_backend_add_breadcrumb(sentry_backend_t *backend, +crashpad_backend_add_breadcrumb(sentry_backend_t *backend, sentry_value_t breadcrumb, const sentry_options_t *options) { - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); size_t max_breadcrumbs = options->max_breadcrumbs; if (!max_breadcrumbs) { @@ -457,17 +466,18 @@ sentry__crashpad_backend_add_breadcrumb(sentry_backend_t *backend, } static void -sentry__crashpad_backend_free(sentry_backend_t *backend) +crashpad_backend_free(sentry_backend_t *backend) { - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); sentry__path_free(data->event_path); sentry__path_free(data->breadcrumb1_path); sentry__path_free(data->breadcrumb2_path); + sentry_value_decref(data->crash_event); sentry_free(data); } static void -sentry__crashpad_backend_except( +crashpad_backend_except( sentry_backend_t *UNUSED(backend), const sentry_ucontext_t *context) { #ifdef SENTRY_PLATFORM_WINDOWS @@ -486,7 +496,7 @@ report_crash_time( { // we do a `+ 1` here, because crashpad timestamps are second resolution, // but our sessions are ms resolution. at least in our integration tests, we - // can have a session that starts at, eg. `0.471`, whereas the crashpad + // can have a session that starts at, e.g. `0.471`, whereas the crashpad // report will be `0`, which would mean our heuristic does not trigger due // to rounding. uint64_t time = ((uint64_t)report.creation_time + 1) * 1000; @@ -496,9 +506,9 @@ report_crash_time( } static uint64_t -sentry__crashpad_backend_last_crash(sentry_backend_t *backend) +crashpad_backend_last_crash(sentry_backend_t *backend) { - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); uint64_t crash_time = 0; @@ -514,9 +524,9 @@ sentry__crashpad_backend_last_crash(sentry_backend_t *backend) } static void -sentry__crashpad_backend_prune_database(sentry_backend_t *backend) +crashpad_backend_prune_database(sentry_backend_t *backend) { - crashpad_state_t *data = (crashpad_state_t *)backend->data; + auto *data = static_cast(backend->data); // We want to eagerly clean up reports older than 2 days, and limit the // complete database to a maximum of 8M. That might still be a lot for @@ -532,29 +542,29 @@ sentry__crashpad_backend_prune_database(sentry_backend_t *backend) sentry_backend_t * sentry__backend_new(void) { - sentry_backend_t *backend = SENTRY_MAKE(sentry_backend_t); + auto *backend = SENTRY_MAKE(sentry_backend_t); if (!backend) { - return NULL; + return nullptr; } memset(backend, 0, sizeof(sentry_backend_t)); - crashpad_state_t *data = SENTRY_MAKE(crashpad_state_t); + auto *data = SENTRY_MAKE(crashpad_state_t); if (!data) { sentry_free(backend); - return NULL; + return nullptr; } memset(data, 0, sizeof(crashpad_state_t)); - - backend->startup_func = sentry__crashpad_backend_startup; - backend->shutdown_func = sentry__crashpad_backend_shutdown; - backend->except_func = sentry__crashpad_backend_except; - backend->free_func = sentry__crashpad_backend_free; - backend->flush_scope_func = sentry__crashpad_backend_flush_scope; - backend->add_breadcrumb_func = sentry__crashpad_backend_add_breadcrumb; - backend->user_consent_changed_func - = sentry__crashpad_backend_user_consent_changed; - backend->get_last_crash_func = sentry__crashpad_backend_last_crash; - backend->prune_database_func = sentry__crashpad_backend_prune_database; + data->crash_event = sentry_value_new_null(); + + backend->startup_func = crashpad_backend_startup; + backend->shutdown_func = crashpad_backend_shutdown; + backend->except_func = crashpad_backend_except; + backend->free_func = crashpad_backend_free; + backend->flush_scope_func = crashpad_backend_flush_scope; + backend->add_breadcrumb_func = crashpad_backend_add_breadcrumb; + backend->user_consent_changed_func = crashpad_backend_user_consent_changed; + backend->get_last_crash_func = crashpad_backend_last_crash; + backend->prune_database_func = crashpad_backend_prune_database; backend->data = data; backend->can_capture_after_shutdown = true; From ad3c98809c228f4d35ac570e7a904059625b540e Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Sat, 27 May 2023 09:05:23 +0200 Subject: [PATCH 112/207] feat: crashpad handler http-proxy support (#847) --- CHANGELOG.md | 1 + external/crashpad | 2 +- src/backends/sentry_backend_crashpad.cpp | 22 ++++++++++++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0c31af4a..f43a8014c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Disable PC adjustment in the backend for libunwindstack ([#839](https://github.com/getsentry/sentry-native/pull/839)) - Crashpad backend allows inspection and enrichment of the crash event in the on_crash/before_send hooks ([#843](https://github.com/getsentry/sentry-native/pull/843)) +- Add http-proxy support to the `crashpad_handler` ([#847](https://github.com/getsentry/sentry-native/pull/847), [crashpad#86](https://github.com/getsentry/crashpad/pull/86)) **Internal**: diff --git a/external/crashpad b/external/crashpad index 1904ae1436..7041349732 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 1904ae14360bb3115e098950733a4fd3da93b25a +Subproject commit 7041349732770d0902e156deaba6e86bfdee1a0a diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index ddb93f2caa..b3bc194400 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -319,15 +319,25 @@ crashpad_backend_startup( // `sentry_init` will persist the upload flag. data->db = crashpad::CrashReportDatabase::Initialize(database).release(); + bool success; crashpad::CrashpadClient client; char *minidump_url = sentry__dsn_get_minidump_url(options->dsn, options->user_agent); - SENTRY_TRACEF("using minidump url \"%s\"", minidump_url); - std::string url = minidump_url ? std::string(minidump_url) : std::string(); - sentry_free(minidump_url); - bool success = client.StartHandler(handler, database, database, url, - annotations, arguments, /* restartable */ true, - /* asynchronous_start */ false, attachments); + if (minidump_url) { + SENTRY_TRACEF("using minidump URL \"%s\"", minidump_url); + success = client.StartHandler(handler, database, database, minidump_url, + options->http_proxy ? options->http_proxy : "", annotations, + arguments, + /* restartable */ true, + /* asynchronous_start */ false, attachments); + sentry_free(minidump_url); + } else { + SENTRY_WARN( + "failed to construct minidump URL (check DSN or user-agent)"); + delete data->db; + data->db = nullptr; + return 1; + } #ifdef CRASHPAD_WER_ENABLED sentry_path_t *handler_dir = sentry__path_dir(absolute_handler_path); From 58054bcccfe9d66d1490847356193a9305fa163d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 30 May 2023 08:28:21 +0000 Subject: [PATCH 113/207] release: 0.6.3 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f43a8014c0..a3f3401f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.3 **Features**: diff --git a/include/sentry.h b/include/sentry.h index e89d96a212..8eaf0b7fe2 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.2" +#define SENTRY_SDK_VERSION "0.6.3" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index ee64989080..982dfde8b6 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.2", + "version": "0.6.3", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.2"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.3"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index f5aa7aef7c..33c716aa34 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -21,7 +21,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.2" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.3" ) From 81572fe5304b7fabafb5a840f2f28fdd9843c451 Mon Sep 17 00:00:00 2001 From: xyz1001 Date: Thu, 15 Jun 2023 23:38:12 +0800 Subject: [PATCH 114/207] Fix compiler error with msvc on non-unicode system (#846) --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f3401f80..98c31d9400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ - Updated Breakpad backend to 2023-05-03. ([#836](https://github.com/getsentry/sentry-native/pull/836), [breakpad#35](https://github.com/getsentry/breakpad/pull/35)) - Updated Crashpad backend to 2023-05-03. ([#837](https://github.com/getsentry/sentry-native/pull/837), [crashpad#82](https://github.com/getsentry/crashpad/pull/82)) +**Fixes**: + +- Fix MSVC compiler error with on non-Unicode systems ([#846](https://github.com/getsentry/sentry-native/pull/846), [crashpad#85](https://github.com/getsentry/crashpad/pull/85)) + ## 0.6.2 **Features**: diff --git a/CMakeLists.txt b/CMakeLists.txt index aeea09f81e..3ec6778999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,9 @@ endif() if(MSVC) option(SENTRY_BUILD_RUNTIMESTATIC "Build sentry-native with static runtime" OFF) + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") endif() if(LINUX) From 302c9f91ccab23d40f89997277ec08b1730bed7b Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 15 Jun 2023 17:40:20 +0200 Subject: [PATCH 115/207] crashpad_handler: log body if minidump endpoint response is not OK (#851) --- CHANGELOG.md | 6 ++++++ external/crashpad | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c31d9400..d27f7bd314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Features**: + +- crashpad_handler: log `body` if minidump endpoint response is not `OK` ([#851](https://github.com/getsentry/sentry-native/pull/851), [crashpad#87](https://github.com/getsentry/crashpad/pull/87)) + ## 0.6.3 **Features**: diff --git a/external/crashpad b/external/crashpad index 7041349732..432ff49ecc 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 7041349732770d0902e156deaba6e86bfdee1a0a +Subproject commit 432ff49ecccc1cdebf1a7646007bb0594ac3481f From 8ce663987c70d030af2d8f96df30504459181074 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 22 Jun 2023 12:51:47 +0200 Subject: [PATCH 116/207] chore: add top-level install directories to gitignore (#855) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f3c4c8a7cd..8b197770eb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,9 @@ CMakeSettings.json .idea -# CMake builds +# CMake builds & install /*build* +/*install* # Run files .sentry-native From adb2689e312c52d04bd37d4dedee58b25763700e Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Fri, 23 Jun 2023 12:20:50 +0200 Subject: [PATCH 117/207] fix: use default level FATAL for crash events in all backends (#852) --------- Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 15 +++++++++++---- src/backends/sentry_backend_breakpad.cpp | 5 ++--- src/backends/sentry_backend_crashpad.cpp | 13 ++++++++++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d27f7bd314..c3e43821f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,21 @@ ## Unreleased +**Fixes**: + +- Crash events are initialized with level `FATAL` ([#852](https://github.com/getsentry/sentry-native/pull/852)) +- Fix MSVC compiler error with on non-Unicode systems ([#846](https://github.com/getsentry/sentry-native/pull/846), [crashpad#85](https://github.com/getsentry/crashpad/pull/85)) + **Features**: - crashpad_handler: log `body` if minidump endpoint response is not `OK` ([#851](https://github.com/getsentry/sentry-native/pull/851), [crashpad#87](https://github.com/getsentry/crashpad/pull/87)) +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@xyz1001](https://github.com/xyz1001) + ## 0.6.3 **Features**: @@ -19,10 +30,6 @@ - Updated Breakpad backend to 2023-05-03. ([#836](https://github.com/getsentry/sentry-native/pull/836), [breakpad#35](https://github.com/getsentry/breakpad/pull/35)) - Updated Crashpad backend to 2023-05-03. ([#837](https://github.com/getsentry/sentry-native/pull/837), [crashpad#82](https://github.com/getsentry/crashpad/pull/82)) -**Fixes**: - -- Fix MSVC compiler error with on non-Unicode systems ([#846](https://github.com/getsentry/sentry-native/pull/846), [crashpad#85](https://github.com/getsentry/crashpad/pull/85)) - ## 0.6.2 **Features**: diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index 1c22e43e95..40dc6a189d 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -92,6 +92,8 @@ sentry__breakpad_backend_callback( dump_path = sentry__path_new(descriptor.path()); #endif sentry_value_t event = sentry_value_new_event(); + sentry_value_set_by_key( + event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); SENTRY_WITH_OPTIONS (options) { sentry__write_crash_marker(options); @@ -116,9 +118,6 @@ sentry__breakpad_backend_callback( if (should_handle) { sentry_envelope_t *envelope = sentry__prepare_event( options, event, nullptr, !options->on_crash_func); - // the event we just prepared is empty, - // so no error is recorded for it - sentry__record_errors_on_current_session(1); sentry_session_t *session = sentry__end_current_session_with_status( SENTRY_SESSION_STATUS_CRASHED); sentry__envelope_add_session(envelope, session); diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index b3bc194400..7a0a7d0d66 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -105,9 +105,14 @@ crashpad_backend_flush_scope( // properties that we do not want here. But in case of a crash we use the // crash-event filled in the crash-handler and on_crash/before_send // respectively. - sentry_value_t event = sentry_value_is_null(data->crash_event) - ? sentry_value_new_object() - : data->crash_event; + sentry_value_t event = data->crash_event; + if (sentry_value_is_null(event)) { + event = sentry_value_new_object(); + // FIXME: This should be handled in the FirstChanceHandler but that does + // not exist for macOS just yet. + sentry_value_set_by_key( + event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL)); + } SENTRY_WITH_SCOPE (scope) { // we want the scope without any modules or breadcrumbs @@ -149,6 +154,8 @@ sentry__crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) auto *data = static_cast(options->backend->data); sentry_value_decref(data->crash_event); data->crash_event = sentry_value_new_event(); + sentry_value_set_by_key(data->crash_event, "level", + sentry__value_new_level(SENTRY_LEVEL_FATAL)); if (options->on_crash_func) { sentry_ucontext_t uctx; From c08cdee88bdfc69021d9b809968d7f086053d1dc Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 23 Jun 2023 10:22:39 +0000 Subject: [PATCH 118/207] release: 0.6.4 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e43821f4..ebc2ccb018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.4 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 8eaf0b7fe2..61b363f189 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.3" +#define SENTRY_SDK_VERSION "0.6.4" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 982dfde8b6..6622c788f9 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,9 +55,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.3", + "version": "0.6.4", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.3"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.4"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 33c716aa34..c304f84417 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -21,7 +21,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.3" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.4" ) From 1a7184ba4b7df84a0acdd0f3bf5d136abb4d16ed Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 27 Jun 2023 13:12:33 +0200 Subject: [PATCH 119/207] test: assert crash-level fatal in integration tests (#856) --- .github/workflows/ci.yml | 2 +- tests/assertions.py | 75 ++++++++++++++++++++++++++++---- tests/requirements.txt | 5 ++- tests/test_integration_http.py | 16 ++++--- tests/test_integration_stdout.py | 25 ++++++----- 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5d03e81af..13c748df83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: submodules: recursive - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' cache: 'pip' - name: Installing Linux Dependencies diff --git a/tests/assertions.py b/tests/assertions.py index 6622c788f9..71a7ba48f7 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -1,9 +1,12 @@ -import datetime import email import gzip import platform import re import sys +from dataclasses import dataclass +from datetime import datetime + +import msgpack from .conditions import is_android @@ -161,7 +164,7 @@ def assert_minidump(envelope): assert minidump.payload.bytes.startswith(b"MDMP") -def assert_timestamp(ts, now=datetime.datetime.utcnow()): +def assert_timestamp(ts, now=datetime.utcnow()): assert ts[:11] == now.isoformat()[:11] @@ -176,6 +179,14 @@ def assert_event(envelope): assert_timestamp(event["timestamp"]) +def assert_breakpad_crash(envelope): + event = envelope.get_event() + expected = { + "level": "fatal", + } + assert_matches(event, expected) + + def assert_exception(envelope): event = envelope.get_event() exception = { @@ -186,7 +197,7 @@ def assert_exception(envelope): assert_timestamp(event["timestamp"]) -def assert_crash(envelope): +def assert_inproc_crash(envelope): event = envelope.get_event() assert_matches(event, {"level": "fatal"}) # depending on the unwinder, we currently don’t get any stack frames from @@ -213,16 +224,62 @@ def assert_no_before_send(envelope): assert ("adapted_by", "before_send") not in event.items() +@dataclass(frozen=True) +class CrashpadAttachments: + event: dict + breadcrumb1: list + breadcrumb2: list + + +def _unpack_breadcrumbs(payload): + unpacker = msgpack.Unpacker() + unpacker.feed(payload) + return [unpacked for unpacked in unpacker] + + +def _load_crashpad_attachments(msg): + event = {} + breadcrumb1 = [] + breadcrumb2 = [] + for part in msg.walk(): + match part.get_filename(): + case "__sentry-event": + event = msgpack.unpackb(part.get_payload(decode=True)) + case "__sentry-breadcrumb1": + breadcrumb1 = _unpack_breadcrumbs(part.get_payload(decode=True)) + case "__sentry-breadcrumb2": + breadcrumb2 = _unpack_breadcrumbs(part.get_payload(decode=True)) + + return CrashpadAttachments(event, breadcrumb1, breadcrumb2) + + +def is_valid_timestamp(timestamp): + try: + datetime.fromisoformat(timestamp) + return True + except ValueError: + return False + + +def _validate_breadcrumb_seq(seq, breadcrumb_func): + for i in seq: + breadcrumb = breadcrumb_func(i) + assert breadcrumb["message"] == str(i) + assert is_valid_timestamp(breadcrumb["timestamp"]) + + def assert_crashpad_upload(req): multipart = gzip.decompress(req.get_data()) msg = email.message_from_bytes(bytes(str(req.headers), encoding="utf8") + multipart) - files = [part.get_filename() for part in msg.walk()] + attachments = _load_crashpad_attachments(msg) + + if len(attachments.breadcrumb1) > 3: + _validate_breadcrumb_seq(range(97), lambda i: attachments.breadcrumb1[3 + i]) + _validate_breadcrumb_seq( + range(97, 101), lambda i: attachments.breadcrumb2[i - 97] + ) - # TODO: - # Actually assert that we get a correct event/breadcrumbs payload - assert "__sentry-breadcrumb1" in files - assert "__sentry-breadcrumb2" in files - assert "__sentry-event" in files + assert attachments.event["level"] == "fatal" assert any( b'name="upload_file_minidump"' in part.as_bytes() diff --git a/tests/requirements.txt b/tests/requirements.txt index 4d181eb9d0..c90370ba09 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,4 @@ black==23.3.0 -pytest==7.2.2 -pytest-httpserver==1.0.6 +pytest==7.4.0 +pytest-httpserver==1.0.8 +msgpack==1.0.5 diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index c304f84417..c17439d759 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1,11 +1,12 @@ -import pytest +import itertools +import json import os import time -import itertools import uuid -import json + +import pytest + from . import make_dsn, run, Envelope -from .conditions import has_http, has_breakpad, has_files from .assertions import ( assert_attachment, assert_meta, @@ -13,10 +14,12 @@ assert_stacktrace, assert_event, assert_exception, - assert_crash, + assert_inproc_crash, assert_session, assert_minidump, + assert_breakpad_crash, ) +from .conditions import has_http, has_breakpad, has_files pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") @@ -233,7 +236,7 @@ def test_inproc_crash_http(cmake, httpserver): assert_breadcrumb(envelope) assert_attachment(envelope) - assert_crash(envelope) + assert_inproc_crash(envelope) def test_inproc_reinstall(cmake, httpserver): @@ -318,6 +321,7 @@ def test_breakpad_crash_http(cmake, httpserver): assert_breadcrumb(envelope) assert_attachment(envelope) + assert_breakpad_crash(envelope) assert_minidump(envelope) diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index ecb2c6f954..a6c71e5b0a 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -12,12 +12,12 @@ assert_breadcrumb, assert_stacktrace, assert_event, - assert_crash, + assert_inproc_crash, assert_minidump, - assert_timestamp, assert_before_send, assert_no_before_send, assert_crash_timestamp, + assert_breakpad_crash, ) from .conditions import has_breakpad, has_files @@ -92,9 +92,9 @@ def test_multi_process(cmake): # while the processes are running, we expect two runs runs = [ - run - for run in os.listdir(os.path.join(cwd, ".sentry-native")) - if run.endswith(".run") + db_run + for db_run in os.listdir(os.path.join(cwd, ".sentry-native")) + if db_run.endswith(".run") ] assert len(runs) == 2 @@ -108,9 +108,9 @@ def test_multi_process(cmake): subprocess.run([cmd], cwd=cwd) runs = [ - run - for run in os.listdir(os.path.join(cwd, ".sentry-native")) - if run.endswith(".run") or run.endswith(".lock") + db_run + for db_run in os.listdir(os.path.join(cwd, ".sentry-native")) + if db_run.endswith(".run") or db_run.endswith(".lock") ] assert len(runs) == 0 @@ -136,7 +136,7 @@ def test_inproc_crash_stdout(cmake): assert_meta(envelope, integration="inproc") assert_breadcrumb(envelope) assert_attachment(envelope) - assert_crash(envelope) + assert_inproc_crash(envelope) def test_inproc_crash_stdout_before_send(cmake): @@ -148,7 +148,7 @@ def test_inproc_crash_stdout_before_send(cmake): assert_meta(envelope, integration="inproc") assert_breadcrumb(envelope) assert_attachment(envelope) - assert_crash(envelope) + assert_inproc_crash(envelope) assert_before_send(envelope) @@ -175,7 +175,7 @@ def test_inproc_crash_stdout_before_send_and_on_crash(cmake): assert_meta(envelope, integration="inproc") assert_breadcrumb(envelope) assert_attachment(envelope) - assert_crash(envelope) + assert_inproc_crash(envelope) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") @@ -189,6 +189,7 @@ def test_breakpad_crash_stdout(cmake): assert_breadcrumb(envelope) assert_attachment(envelope) assert_minidump(envelope) + assert_breakpad_crash(envelope) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") @@ -203,6 +204,7 @@ def test_breakpad_crash_stdout_before_send(cmake): assert_attachment(envelope) assert_minidump(envelope) assert_before_send(envelope) + assert_breakpad_crash(envelope) @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") @@ -230,3 +232,4 @@ def test_breakpad_crash_stdout_before_send_and_on_crash(cmake): assert_meta(envelope, integration="breakpad") assert_breadcrumb(envelope) assert_attachment(envelope) + assert_breakpad_crash(envelope) From e340df17bdff39685d77ff8e566619cdfc713158 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 3 Jul 2023 17:22:36 +0200 Subject: [PATCH 120/207] fix: deadlock in dynamic sdk-name scope init... (#858) --- CHANGELOG.md | 6 ++++++ CONTRIBUTING.md | 1 + examples/example.c | 4 ++++ src/sentry_core.c | 13 +++++++++---- src/sentry_scope.c | 13 +++---------- tests/test_integration_stdout.py | 20 ++++++++++++++++++++ tests/unit/test_concurrency.c | 6 +++--- tests/unit/test_options.c | 2 +- 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc2ccb018..f2a7ffde7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Fixes**: + +- Remove deadlock pattern in dynamic sdk-name assignment ([#858](https://github.com/getsentry/sentry-native/pull/858)) + ## 0.6.4 **Fixes**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61bc6f297e..2af86e966e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,6 +144,7 @@ The example currently supports the following commands: - `discarding-before-send`: Installs a `before_send()` callback that discards the event. - `on-crash`: Installs an `on_crash()` callback that retains the crash event. - `discarding-on-crash`: Installs an `on_crash()` callback that discards the crash event. +- `override-sdk-name`: Changes the SDK name via the options at runtime. Only on Windows using crashpad with its WER handler module: diff --git a/examples/example.c b/examples/example.c index ad9e594f94..7e1b71b0cc 100644 --- a/examples/example.c +++ b/examples/example.c @@ -218,6 +218,10 @@ main(int argc, char **argv) options, discarding_on_crash_callback, NULL); } + if (has_arg(argc, argv, "override-sdk-name")) { + sentry_options_set_sdk_name(options, "sentry.native.android.flutter"); + } + sentry_init(options); if (!has_arg(argc, argv, "no-setup")) { diff --git a/src/sentry_core.c b/src/sentry_core.c index 8cbb39a031..43309b67f2 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -163,11 +163,16 @@ sentry_init(sentry_options_t *options) g_options = options; // *after* setting the global options, trigger a scope and consent flush, - // since at least crashpad needs that. - // 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 + // since at least crashpad needs that. At this point we also freeze the + // `client_sdk` in the `scope` because some downstream SDKs want to override + // it at runtime via the options interface. SENTRY_WITH_SCOPE_MUT (scope) { - (void)scope; + if (options->sdk_name) { + sentry_value_t sdk_name + = sentry_value_new_string(options->sdk_name); + sentry_value_set_by_key(scope->client_sdk, "name", sdk_name); + } + sentry_value_freeze(scope->client_sdk); } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); diff --git a/src/sentry_scope.c b/src/sentry_scope.c index a98a583ba3..ba246ab2e7 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -29,15 +29,9 @@ get_client_sdk(void) { sentry_value_t client_sdk = sentry_value_new_object(); - SENTRY_WITH_OPTIONS (options) { - sentry_value_t sdk_name = sentry_value_new_string(options->sdk_name); - sentry_value_set_by_key(client_sdk, "name", sdk_name); - } - // in case the SDK is not initialized yet, fallback to build-time value - if (sentry_value_is_null(sentry_value_get_by_key(client_sdk, "name"))) { - sentry_value_t sdk_name = sentry_value_new_string(SENTRY_SDK_NAME); - sentry_value_set_by_key(client_sdk, "name", sdk_name); - } + // the SDK is not initialized yet, fallback to build-time value + sentry_value_t sdk_name = sentry_value_new_string(SENTRY_SDK_NAME); + sentry_value_set_by_key(client_sdk, "name", sdk_name); sentry_value_t version = sentry_value_new_string(SENTRY_SDK_VERSION); sentry_value_set_by_key(client_sdk, "version", version); @@ -61,7 +55,6 @@ get_client_sdk(void) sentry_value_set_by_key(client_sdk, "integrations", integrations); #endif - sentry_value_freeze(client_sdk); return client_sdk; } diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py index a6c71e5b0a..e44a90ead7 100644 --- a/tests/test_integration_stdout.py +++ b/tests/test_integration_stdout.py @@ -46,6 +46,26 @@ def test_capture_stdout(cmake): assert_event(envelope) +def test_dynamic_sdk_name_override(cmake): + tmp_path = cmake( + ["sentry_example"], + { + "SENTRY_BACKEND": "none", + "SENTRY_TRANSPORT": "none", + }, + ) + + output = check_output( + tmp_path, + "sentry_example", + ["stdout", "override-sdk-name", "capture-event"], + ) + envelope = Envelope.deserialize(output) + + assert_meta(envelope, sdk_override="sentry.native.android.flutter") + assert_event(envelope) + + def test_sdk_name_override(cmake): sdk_name = "cUsToM.SDK" tmp_path = cmake( diff --git a/tests/unit/test_concurrency.c b/tests/unit/test_concurrency.c index 946081dba5..47fc691a9f 100644 --- a/tests/unit/test_concurrency.c +++ b/tests/unit/test_concurrency.c @@ -43,7 +43,7 @@ SENTRY_TEST(multiple_inits) SENTRY_LEVEL_INFO, "root", "Hello World!")); sentry_value_t obj = sentry_value_new_object(); - // something that is not a uuid, as this will be forcibly changed + // something that is not a UUID, as this will be forcibly changed sentry_value_set_by_key(obj, "event_id", sentry_value_new_int32(1234)); sentry_capture_event(obj); @@ -64,7 +64,7 @@ thread_worker(void *called) SENTRY_LEVEL_INFO, "root", "Hello World!")); sentry_value_t obj = sentry_value_new_object(); - // something that is not a uuid, as this will be forcibly changed + // something that is not a UUID, as this will be forcibly changed sentry_value_set_by_key(obj, "event_id", sentry_value_new_int32(1234)); sentry_capture_event(obj); @@ -75,7 +75,7 @@ SENTRY_TEST(concurrent_init) { long called = 0; -#define THREADS_NUM 10 +#define THREADS_NUM 100 sentry_threadid_t threads[THREADS_NUM]; for (size_t i = 0; i < THREADS_NUM; i++) { diff --git a/tests/unit/test_options.c b/tests/unit/test_options.c index 529b914725..c9115a434a 100644 --- a/tests/unit/test_options.c +++ b/tests/unit/test_options.c @@ -43,7 +43,7 @@ SENTRY_TEST(options_sdk_name_invalid) const char *sdk_name = NULL; const int result = sentry_options_set_sdk_name(options, sdk_name); - // then the value should should be ignored + // then the value should be ignored TEST_CHECK_INT_EQUAL(result, 1); TEST_CHECK_STRING_EQUAL( sentry_options_get_sdk_name(options), SENTRY_SDK_NAME); From 79899a808a4675a66db6b76a144cecea954506ea Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 5 Jul 2023 11:54:45 +0000 Subject: [PATCH 121/207] release: 0.6.5 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a7ffde7c..ea9f639d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.5 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 61b363f189..180a22bc21 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.4" +#define SENTRY_SDK_VERSION "0.6.5" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 71a7ba48f7..65bbd98b50 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -58,9 +58,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.4", + "version": "0.6.5", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.4"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.5"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index c17439d759..bfc87552bc 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.4" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.5" ) From 99789b313a7c33654594c27d6acc5f0daee893f6 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 7 Jul 2023 20:12:07 +0200 Subject: [PATCH 122/207] Update README.md (#862) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index a05faf493f..04295f9641 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,6 @@ applications, optimized for C and C++. Sentry allows to add tags, breadcrumbs and arbitrary custom context to enrich error reports. Supports Sentry _20.6.0_ and later. -**Note**: This SDK is being actively developed and still in Beta. We recommend -to check for updates regularly to benefit from latest features and bug fixes. -Please see [Known Limitations](#known-limitations). - ## Resources - [Discord](https://discord.gg/ez5KZN7) server for project discussions. From 6de2b982e3065b56f237191a99c01fbbf0a46668 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 28 Jul 2023 20:45:48 +0200 Subject: [PATCH 123/207] fix: conan-badge link in README.md (#867) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04295f9641..681aca0939 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Conan Center](https://shields.io/conan/v/sentry-native)](https://conan.io/center/sentry-native) [![nixpkgs unstable](https://repology.org/badge/version-for-repo/nix_unstable/sentry-native.svg)](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/libraries/sentry-native/default.nix) [![vcpkg](https://shields.io/vcpkg/v/sentry-native)](https://vcpkg.link/ports/sentry-native) +[![Conan Center](https://shields.io/conan/v/sentry-native)](https://conan.io/center/recipes/sentry-native) [![nixpkgs unstable](https://repology.org/badge/version-for-repo/nix_unstable/sentry-native.svg)](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/libraries/sentry-native/default.nix) [![vcpkg](https://shields.io/vcpkg/v/sentry-native)](https://vcpkg.link/ports/sentry-native)

From d9e901951ac5ba068e095e54a7d9c59a9330665d Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Mon, 31 Jul 2023 11:18:44 +0200 Subject: [PATCH 124/207] Add docs link to readme (#868) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 681aca0939..6cc4b1454b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ and later. ## Resources -- [Discord](https://discord.gg/ez5KZN7) server for project discussions. +- [SDK Documentation](https://docs.sentry.io/platforms/native/) +- [Discord](https://discord.gg/ez5KZN7) server for project discussions - Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates ## Table of Contents From b2c2ee06f166ae83bbc5fc35589a20d7a715217e Mon Sep 17 00:00:00 2001 From: sappho Date: Wed, 13 Sep 2023 04:50:00 -0400 Subject: [PATCH 125/207] docs: clarify behavior of sentry_flush + sentry_close (#883) --------- Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 8 ++++++++ include/sentry.h | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9f639d2f..c27ce9550a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@sapphonie](https://github.com/sapphonie) + ## 0.6.5 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 180a22bc21..8bf3930b23 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1267,6 +1267,10 @@ SENTRY_API int sentry_init(sentry_options_t *options); * The `timeout` parameter is in milliseconds. * * Returns 0 on success, or a non-zero return value in case the timeout is hit. + * + * Note that this function will block the thread it was called from until the + * sentry background worker has finished its work or it timed out, whichever + * comes first. */ SENTRY_API int sentry_flush(uint64_t timeout); @@ -1274,6 +1278,14 @@ SENTRY_API int sentry_flush(uint64_t timeout); * Shuts down the sentry client and forces transports to flush out. * * Returns 0 on success. + * + * Note that this does not uninstall any crash handler installed by our + * backends, which will still process crashes after `sentry_close()`, except + * when using `crashpad` on Linux or the `inproc` backend. + * + * Further note that this function will block the thread it was called from + * until the sentry background worker has finished its work or it timed out, + * whichever comes first. */ SENTRY_API int sentry_close(void); From d4f8268495169f23ec46e0a0218cb2d4fde5d461 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 14 Sep 2023 14:02:48 +0200 Subject: [PATCH 126/207] chore: update libunwindstack 2023-09-13 (#884) --- CHANGELOG.md | 4 ++++ external/libunwindstack-ndk | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c27ce9550a..9c6dc3b070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Internal**: + +- Updated `libunwindstack` to 2023-09-13. ([#884](https://github.com/getsentry/sentry-native/pull/884), [libunwindstack-ndk#8](https://github.com/getsentry/libunwindstack-ndk/pull/8)) + **Thank you**: Features, fixes and improvements in this release have been contributed by: diff --git a/external/libunwindstack-ndk b/external/libunwindstack-ndk index 1929f7b601..f064cc8da6 160000 --- a/external/libunwindstack-ndk +++ b/external/libunwindstack-ndk @@ -1 +1 @@ -Subproject commit 1929f7b601797fc8b2cac092d563b31d01d46a76 +Subproject commit f064cc8da606f38450ff5d345ae716ff9dab3d7c From 4282e515bb7ffbbfa6a24b6963d8091d5acf25bf Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 2 Oct 2023 13:10:15 +0200 Subject: [PATCH 127/207] chore: update crashpad 2023 09 28 (#891) --- CHANGELOG.md | 5 +++++ external/crashpad | 2 +- scripts/install-llvm-mingw.ps1 | 1 + src/sentry_sync.c | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6dc3b070..a808c3df44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,14 @@ ## Unreleased +**Fixes**: + +- Use a more up-to-date version of `mini_chromium` as a `crashpad` dependency, which fixes a build error on some systems. ([#891](https://github.com/getsentry/sentry-native/pull/891), [crashpad#88](https://github.com/getsentry/crashpad/pull/88)) + **Internal**: - Updated `libunwindstack` to 2023-09-13. ([#884](https://github.com/getsentry/sentry-native/pull/884), [libunwindstack-ndk#8](https://github.com/getsentry/libunwindstack-ndk/pull/8)) +- Updated `crashpad` to 2023-09-28. ([#891](https://github.com/getsentry/sentry-native/pull/891), [crashpad#88](https://github.com/getsentry/crashpad/pull/88)) **Thank you**: diff --git a/external/crashpad b/external/crashpad index 432ff49ecc..f37da0f96a 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 432ff49ecccc1cdebf1a7646007bb0594ac3481f +Subproject commit f37da0f96ac60e919c0db6091d40000450ce7d6a diff --git a/scripts/install-llvm-mingw.ps1 b/scripts/install-llvm-mingw.ps1 index a81acaf4db..835c884f6a 100755 --- a/scripts/install-llvm-mingw.ps1 +++ b/scripts/install-llvm-mingw.ps1 @@ -49,6 +49,7 @@ New-Item -ItemType Directory -Force -Path "${NINJA_INSTALL_PATH}" Expand-Archive -LiteralPath "${NINJA_DL_PATH}" -DestinationPath "${NINJA_INSTALL_PATH}" # Export the NINJA executable path echo "NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +echo "PATH=${NINJA_INSTALL_PATH};$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Add CMAKE_DEFINES echo "CMAKE_DEFINES=-DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER} -GNinja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/src/sentry_sync.c b/src/sentry_sync.c index 3e4c1940b6..bcc44b374f 100644 --- a/src/sentry_sync.c +++ b/src/sentry_sync.c @@ -21,7 +21,7 @@ typedef struct { # pragma pack(pop) sentry_threadid_t -sentry__thread_get_current_threadid() +sentry__thread_get_current_threadid(void) { return GetCurrentThread(); } From f94b9287884d3a9f2770aa565337080bf978a3f6 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 2 Oct 2023 16:15:37 +0200 Subject: [PATCH 128/207] chore: update breakpad 2023-10-02 (#892) --- CHANGELOG.md | 1 + external/breakpad | 2 +- external/third_party/lss | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a808c3df44..b1729f76f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Updated `libunwindstack` to 2023-09-13. ([#884](https://github.com/getsentry/sentry-native/pull/884), [libunwindstack-ndk#8](https://github.com/getsentry/libunwindstack-ndk/pull/8)) - Updated `crashpad` to 2023-09-28. ([#891](https://github.com/getsentry/sentry-native/pull/891), [crashpad#88](https://github.com/getsentry/crashpad/pull/88)) +- Updated `breakpad` to 2023-10-02. ([#892](https://github.com/getsentry/sentry-native/pull/892), [breakpad#38](https://github.com/getsentry/breakpad/pull/38)) **Thank you**: diff --git a/external/breakpad b/external/breakpad index 250c6bc334..eb28e7ed9c 160000 --- a/external/breakpad +++ b/external/breakpad @@ -1 +1 @@ -Subproject commit 250c6bc334f2783efc2d8c029b3873cf109e0e49 +Subproject commit eb28e7ed9c1c1e1a717fa34ce0178bf471a6311f diff --git a/external/third_party/lss b/external/third_party/lss index 171a36a8e0..9719c1e1e6 160000 --- a/external/third_party/lss +++ b/external/third_party/lss @@ -1 +1 @@ -Subproject commit 171a36a8e0d1e456f63d342a09f811f9273a64af +Subproject commit 9719c1e1e676814c456b55f5f070eabad6709d31 From c97bcc63fa89ae557cef9c9b6e3acb11a72ff97d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 9 Oct 2023 15:11:41 +0000 Subject: [PATCH 129/207] release: 0.6.6 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1729f76f5..7a80b24790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.6 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 8bf3930b23..6c6d0bfe33 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.5" +#define SENTRY_SDK_VERSION "0.6.6" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 65bbd98b50..b52dbd9d44 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -58,9 +58,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.5", + "version": "0.6.6", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.5"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.6"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index bfc87552bc..99349ff522 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.5" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.6" ) From 1040b510d5e8fdfc08c5954157323ce4db0c682a Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 10 Nov 2023 18:47:54 +0100 Subject: [PATCH 130/207] fix: stuck crashpad_client on windows (#902) --- CHANGELOG.md | 6 ++++++ external/crashpad | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a80b24790..375844581c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Fixes**: + +- Prevent stuck crashpad-client on Windows ([#902](https://github.com/getsentry/sentry-native/pull/902), [crashpad#89](https://github.com/getsentry/crashpad/pull/89)) + ## 0.6.6 **Fixes**: diff --git a/external/crashpad b/external/crashpad index f37da0f96a..3182e3be21 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit f37da0f96ac60e919c0db6091d40000450ce7d6a +Subproject commit 3182e3be21a8a753f9f269f0a590370d49c8f3cf From 02fe0c4710186c796a5bff81f38d92a812754eb8 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 10 Nov 2023 18:50:12 +0100 Subject: [PATCH 131/207] fix: disable sigaltstack on Android (#901) --- CHANGELOG.md | 1 + src/backends/sentry_backend_inproc.c | 35 ++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 375844581c..b02a3909e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ **Fixes**: +- Disable sigaltstack on Android ([#901](https://github.com/getsentry/sentry-native/pull/901)) - Prevent stuck crashpad-client on Windows ([#902](https://github.com/getsentry/sentry-native/pull/902), [crashpad#89](https://github.com/getsentry/crashpad/pull/89)) ## 0.6.6 diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index 4d1b9e4d5e..a18b2944b4 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -13,6 +13,31 @@ #include "transports/sentry_disk_transport.h" #include +/** + * Android's bionic libc seems to allocate alternate signal handler stacks for + * every thread and also references them from their internal maintenance + * structs. + * + * The way we currently set up our sigaltstack seems to interfere with this + * setup and causes crashes whenever an ART signal handler touches the thread + * that called `sentry_init()`. + * + * In addition to this problem, it also means there is no need for our own + * sigaltstack on Android since our signal handler will always be running on + * an alternate stack managed by bionic. + * + * Note: In bionic the sigaltstacks for 32-bit devices have a size of 16KiB and + * on 64-bit devices they have 32KiB. The size of our own was set to 64KiB + * independent of the device. If this is a problem, we need figure out + * together with Google if there is a way in which our configs can coexist. + * + * Both breakpad and crashpad are way more defensive in the setup of their + * signal stacks and take existing stacks into account (or reuse them). + */ +#ifndef SENTRY_PLATFORM_ANDROID +# define SETUP_SIGALTSTACK +#endif + #define SIGNAL_DEF(Sig, Desc) \ { \ Sig, #Sig, Desc \ @@ -32,8 +57,9 @@ struct signal_slot { # define SIGNAL_STACK_SIZE 65536 static struct sigaction g_sigaction; static struct sigaction g_previous_handlers[SIGNAL_COUNT]; +# ifdef SETUP_SIGALTSTACK static stack_t g_signal_stack; - +# endif static const struct signal_slot SIGNAL_DEFINITIONS[SIGNAL_COUNT] = { SIGNAL_DEF(SIGILL, "IllegalInstruction"), SIGNAL_DEF(SIGTRAP, "Trap"), @@ -87,6 +113,7 @@ startup_inproc_backend( } // install our own signal handler +# ifdef SETUP_SIGALTSTACK g_signal_stack.ss_sp = sentry_malloc(SIGNAL_STACK_SIZE); if (!g_signal_stack.ss_sp) { return 1; @@ -94,7 +121,7 @@ startup_inproc_backend( g_signal_stack.ss_size = SIGNAL_STACK_SIZE; g_signal_stack.ss_flags = 0; sigaltstack(&g_signal_stack, 0); - +# endif sigemptyset(&g_sigaction.sa_mask); g_sigaction.sa_sigaction = handle_signal; g_sigaction.sa_flags = SA_SIGINFO | SA_ONSTACK; @@ -107,10 +134,12 @@ startup_inproc_backend( static void shutdown_inproc_backend(sentry_backend_t *UNUSED(backend)) { +# ifdef SETUP_SIGALTSTACK g_signal_stack.ss_flags = SS_DISABLE; sigaltstack(&g_signal_stack, 0); sentry_free(g_signal_stack.ss_sp); g_signal_stack.ss_sp = NULL; +# endif reset_signal_handlers(); } @@ -472,6 +501,8 @@ make_signal_event( void *backtrace[MAX_FRAMES]; size_t frame_count = sentry_unwind_stack_from_ucontext(uctx, &backtrace[0], MAX_FRAMES); + SENTRY_TRACEF( + "captured backtrace from ucontext with %lu frames", frame_count); // if unwinding from a ucontext didn't yield any results, try again with a // direct unwind. this is most likely the case when using `libbacktrace`, // since that does not allow to unwind from a ucontext at all. From fcd97cd1fbb4b44b5e7cac16c629cf49beb8ea6b Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Sun, 12 Nov 2023 22:14:07 +0100 Subject: [PATCH 132/207] ci: change emulator boot-condition for android tests (#904) --- scripts/start-android.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/start-android.sh b/scripts/start-android.sh index d313c00ed9..6b7fecb5d7 100755 --- a/scripts/start-android.sh +++ b/scripts/start-android.sh @@ -17,7 +17,7 @@ echo "Starting emulator..." # Start emulator in background nohup $ANDROID_HOME/emulator/emulator -avd $AVD_EMULATOR_NAME -no-snapshot > /dev/null 2>&1 & -$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82' +$ANDROID_HOME/platform-tools/adb wait-for-device shell 'ls' $ANDROID_HOME/platform-tools/adb devices echo "Emulator started." From a3d58622a807b9dda174cb9fc18fa0f98c89d043 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Sun, 12 Nov 2023 21:23:23 +0000 Subject: [PATCH 133/207] release: 0.6.7 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b02a3909e9..3786b9f66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.6.7 **Fixes**: diff --git a/include/sentry.h b/include/sentry.h index 6c6d0bfe33..fe9918488e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.6" +#define SENTRY_SDK_VERSION "0.6.7" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index b52dbd9d44..08f2936aba 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -58,9 +58,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.6", + "version": "0.6.7", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.6"}, + {"name": "github:getsentry/sentry-native", "version": "0.6.7"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 99349ff522..fe3740a6cd 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.6" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.7" ) From 671acff916521719e6764b1d45ec95783a350157 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 28 Nov 2023 10:11:53 +0100 Subject: [PATCH 134/207] fix: Maintain client in crashpad state (#910) --- CHANGELOG.md | 10 ++++-- src/backends/sentry_backend_crashpad.cpp | 40 ++++++++++++++++-------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3786b9f66e..f64877f423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ # Changelog +## Unreleased + +**Fixes**: + +- Maintain crashpad client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) + ## 0.6.7 **Fixes**: -- Disable sigaltstack on Android ([#901](https://github.com/getsentry/sentry-native/pull/901)) -- Prevent stuck crashpad-client on Windows ([#902](https://github.com/getsentry/sentry-native/pull/902), [crashpad#89](https://github.com/getsentry/crashpad/pull/89)) +- Disable sigaltstack on Android. ([#901](https://github.com/getsentry/sentry-native/pull/901)) +- Prevent stuck crashpad-client on Windows. ([#902](https://github.com/getsentry/sentry-native/pull/902), [crashpad#89](https://github.com/getsentry/crashpad/pull/89)) ## 0.6.6 diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 7a0a7d0d66..96d9616575 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -43,6 +43,14 @@ extern "C" { # pragma warning(pop) #endif +template +static void +safe_delete(T *&ptr) +{ + delete ptr; + ptr = nullptr; +} + extern "C" { #ifdef SENTRY_PLATFORM_LINUX @@ -74,6 +82,7 @@ constexpr int g_CrashSignals[] = { typedef struct { crashpad::CrashReportDatabase *db; + crashpad::CrashpadClient *client; sentry_path_t *event_path; sentry_path_t *breadcrumb1_path; sentry_path_t *breadcrumb2_path; @@ -81,6 +90,16 @@ typedef struct { sentry_value_t crash_event; } crashpad_state_t; +/** + * Correctly destruct C++ members of the crashpad state. + */ +static void +crashpad_state_dtor(crashpad_state_t *state) +{ + safe_delete(state->client); + safe_delete(state->db); +} + static void crashpad_backend_user_consent_changed(sentry_backend_t *backend) { @@ -325,24 +344,22 @@ crashpad_backend_startup( // Initialize database first, flushing the consent later on as part of // `sentry_init` will persist the upload flag. data->db = crashpad::CrashReportDatabase::Initialize(database).release(); - + data->client = new crashpad::CrashpadClient; bool success; - crashpad::CrashpadClient client; char *minidump_url = sentry__dsn_get_minidump_url(options->dsn, options->user_agent); if (minidump_url) { SENTRY_TRACEF("using minidump URL \"%s\"", minidump_url); - success = client.StartHandler(handler, database, database, minidump_url, - options->http_proxy ? options->http_proxy : "", annotations, - arguments, + success = data->client->StartHandler(handler, database, database, + minidump_url, options->http_proxy ? options->http_proxy : "", + annotations, arguments, /* restartable */ true, /* asynchronous_start */ false, attachments); sentry_free(minidump_url); } else { SENTRY_WARN( "failed to construct minidump URL (check DSN or user-agent)"); - delete data->db; - data->db = nullptr; + crashpad_state_dtor(data); return 1; } @@ -369,7 +386,7 @@ crashpad_backend_startup( SENTRY_WARN("registering crashpad WER handler in registry failed"); } else { std::wstring wer_path_string(wer_path->path); - if (!client.RegisterWerModule(wer_path_string)) { + if (!data->client->RegisterWerModule(wer_path_string)) { SENTRY_WARN("registering crashpad WER handler module failed"); } } @@ -387,8 +404,7 @@ crashpad_backend_startup( } else { SENTRY_WARN("failed to start crashpad client handler"); // not calling `shutdown` - delete data->db; - data->db = nullptr; + crashpad_state_dtor(data); return 1; } @@ -432,9 +448,7 @@ crashpad_backend_shutdown(sentry_backend_t *backend) } #endif - auto *data = static_cast(backend->data); - delete data->db; - data->db = nullptr; + crashpad_state_dtor(static_cast(backend->data)); #ifdef SENTRY_PLATFORM_LINUX g_signal_stack.ss_flags = SS_DISABLE; From d88cd769f0cf091a2bac851a40af9bd6d5bf71d5 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 28 Nov 2023 17:46:18 +0100 Subject: [PATCH 135/207] chore: update crashpad 2023-11-24 (#912) --- CHANGELOG.md | 10 ++++++++++ external/crashpad | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f64877f423..1902c36e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ - Maintain crashpad client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) +**Internal**: + +- Updated `crashpad` to 2023-11-24. ([#912](https://github.com/getsentry/sentry-native/pull/912), [crashpad#91](https://github.com/getsentry/crashpad/pull/91)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@compnerd](https://github.com/compnerd) + ## 0.6.7 **Fixes**: diff --git a/external/crashpad b/external/crashpad index 3182e3be21..e63d0c295c 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 3182e3be21a8a753f9f269f0a590370d49c8f3cf +Subproject commit e63d0c295cece2965680a8ea71b0aa94ee312bd9 From 0b17731c74e40202dea2e10b6c4aec8d6e66a1cd Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 6 Dec 2023 16:42:37 +0100 Subject: [PATCH 136/207] fix: crashpad build for Windows on ARM64 (#919) --- CHANGELOG.md | 1 + external/crashpad | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1902c36e03..0550d47243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ **Internal**: - Updated `crashpad` to 2023-11-24. ([#912](https://github.com/getsentry/sentry-native/pull/912), [crashpad#91](https://github.com/getsentry/crashpad/pull/91)) +- Fixing `crashpad` build for Windows on ARM64. ([#919](https://github.com/getsentry/sentry-native/pull/919), [crashpad#90](https://github.com/getsentry/crashpad/pull/90), [crashpad#92](https://github.com/getsentry/crashpad/pull/92), [crashpad#93](https://github.com/getsentry/crashpad/pull/93), [crashpad#94](https://github.com/getsentry/crashpad/pull/94)) **Thank you**: diff --git a/external/crashpad b/external/crashpad index e63d0c295c..89991e9910 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit e63d0c295cece2965680a8ea71b0aa94ee312bd9 +Subproject commit 89991e9910bc4c0893e45c8cfad0bdd31cc25a5c From 613f4708abe45d65276597fd7024f4ea19773335 Mon Sep 17 00:00:00 2001 From: "R. Savchenko" Date: Tue, 12 Dec 2023 18:22:41 +0100 Subject: [PATCH 137/207] Remove options memory leak during consent setting (#922) --- CHANGELOG.md | 1 + src/sentry_core.c | 40 +++++++++++++++++++-------------------- tests/unit/test_consent.c | 4 ++++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0550d47243..88daef74d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Updated `crashpad` to 2023-11-24. ([#912](https://github.com/getsentry/sentry-native/pull/912), [crashpad#91](https://github.com/getsentry/crashpad/pull/91)) - Fixing `crashpad` build for Windows on ARM64. ([#919](https://github.com/getsentry/sentry-native/pull/919), [crashpad#90](https://github.com/getsentry/crashpad/pull/90), [crashpad#92](https://github.com/getsentry/crashpad/pull/92), [crashpad#93](https://github.com/getsentry/crashpad/pull/93), [crashpad#94](https://github.com/getsentry/crashpad/pull/94)) +- Remove options memory leak during consent setting. ([#922](https://github.com/getsentry/sentry-native/pull/922)) **Thank you**: diff --git a/src/sentry_core.c b/src/sentry_core.c index 43309b67f2..24cf525393 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -292,29 +292,27 @@ set_user_consent(sentry_user_consent_t new_val) { SENTRY_WITH_OPTIONS (options) { if (sentry__atomic_store((long *)&options->user_consent, new_val) - == new_val) { - // nothing was changed - break; // SENTRY_WITH_OPTIONS - } - - if (options->backend && options->backend->user_consent_changed_func) { - options->backend->user_consent_changed_func(options->backend); - } + != new_val) { + if (options->backend + && options->backend->user_consent_changed_func) { + options->backend->user_consent_changed_func(options->backend); + } - sentry_path_t *consent_path - = sentry__path_join_str(options->database_path, "user-consent"); - switch (new_val) { - case SENTRY_USER_CONSENT_GIVEN: - sentry__path_write_buffer(consent_path, "1\n", 2); - break; - case SENTRY_USER_CONSENT_REVOKED: - sentry__path_write_buffer(consent_path, "0\n", 2); - break; - case SENTRY_USER_CONSENT_UNKNOWN: - sentry__path_remove(consent_path); - break; + sentry_path_t *consent_path + = sentry__path_join_str(options->database_path, "user-consent"); + switch (new_val) { + case SENTRY_USER_CONSENT_GIVEN: + sentry__path_write_buffer(consent_path, "1\n", 2); + break; + case SENTRY_USER_CONSENT_REVOKED: + sentry__path_write_buffer(consent_path, "0\n", 2); + break; + case SENTRY_USER_CONSENT_UNKNOWN: + sentry__path_remove(consent_path); + break; + } + sentry__path_free(consent_path); } - sentry__path_free(consent_path); } } diff --git a/tests/unit/test_consent.c b/tests/unit/test_consent.c index f26af345bd..d81476c277 100644 --- a/tests/unit/test_consent.c +++ b/tests/unit/test_consent.c @@ -28,6 +28,10 @@ SENTRY_TEST(basic_consent_tracking) init_consenting_sentry(); sentry_user_consent_give(); + // testing correct options ref/decref during double + // `sentry_user_consent_give` call see + // https://github.com/getsentry/sentry-native/pull/922 + sentry_user_consent_give(); TEST_CHECK_INT_EQUAL(sentry_user_consent_get(), SENTRY_USER_CONSENT_GIVEN); sentry_close(); init_consenting_sentry(); From 00061be4a25c0ce019aa35e8abc70782861cfa2a Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 19 Dec 2023 17:52:03 +0100 Subject: [PATCH 138/207] fix: specify correct dependencies for CMake client projects... (#926) ...which use a system-provided breakpad (instead of our vendored fork). The problem [here](https://github.com/getsentry/sentry-native/issues/877) was that people who introduce sentry-native via CMake `find_package()` will get insufficient dependencies, which leads to configuration errors in the client CMake project. There are two aspects to this problem: * if the user builds sentry as a shared library, it shouldn't be necessary to specify the dependencies. This can be fixed by defining `breakpad` as a `PRIVATE` dependency. This should also fix the Gentoo issue because it uses sentry as a shared library afaict. * if the user builds sentry as a static library, then we must stay with the `PUBLIC` dependency, but we also need to correctly search for `breakpad`, `libcurl`, and `pthread` in the context of the client project. --- CHANGELOG.md | 2 ++ CMakeLists.txt | 6 +++++- sentry-config.cmake.in | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88daef74d6..eaade5f1c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Maintain crashpad client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) +- Specify correct dependencies for CMake client projects using a system-provided breakpad. ([#926](https://github.com/getsentry/sentry-native/pull/926)) **Internal**: @@ -17,6 +18,7 @@ Features, fixes and improvements in this release have been contributed by: - [@compnerd](https://github.com/compnerd) +- [@stima](https://github.com/stima) ## 0.6.7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ec6778999..8c7a8ba55c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,7 +496,11 @@ elseif(SENTRY_BACKEND_BREAKPAD) # system breakpad is using pkg-config, see `external/breakpad/breakpad-client.pc.in` find_package(PkgConfig REQUIRED) pkg_check_modules(BREAKPAD REQUIRED IMPORTED_TARGET breakpad-client) - target_link_libraries(sentry PUBLIC PkgConfig::BREAKPAD) + if(SENTRY_BUILD_SHARED_LIBS) + target_link_libraries(sentry PRIVATE PkgConfig::BREAKPAD) + else() + target_link_libraries(sentry PUBLIC PkgConfig::BREAKPAD) + endif() else() add_subdirectory(external) target_include_directories(sentry PRIVATE diff --git a/sentry-config.cmake.in b/sentry-config.cmake.in index 89ea345118..483d92c0af 100644 --- a/sentry-config.cmake.in +++ b/sentry-config.cmake.in @@ -2,19 +2,34 @@ set(SENTRY_BACKEND @SENTRY_BACKEND@) set(SENTRY_TRANSPORT @SENTRY_TRANSPORT@) +set(SENTRY_BUILD_SHARED_LIBS @SENTRY_BUILD_SHARED_LIBS@) +set(SENTRY_LINK_PTHREAD @SENTRY_LINK_PTHREAD@) if(SENTRY_BACKEND STREQUAL "crashpad") - if(@SENTRY_CRASHPAD_SYSTEM@) + set(SENTRY_CRASHPAD_SYSTEM @SENTRY_CRASHPAD_SYSTEM@) + if(SENTRY_CRASHPAD_SYSTEM) find_package(crashpad REQUIRED) else() include("${CMAKE_CURRENT_LIST_DIR}/sentry_crashpad-targets.cmake") endif() endif() +if(SENTRY_BACKEND STREQUAL "breakpad" AND NOT SENTRY_BUILD_SHARED_LIBS) + set(SENTRY_BREAKPAD_SYSTEM @SENTRY_BREAKPAD_SYSTEM@) + if(SENTRY_BREAKPAD_SYSTEM) + find_package(PkgConfig REQUIRED) + pkg_check_modules(BREAKPAD REQUIRED IMPORTED_TARGET breakpad-client) + endif() +endif() + include("${CMAKE_CURRENT_LIST_DIR}/sentry-targets.cmake") -if(SENTRY_TRANSPORT STREQUAL "curl" AND NOT @BUILD_SHARED_LIBS@) +if(SENTRY_TRANSPORT STREQUAL "curl" AND (NOT @BUILD_SHARED_LIBS@ OR NOT SENTRY_BUILD_SHARED_LIBS)) find_package(CURL REQUIRED) set_property(TARGET sentry::sentry APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${CURL_LIBRARIES}) endif() + +if(SENTRY_LINK_PTHREAD AND NOT SENTRY_BUILD_SHARED_LIBS) + find_package(Threads REQUIRED) +endif() From 164da7919172b0df9c7b75efbc36e6e897124415 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 20 Dec 2023 10:09:44 +0100 Subject: [PATCH 139/207] build: make crashpad the default backend on Linux (#927) --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 20 +++++++++----------- README.md | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaade5f1c3..80d99df681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Breaking changes**: + +- Make `crashpad` the default backend for Linux. ([#927](https://github.com/getsentry/sentry-native/pull/927)) + **Fixes**: - Maintain crashpad client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c7a8ba55c..eb9d54a9bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,11 +26,11 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) endif() if(NOT CMAKE_C_STANDARD) - set(CMAKE_C_STANDARD 11) + set(CMAKE_C_STANDARD 11) endif() if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 17) endif() include(GNUInstallDirs) @@ -96,7 +96,7 @@ else() endif() set(SENTRY_TRANSPORT ${SENTRY_DEFAULT_TRANSPORT} CACHE STRING - "The HTTP transport that sentry uses to submit events to the sentry server, can be either 'none', 'curl' or 'winhttp' on windows.") + "The HTTP transport that sentry uses to submit events to the sentry server, can be either 'none', 'curl' or 'winhttp' on windows.") if(SENTRY_TRANSPORT STREQUAL "winhttp") set(SENTRY_TRANSPORT_WINHTTP TRUE) @@ -125,10 +125,8 @@ option(SENTRY_ENABLE_INSTALL "Enable sentry installation" "${SENTRY_MAIN_PROJECT if(MSVC AND CMAKE_GENERATOR_TOOLSET MATCHES "_xp$") message(WARNING "Crashpad is not supported for MSVC with XP toolset. Default backend was switched to 'breakpad'") set(SENTRY_DEFAULT_BACKEND "breakpad") -elseif((APPLE AND NOT IOS) OR WIN32) +elseif((APPLE AND NOT IOS) OR WIN32 OR LINUX) set(SENTRY_DEFAULT_BACKEND "crashpad") -elseif(LINUX) - set(SENTRY_DEFAULT_BACKEND "breakpad") else() set(SENTRY_DEFAULT_BACKEND "inproc") endif() @@ -367,11 +365,11 @@ endif() # handle platform libraries if(ANDROID) - set(_SENTRY_PLATFORM_LIBS "dl" "log") + set(_SENTRY_PLATFORM_LIBS "dl" "log") elseif(LINUX) - set(_SENTRY_PLATFORM_LIBS "dl" "rt") + set(_SENTRY_PLATFORM_LIBS "dl" "rt") elseif(WIN32) - set(_SENTRY_PLATFORM_LIBS "dbghelp" "shlwapi" "version") + set(_SENTRY_PLATFORM_LIBS "dbghelp" "shlwapi" "version") endif() if(SENTRY_TRANSPORT_WINHTTP) @@ -385,9 +383,9 @@ endif() # apply platform libraries to sentry library if(SENTRY_LIBRARY_TYPE STREQUAL "STATIC") - target_link_libraries(sentry PUBLIC ${_SENTRY_PLATFORM_LIBS}) + target_link_libraries(sentry PUBLIC ${_SENTRY_PLATFORM_LIBS}) else() - target_link_libraries(sentry PRIVATE ${_SENTRY_PLATFORM_LIBS}) + target_link_libraries(sentry PRIVATE ${_SENTRY_PLATFORM_LIBS}) endif() # suppress some errors and warnings for MinGW target diff --git a/README.md b/README.md index 6cc4b1454b..8d49945384 100644 --- a/README.md +++ b/README.md @@ -227,9 +227,9 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. Sentry can use different backends depending on platform. - **crashpad**: This uses the out-of-process crashpad handler. It is currently - only supported on Desktop OSs, and used as the default on Windows and macOS. + only supported on Desktop OSs, and used as the default on Windows, Linux and macOS. - **breakpad**: This uses the in-process breakpad handler. It is currently - only supported on Desktop OSs, and used as the default on Linux. + only supported on Desktop OSs. - **inproc**: A small in-process handler which is supported on all platforms, and is used as default on Android. - **none**: This builds `sentry-native` without a backend, so it does not handle From 8c748b43a0d1e2bffa2ccb343f34a541625055cf Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 8 Jan 2024 10:23:17 +0100 Subject: [PATCH 140/207] fix: remove the SENTRY_CRASHPAD_SYSTEM build option (#928) --- CHANGELOG.md | 3 +- CMakeLists.txt | 114 +++++++++++++++++++---------------------- README.md | 8 +-- sentry-config.cmake.in | 10 ++-- 4 files changed, 62 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d99df681..756bcbc671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ **Breaking changes**: - Make `crashpad` the default backend for Linux. ([#927](https://github.com/getsentry/sentry-native/pull/927)) +- Remove build option `SENTRY_CRASHPAD_SYSTEM`. ([#928](https://github.com/getsentry/sentry-native/pull/928)) **Fixes**: -- Maintain crashpad client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) +- Maintain `crashpad` client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) - Specify correct dependencies for CMake client projects using a system-provided breakpad. ([#926](https://github.com/getsentry/sentry-native/pull/926)) **Internal**: diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9d54a9bd..8e460cdedf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -413,74 +413,68 @@ if(SENTRY_WITH_LIBUNWINDSTACK) endif() if(SENTRY_BACKEND_CRASHPAD) - option(SENTRY_CRASHPAD_SYSTEM "Use system crashpad" OFF) - if(SENTRY_CRASHPAD_SYSTEM) - find_package(crashpad REQUIRED) - target_link_libraries(sentry PUBLIC crashpad::client) + # FIXME: required for cmake 3.12 and lower: + # - NEW behavior lets normal variable override option + cmake_policy(SET CMP0077 NEW) + if(SENTRY_BUILD_SHARED_LIBS) + set(CRASHPAD_ENABLE_INSTALL OFF CACHE BOOL "Enable crashpad installation" FORCE) else() - # FIXME: required for cmake 3.12 and lower: - # - NEW behavior lets normal variable override option - cmake_policy(SET CMP0077 NEW) - if(SENTRY_BUILD_SHARED_LIBS) - set(CRASHPAD_ENABLE_INSTALL OFF CACHE BOOL "Enable crashpad installation" FORCE) - else() - set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) - endif() - add_subdirectory(external/crashpad crashpad_build) + set(CRASHPAD_ENABLE_INSTALL ON CACHE BOOL "Enable crashpad installation" FORCE) + endif() + add_subdirectory(external/crashpad crashpad_build) - if(CRASHPAD_WER_ENABLED) - add_dependencies(sentry crashpad::wer) - endif() + if(CRASHPAD_WER_ENABLED) + add_dependencies(sentry crashpad::wer) + endif() - # set static runtime if enabled - if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) - set_property(TARGET crashpad_client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_compat PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_getopt PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_handler PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_handler_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_minidump PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - if(CRASHPAD_WER_ENABLED) - set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - endif() - set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + # set static runtime if enabled + if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC) + set_property(TARGET crashpad_client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_compat PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_getopt PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_handler PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_handler_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_minidump PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + if(CRASHPAD_WER_ENABLED) + set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() - if(DEFINED SENTRY_FOLDER) - set_target_properties(crashpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_compat PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_getopt PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_handler PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_handler_lib PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_minidump PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_snapshot PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_tools PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) - set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) - if(CRASHPAD_WER_ENABLED) - set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) - endif() + if(DEFINED SENTRY_FOLDER) + set_target_properties(crashpad_client PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_compat PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_getopt PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_handler_lib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_minidump PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_snapshot PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_tools PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) + set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) + if(CRASHPAD_WER_ENABLED) + set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) endif() + endif() - target_link_libraries(sentry PRIVATE - $ - $ - ) - install(EXPORT crashpad_export NAMESPACE sentry_crashpad:: FILE sentry_crashpad-targets.cmake - DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" - ) - if(WIN32 AND MSVC) - sentry_install(FILES $ + target_link_libraries(sentry PRIVATE + $ + $ + ) + install(EXPORT crashpad_export NAMESPACE sentry_crashpad:: FILE sentry_crashpad-targets.cmake + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" + ) + if(WIN32 AND MSVC) + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + if (CRASHPAD_WER_ENABLED) + sentry_install(FILES $ DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) - if (CRASHPAD_WER_ENABLED) - sentry_install(FILES $ - DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) - endif() endif() endif() add_dependencies(sentry crashpad::handler) diff --git a/README.md b/README.md index 8d49945384..3c809b50cd 100644 --- a/README.md +++ b/README.md @@ -238,12 +238,8 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. - `SENTRY_INTEGRATION_QT` (Default: OFF): Builds the Qt integration, which turns Qt log messages into breadcrumbs. -- `SENTRY_BREAKPAD_SYSTEM` / `SENTRY_CRASHPAD_SYSTEM` (Default: OFF): - This instructs the build system to use system-installed breakpad or crashpad - libraries instead of using the in-tree version. This is generally not recommended - for crashpad, as sentry uses a patched version that has attachment support. - This is being worked on upstream as well, and a future version might work with - an unmodified crashpad version as well. +- `SENTRY_BREAKPAD_SYSTEM` (Default: OFF): + This instructs the build system to use system-installed breakpad libraries instead of using the in-tree version. | Feature | Windows | macOS | Linux | Android | iOS | | ---------- | ------- | ----- | ----- | ------- | --- | diff --git a/sentry-config.cmake.in b/sentry-config.cmake.in index 483d92c0af..70ce7d3c5a 100644 --- a/sentry-config.cmake.in +++ b/sentry-config.cmake.in @@ -5,12 +5,10 @@ set(SENTRY_TRANSPORT @SENTRY_TRANSPORT@) set(SENTRY_BUILD_SHARED_LIBS @SENTRY_BUILD_SHARED_LIBS@) set(SENTRY_LINK_PTHREAD @SENTRY_LINK_PTHREAD@) -if(SENTRY_BACKEND STREQUAL "crashpad") - set(SENTRY_CRASHPAD_SYSTEM @SENTRY_CRASHPAD_SYSTEM@) - if(SENTRY_CRASHPAD_SYSTEM) - find_package(crashpad REQUIRED) - else() - include("${CMAKE_CURRENT_LIST_DIR}/sentry_crashpad-targets.cmake") +if(SENTRY_BACKEND STREQUAL "crashpad" AND NOT SENTRY_BUILD_SHARED_LIBS) + include("${CMAKE_CURRENT_LIST_DIR}/sentry_crashpad-targets.cmake") + if(NOT MSVC AND NOT SENTRY_BUILD_SHARED_LIBS) + find_package(ZLIB REQUIRED) endif() endif() From b8fadf90dd1b8c36941125f8706529f77832195a Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Tue, 9 Jan 2024 01:48:55 -0800 Subject: [PATCH 141/207] include to ensure that sentry.h is modularized correctly (#935) --- CHANGELOG.md | 3 ++- include/sentry.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 756bcbc671..fe2a44b614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,14 @@ - Maintain `crashpad` client instance during Native SDK lifecycle. ([#910](https://github.com/getsentry/sentry-native/pull/910)) - Specify correct dependencies for CMake client projects using a system-provided breakpad. ([#926](https://github.com/getsentry/sentry-native/pull/926)) +- Correct the Windows header include used by `sentry.h`, which fixes the build of [Swift bindings](https://github.com/thebrowsercompany/swift-sentry). ([#935](https://github.com/getsentry/sentry-native/pull/935)) **Internal**: - Updated `crashpad` to 2023-11-24. ([#912](https://github.com/getsentry/sentry-native/pull/912), [crashpad#91](https://github.com/getsentry/crashpad/pull/91)) - Fixing `crashpad` build for Windows on ARM64. ([#919](https://github.com/getsentry/sentry-native/pull/919), [crashpad#90](https://github.com/getsentry/crashpad/pull/90), [crashpad#92](https://github.com/getsentry/crashpad/pull/92), [crashpad#93](https://github.com/getsentry/crashpad/pull/93), [crashpad#94](https://github.com/getsentry/crashpad/pull/94)) - Remove options memory leak during consent setting. ([#922](https://github.com/getsentry/sentry-native/pull/922)) - + **Thank you**: Features, fixes and improvements in this release have been contributed by: diff --git a/include/sentry.h b/include/sentry.h index fe9918488e..f52f5ae56e 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -90,7 +90,7 @@ extern "C" { /* context type dependencies */ #ifdef _WIN32 -# include +# include #else # include #endif From 4ec95c0725df5f34440db8fa8d37b4c519fce74e Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 9 Jan 2024 09:49:59 +0000 Subject: [PATCH 142/207] release: 0.7.0 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2a44b614..dcaaf1ebf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.7.0 **Breaking changes**: diff --git a/include/sentry.h b/include/sentry.h index f52f5ae56e..bba0dd0e7f 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.6.7" +#define SENTRY_SDK_VERSION "0.7.0" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 08f2936aba..f12ec9972a 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -58,9 +58,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.6.7", + "version": "0.7.0", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.6.7"}, + {"name": "github:getsentry/sentry-native", "version": "0.7.0"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index fe3740a6cd..ee915f00e1 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -24,7 +24,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.6.7" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.0" ) From 5596cbbaf51c981a6663e4fbda19916f8476763b Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 14 Feb 2024 10:48:01 +0100 Subject: [PATCH 143/207] build: remove obsolete CRASHPAD_WER_ENABLED (#950) * build: remove obsolete CRASHPAD_WER_ENABLED we initially introduced the build flag because of a potentially missing struct member if an older target version than 19041 was selected in a build: https://github.com/getsentry/crashpad/pull/70 But upstream fixed that with a local compatibility struct a while ago: https://chromium.googlesource.com/crashpad/crashpad/+/ca928c8d6b651b7123f1a5cad36dba08ca2416bc That means we can get rid of the build-flag entirely, because this will build on all supported platforms and add a runtime version check for `build == 19041`, so that we don't even register the module (including not polluting the registry) on WER versions that don't support fast-fail crashes . --- CHANGELOG.md | 6 ++ CMakeLists.txt | 27 ++---- examples/example.c | 9 +- external/crashpad | 2 +- src/backends/sentry_backend_crashpad.cpp | 88 +++++++++++------- src/sentry_os.c | 112 ++++++++++++++--------- src/sentry_os.h | 12 +++ 7 files changed, 157 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcaaf1ebf8..6b785c79de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +**Internal**: + +- Remove the `CRASHPAD_WER_ENABLED` build flag. The WER module is now built for all supported Windows targets, and registration is conditional on runtime Windows version checks. ([#950](https://github.com/getsentry/sentry-native/pull/950), [crashpad#96](https://github.com/getsentry/crashpad/pull/96)) + ## 0.7.0 **Breaking changes**: diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e460cdedf..2e8ffcdacf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,7 +423,7 @@ if(SENTRY_BACKEND_CRASHPAD) endif() add_subdirectory(external/crashpad crashpad_build) - if(CRASHPAD_WER_ENABLED) + if(WIN32) add_dependencies(sentry crashpad::wer) endif() @@ -438,9 +438,7 @@ if(SENTRY_BACKEND_CRASHPAD) set_property(TARGET crashpad_snapshot PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_tools PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_util PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - if(CRASHPAD_WER_ENABLED) - set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - endif() + set_property(TARGET crashpad_wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET crashpad_zlib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set_property(TARGET mini_chromium PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() @@ -457,9 +455,7 @@ if(SENTRY_BACKEND_CRASHPAD) set_target_properties(crashpad_util PROPERTIES FOLDER ${SENTRY_FOLDER}) set_target_properties(crashpad_zlib PROPERTIES FOLDER ${SENTRY_FOLDER}) set_target_properties(mini_chromium PROPERTIES FOLDER ${SENTRY_FOLDER}) - if(CRASHPAD_WER_ENABLED) - set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) - endif() + set_target_properties(crashpad_wer PROPERTIES FOLDER ${SENTRY_FOLDER}) endif() target_link_libraries(sentry PRIVATE @@ -472,16 +468,10 @@ if(SENTRY_BACKEND_CRASHPAD) if(WIN32 AND MSVC) sentry_install(FILES $ DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) - if (CRASHPAD_WER_ENABLED) - sentry_install(FILES $ - DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) - endif() + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) endif() add_dependencies(sentry crashpad::handler) - - if(CRASHPAD_WER_ENABLED) - add_compile_definitions(CRASHPAD_WER_ENABLED) - endif() elseif(SENTRY_BACKEND_BREAKPAD) option(SENTRY_BREAKPAD_SYSTEM "Use system breakpad" OFF) if(SENTRY_BREAKPAD_SYSTEM) @@ -575,10 +565,9 @@ if(SENTRY_BUILD_EXAMPLES) if(MSVC) target_compile_options(sentry_example PRIVATE $) - if(CRASHPAD_WER_ENABLED) - # to test handling SEH by-passing exceptions we need to enable the control flow guard - target_compile_options(sentry_example PRIVATE $) - endif() + + # to test handling SEH by-passing exceptions we need to enable the control flow guard + target_compile_options(sentry_example PRIVATE $) endif() # set static runtime if enabled diff --git a/examples/example.c b/examples/example.c index 7e1b71b0cc..28f5355049 100644 --- a/examples/example.c +++ b/examples/example.c @@ -93,7 +93,9 @@ has_arg(int argc, char **argv, const char *arg) return false; } -#ifdef CRASHPAD_WER_ENABLED +#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(__MINGW32__) \ + && !defined(__MINGW64__) + int call_rffe_many_times() { @@ -138,7 +140,7 @@ trigger_fastfail_crash() __fastfail(77); } -#endif // CRASHPAD_WER_ENABLED +#endif #ifdef SENTRY_PLATFORM_AIX // AIX has a null page mapped to the bottom of memory, which means null derefs @@ -301,7 +303,8 @@ main(int argc, char **argv) if (has_arg(argc, argv, "crash")) { trigger_crash(); } -#ifdef CRASHPAD_WER_ENABLED +#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(__MINGW32__) \ + && !defined(__MINGW64__) if (has_arg(argc, argv, "fastfail")) { trigger_fastfail_crash(); } diff --git a/external/crashpad b/external/crashpad index 89991e9910..6c9b05f368 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 89991e9910bc4c0893e45c8cfad0bdd31cc25a5c +Subproject commit 6c9b05f368edb80ac113a54b49007c053eee1c97 diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 96d9616575..711dc5700c 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -7,10 +7,15 @@ extern "C" { #include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_options.h" +#ifdef SENTRY_PLATFORM_WINDOWS +# include "sentry_os.h" +#endif #include "sentry_path.h" #include "sentry_sync.h" #include "sentry_transport.h" -#include "sentry_unix_pageallocator.h" +#ifdef SENTRY_PLATFORM_LINUX +# include "sentry_unix_pageallocator.h" +#endif #include "sentry_utils.h" #include "transports/sentry_disk_transport.h" } @@ -110,6 +115,51 @@ crashpad_backend_user_consent_changed(sentry_backend_t *backend) data->db->GetSettings()->SetUploadsEnabled(!sentry__should_skip_upload()); } +#ifdef SENTRY_PLATFORM_WINDOWS +static void +crashpad_register_wer_module( + const sentry_path_t *absolute_handler_path, const crashpad_state_t *data) +{ + windows_version_t win_ver; + if (!sentry__get_windows_version(&win_ver) || win_ver.build < 19041) { + SENTRY_WARN("Crashpad WER module not registered, because Windows " + "doesn't meet version requirements (build >= 19041)."); + return; + } + sentry_path_t *handler_dir = sentry__path_dir(absolute_handler_path); + sentry_path_t *wer_path = nullptr; + if (handler_dir) { + wer_path = sentry__path_join_str(handler_dir, "crashpad_wer.dll"); + sentry__path_free(handler_dir); + } + + if (wer_path && sentry__path_is_file(wer_path)) { + SENTRY_TRACEF("registering crashpad WER handler " + "\"%" SENTRY_PATH_PRI "\"", + wer_path->path); + + // The WER handler needs to be registered in the registry first. + constexpr DWORD dwOne = 1; + const LSTATUS reg_res = RegSetKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\Windows Error Reporting\\" + L"RuntimeExceptionHelperModules", + wer_path->path, REG_DWORD, &dwOne, sizeof(DWORD)); + if (reg_res != ERROR_SUCCESS) { + SENTRY_WARN("registering crashpad WER handler in registry failed"); + } else { + const std::wstring wer_path_string(wer_path->path); + if (!data->client->RegisterWerModule(wer_path_string)) { + SENTRY_WARN("registering crashpad WER handler module failed"); + } + } + + sentry__path_free(wer_path); + } else { + SENTRY_WARN("crashpad WER handler module not found"); + } +} +#endif + static void crashpad_backend_flush_scope( sentry_backend_t *backend, const sentry_options_t *options) @@ -363,39 +413,9 @@ crashpad_backend_startup( return 1; } -#ifdef CRASHPAD_WER_ENABLED - sentry_path_t *handler_dir = sentry__path_dir(absolute_handler_path); - sentry_path_t *wer_path = nullptr; - if (handler_dir) { - wer_path = sentry__path_join_str(handler_dir, "crashpad_wer.dll"); - sentry__path_free(handler_dir); - } - - if (wer_path && sentry__path_is_file(wer_path)) { - SENTRY_TRACEF("registering crashpad WER handler " - "\"%" SENTRY_PATH_PRI "\"", - wer_path->path); - - // The WER handler needs to be registered in the registry first. - DWORD dwOne = 1; - LSTATUS reg_res = RegSetKeyValueW(HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\Windows Error Reporting\\" - L"RuntimeExceptionHelperModules", - wer_path->path, REG_DWORD, &dwOne, sizeof(DWORD)); - if (reg_res != ERROR_SUCCESS) { - SENTRY_WARN("registering crashpad WER handler in registry failed"); - } else { - std::wstring wer_path_string(wer_path->path); - if (!data->client->RegisterWerModule(wer_path_string)) { - SENTRY_WARN("registering crashpad WER handler module failed"); - } - } - - sentry__path_free(wer_path); - } else { - SENTRY_WARN("crashpad WER handler module not found"); - } -#endif // CRASHPAD_WER_ENABLED +#ifdef SENTRY_PLATFORM_WINDOWS + crashpad_register_wer_module(absolute_handler_path, data); +#endif sentry__path_free(absolute_handler_path); diff --git a/src/sentry_os.c b/src/sentry_os.c index fde7066845..4675575c2b 100644 --- a/src/sentry_os.c +++ b/src/sentry_os.c @@ -8,10 +8,9 @@ # define CURRENT_VERSION "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" void * -sentry__try_file_version(LPCWSTR filename) +sentry__try_file_version(const LPCWSTR filename) { - - DWORD size = GetFileVersionInfoSizeW(filename, NULL); + const DWORD size = GetFileVersionInfoSizeW(filename, NULL); if (!size) { return NULL; } @@ -24,86 +23,115 @@ sentry__try_file_version(LPCWSTR filename) return ffibuf; } -sentry_value_t -sentry__get_os_context(void) +int +sentry__get_kernel_version(windows_version_t *win_ver) { - sentry_value_t os = sentry_value_new_object(); - if (sentry_value_is_null(os)) { - return os; - } - - sentry_value_set_by_key(os, "name", sentry_value_new_string("Windows")); - void *ffibuf = sentry__try_file_version(L"ntoskrnl.exe"); if (!ffibuf) { ffibuf = sentry__try_file_version(L"kernel32.dll"); } if (!ffibuf) { - goto fail; + return 0; } VS_FIXEDFILEINFO *ffi; UINT ffi_size; - if (!VerQueryValueW(ffibuf, L"\\", &ffi, &ffi_size)) { - goto fail; + if (!VerQueryValueW(ffibuf, L"\\", (LPVOID *)&ffi, &ffi_size)) { + sentry_free(ffibuf); + return 0; } ffi->dwFileFlags &= ffi->dwFileFlagsMask; - uint32_t major_version = ffi->dwFileVersionMS >> 16; - uint32_t minor_version = ffi->dwFileVersionMS & 0xffff; - uint32_t build_version = ffi->dwFileVersionLS >> 16; - uint32_t ubr = ffi->dwFileVersionLS & 0xffff; - - char buf[32]; - snprintf(buf, sizeof(buf), "%u.%u.%u.%lu", major_version, minor_version, - build_version, ubr); - sentry_value_set_by_key(os, "kernel_version", sentry_value_new_string(buf)); + win_ver->major = ffi->dwFileVersionMS >> 16; + win_ver->minor = ffi->dwFileVersionMS & 0xffff; + win_ver->build = ffi->dwFileVersionLS >> 16; + win_ver->ubr = ffi->dwFileVersionLS & 0xffff; sentry_free(ffibuf); + return 1; +} + +int +sentry__get_windows_version(windows_version_t *win_ver) +{ // The `CurrentMajorVersionNumber`, `CurrentMinorVersionNumber` and `UBR` // are DWORD, while `CurrentBuild` is a SZ (text). - uint32_t reg_version = 0; DWORD buf_size = sizeof(uint32_t); if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentMajorVersionNumber", RRF_RT_REG_DWORD, NULL, ®_version, &buf_size) - == ERROR_SUCCESS) { - major_version = reg_version; + != ERROR_SUCCESS) { + return 0; } + win_ver->major = reg_version; + buf_size = sizeof(uint32_t); if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentMinorVersionNumber", RRF_RT_REG_DWORD, NULL, ®_version, &buf_size) - == ERROR_SUCCESS) { - minor_version = reg_version; + != ERROR_SUCCESS) { + return 0; } + win_ver->minor = reg_version; + + char buf[32]; buf_size = sizeof(buf); if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild", RRF_RT_REG_SZ, NULL, buf, &buf_size) - == ERROR_SUCCESS) { - build_version = (uint32_t)sentry__strtod_c(buf, NULL); + != ERROR_SUCCESS) { + return 0; } + win_ver->build = (uint32_t)sentry__strtod_c(buf, NULL); + buf_size = sizeof(uint32_t); if (RegGetValueA(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "UBR", RRF_RT_REG_DWORD, NULL, ®_version, &buf_size) - == ERROR_SUCCESS) { - ubr = reg_version; + != ERROR_SUCCESS) { + return 0; } + win_ver->ubr = reg_version; - snprintf(buf, sizeof(buf), "%u.%u.%u", major_version, minor_version, - build_version); - sentry_value_set_by_key(os, "version", sentry_value_new_string(buf)); + return 1; +} - snprintf(buf, sizeof(buf), "%lu", ubr); - sentry_value_set_by_key(os, "build", sentry_value_new_string(buf)); +sentry_value_t +sentry__get_os_context(void) +{ + const sentry_value_t os = sentry_value_new_object(); + if (sentry_value_is_null(os)) { + return os; + } + sentry_value_set_by_key(os, "name", sentry_value_new_string("Windows")); - sentry_value_freeze(os); - return os; + bool at_least_one_key_successful = false; + char buf[32]; + windows_version_t win_ver; + if (sentry__get_kernel_version(&win_ver)) { + at_least_one_key_successful = true; -fail: - sentry_free(ffibuf); + snprintf(buf, sizeof(buf), "%u.%u.%u.%lu", win_ver.major, win_ver.minor, + win_ver.build, win_ver.ubr); + sentry_value_set_by_key( + os, "kernel_version", sentry_value_new_string(buf)); + } + + if (sentry__get_windows_version(&win_ver)) { + at_least_one_key_successful = true; + + snprintf(buf, sizeof(buf), "%u.%u.%u", win_ver.major, win_ver.minor, + win_ver.build); + sentry_value_set_by_key(os, "version", sentry_value_new_string(buf)); + + snprintf(buf, sizeof(buf), "%lu", win_ver.ubr); + sentry_value_set_by_key(os, "build", sentry_value_new_string(buf)); + } + + if (at_least_one_key_successful) { + sentry_value_freeze(os); + return os; + } sentry_value_decref(os); return sentry_value_new_null(); diff --git a/src/sentry_os.h b/src/sentry_os.h index 0a0488fd14..cefff79786 100644 --- a/src/sentry_os.h +++ b/src/sentry_os.h @@ -3,6 +3,18 @@ #include "sentry_boot.h" +#ifdef SENTRY_PLATFORM_WINDOWS +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t build; + uint32_t ubr; +} windows_version_t; + +int sentry__get_kernel_version(windows_version_t *win_ver); +int sentry__get_windows_version(windows_version_t *win_ver); +#endif + sentry_value_t sentry__get_os_context(void); #endif From 0043edf8d97459635da36a2aad1a779d1f704521 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 15 Feb 2024 09:48:46 +0100 Subject: [PATCH 144/207] docs: provide example for breadcrumb data property (#951) --- CHANGELOG.md | 4 ++++ examples/example.c | 19 +++++++++++++++++++ tests/assertions.py | 6 ++++++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b785c79de..50bed07b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Remove the `CRASHPAD_WER_ENABLED` build flag. The WER module is now built for all supported Windows targets, and registration is conditional on runtime Windows version checks. ([#950](https://github.com/getsentry/sentry-native/pull/950), [crashpad#96](https://github.com/getsentry/crashpad/pull/96)) +**Docs**: + +- Add usage of the breadcrumb `data` property to the example. [#951](https://github.com/getsentry/sentry-native/pull/951) + ## 0.7.0 **Breaking changes**: diff --git a/examples/example.c b/examples/example.c index 28f5355049..6abe54eaee 100644 --- a/examples/example.c +++ b/examples/example.c @@ -9,17 +9,21 @@ #include #include #include + #ifdef NDEBUG # undef NDEBUG #endif + #include #ifdef SENTRY_PLATFORM_WINDOWS # include # define sleep_s(SECONDS) Sleep((SECONDS)*1000) #else + # include # include + # define sleep_s(SECONDS) sleep(SECONDS) #endif @@ -260,6 +264,21 @@ main(int argc, char **argv) debug_crumb, "category", sentry_value_new_string("example!")); sentry_value_set_by_key( debug_crumb, "level", sentry_value_new_string("debug")); + + // extend the `http` crumb with (optional) data properties as documented + // here: + // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + sentry_value_t http_data = sentry_value_new_object(); + sentry_value_set_by_key(http_data, "url", + sentry_value_new_string("https://example.com/api/1.0/users")); + sentry_value_set_by_key( + http_data, "method", sentry_value_new_string("GET")); + sentry_value_set_by_key( + http_data, "status_code", sentry_value_new_int32(200)); + sentry_value_set_by_key( + http_data, "reason", sentry_value_new_string("OK")); + sentry_value_set_by_key(debug_crumb, "data", http_data); + sentry_add_breadcrumb(debug_crumb); sentry_value_t nl_crumb diff --git a/tests/assertions.py b/tests/assertions.py index f12ec9972a..63b126ded7 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -142,6 +142,12 @@ def assert_breadcrumb(envelope): "message": "debug crumb", "category": "example!", "level": "debug", + "data": { + "url": "https://example.com/api/1.0/users", + "method": "GET", + "status_code": 200, + "reason": "OK", + }, } assert any(matches(b, expected) for b in event["breadcrumbs"]) From 2ae5de657086c92b0101a51bd85f3dab72b36bf1 Mon Sep 17 00:00:00 2001 From: Karl Heinz Struggl Date: Fri, 16 Feb 2024 01:40:23 -0800 Subject: [PATCH 145/207] chore: add note about experimental state of standalone SDK (#952) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c809b50cd..2577e82c6c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ applications, optimized for C and C++. Sentry allows to add tags, breadcrumbs and arbitrary custom context to enrich error reports. Supports Sentry _20.6.0_ and later. +### Note + +Using the `sentry-native` SDK in a standalone use case is currently an experimental feature. The SDK’s primary function is to fuel our other SDKs, like [`sentry-java`](https://github.com/getsentry/sentry-java) or [`sentry-unreal`](https://github.com/getsentry/sentry-unreal). Support from our side is best effort and we do what we can to respond to issues in a timely fashion, but please understand if we won’t be able to address your issues or feature suggestions. + ## Resources - [SDK Documentation](https://docs.sentry.io/platforms/native/) @@ -239,7 +243,7 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. Builds the Qt integration, which turns Qt log messages into breadcrumbs. - `SENTRY_BREAKPAD_SYSTEM` (Default: OFF): - This instructs the build system to use system-installed breakpad libraries instead of using the in-tree version. + This instructs the build system to use system-installed breakpad libraries instead of using the in-tree version. | Feature | Windows | macOS | Linux | Android | iOS | | ---------- | ------- | ----- | ----- | ------- | --- | From 8f26229922fa702b3159cef94478cf20893e14d1 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Thu, 14 Mar 2024 16:53:22 +0100 Subject: [PATCH 146/207] ci: build zlib for mingw (#964) --- scripts/install-llvm-mingw.ps1 | 45 ++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/scripts/install-llvm-mingw.ps1 b/scripts/install-llvm-mingw.ps1 index 835c884f6a..b827d803bb 100755 --- a/scripts/install-llvm-mingw.ps1 +++ b/scripts/install-llvm-mingw.ps1 @@ -14,7 +14,8 @@ $CurlArguments = '-s', '-Lf', '-o', "${LLVM_MINGW_DL_PATH}", "${LLVM_MINGW_DL_UR $dl_zip_hash = Get-FileHash -LiteralPath "${LLVM_MINGW_DL_PATH}" -Algorithm SHA512 if ($dl_zip_hash.Hash -eq $LLVM_MINGW_DL_SHA512) { Write-Host "Successfully downloaded LLVM-mingw .zip" -} Else { +} +Else { Write-Error "The downloaded LLVM-mingw zip hash '$($dl_zip_hash.Hash)' does not match the expected hash: '$LLVM_MINGW_DL_SHA512'" } @@ -25,10 +26,10 @@ New-Item -ItemType Directory -Force -Path "${LLVM_MINGW_INSTALL_PATH}" Expand-Archive -LiteralPath "${LLVM_MINGW_DL_PATH}" -DestinationPath "${LLVM_MINGW_INSTALL_PATH}" # Export the LLVM-mingw install path $LLVM_MINGW_INSTALL_PATH = "${LLVM_MINGW_INSTALL_PATH}\${LLVM_MINGW_PKG}" -echo "LLVM_MINGW_INSTALL_PATH=${LLVM_MINGW_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +Write-Output "LLVM_MINGW_INSTALL_PATH=${LLVM_MINGW_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Prepend bin path to the system PATH -echo "Path to LLVM-mingw bin folder: ${LLVM_MINGW_INSTALL_PATH}\bin" -echo "${LLVM_MINGW_INSTALL_PATH}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append +Write-Output "Path to LLVM-mingw bin folder: ${LLVM_MINGW_INSTALL_PATH}\bin" +Write-Output "${LLVM_MINGW_INSTALL_PATH}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append # Download ninja-build $NINJA_DL_URL = "https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-win.zip" @@ -39,7 +40,8 @@ $CurlArguments = '-s', '-Lf', '-o', "${NINJA_DL_PATH}", "${NINJA_DL_URL}" $ninja_zip_hash = Get-FileHash -LiteralPath "${NINJA_DL_PATH}" -Algorithm SHA512 if ($ninja_zip_hash.Hash -eq $NINJA_DL_SHA512) { Write-Host "Successfully downloaded Ninja-Build .zip" -} Else { +} +Else { Write-Error "The downloaded Ninja-build zip hash '$($ninja_zip_hash.Hash)' does not match the expected hash: '$NINJA_DL_SHA512'" } @@ -48,8 +50,35 @@ $NINJA_INSTALL_PATH = "$env:GITHUB_WORKSPACE\buildtools\ninja" New-Item -ItemType Directory -Force -Path "${NINJA_INSTALL_PATH}" Expand-Archive -LiteralPath "${NINJA_DL_PATH}" -DestinationPath "${NINJA_INSTALL_PATH}" # Export the NINJA executable path -echo "NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -echo "PATH=${NINJA_INSTALL_PATH};$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +Write-Output "NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +Write-Output "PATH=${NINJA_INSTALL_PATH}; $env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +$env:PATH = "${NINJA_INSTALL_PATH}; $env:PATH" + +# Download zlib +$ZLIB_RELEASE = "1.3.1"; +$ZLIB_RELEASE_ASSET = "zlib131.zip" +$ZLIB_DL_URL = "https://github.com/madler/zlib/releases/download/v${ZLIB_RELEASE}/${ZLIB_RELEASE_ASSET}" +$ZLIB_DL_SHA512 = "1f171880153b0120e1364baaf7d0a17f65086eff279f8f8c8538e5950097d1feee37cc173181676ba1e2aeb4565ba68749c814cd3e25bfb06271bea02feb7d94" +$ZLIB_DL_PATH = "${DL_BASEDIR}\${ZLIB_RELEASE_ASSET}" +$CurlArguments = '-s', '-Lf', '-o', "${ZLIB_DL_PATH}", "${ZLIB_DL_URL}" +& curl.exe @CurlArguments +$zlib_zip_hash = Get-FileHash -LiteralPath "${ZLIB_DL_PATH}" -Algorithm SHA512 +if ($zlib_zip_hash.Hash -eq $ZLIB_DL_SHA512) { + Write-Host "Successfully downloaded ${ZLIB_RELEASE_ASSET}" +} +Else { + Write-Error "The downloaded ${ZLIB_RELEASE_ASSET} hash '$($zlib_zip_hash.Hash)' does not match the expected hash: '$ZLIB_DL_SHA512'" +} + +Write-Host "Extracting zlib source..." +$ZLIB_SOURCE_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib-${ZLIB_RELEASE}" +Expand-Archive -LiteralPath "${ZLIB_DL_PATH}" -DestinationPath "$env:GITHUB_WORKSPACE\buildtools" + +Write-Host "Building zlib source..." +$ZLIB_BUILD_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib_build" +cmake.exe -B "${ZLIB_BUILD_PATH}" -S "${ZLIB_SOURCE_PATH}" -DCMAKE_C_COMPILER="${env:MINGW_PKG_PREFIX}-gcc" -DCMAKE_CXX_COMPILER="${env:MINGW_PKG_PREFIX}-g++" -DCMAKE_RC_COMPILER="${env:MINGW_PKG_PREFIX}-windres" -DCMAKE_ASM_MASM_COMPILER="${env:MINGW_ASM_MASM_COMPILER}" -GNinja +cmake.exe --build "${ZLIB_BUILD_PATH}" --target zlibstatic +Copy-Item "${ZLIB_SOURCE_PATH}\zlib.h" "${ZLIB_BUILD_PATH}" # Add CMAKE_DEFINES -echo "CMAKE_DEFINES=-DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER} -GNinja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +Write-Output "CMAKE_DEFINES=-DZLIB_LIBRARY=${ZLIB_BUILD_PATH}\libzlibstatic.a -DZLIB_INCLUDE_DIR=${ZLIB_BUILD_PATH} -DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER} -GNinja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append From e812e7c72354b1a8f91fcb99a339348cf50aea52 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Fri, 15 Mar 2024 14:56:11 +0100 Subject: [PATCH 147/207] fix: failing clang-asan/llvm-cov tests (#965) * Update Python test dependencies * Update clang on Linux to 15.0.7 * Adapt mmap_rnd_bit according to https://github.com/actions/runner-images/pull/9513 --- .github/workflows/ci.yml | 13 +++++++++---- tests/assertions.py | 5 ++++- tests/requirements.txt | 8 ++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13c748df83..38cb54361a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,14 +49,14 @@ jobs: # RUN_ANALYZER: gcc - name: Linux (clang + asan + llvm-cov) os: ubuntu-22.04 - CC: clang-14 - CXX: clang++-14 + CC: clang-15 + CXX: clang++-15 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: asan,llvm-cov - name: Linux (clang + kcov) os: ubuntu-22.04 - CC: clang-14 - CXX: clang++-14 + CC: clang-15 + CXX: clang++-15 ERROR_ON_WARNINGS: 1 RUN_ANALYZER: kcov - name: Linux (gcc + code-checker + valgrind) @@ -137,6 +137,11 @@ jobs: sudo apt update sudo apt install cmake gcc-7-multilib g++-7-multilib zlib1g-dev:i386 libssl-dev:i386 libcurl4-openssl-dev:i386 + # https://github.com/actions/runner-images/issues/9491 + - name: Decrease vm.mmap_rnd_bit to prevent ASLR ASAN issues + if: ${{ runner.os == 'Linux' && contains(env['RUN_ANALYZER'], 'asan') }} + run: sudo sysctl vm.mmap_rnd_bits=28 + - name: Installing CodeChecker if: ${{ contains(env['RUN_ANALYZER'], 'code-checker') }} run: sudo snap install codechecker --classic diff --git a/tests/assertions.py b/tests/assertions.py index 63b126ded7..de97e9b6ac 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -54,7 +54,10 @@ def assert_meta( "user": {"id": 42, "username": "some_name"}, "transaction": transaction, "tags": {"expected-tag": "some value"}, - "extra": {"extra stuff": "some value", "…unicode key…": "őá…–🤮🚀¿ 한글 테스트"}, + "extra": { + "extra stuff": "some value", + "…unicode key…": "őá…–🤮🚀¿ 한글 테스트", + }, } expected_sdk = { "name": "sentry.native", diff --git a/tests/requirements.txt b/tests/requirements.txt index c90370ba09..f2b95fabdf 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,4 @@ -black==23.3.0 -pytest==7.4.0 -pytest-httpserver==1.0.8 -msgpack==1.0.5 +black==24.2.0 +pytest==8.0.1 +pytest-httpserver==1.0.10 +msgpack==1.0.8 From 9b1bc43fbcd1a89dc26552dd8622543ec142336f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:33:52 +0100 Subject: [PATCH 148/207] build(deps): bump black from 24.2.0 to 24.3.0 in /tests (#967) Bumps [black](https://github.com/psf/black) from 24.2.0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/24.2.0...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index f2b95fabdf..ae5bbcfa37 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,4 @@ -black==24.2.0 +black==24.3.0 pytest==8.0.1 pytest-httpserver==1.0.10 msgpack==1.0.8 From fda2a3727669b0cb2ced613c0de069e2f6733a49 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Thu, 21 Mar 2024 19:41:28 +0200 Subject: [PATCH 149/207] Add user feedback capability to the Native SDK (#966) --- CHANGELOG.md | 4 ++++ examples/example.c | 10 ++++++++++ include/sentry.h | 21 ++++++++++++++++++++ src/sentry_core.c | 34 ++++++++++++++++++++++++++++++++ src/sentry_envelope.c | 30 ++++++++++++++++++++++++++++ src/sentry_envelope.h | 6 ++++++ src/sentry_value.c | 36 ++++++++++++++++++++++++++++++++++ tests/__init__.py | 2 +- tests/assertions.py | 16 +++++++++++++-- tests/test_integration_http.py | 30 ++++++++++++++++++++++++++++ tests/unit/test_envelopes.c | 29 +++++++++++++++++++++++++++ tests/unit/test_value.c | 21 ++++++++++++++++++++ tests/unit/tests.inc | 2 ++ 13 files changed, 238 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50bed07b51..5f20a790f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Features** + + Add user feedback capability to the Native SDK ([#966](https://github.com/getsentry/sentry-native/pull/966)) + **Internal**: - Remove the `CRASHPAD_WER_ENABLED` build flag. The WER module is now built for all supported Windows targets, and registration is conditional on runtime Windows version checks. ([#950](https://github.com/getsentry/sentry-native/pull/950), [crashpad#96](https://github.com/getsentry/crashpad/pull/96)) diff --git a/examples/example.c b/examples/example.c index 6abe54eaee..36a84bb44a 100644 --- a/examples/example.c +++ b/examples/example.c @@ -365,6 +365,16 @@ main(int argc, char **argv) sentry_capture_event(event); } + if (has_arg(argc, argv, "capture-user-feedback")) { + sentry_value_t event = sentry_value_new_message_event( + SENTRY_LEVEL_INFO, "my-logger", "Hello user feedback!"); + sentry_uuid_t event_id = sentry_capture_event(event); + + sentry_value_t user_feedback = sentry_value_new_user_feedback( + &event_id, "some-name", "some-email", "some-comment"); + + sentry_capture_user_feedback(user_feedback); + } if (has_arg(argc, argv, "capture-transaction")) { sentry_transaction_context_t *tx_ctx diff --git a/include/sentry.h b/include/sentry.h index bba0dd0e7f..17fc8ec19d 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1888,6 +1888,27 @@ SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name( SENTRY_EXPERIMENTAL_API void sentry_transaction_set_name_n( sentry_transaction_t *transaction, const char *name, size_t name_len); +/** + * Creates a new User Feedback with a specific name, email and comments. + * + * See https://develop.sentry.dev/sdk/envelopes/#user-feedback + * + * User Feedback has to be associated with a specific event that has been + * sent to Sentry earlier. + */ +SENTRY_API sentry_value_t sentry_value_new_user_feedback( + const sentry_uuid_t *uuid, const char *name, const char *email, + const char *comments); +SENTRY_API sentry_value_t sentry_value_new_user_feedback_n( + const sentry_uuid_t *uuid, const char *name, size_t name_len, + const char *email, size_t email_len, const char *comments, + size_t comments_len); + +/** + * Captures a manually created User Feedback and sends it to Sentry. + */ +SENTRY_API void sentry_capture_user_feedback(sentry_value_t user_feedback); + /** * The status of a Span or Transaction. * diff --git a/src/sentry_core.c b/src/sentry_core.c index 24cf525393..c9ef8cd45f 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -550,6 +550,26 @@ sentry__prepare_transaction(const sentry_options_t *options, return NULL; } +sentry_envelope_t * +sentry__prepare_user_feedback(sentry_value_t user_feedback) +{ + sentry_envelope_t *envelope = NULL; + + envelope = sentry__envelope_new(); + if (!envelope + || !sentry__envelope_add_user_feedback(envelope, user_feedback)) { + goto fail; + } + + return envelope; + +fail: + SENTRY_WARN("dropping user feedback"); + sentry_envelope_free(envelope); + sentry_value_decref(user_feedback); + return NULL; +} + void sentry_handle_exception(const sentry_ucontext_t *uctx) { @@ -1120,6 +1140,20 @@ sentry_span_finish(sentry_span_t *opaque_span) sentry__span_decref(opaque_span); } +void +sentry_capture_user_feedback(sentry_value_t user_feedback) +{ + sentry_envelope_t *envelope = NULL; + + SENTRY_WITH_OPTIONS (options) { + envelope = sentry__prepare_user_feedback(user_feedback); + if (envelope) { + sentry__capture_envelope(options->transport, envelope); + } + } + sentry_value_decref(user_feedback); +} + int sentry_get_crashed_last_run(void) { diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 4b68a27871..b71242c1df 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -295,6 +295,36 @@ sentry__envelope_add_transaction( return item; } +sentry_envelope_item_t * +sentry__envelope_add_user_feedback( + sentry_envelope_t *envelope, sentry_value_t user_feedback) +{ + sentry_envelope_item_t *item = envelope_add_item(envelope); + if (!item) { + return NULL; + } + + sentry_jsonwriter_t *jw = sentry__jsonwriter_new(NULL); + if (!jw) { + return NULL; + } + + sentry_value_t event_id = sentry__ensure_event_id(user_feedback, NULL); + + sentry__jsonwriter_write_value(jw, user_feedback); + item->payload = sentry__jsonwriter_into_string(jw, &item->payload_len); + + sentry__envelope_item_set_header( + item, "type", sentry_value_new_string("user_report")); + sentry_value_t length = sentry_value_new_int32((int32_t)item->payload_len); + sentry__envelope_item_set_header(item, "length", length); + + sentry_value_incref(event_id); + sentry__envelope_set_header(envelope, "event_id", event_id); + + return item; +} + sentry_envelope_item_t * sentry__envelope_add_session( sentry_envelope_t *envelope, const sentry_session_t *session) diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index b5a8f1ab09..8ea986827d 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -42,6 +42,12 @@ sentry_envelope_item_t *sentry__envelope_add_event( sentry_envelope_item_t *sentry__envelope_add_transaction( sentry_envelope_t *envelope, sentry_value_t transaction); +/** + * Add a user feedback to this envelope. + */ +sentry_envelope_item_t *sentry__envelope_add_user_feedback( + sentry_envelope_t *envelope, sentry_value_t user_feedback); + /** * Add a session to this envelope. */ diff --git a/src/sentry_value.c b/src/sentry_value.c index e46eb79f5e..3c3e344c9b 100644 --- a/src/sentry_value.c +++ b/src/sentry_value.c @@ -1265,6 +1265,42 @@ sentry_value_new_stacktrace(void **ips, size_t len) return stacktrace; } +sentry_value_t +sentry_value_new_user_feedback(const sentry_uuid_t *uuid, const char *name, + const char *email, const char *comments) +{ + size_t name_len = name ? strlen(name) : 0; + size_t email_len = email ? strlen(email) : 0; + size_t comments_len = email ? strlen(comments) : 0; + return sentry_value_new_user_feedback_n( + uuid, name, name_len, email, email_len, comments, comments_len); +} + +sentry_value_t +sentry_value_new_user_feedback_n(const sentry_uuid_t *uuid, const char *name, + size_t name_len, const char *email, size_t email_len, const char *comments, + size_t comments_len) +{ + sentry_value_t rv = sentry_value_new_object(); + + sentry_value_set_by_key(rv, "event_id", sentry__value_new_uuid(uuid)); + + if (name) { + sentry_value_set_by_key( + rv, "name", sentry_value_new_string_n(name, name_len)); + } + if (email) { + sentry_value_set_by_key( + rv, "email", sentry_value_new_string_n(email, email_len)); + } + if (comments) { + sentry_value_set_by_key( + rv, "comments", sentry_value_new_string_n(comments, comments_len)); + } + + return rv; +} + static sentry_value_t sentry__get_or_insert_values_list(sentry_value_t parent, const char *key) { diff --git a/tests/__init__.py b/tests/__init__.py index b34e1c2ea5..d7374c5b45 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -254,7 +254,7 @@ def deserialize_from( headers = json.loads(line) length = headers["length"] payload = f.read(length) - if headers.get("type") in ["event", "session", "transaction"]: + if headers.get("type") in ["event", "session", "transaction", "user_report"]: rv = cls(headers=headers, payload=PayloadRef(json=json.loads(payload))) else: rv = cls(headers=headers, payload=payload) diff --git a/tests/assertions.py b/tests/assertions.py index de97e9b6ac..041188378a 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -38,6 +38,18 @@ def assert_session(envelope, extra_assertion=None): assert_matches(session, extra_assertion) +def assert_user_feedback(envelope): + user_feedback = None + for item in envelope: + if item.headers.get("type") == "user_report" and item.payload.json is not None: + user_feedback = item.payload.json + + assert user_feedback is not None + assert user_feedback["name"] == "some-name" + assert user_feedback["email"] == "some-email" + assert user_feedback["comments"] == "some-comment" + + def assert_meta( envelope, release="test-example-release", @@ -177,12 +189,12 @@ def assert_timestamp(ts, now=datetime.utcnow()): assert ts[:11] == now.isoformat()[:11] -def assert_event(envelope): +def assert_event(envelope, message="Hello World!"): event = envelope.get_event() expected = { "level": "info", "logger": "my-logger", - "message": {"formatted": "Hello World!"}, + "message": {"formatted": message}, } assert_matches(event, expected) assert_timestamp(event["timestamp"]) diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index ee915f00e1..7c1e76cff6 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -16,6 +16,7 @@ assert_exception, assert_inproc_crash, assert_session, + assert_user_feedback, assert_minidump, assert_breakpad_crash, ) @@ -117,6 +118,35 @@ def test_capture_and_session_http(cmake, httpserver): assert_session(envelope, {"status": "exited", "errors": 0}) +def test_user_feedback_http(cmake, httpserver): + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) + + httpserver.expect_request( + "/api/123456/envelope/", + headers={"x-sentry-auth": auth_header}, + ).respond_with_data("OK") + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + + run( + tmp_path, + "sentry_example", + ["log", "capture-user-feedback"], + check=True, + env=env, + ) + + assert len(httpserver.log) == 2 + output = httpserver.log[0][0].get_data() + envelope = Envelope.deserialize(output) + + assert_event(envelope, "Hello user feedback!") + + output = httpserver.log[1][0].get_data() + envelope = Envelope.deserialize(output) + + assert_user_feedback(envelope) + + def test_exception_and_session_http(cmake, httpserver): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 7be59db4a5..5aa1170d56 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -75,6 +75,35 @@ SENTRY_TEST(basic_http_request_preparation_for_transaction) sentry__dsn_decref(dsn); } +SENTRY_TEST(basic_http_request_preparation_for_user_feedback) +{ + sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.invalid/42"); + + sentry_uuid_t event_id + = sentry_uuid_from_string("c993afb6-b4ac-48a6-b61b-2558e601d65d"); + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry_value_t user_feedback = sentry_value_new_user_feedback( + &event_id, "some-name", "some-email", "some-comment"); + sentry__envelope_add_user_feedback(envelope, user_feedback); + + sentry_prepared_http_request_t *req + = sentry__prepare_http_request(envelope, dsn, NULL, NULL); + TEST_CHECK_STRING_EQUAL(req->method, "POST"); + TEST_CHECK_STRING_EQUAL( + req->url, "https://sentry.invalid:443/api/42/envelope/"); + TEST_CHECK_STRING_EQUAL(req->body, + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" + "{\"type\":\"user_report\",\"length\":117}\n" + "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"name\":" + "\"some-name\",\"email\":\"some-email\",\"comments\":" + "\"some-comment\"}"); + sentry__prepared_http_request_free(req); + sentry_value_decref(user_feedback); + sentry_envelope_free(envelope); + + sentry__dsn_decref(dsn); +} + SENTRY_TEST(basic_http_request_preparation_for_event_with_attachment) { sentry_dsn_t *dsn = sentry__dsn_new("https://foo@sentry.invalid/42"); diff --git a/tests/unit/test_value.c b/tests/unit/test_value.c index 92e195d580..d1012602b6 100644 --- a/tests/unit/test_value.c +++ b/tests/unit/test_value.c @@ -772,3 +772,24 @@ SENTRY_TEST(thread_without_name_still_valid) test_name); sentry_value_decref(thread); } + +SENTRY_TEST(user_feedback_is_valid) +{ + sentry_uuid_t event_id + = sentry_uuid_from_string("c993afb6-b4ac-48a6-b61b-2558e601d65d"); + sentry_value_t user_feedback = sentry_value_new_user_feedback( + &event_id, "some-name", "some-email", "some-comment"); + + TEST_CHECK(!sentry_value_is_null(user_feedback)); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(user_feedback, "name")), + "some-name"); + TEST_CHECK_STRING_EQUAL( + sentry_value_as_string(sentry_value_get_by_key(user_feedback, "email")), + "some-email"); + TEST_CHECK_STRING_EQUAL(sentry_value_as_string(sentry_value_get_by_key( + user_feedback, "comments")), + "some-comment"); + + sentry_value_decref(user_feedback); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 1770edfc11..d7f9fae29c 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -9,6 +9,7 @@ XX(basic_http_request_preparation_for_event) XX(basic_http_request_preparation_for_event_with_attachment) XX(basic_http_request_preparation_for_minidump) XX(basic_http_request_preparation_for_transaction) +XX(basic_http_request_preparation_for_user_feedback) XX(basic_spans) XX(basic_tracing_context) XX(basic_transaction) @@ -107,6 +108,7 @@ XX(update_from_header_null_ctx) XX(url_parsing_complete) XX(url_parsing_invalid) XX(url_parsing_partial) +XX(user_feedback_is_valid) XX(uuid_api) XX(uuid_v4) XX(value_bool) From 9bc0fc75e34cb43e7019d76b0decb6c0cddbfd34 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Fri, 22 Mar 2024 09:23:45 +0000 Subject: [PATCH 150/207] release: 0.7.1 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f20a790f5..480d4fdabe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.7.1 **Features** diff --git a/include/sentry.h b/include/sentry.h index 17fc8ec19d..a6ceadc415 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.7.0" +#define SENTRY_SDK_VERSION "0.7.1" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 041188378a..e3a0c11c9f 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -73,9 +73,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.7.0", + "version": "0.7.1", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.7.0"}, + {"name": "github:getsentry/sentry-native", "version": "0.7.1"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 7c1e76cff6..0cbd304dab 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -25,7 +25,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.0" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.1" ) From 9f9436aee8f1dbee24d8279c9889ab886ebc5cec Mon Sep 17 00:00:00 2001 From: Strive-Sun <50010647+Strive-Sun@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:08:51 +0800 Subject: [PATCH 151/207] feat: add gzip transport encoding (#954) --------- Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 10 ++++- CMakeLists.txt | 30 ++++++++------- external/crashpad | 2 +- src/sentry_transport.c | 85 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480d4fdabe..feda939274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## 0.7.1 -**Features** +**Features**: - Add user feedback capability to the Native SDK ([#966](https://github.com/getsentry/sentry-native/pull/966)) +- Add optional Gzip transport compression via build option `SENTRY_TRANSPORT_COMPRESSION`. Requires system `zlib`. ([#954](https://github.com/getsentry/sentry-native/pull/954)) +- Add user feedback capability to the Native SDK ([#966](https://github.com/getsentry/sentry-native/pull/966)) **Internal**: @@ -14,6 +15,10 @@ - Add usage of the breadcrumb `data` property to the example. [#951](https://github.com/getsentry/sentry-native/pull/951) +**Thank you**: + +- [@Strive-Sun](https://github.com/Strive-Sun) + ## 0.7.0 **Breaking changes**: @@ -39,6 +44,7 @@ Features, fixes and improvements in this release have been contributed by: - [@compnerd](https://github.com/compnerd) - [@stima](https://github.com/stima) +- [@hyp](https://github.com/hyp) ## 0.6.7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e8ffcdacf..3996e83db8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ endif() option(SENTRY_PIC "Build sentry (and dependent) libraries as position independent libraries" ON) +option(SENTRY_TRANSPORT_COMPRESSION "Enable transport gzip compression" OFF) + option(SENTRY_BUILD_TESTS "Build sentry-native tests" "${SENTRY_MAIN_PROJECT}") option(SENTRY_BUILD_EXAMPLES "Build sentry-native example(s)" "${SENTRY_MAIN_PROJECT}") @@ -278,17 +280,20 @@ if(SENTRY_TRANSPORT_CURL) find_package(CURL REQUIRED COMPONENTS AsynchDNS) endif() - if(TARGET CURL::libcurl) # Only available in cmake 3.12+ - target_link_libraries(sentry PRIVATE CURL::libcurl) - else() - # Needed for cmake < 3.12 support (cmake 3.12 introduced the target CURL::libcurl) - target_include_directories(sentry PRIVATE ${CURL_INCLUDE_DIR}) - # The exported sentry target must not contain any path of the build machine, therefore use generator expressions - string(REPLACE ";" "$" GENEX_CURL_LIBRARIES "${CURL_LIBRARIES}") - string(REPLACE ";" "$" GENEX_CURL_COMPILE_DEFINITIONS "${CURL_COMPILE_DEFINITIONS}") - target_link_libraries(sentry PRIVATE $) - target_compile_definitions(sentry PRIVATE $) + target_link_libraries(sentry PRIVATE CURL::libcurl) +endif() + +if(SENTRY_TRANSPORT_COMPRESSION) + if(NOT ZLIB_FOUND) + find_package(ZLIB REQUIRED) endif() + + if(SENTRY_BACKEND_CRASHPAD) + set(CRASHPAD_ZLIB_SYSTEM ON CACHE BOOL "Force CRASHPAD_ZLIB_SYSTEM when enabling transport compression" FORCE) + endif() + + target_link_libraries(sentry PRIVATE ZLIB::ZLIB) + target_compile_definitions(sentry PRIVATE SENTRY_TRANSPORT_COMPRESSION) endif() set_property(TARGET sentry PROPERTY C_VISIBILITY_PRESET hidden) @@ -413,9 +418,6 @@ if(SENTRY_WITH_LIBUNWINDSTACK) endif() if(SENTRY_BACKEND_CRASHPAD) - # FIXME: required for cmake 3.12 and lower: - # - NEW behavior lets normal variable override option - cmake_policy(SET CMP0077 NEW) if(SENTRY_BUILD_SHARED_LIBS) set(CRASHPAD_ENABLE_INSTALL OFF CACHE BOOL "Enable crashpad installation" FORCE) else() @@ -593,4 +595,4 @@ endif() if(SENTRY_BUILD_SHARED_LIBS) target_link_libraries(sentry PRIVATE "$<$,$>:-Wl,--build-id=sha1,--version-script=${PROJECT_SOURCE_DIR}/src/exports.map>") -endif() \ No newline at end of file +endif() diff --git a/external/crashpad b/external/crashpad index 6c9b05f368..6bde832e99 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 6c9b05f368edb80ac113a54b49007c053eee1c97 +Subproject commit 6bde832e99297b2db4c98676067368b6a8428ecc diff --git a/src/sentry_transport.c b/src/sentry_transport.c index ea62c56204..06ba3524b2 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -5,9 +5,19 @@ #include "sentry_ratelimiter.h" #include "sentry_string.h" +#ifdef SENTRY_TRANSPORT_COMPRESSION +# include "zlib.h" +#endif + #define ENVELOPE_MIME "application/x-sentry-envelope" +#ifdef SENTRY_TRANSPORT_COMPRESSION +// The headers we use are: `x-sentry-auth`, `content-type`, `content-encoding`, +// `content-length` +# define MAX_HTTP_HEADERS 4 +#else // The headers we use are: `x-sentry-auth`, `content-type`, `content-length` -#define MAX_HTTP_HEADERS 3 +# define MAX_HTTP_HEADERS 3 +#endif typedef struct sentry_transport_s { void (*send_envelope_func)(sentry_envelope_t *envelope, void *state); @@ -148,6 +158,56 @@ sentry_transport_free(sentry_transport_t *transport) sentry_free(transport); } +#ifdef SENTRY_TRANSPORT_COMPRESSION +static bool +gzipped_with_compression(const char *body, const size_t body_len, + char **compressed_body, size_t *compressed_body_len) +{ + if (!body || body_len == 0) { + return false; + } + + z_stream stream; + memset(&stream, 0, sizeof(stream)); + stream.next_in = body; + stream.avail_in = body_len; + + int err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + MAX_WBITS + 16, 9, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + SENTRY_TRACEF("deflateInit2 failed: %d", err); + return false; + } + + size_t len = compressBound(body_len); + char *buffer = sentry_malloc(len); + if (!buffer) { + deflateEnd(&stream); + return false; + } + + while (err == Z_OK) { + stream.next_out = buffer + stream.total_out; + stream.avail_out = len - stream.total_out; + err = deflate(&stream, Z_FINISH); + } + + if (err != Z_STREAM_END) { + SENTRY_TRACEF("deflate failed: %d", err); + sentry_free(buffer); + buffer = NULL; + deflateEnd(&stream); + return false; + } + + *compressed_body_len = stream.total_out; + *compressed_body = buffer; + + deflateEnd(&stream); + return true; +} +#endif + sentry_prepared_http_request_t * sentry__prepare_http_request(sentry_envelope_t *envelope, const sentry_dsn_t *dsn, const sentry_rate_limiter_t *rl, @@ -165,6 +225,23 @@ sentry__prepare_http_request(sentry_envelope_t *envelope, return NULL; } + bool compressed = false; +#ifdef SENTRY_TRANSPORT_COMPRESSION + char *compressed_body = NULL; + size_t compressed_body_len = 0; + compressed = gzipped_with_compression( + body, body_len, &compressed_body, &compressed_body_len); + if (compressed) { + if (body_owned) { + sentry_free(body); + body_owned = false; + } + body = compressed_body; + body_len = compressed_body_len; + body_owned = true; + } +#endif + sentry_prepared_http_request_t *req = SENTRY_MAKE(sentry_prepared_http_request_t); if (!req) { @@ -196,6 +273,12 @@ sentry__prepare_http_request(sentry_envelope_t *envelope, h->key = "content-type"; h->value = sentry__string_clone(ENVELOPE_MIME); + if (compressed) { + h = &req->headers[req->headers_len++]; + h->key = "content-encoding"; + h->value = sentry__string_clone("gzip"); + } + h = &req->headers[req->headers_len++]; h->key = "content-length"; h->value = sentry__int64_to_string((int64_t)body_len); From fda905adcc5cb6a4bac773da6982c6f4b57bff95 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 25 Mar 2024 14:40:28 +0100 Subject: [PATCH 152/207] fix: exclude custom_logger from transport test-suite (#968) --- include/sentry.h | 5 +++++ tests/test_unit.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/include/sentry.h b/include/sentry.h index a6ceadc415..16b7e03fc7 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -1068,6 +1068,11 @@ typedef void (*sentry_logger_function_t)( * Sets the sentry-native logger function. * * Used for logging debug events when the `debug` option is set to true. + * + * Note: Multiple threads may invoke your `func`. If you plan to mutate any data + * inside the `userdata` argument after initialization, you must ensure proper + * synchronization inside the logger function. + * */ SENTRY_API void sentry_options_set_logger( sentry_options_t *opts, sentry_logger_function_t func, void *userdata); diff --git a/tests/test_unit.py b/tests/test_unit.py index f75a719d77..3633efec92 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -14,6 +14,9 @@ def test_unit(cmake, unittest): @pytest.mark.skipif(not has_http, reason="tests need http transport") def test_unit_transport(cmake, unittest): + if unittest in ["custom_logger"]: + pytest.skip("excluded from transport test-suite") + cwd = cmake(["sentry_test_unit"], {"SENTRY_BACKEND": "none"}) env = dict(os.environ) run(cwd, "sentry_test_unit", ["--no-summary", unittest], check=True, env=env) From 0a11dc0c0d499771ace9e2d6d820c507b3eb8a73 Mon Sep 17 00:00:00 2001 From: Joel Winarske Date: Fri, 29 Mar 2024 11:32:50 -0700 Subject: [PATCH 153/207] fix: Linux build targeting RISC-V (#972) --------- Signed-off-by: Joel Winarske Co-authored-by: Mischan Toosarani-Hausberger --- CHANGELOG.md | 22 ++++++++++++++------ src/modulefinder/sentry_modulefinder_linux.c | 10 +++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feda939274..bd535ce5af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,25 @@ # Changelog -## 0.7.1 +## Unreleased **Features**: - Add optional Gzip transport compression via build option `SENTRY_TRANSPORT_COMPRESSION`. Requires system `zlib`. ([#954](https://github.com/getsentry/sentry-native/pull/954)) -- Add user feedback capability to the Native SDK ([#966](https://github.com/getsentry/sentry-native/pull/966)) + +**Fixes**: + +- Fix the Linux build when targeting RISC-V. ([#972](https://github.com/getsentry/sentry-native/pull/972)) + +**Thank you**: + +- [@Strive-Sun](https://github.com/Strive-Sun) +- [@jwinarske](https://github.com/jwinarske) + +## 0.7.1 + +**Features**: + +- Add user feedback capability to the Native SDK. ([#966](https://github.com/getsentry/sentry-native/pull/966)) **Internal**: @@ -15,10 +29,6 @@ - Add usage of the breadcrumb `data` property to the example. [#951](https://github.com/getsentry/sentry-native/pull/951) -**Thank you**: - -- [@Strive-Sun](https://github.com/Strive-Sun) - ## 0.7.0 **Breaking changes**: diff --git a/src/modulefinder/sentry_modulefinder_linux.c b/src/modulefinder/sentry_modulefinder_linux.c index 1d459c12f0..1ae8e2b730 100644 --- a/src/modulefinder/sentry_modulefinder_linux.c +++ b/src/modulefinder/sentry_modulefinder_linux.c @@ -568,12 +568,14 @@ try_append_module(sentry_value_t modules, const sentry_module_t *module) } // copied from: -// https://github.com/google/breakpad/blob/216cea7bca53fa441a3ee0d0f5fd339a3a894224/src/client/linux/minidump_writer/linux_dumper.h#L61-L70 +// https://github.com/google/breakpad/blob/eb28e7ed9c1c1e1a717fa34ce0178bf471a6311f/src/client/linux/minidump_writer/linux_dumper.h#L61-L69 #if defined(__i386) || defined(__ARM_EABI__) \ - || (defined(__mips__) && _MIPS_SIM == _ABIO32) + || (defined(__mips__) && _MIPS_SIM == _ABIO32) \ + || (defined(__riscv) && __riscv_xlen == 32) typedef Elf32_auxv_t elf_aux_entry; -#elif defined(__x86_64) || defined(__aarch64__) || defined(__powerpc64__) \ - || (defined(__mips__) && _MIPS_SIM != _ABIO32) +#elif defined(__x86_64) || defined(__aarch64__) \ + || (defined(__mips__) && _MIPS_SIM != _ABIO32) \ + || (defined(__riscv) && __riscv_xlen == 64) typedef Elf64_auxv_t elf_aux_entry; #endif From aa32df6840c38a8829f8e44135835ca5c7b083b4 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 2 Apr 2024 10:11:55 +0200 Subject: [PATCH 154/207] test: integration-tests for transport compression (#969) --- .github/workflows/ci.yml | 5 ++ scripts/install-llvm-mingw.ps1 | 42 +++----------- scripts/install-zlib.ps1 | 44 +++++++++++++++ src/sentry_transport.c | 14 +++-- tests/__init__.py | 12 +++- tests/assertions.py | 12 +++- tests/cmake.py | 7 ++- tests/requirements.txt | 3 +- tests/test_integration_crashpad.py | 37 +++++++++---- tests/test_integration_http.py | 88 +++++++++++++++++++++++++----- tests/unit/test_envelopes.c | 11 +++- 11 files changed, 201 insertions(+), 74 deletions(-) create mode 100644 scripts/install-zlib.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38cb54361a..833b4269b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,6 +158,11 @@ jobs: MINGW_ASM_MASM_COMPILER: ${{ matrix.MINGW_ASM_MASM_COMPILER }} run: . "scripts\install-llvm-mingw.ps1" + - name: Set up zlib for Windows + if: ${{ runner.os == 'Windows' }} + shell: powershell + run: . "scripts\install-zlib.ps1" + - name: Installing Android SDK Dependencies if: ${{ env['ANDROID_API'] }} run: | diff --git a/scripts/install-llvm-mingw.ps1 b/scripts/install-llvm-mingw.ps1 index b827d803bb..2af029272b 100755 --- a/scripts/install-llvm-mingw.ps1 +++ b/scripts/install-llvm-mingw.ps1 @@ -26,10 +26,10 @@ New-Item -ItemType Directory -Force -Path "${LLVM_MINGW_INSTALL_PATH}" Expand-Archive -LiteralPath "${LLVM_MINGW_DL_PATH}" -DestinationPath "${LLVM_MINGW_INSTALL_PATH}" # Export the LLVM-mingw install path $LLVM_MINGW_INSTALL_PATH = "${LLVM_MINGW_INSTALL_PATH}\${LLVM_MINGW_PKG}" -Write-Output "LLVM_MINGW_INSTALL_PATH=${LLVM_MINGW_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +"LLVM_MINGW_INSTALL_PATH=${LLVM_MINGW_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Prepend bin path to the system PATH -Write-Output "Path to LLVM-mingw bin folder: ${LLVM_MINGW_INSTALL_PATH}\bin" -Write-Output "${LLVM_MINGW_INSTALL_PATH}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append +Write-Host "Path to LLVM-mingw bin folder: ${LLVM_MINGW_INSTALL_PATH}\bin" +"${LLVM_MINGW_INSTALL_PATH}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append # Download ninja-build $NINJA_DL_URL = "https://github.com/ninja-build/ninja/releases/download/v1.11.1/ninja-win.zip" @@ -49,36 +49,10 @@ Write-Host "Extracting Ninja-Build..." $NINJA_INSTALL_PATH = "$env:GITHUB_WORKSPACE\buildtools\ninja" New-Item -ItemType Directory -Force -Path "${NINJA_INSTALL_PATH}" Expand-Archive -LiteralPath "${NINJA_DL_PATH}" -DestinationPath "${NINJA_INSTALL_PATH}" -# Export the NINJA executable path -Write-Output "NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -Write-Output "PATH=${NINJA_INSTALL_PATH}; $env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -$env:PATH = "${NINJA_INSTALL_PATH}; $env:PATH" - -# Download zlib -$ZLIB_RELEASE = "1.3.1"; -$ZLIB_RELEASE_ASSET = "zlib131.zip" -$ZLIB_DL_URL = "https://github.com/madler/zlib/releases/download/v${ZLIB_RELEASE}/${ZLIB_RELEASE_ASSET}" -$ZLIB_DL_SHA512 = "1f171880153b0120e1364baaf7d0a17f65086eff279f8f8c8538e5950097d1feee37cc173181676ba1e2aeb4565ba68749c814cd3e25bfb06271bea02feb7d94" -$ZLIB_DL_PATH = "${DL_BASEDIR}\${ZLIB_RELEASE_ASSET}" -$CurlArguments = '-s', '-Lf', '-o', "${ZLIB_DL_PATH}", "${ZLIB_DL_URL}" -& curl.exe @CurlArguments -$zlib_zip_hash = Get-FileHash -LiteralPath "${ZLIB_DL_PATH}" -Algorithm SHA512 -if ($zlib_zip_hash.Hash -eq $ZLIB_DL_SHA512) { - Write-Host "Successfully downloaded ${ZLIB_RELEASE_ASSET}" -} -Else { - Write-Error "The downloaded ${ZLIB_RELEASE_ASSET} hash '$($zlib_zip_hash.Hash)' does not match the expected hash: '$ZLIB_DL_SHA512'" -} -Write-Host "Extracting zlib source..." -$ZLIB_SOURCE_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib-${ZLIB_RELEASE}" -Expand-Archive -LiteralPath "${ZLIB_DL_PATH}" -DestinationPath "$env:GITHUB_WORKSPACE\buildtools" - -Write-Host "Building zlib source..." -$ZLIB_BUILD_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib_build" -cmake.exe -B "${ZLIB_BUILD_PATH}" -S "${ZLIB_SOURCE_PATH}" -DCMAKE_C_COMPILER="${env:MINGW_PKG_PREFIX}-gcc" -DCMAKE_CXX_COMPILER="${env:MINGW_PKG_PREFIX}-g++" -DCMAKE_RC_COMPILER="${env:MINGW_PKG_PREFIX}-windres" -DCMAKE_ASM_MASM_COMPILER="${env:MINGW_ASM_MASM_COMPILER}" -GNinja -cmake.exe --build "${ZLIB_BUILD_PATH}" --target zlibstatic -Copy-Item "${ZLIB_SOURCE_PATH}\zlib.h" "${ZLIB_BUILD_PATH}" +# Export the NINJA executable path +"NINJA_INSTALL_PATH=${NINJA_INSTALL_PATH}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +"PATH=${NINJA_INSTALL_PATH}; $env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -# Add CMAKE_DEFINES -Write-Output "CMAKE_DEFINES=-DZLIB_LIBRARY=${ZLIB_BUILD_PATH}\libzlibstatic.a -DZLIB_INCLUDE_DIR=${ZLIB_BUILD_PATH} -DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER} -GNinja" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +# export CMAKE_DEFINES to the runner environment +"CMAKE_DEFINES=-DCMAKE_C_COMPILER=${env:MINGW_PKG_PREFIX}-gcc -DCMAKE_CXX_COMPILER=${env:MINGW_PKG_PREFIX}-g++ -DCMAKE_RC_COMPILER=${env:MINGW_PKG_PREFIX}-windres -DCMAKE_ASM_MASM_COMPILER=${env:MINGW_ASM_MASM_COMPILER}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append \ No newline at end of file diff --git a/scripts/install-zlib.ps1 b/scripts/install-zlib.ps1 new file mode 100644 index 0000000000..c9b543638b --- /dev/null +++ b/scripts/install-zlib.ps1 @@ -0,0 +1,44 @@ +$DL_BASEDIR = "$env:GITHUB_WORKSPACE\dl" +if (!(Test-Path -Path "$DL_BASEDIR")) { New-Item -ItemType Directory -Force -Path "$DL_BASEDIR" } + +$ZLIB_RELEASE = "1.3.1"; +$ZLIB_RELEASE_ASSET = "zlib131.zip" +$ZLIB_DL_URL = "https://github.com/madler/zlib/releases/download/v${ZLIB_RELEASE}/${ZLIB_RELEASE_ASSET}" +$ZLIB_DL_SHA512 = "1f171880153b0120e1364baaf7d0a17f65086eff279f8f8c8538e5950097d1feee37cc173181676ba1e2aeb4565ba68749c814cd3e25bfb06271bea02feb7d94" +$ZLIB_DL_PATH = "${DL_BASEDIR}\${ZLIB_RELEASE_ASSET}" +$CurlArguments = '-s', '-Lf', '-o', "${ZLIB_DL_PATH}", "${ZLIB_DL_URL}" +& curl.exe @CurlArguments +$zlib_zip_hash = Get-FileHash -LiteralPath "${ZLIB_DL_PATH}" -Algorithm SHA512 +if ($zlib_zip_hash.Hash -eq $ZLIB_DL_SHA512) { + Write-Host "Successfully downloaded ${ZLIB_RELEASE_ASSET}" +} +Else { + Write-Error "The downloaded ${ZLIB_RELEASE_ASSET} hash '$($zlib_zip_hash.Hash)' does not match the expected hash: '$ZLIB_DL_SHA512'" +} + +Write-Host "Extracting zlib source..." +$ZLIB_SOURCE_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib-${ZLIB_RELEASE}" +Expand-Archive -LiteralPath "${ZLIB_DL_PATH}" -DestinationPath "$env:GITHUB_WORKSPACE\buildtools" + +Write-Host "Building zlib source..." +$ZLIB_BUILD_PATH = "$env:GITHUB_WORKSPACE\buildtools\zlib_build" +if ($env:TEST_MINGW -eq 1) { + cmake.exe -B "${ZLIB_BUILD_PATH}" -S "${ZLIB_SOURCE_PATH}" -GNinja +} +Elseif ($env:TEST_X86 -eq 1) { + cmake.exe -B "${ZLIB_BUILD_PATH}" -S "${ZLIB_SOURCE_PATH}" -AWin32 +} +Else { + cmake.exe -B "${ZLIB_BUILD_PATH}" -S "${ZLIB_SOURCE_PATH}" +} +cmake.exe --build "${ZLIB_BUILD_PATH}" --target zlibstatic +Copy-Item "${ZLIB_SOURCE_PATH}\zlib.h" "${ZLIB_BUILD_PATH}" + +# Append zlib CMAKE_DEFINES to the runner env. +if ($env:TEST_MINGW -eq 1) { + $NEW_CMAKE_DEFINES="CMAKE_DEFINES=${env:CMAKE_DEFINES} -DZLIB_LIBRARY=${ZLIB_BUILD_PATH}\libzlibstatic.a -DZLIB_INCLUDE_DIR=${ZLIB_BUILD_PATH} -GNinja" +} +Else { + $NEW_CMAKE_DEFINES="CMAKE_DEFINES=-DZLIB_LIBRARY=${ZLIB_BUILD_PATH}\Debug\zlibstaticd.lib -DZLIB_INCLUDE_DIR=${ZLIB_BUILD_PATH}" +} +$NEW_CMAKE_DEFINES | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append \ No newline at end of file diff --git a/src/sentry_transport.c b/src/sentry_transport.c index 06ba3524b2..a01bb87d5b 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -169,8 +169,8 @@ gzipped_with_compression(const char *body, const size_t body_len, z_stream stream; memset(&stream, 0, sizeof(stream)); - stream.next_in = body; - stream.avail_in = body_len; + stream.next_in = (unsigned char *)body; + stream.avail_in = (unsigned int)body_len; int err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 9, Z_DEFAULT_STRATEGY); @@ -179,7 +179,7 @@ gzipped_with_compression(const char *body, const size_t body_len, return false; } - size_t len = compressBound(body_len); + size_t len = compressBound((unsigned long)body_len); char *buffer = sentry_malloc(len); if (!buffer) { deflateEnd(&stream); @@ -187,8 +187,8 @@ gzipped_with_compression(const char *body, const size_t body_len, } while (err == Z_OK) { - stream.next_out = buffer + stream.total_out; - stream.avail_out = len - stream.total_out; + stream.next_out = (unsigned char *)(buffer + stream.total_out); + stream.avail_out = (unsigned int)(len - stream.total_out); err = deflate(&stream, Z_FINISH); } @@ -225,8 +225,8 @@ sentry__prepare_http_request(sentry_envelope_t *envelope, return NULL; } - bool compressed = false; #ifdef SENTRY_TRANSPORT_COMPRESSION + bool compressed = false; char *compressed_body = NULL; size_t compressed_body_len = 0; compressed = gzipped_with_compression( @@ -273,11 +273,13 @@ sentry__prepare_http_request(sentry_envelope_t *envelope, h->key = "content-type"; h->value = sentry__string_clone(ENVELOPE_MIME); +#ifdef SENTRY_TRANSPORT_COMPRESSION if (compressed) { h = &req->headers[req->headers_len++]; h->key = "content-encoding"; h->value = sentry__string_clone("gzip"); } +#endif h = &req->headers[req->headers_len++]; h->key = "content-length"; diff --git a/tests/__init__.py b/tests/__init__.py index d7374c5b45..61380f90b1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,4 @@ +import gzip import subprocess import os import io @@ -162,10 +163,17 @@ def deserialize_from( @classmethod def deserialize( - cls, bytes # type: bytes + cls, data # type: bytes ): # type: (...) -> Envelope - return cls.deserialize_from(io.BytesIO(bytes)) + + # check if the data is gzip encoded and extract it before deserialization. + # 0x1f8b: gzip-magic, 0x08: `DEFLATE` compression method. + if data[:3] == b"\x1f\x8b\x08": + with gzip.open(io.BytesIO(data), "rb") as output: + return cls.deserialize_from(output) + + return cls.deserialize_from(io.BytesIO(data)) def print_verbose(self, indent=0): """Pretty prints the envelope.""" diff --git a/tests/assertions.py b/tests/assertions.py index e3a0c11c9f..9ab79c18a1 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -4,7 +4,7 @@ import re import sys from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, UTC import msgpack @@ -185,7 +185,7 @@ def assert_minidump(envelope): assert minidump.payload.bytes.startswith(b"MDMP") -def assert_timestamp(ts, now=datetime.utcnow()): +def assert_timestamp(ts, now=datetime.now(UTC)): assert ts[:11] == now.isoformat()[:11] @@ -307,3 +307,11 @@ def assert_crashpad_upload(req): and b"\n\nMDMP" in part.as_bytes() for part in msg.walk() ) + + +def assert_gzip_file_header(output): + assert output[:3] == b"\x1f\x8b\x08" + + +def assert_gzip_content_encoding(req): + assert req.content_encoding == "gzip" diff --git a/tests/cmake.py b/tests/cmake.py index 35c48a8b57..906b8afadf 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -1,9 +1,10 @@ -import os import json -import sys +import os +import shutil import subprocess +import sys + import pytest -import shutil class CMake: diff --git a/tests/requirements.txt b/tests/requirements.txt index ae5bbcfa37..cbc8051ecb 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,5 @@ black==24.3.0 -pytest==8.0.1 +pytest==8.1.1 pytest-httpserver==1.0.10 msgpack==1.0.8 +pytest-xdist==3.5.0 \ No newline at end of file diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index fda314da6b..28647bcc44 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -1,11 +1,17 @@ -import pytest import os import shutil import sys import time + +import pytest + from . import make_dsn, run, Envelope +from .assertions import ( + assert_crashpad_upload, + assert_session, + assert_gzip_file_header, +) from .conditions import has_crashpad -from .assertions import assert_crashpad_upload, assert_session pytestmark = pytest.mark.skipif(not has_crashpad, reason="tests need crashpad backend") @@ -118,13 +124,15 @@ def test_crashpad_wer_crash(cmake, httpserver, run_args): @pytest.mark.parametrize( - "run_args", + "run_args,build_args", [ # if we crash, we want a dump - ([]), + ([], {"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + ([], {"SENTRY_TRANSPORT_COMPRESSION": "On"}), # if we crash and before-send doesn't discard, we want a dump pytest.param( ["before-send"], + {}, marks=pytest.mark.skipif( sys.platform == "darwin", reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", @@ -133,6 +141,7 @@ def test_crashpad_wer_crash(cmake, httpserver, run_args): # if on_crash() is non-discarding, a discarding before_send() is overruled, so we get a dump pytest.param( ["discarding-before-send", "on-crash"], + {}, marks=pytest.mark.skipif( sys.platform == "darwin", reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", @@ -140,8 +149,9 @@ def test_crashpad_wer_crash(cmake, httpserver, run_args): ), ], ) -def test_crashpad_dumping_crash(cmake, httpserver, run_args): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "crashpad"}) +def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): + build_args.update({"SENTRY_BACKEND": "crashpad"}) + tmp_path = cmake(["sentry_example"], build_args) # make sure we are isolated from previous runs shutil.rmtree(tmp_path / ".sentry-native", ignore_errors=True) @@ -169,19 +179,24 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args): run(tmp_path, "sentry_example", ["log", "no-setup"], check=True, env=env) assert len(httpserver.log) == 2 - outputs = (httpserver.log[0][0], httpserver.log[1][0]) session, multipart = ( - (outputs[0].get_data(), outputs[1]) - if b'"type":"session"' in outputs[0].get_data() - else (outputs[1].get_data(), outputs[0]) + (httpserver.log[0][0], httpserver.log[1][0]) + if is_session_envelope(httpserver.log[0][0].get_data()) + else (httpserver.log[1][0], httpserver.log[0][0]) ) - envelope = Envelope.deserialize(session) + if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On": + assert_gzip_file_header(session.get_data()) + envelope = Envelope.deserialize(session.get_data()) assert_session(envelope, {"status": "crashed", "errors": 1}) assert_crashpad_upload(multipart) +def is_session_envelope(data): + return b'"type":"session"' in data + + @pytest.mark.skipif( sys.platform == "darwin", reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 0cbd304dab..1f5eb91326 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -19,6 +19,8 @@ assert_user_feedback, assert_minidump, assert_breakpad_crash, + assert_gzip_content_encoding, + assert_gzip_file_header, ) from .conditions import has_http, has_breakpad, has_files @@ -29,8 +31,16 @@ ) -def test_capture_http(cmake, httpserver): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) +@pytest.mark.parametrize( + "build_args", + [ + ({"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + ({"SENTRY_TRANSPORT_COMPRESSION": "On"}), + ], +) +def test_capture_http(cmake, httpserver, build_args): + build_args.update({"SENTRY_BACKEND": "none"}) + tmp_path = cmake(["sentry_example"], build_args) httpserver.expect_oneshot_request( "/api/123456/envelope/", @@ -47,8 +57,14 @@ def test_capture_http(cmake, httpserver): ) assert len(httpserver.log) == 1 - output = httpserver.log[0][0].get_data() - envelope = Envelope.deserialize(output) + req = httpserver.log[0][0] + body = req.get_data() + + if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On": + assert_gzip_content_encoding(req) + assert_gzip_file_header(body) + + envelope = Envelope.deserialize(body) assert_meta(envelope, "🤮🚀") assert_breadcrumb(envelope) @@ -232,8 +248,16 @@ def test_abnormal_session(cmake, httpserver): assert_session(envelope1, {"status": "abnormal", "errors": 0, "duration": 10}) -def test_inproc_crash_http(cmake, httpserver): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"}) +@pytest.mark.parametrize( + "build_args", + [ + ({"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + ({"SENTRY_TRANSPORT_COMPRESSION": "On"}), + ], +) +def test_inproc_crash_http(cmake, httpserver, build_args): + build_args.update({"SENTRY_BACKEND": "inproc"}) + tmp_path = cmake(["sentry_example"], build_args) httpserver.expect_request( "/api/123456/envelope/", @@ -258,7 +282,14 @@ def test_inproc_crash_http(cmake, httpserver): ) assert len(httpserver.log) == 1 - envelope = Envelope.deserialize(httpserver.log[0][0].get_data()) + req = httpserver.log[0][0] + body = req.get_data() + + if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On": + assert_gzip_content_encoding(req) + assert_gzip_file_header(body) + + envelope = Envelope.deserialize(body) assert_session(envelope, {"init": True, "status": "crashed", "errors": 1}) @@ -317,8 +348,16 @@ def test_inproc_dump_inflight(cmake, httpserver): @pytest.mark.skipif(not has_breakpad, reason="test needs breakpad backend") -def test_breakpad_crash_http(cmake, httpserver): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "breakpad"}) +@pytest.mark.parametrize( + "build_args", + [ + ({"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + ({"SENTRY_TRANSPORT_COMPRESSION": "On"}), + ], +) +def test_breakpad_crash_http(cmake, httpserver, build_args): + build_args.update({"SENTRY_BACKEND": "breakpad"}) + tmp_path = cmake(["sentry_example"], build_args) httpserver.expect_request( "/api/123456/envelope/", @@ -343,7 +382,14 @@ def test_breakpad_crash_http(cmake, httpserver): ) assert len(httpserver.log) == 1 - envelope = Envelope.deserialize(httpserver.log[0][0].get_data()) + req = httpserver.log[0][0] + body = req.get_data() + + if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On": + assert_gzip_content_encoding(req) + assert_gzip_file_header(body) + + envelope = Envelope.deserialize(body) assert_session(envelope, {"init": True, "status": "crashed", "errors": 1}) @@ -453,8 +499,16 @@ def delayed(req): RFC3339_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" -def test_transaction_only(cmake, httpserver): - tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "none"}) +@pytest.mark.parametrize( + "build_args", + [ + ({"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + ({"SENTRY_TRANSPORT_COMPRESSION": "On"}), + ], +) +def test_transaction_only(cmake, httpserver, build_args): + build_args.update({"SENTRY_BACKEND": "none"}) + tmp_path = cmake(["sentry_example"], build_args) httpserver.expect_oneshot_request( "/api/123456/envelope/", @@ -471,8 +525,14 @@ def test_transaction_only(cmake, httpserver): ) assert len(httpserver.log) == 1 - output = httpserver.log[0][0].get_data() - envelope = Envelope.deserialize(output) + req = httpserver.log[0][0] + body = req.get_data() + + if build_args.get("SENTRY_TRANSPORT_COMPRESSION") == "On": + assert_gzip_content_encoding(req) + assert_gzip_file_header(body) + + envelope = Envelope.deserialize(body) # Show what the envelope looks like if the test fails. envelope.print_verbose() diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 5aa1170d56..803fb4730a 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -33,10 +33,12 @@ SENTRY_TEST(basic_http_request_preparation_for_event) TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); +#ifndef SENTRY_TRANSPORT_COMPRESSION TEST_CHECK_STRING_EQUAL(req->body, "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" "{\"type\":\"event\",\"length\":51}\n" "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}"); +#endif sentry__prepared_http_request_free(req); sentry_envelope_free(envelope); @@ -62,13 +64,14 @@ SENTRY_TEST(basic_http_request_preparation_for_transaction) TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); +#ifndef SENTRY_TRANSPORT_COMPRESSION TEST_CHECK_STRING_EQUAL(req->body, "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"sent_at\":" "\"2021-12-16T05:53:59.343Z\"}\n" "{\"type\":\"transaction\",\"length\":72}\n" "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"type\":" "\"transaction\"}"); - +#endif sentry__prepared_http_request_free(req); sentry_envelope_free(envelope); @@ -91,12 +94,14 @@ SENTRY_TEST(basic_http_request_preparation_for_user_feedback) TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); +#ifndef SENTRY_TRANSPORT_COMPRESSION TEST_CHECK_STRING_EQUAL(req->body, "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" "{\"type\":\"user_report\",\"length\":117}\n" "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\",\"name\":" "\"some-name\",\"email\":\"some-email\",\"comments\":" "\"some-comment\"}"); +#endif sentry__prepared_http_request_free(req); sentry_value_decref(user_feedback); sentry_envelope_free(envelope); @@ -124,12 +129,14 @@ SENTRY_TEST(basic_http_request_preparation_for_event_with_attachment) TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); +#ifndef SENTRY_TRANSPORT_COMPRESSION TEST_CHECK_STRING_EQUAL(req->body, "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" "{\"type\":\"event\",\"length\":51}\n" "{\"event_id\":\"c993afb6-b4ac-48a6-b61b-2558e601d65d\"}\n" "{\"type\":\"attachment\",\"length\":12}\n" "Hello World!"); +#endif sentry__prepared_http_request_free(req); sentry_envelope_free(envelope); @@ -153,12 +160,14 @@ SENTRY_TEST(basic_http_request_preparation_for_minidump) TEST_CHECK_STRING_EQUAL(req->method, "POST"); TEST_CHECK_STRING_EQUAL( req->url, "https://sentry.invalid:443/api/42/envelope/"); +#ifndef SENTRY_TRANSPORT_COMPRESSION TEST_CHECK_STRING_EQUAL(req->body, "{}\n" "{\"type\":\"minidump\",\"length\":4}\n" "MDMP\n" "{\"type\":\"attachment\",\"length\":12}\n" "Hello World!"); +#endif sentry__prepared_http_request_free(req); sentry_envelope_free(envelope); From 004402c7957fc95750b75ba08f45f02f884302c2 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Tue, 2 Apr 2024 10:13:08 +0200 Subject: [PATCH 155/207] feat: enable automatic MIME detection for crashpad attachments. (#973) --- CHANGELOG.md | 3 ++- external/crashpad | 2 +- tests/assertions.py | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd535ce5af..4a3497eeb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ **Features**: - Add optional Gzip transport compression via build option `SENTRY_TRANSPORT_COMPRESSION`. Requires system `zlib`. ([#954](https://github.com/getsentry/sentry-native/pull/954)) - +- Enable automatic MIME detection of attachments sent with crash-reports from the `crashpad_handler`. ([#973](https://github.com/getsentry/sentry-native/pull/973), [crashpad#98](https://github.com/getsentry/crashpad/pull/98)) + **Fixes**: - Fix the Linux build when targeting RISC-V. ([#972](https://github.com/getsentry/sentry-native/pull/972)) diff --git a/external/crashpad b/external/crashpad index 6bde832e99..3f8bcc49bb 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 6bde832e99297b2db4c98676067368b6a8428ecc +Subproject commit 3f8bcc49bbfff0662bcfa59e831b65a5d050c9f0 diff --git a/tests/assertions.py b/tests/assertions.py index 9ab79c18a1..4d5ef3393e 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -263,6 +263,9 @@ def _load_crashpad_attachments(msg): breadcrumb1 = [] breadcrumb2 = [] for part in msg.walk(): + if part.get_filename() is not None: + assert part.get("Content-Type") is None + match part.get_filename(): case "__sentry-event": event = msgpack.unpackb(part.get_payload(decode=True)) From 0f1d664759cba187a846a562f9d55f3c62dffaa3 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 2 Apr 2024 10:04:25 +0000 Subject: [PATCH 156/207] release: 0.7.2 --- CHANGELOG.md | 2 +- include/sentry.h | 2 +- tests/assertions.py | 4 ++-- tests/test_integration_http.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3497eeb9..12bb6c0228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 0.7.2 **Features**: diff --git a/include/sentry.h b/include/sentry.h index 16b7e03fc7..43203151a4 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -30,7 +30,7 @@ extern "C" { # define SENTRY_SDK_NAME "sentry.native" # endif #endif -#define SENTRY_SDK_VERSION "0.7.1" +#define SENTRY_SDK_VERSION "0.7.2" #define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION /* common platform detection */ diff --git a/tests/assertions.py b/tests/assertions.py index 4d5ef3393e..93f1b14abd 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -73,9 +73,9 @@ def assert_meta( } expected_sdk = { "name": "sentry.native", - "version": "0.7.1", + "version": "0.7.2", "packages": [ - {"name": "github:getsentry/sentry-native", "version": "0.7.1"}, + {"name": "github:getsentry/sentry-native", "version": "0.7.2"}, ], } if is_android: diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index 1f5eb91326..d0ee056cbc 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -27,7 +27,7 @@ pytestmark = pytest.mark.skipif(not has_http, reason="tests need http") auth_header = ( - "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.1" + "Sentry sentry_key=uiaeosnrtdy, sentry_version=7, sentry_client=sentry.native/0.7.2" ) From 4df5326903d7ead8e4f68f1e60d4fd0ebc17d16f Mon Sep 17 00:00:00 2001 From: Vitalii Koshura Date: Thu, 4 Apr 2024 13:28:31 +0200 Subject: [PATCH 157/207] docs: Add SENTRY_TRANSPORT_COMPRESSION to the README (#976) Signed-off-by: Vitalii Koshura --- CHANGELOG.md | 4 ++++ README.md | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12bb6c0228..558c446d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- [Documentation] Add missed compile time flag SENTRY_TRANSPORT_COMPRESSION description to the README.md file ([#976](https://github.com/getsentry/sentry-native/pull/976)) + ## 0.7.2 **Features**: diff --git a/README.md b/README.md index 2577e82c6c..e4b533c642 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,9 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`. - `SENTRY_BREAKPAD_SYSTEM` (Default: OFF): This instructs the build system to use system-installed breakpad libraries instead of using the in-tree version. +- `SENTRY_TRANSPORT_COMPRESSION` (Default: OFF): + Adds Gzip transport compression. Requires `zlib`. + | Feature | Windows | macOS | Linux | Android | iOS | | ---------- | ------- | ----- | ----- | ------- | --- | | Transports | | | | | | From dc43150d35a4171634855f3c0dd915e78754f90b Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 15 Apr 2024 11:49:51 +0200 Subject: [PATCH 158/207] fix: allow crashpad to run with Epic's Anti-Cheat client. (#980) --- CHANGELOG.md | 13 ++++++++++++- external/crashpad | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 558c446d85..4a3a56019b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,18 @@ ## Unreleased -- [Documentation] Add missed compile time flag SENTRY_TRANSPORT_COMPRESSION description to the README.md file ([#976](https://github.com/getsentry/sentry-native/pull/976)) +**Fixes**: + +- Allow `crashpad` to run under [Epic's Anti-Cheat Client](https://dev.epicgames.com/docs/game-services/anti-cheat/using-anti-cheat#external-crash-dumpers) by deferring the full `crashpad_handler` access rights to the client application until a crash occurred. ([#980](https://github.com/getsentry/sentry-native/pull/980), [crashpad#99](https://github.com/getsentry/crashpad/pull/99)) + +**Docs**: + +- Add compile-time flag `SENTRY_TRANSPORT_COMPRESSION` description to the `README.md` file. ([#976](https://github.com/getsentry/sentry-native/pull/976)) + +**Thank you**: + +- [@AenBleidd](https://github.com/AenBleidd) +- [@kristjanvalur](https://github.com/kristjanvalur) ## 0.7.2 diff --git a/external/crashpad b/external/crashpad index 3f8bcc49bb..f4c7710dc1 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit 3f8bcc49bbfff0662bcfa59e831b65a5d050c9f0 +Subproject commit f4c7710dc178218e1e58d9e0b65be91cc5dbd66c From 46456ca36188a38eed8a33c9c1a30440b57c2535 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Mon, 6 May 2024 10:37:40 +0200 Subject: [PATCH 159/207] fix: android test emulator start-script (#984) * fix: android test emulator start-script * Update start-android.sh * Update start-android.sh * pin android-test images to macOS 12 * try macOS 13 for the Android test runners * pin to macOS 12 since 13 also failed * Add clearer logging to emulator start script * fix typo --- .github/workflows/ci.yml | 8 ++++---- scripts/start-android.sh | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 833b4269b6..de6371f4b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,14 +91,14 @@ jobs: # The Android emulator is currently only available on macos, see: # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator - name: Android (old API/NDK) - os: macOs-latest + os: macOs-12 ANDROID_API: 16 ANDROID_NDK: 20.1.5948944 ANDROID_ARCH: x86 - name: Android (new API/NDK) - os: macOs-latest - ANDROID_API: 32 - ANDROID_NDK: 25.0.8775105 + os: macOs-12 + ANDROID_API: 34 + ANDROID_NDK: 26.1.10909125 ANDROID_ARCH: x86_64 name: ${{ matrix.name }} diff --git a/scripts/start-android.sh b/scripts/start-android.sh index 6b7fecb5d7..02b4cc3065 100755 --- a/scripts/start-android.sh +++ b/scripts/start-android.sh @@ -3,21 +3,34 @@ # Adapted from: # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator +echo "java version:" +java -version +echo "javac version" +javac -version +echo "PATH: $PATH" + ARCH=${ANDROID_ARCH:-"x86"} API_LEVEL=${ANDROID_API:-"29"} AVD_EMULATOR_NAME="sentry_android_${ARCH}" IMAGE=${ANDROID_IMAGE:-"system-images;android-${API_LEVEL};google_apis;${ARCH}"} # Create an Android Virtual Device +echo "Create Test AVDs with..." +echo "ARCH = $ARCH" +echo "API_LEVEL = $API_LEVEL" +echo "AVD_EMULATOR_NAME = $AVD_EMULATOR_NAME" +echo "IMAGE = $IMAGE" echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n $AVD_EMULATOR_NAME -k "$IMAGE" --force +echo "List available AVDs..." $ANDROID_HOME/emulator/emulator -list-avds -echo "Starting emulator..." - # Start emulator in background +echo "Starting emulator..." nohup $ANDROID_HOME/emulator/emulator -avd $AVD_EMULATOR_NAME -no-snapshot > /dev/null 2>&1 & +echo "Wait for emulator availability..." $ANDROID_HOME/platform-tools/adb wait-for-device shell 'ls' +echo "Verify emulator devices as running..." $ANDROID_HOME/platform-tools/adb devices echo "Emulator started." From 97b9c765fe7e02276e574b1762274fa0efe27558 Mon Sep 17 00:00:00 2001 From: Mischan Toosarani-Hausberger Date: Wed, 8 May 2024 16:33:29 +0200 Subject: [PATCH 160/207] fix: store transaction `data` in event `extra` (#986) --- CHANGELOG.md | 1 + examples/example.c | 6 +++ src/sentry_tracing.c | 68 +++++++++++++++------------------- tests/assertions.py | 13 +++++-- tests/test_integration_http.py | 6 ++- tests/unit/test_tracing.c | 8 ++-- 6 files changed, 55 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3a56019b..6eedc7f485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Allow `crashpad` to run under [Epic's Anti-Cheat Client](https://dev.epicgames.com/docs/game-services/anti-cheat/using-anti-cheat#external-crash-dumpers) by deferring the full `crashpad_handler` access rights to the client application until a crash occurred. ([#980](https://github.com/getsentry/sentry-native/pull/980), [crashpad#99](https://github.com/getsentry/crashpad/pull/99)) +- Store transaction `data` in the event property `extra` since the `data` property is discarded by `relay`. ([#986](https://github.com/getsentry/sentry-native/issues/986)) **Docs**: diff --git a/examples/example.c b/examples/example.c index 36a84bb44a..5eac53ac8a 100644 --- a/examples/example.c +++ b/examples/example.c @@ -387,6 +387,9 @@ main(int argc, char **argv) sentry_transaction_t *tx = sentry_transaction_start(tx_ctx, sentry_value_new_null()); + sentry_transaction_set_data( + tx, "url", sentry_value_new_string("https://example.com")); + if (has_arg(argc, argv, "error-status")) { sentry_transaction_set_status( tx, SENTRY_SPAN_STATUS_INTERNAL_ERROR); @@ -398,6 +401,9 @@ main(int argc, char **argv) sentry_span_t *grandchild = sentry_span_start_child(child, "littlest.teapot", NULL); + sentry_span_set_data( + child, "span_data_says", sentry_value_new_string("hi!")); + if (has_arg(argc, argv, "error-status")) { sentry_span_set_status(child, SENTRY_SPAN_STATUS_NOT_FOUND); sentry_span_set_status( diff --git a/src/sentry_tracing.c b/src/sentry_tracing.c index b32c30496a..bbc7f00b44 100644 --- a/src/sentry_tracing.c +++ b/src/sentry_tracing.c @@ -512,24 +512,14 @@ sentry_span_remove_tag_n(sentry_span_t *span, const char *tag, size_t tag_len) } static void -set_data(sentry_value_t item, const char *key, sentry_value_t value) +set_data(sentry_value_t item, const char *data_key, size_t data_key_len, + const char *key, size_t key_len, sentry_value_t value) { - sentry_value_t data = sentry_value_get_by_key(item, "data"); + sentry_value_t data + = sentry_value_get_by_key_n(item, data_key, data_key_len); if (sentry_value_is_null(data)) { data = sentry_value_new_object(); - sentry_value_set_by_key(item, "data", data); - } - sentry_value_set_by_key(data, key, value); -} - -static void -set_data_n( - sentry_value_t item, const char *key, size_t key_len, sentry_value_t value) -{ - sentry_value_t data = sentry_value_get_by_key(item, "data"); - if (sentry_value_is_null(data)) { - data = sentry_value_new_object(); - sentry_value_set_by_key(item, "data", data); + sentry_value_set_by_key_n(item, data_key, data_key_len, data); } sentry_value_set_by_key_n(data, key, key_len, value); } @@ -538,50 +528,51 @@ void sentry_transaction_set_data( sentry_transaction_t *tx, const char *key, sentry_value_t value) { - if (tx) { - set_data(tx->inner, key, value); + if (key) { + sentry_transaction_set_data_n(tx, key, strlen(key), value); } } +static const char txn_data_key[] = "extra"; +static const size_t txn_data_key_len = sizeof(txn_data_key) - 1; + void sentry_transaction_set_data_n(sentry_transaction_t *tx, const char *key, size_t key_len, sentry_value_t value) { if (tx) { - set_data_n(tx->inner, key, key_len, value); + set_data( + tx->inner, txn_data_key, txn_data_key_len, key, key_len, value); } } void sentry_span_set_data(sentry_span_t *span, const char *key, sentry_value_t value) { - if (span) { - set_data(span->inner, key, value); + if (key) { + sentry_span_set_data_n(span, key, strlen(key), value); } } +static const char span_data_key[] = "data"; +static const size_t span_data_key_len = sizeof(span_data_key) - 1; + void sentry_span_set_data_n( sentry_span_t *span, const char *key, size_t key_len, sentry_value_t value) { if (span) { - set_data_n(span->inner, key, key_len, value); + set_data( + span->inner, span_data_key, span_data_key_len, key, key_len, value); } } static void -remove_data(sentry_value_t item, const char *key) +remove_data(sentry_value_t item, const char *data_key, size_t data_key_len, + const char *key, size_t key_len) { - sentry_value_t data = sentry_value_get_by_key(item, "data"); - if (!sentry_value_is_null(data)) { - sentry_value_remove_by_key(data, key); - } -} - -static void -remove_data_n(sentry_value_t item, const char *key, size_t key_len) -{ - sentry_value_t data = sentry_value_get_by_key(item, "data"); + sentry_value_t data + = sentry_value_get_by_key_n(item, data_key, data_key_len); if (!sentry_value_is_null(data)) { sentry_value_remove_by_key_n(data, key, key_len); } @@ -590,8 +581,8 @@ remove_data_n(sentry_value_t item, const char *key, size_t key_len) void sentry_transaction_remove_data(sentry_transaction_t *tx, const char *key) { - if (tx) { - remove_data(tx->inner, key); + if (key) { + sentry_transaction_remove_data_n(tx, key, strlen(key)); } } @@ -600,15 +591,15 @@ sentry_transaction_remove_data_n( sentry_transaction_t *tx, const char *key, size_t key_len) { if (tx) { - remove_data_n(tx->inner, key, key_len); + remove_data(tx->inner, txn_data_key, txn_data_key_len, key, key_len); } } void sentry_span_remove_data(sentry_span_t *span, const char *key) { - if (span) { - remove_data(span->inner, key); + if (key) { + sentry_span_remove_data_n(span, key, strlen(key)); } } @@ -616,7 +607,8 @@ void sentry_span_remove_data_n(sentry_span_t *span, const char *key, size_t key_len) { if (span) { - remove_data_n(span->inner, key, key_len); + remove_data( + span->inner, span_data_key, span_data_key_len, key, key_len); } } diff --git a/tests/assertions.py b/tests/assertions.py index 93f1b14abd..e9d86878cd 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -55,10 +55,18 @@ def assert_meta( release="test-example-release", integration=None, transaction="test-transaction", + transaction_data=None, sdk_override=None, ): event = envelope.get_event() + extra = { + "extra stuff": "some value", + "…unicode key…": "őá…–🤮🚀¿ 한글 테스트", + } + if transaction_data: + extra.update(transaction_data) + expected = { "platform": "native", "environment": "development", @@ -66,10 +74,7 @@ def assert_meta( "user": {"id": 42, "username": "some_name"}, "transaction": transaction, "tags": {"expected-tag": "some value"}, - "extra": { - "extra stuff": "some value", - "…unicode key…": "őá…–🤮🚀¿ 한글 테스트", - }, + "extra": extra, } expected_sdk = { "name": "sentry.native", diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index d0ee056cbc..02d00a07af 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -538,7 +538,11 @@ def test_transaction_only(cmake, httpserver, build_args): envelope.print_verbose() # The transaction is overwritten. - assert_meta(envelope, transaction="little.teapot") + assert_meta( + envelope, + transaction="little.teapot", + transaction_data={"url": "https://example.com"}, + ) # Extract the one-and-only-item (event,) = envelope.items diff --git a/tests/unit/test_tracing.c b/tests/unit/test_tracing.c index bdac0921fd..bbc23107ca 100644 --- a/tests/unit/test_tracing.c +++ b/tests/unit/test_tracing.c @@ -972,10 +972,10 @@ SENTRY_TEST(txn_data) sentry_transaction_set_data( txn, "os.name", sentry_value_new_string("Linux")); - check_after_set(txn->inner, "data", "os.name", "Linux"); + check_after_set(txn->inner, "extra", "os.name", "Linux"); sentry_transaction_remove_data(txn, "os.name"); - check_after_remove(txn->inner, "data", "os.name"); + check_after_remove(txn->inner, "extra", "os.name"); sentry__transaction_decref(txn); } @@ -1022,10 +1022,10 @@ SENTRY_TEST(txn_data_n) sentry_value_t data_value = sentry_value_new_string_n(data_v, sizeof(data_v)); sentry_transaction_set_data_n(txn, data_k, sizeof(data_k), data_value); - check_after_set(txn->inner, "data", "os.name", "Linux"); + check_after_set(txn->inner, "extra", "os.name", "Linux"); sentry_transaction_remove_data_n(txn, data_k, sizeof(data_k)); - check_after_remove(txn->inner, "data", "os.name"); + check_after_remove(txn->inner, "extra", "os.name"); sentry__transaction_decref(txn); } From 65446506fdab48b4337818540a473cf0a5dad7d3 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 22 May 2024 09:56:04 +0200 Subject: [PATCH 161/207] Move NDK from sentry-java to sentry-native (#944) * Added initial poc * Re-structure package names to NDK * Post PoC cleanup * setup module name for prefab * Prepare for publishing * Switch to shared STL instead of static * Wire ndk artifact generation with github actions * Fix distDir * Update Changelog * Setup Java/Gradle for CI * Update READMEs * Fix typos * Build libraries with c++_shared to comply with prefab * Address PR feedback --- .craft.yml | 10 + .github/workflows/ci.yml | 32 +- .gitignore | 7 + CHANGELOG.md | 5 + README.md | 6 +- ndk/README.md | 75 +++ ndk/build.gradle.kts | 207 ++++++++ ndk/debug.keystore | Bin 0 -> 2473 bytes ndk/gradle.properties | 53 ++ ndk/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes ndk/gradle/wrapper/gradle-wrapper.properties | 7 + ndk/gradlew | 249 +++++++++ ndk/gradlew.bat | 92 ++++ ndk/lib/CMakeLists.txt | 17 + ndk/lib/api/sentry-android-ndk.api | 29 ++ ndk/lib/build.gradle.kts | 93 ++++ ndk/lib/proguard-rules.pro | 22 + .../main/java/io/sentry/ndk/DebugImage.java | 199 +++++++ .../main/java/io/sentry/ndk/INativeScope.java | 18 + .../io/sentry/ndk/NativeModuleListLoader.java | 18 + .../main/java/io/sentry/ndk/NativeScope.java | 55 ++ .../main/java/io/sentry/ndk/NdkOptions.java | 64 +++ .../main/java/io/sentry/ndk/SentryNdk.java | 42 ++ ndk/lib/src/main/jni/sentry.c | 489 ++++++++++++++++++ ndk/lib/src/main/res/values/public.xml | 4 + ndk/sample/CMakeLists.txt | 18 + ndk/sample/build.gradle.kts | 70 +++ ndk/sample/proguard-rules.pro | 34 ++ ndk/sample/src/main/AndroidManifest.xml | 18 + ndk/sample/src/main/cpp/ndk-sample.cpp | 25 + .../io/sentry/ndk/sample/MainActivity.java | 54 ++ .../java/io/sentry/ndk/sample/NdkSample.java | 11 + .../src/main/res/layout/activity_main.xml | 36 ++ ndk/settings.gradle | 19 + scripts/bump-version.sh | 1 + scripts/mvnw | 310 +++++++++++ scripts/mvnw.cmd | 182 +++++++ scripts/settings.xml | 12 + 38 files changed, 2574 insertions(+), 9 deletions(-) create mode 100644 ndk/README.md create mode 100644 ndk/build.gradle.kts create mode 100644 ndk/debug.keystore create mode 100644 ndk/gradle.properties create mode 100644 ndk/gradle/wrapper/gradle-wrapper.jar create mode 100644 ndk/gradle/wrapper/gradle-wrapper.properties create mode 100755 ndk/gradlew create mode 100644 ndk/gradlew.bat create mode 100644 ndk/lib/CMakeLists.txt create mode 100644 ndk/lib/api/sentry-android-ndk.api create mode 100644 ndk/lib/build.gradle.kts create mode 100644 ndk/lib/proguard-rules.pro create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java create mode 100644 ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java create mode 100644 ndk/lib/src/main/jni/sentry.c create mode 100644 ndk/lib/src/main/res/values/public.xml create mode 100644 ndk/sample/CMakeLists.txt create mode 100644 ndk/sample/build.gradle.kts create mode 100644 ndk/sample/proguard-rules.pro create mode 100644 ndk/sample/src/main/AndroidManifest.xml create mode 100644 ndk/sample/src/main/cpp/ndk-sample.cpp create mode 100644 ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java create mode 100644 ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java create mode 100644 ndk/sample/src/main/res/layout/activity_main.xml create mode 100644 ndk/settings.gradle create mode 100755 scripts/mvnw create mode 100755 scripts/mvnw.cmd create mode 100755 scripts/settings.xml diff --git a/.craft.yml b/.craft.yml index 6908dce360..dc3321ba09 100644 --- a/.craft.yml +++ b/.craft.yml @@ -5,6 +5,7 @@ targets: - name: registry sdks: github:getsentry/sentry-native: + maven:io.sentry:sentry-native-ndk: - name: gcs bucket: sentry-sdk-assets paths: @@ -14,5 +15,14 @@ targets: - path: /sentry-native/latest/ metadata: cacheControl: public, max-age=600 + - name: maven + mavenCliPath: scripts/mvnw + mavenSettingsPath: scripts/settings.xml + mavenRepoId: ossrh + mavenRepoUrl: https://oss.sonatype.org/service/local/staging/deploy/maven2/ + android: + distDirRegex: /^(sentry-native-ndk).*$/ + fileReplaceeRegex: /\d+\.\d+\.\d+(-\w+(\.\d+)?)?(-SNAPSHOT)?/ + fileReplacerStr: release.aar requireNames: - /^sentry-native.zip$/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de6371f4b6..05d160c15a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,8 +121,8 @@ jobs: submodules: recursive - uses: actions/setup-python@v4 with: - python-version: '3.11' - cache: 'pip' + python-version: "3.11" + cache: "pip" - name: Installing Linux Dependencies if: ${{ runner.os == 'Linux' && !env['TEST_X86'] }} @@ -149,7 +149,7 @@ jobs: - name: Expose llvm PATH for Mac if: ${{ runner.os == 'macOS' }} run: echo $(brew --prefix llvm@15)/bin >> $GITHUB_PATH - + - name: Installing LLVM-MINGW Dependencies if: ${{ runner.os == 'Windows' && env['TEST_MINGW'] }} shell: powershell @@ -203,15 +203,31 @@ jobs: with: submodules: recursive + - name: Setup Java Version + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@579fbbe7221704325eb4c4d4bf20c2b0859fba76 # pin@v3 + with: + gradle-home-cache-cleanup: true + - name: Create source archive run: | rm -rf build .c* .e* .git* scripts Makefile external/breakpad/src/tools external/breakpad/src/processor zip -r sentry-native.zip . - - name: Upload source artifact - uses: actions/upload-artifact@v3 + - name: Build NDK artifacts + working-directory: ndk + run: ./gradlew clean distZip + + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: name: ${{ github.sha }} - # When downloading artifacts, they are double-zipped: - # https://github.com/actions/upload-artifact#zipped-artifact-downloads - path: sentry-native.zip + if-no-files-found: error + path: | + ./*/build/distributions/*.zip + sentry-native.zip diff --git a/.gitignore b/.gitignore index 8b197770eb..cf2764d181 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,10 @@ CodeChecker # Coverage coverage + +# Gradle / Java +local.properties +.gradle +.cxx +build + diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eedc7f485..fefb2521bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ - Add compile-time flag `SENTRY_TRANSPORT_COMPRESSION` description to the `README.md` file. ([#976](https://github.com/getsentry/sentry-native/pull/976)) +**Internal**: + +- Move sentry-android-ndk JNI related parts from sentry-java to sentry-native ([#944](https://github.com/getsentry/sentry-native/pull/944)) + This will create a pre-built `io.sentry:sentry-native-ndk` maven artifact, suitable for being consumed by Android apps. + **Thank you**: - [@AenBleidd](https://github.com/AenBleidd) diff --git a/README.md b/README.md index e4b533c642..b3223d6cf9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ The SDK bundle contains the following folders: directory or copy the header file to your source tree so that it is available during the build. - `src`: Sources of the Sentry SDK required for building. +- `ndk`: Sources for the Android NDK JNI layer. ## Platform and Feature Support @@ -122,11 +123,14 @@ Please refer to the CMake Manual for more details. **Android**: The CMake project can also be configured to correctly work with the Android NDK, -see the dedicated [CMake Guide] for details on how to integrate it with gradle +see the dedicated [CMake Guide] for details on how to integrate it with Gradle or use it on the command line. +The `ndk` folder provides Gradle project which adds a Java JNI layer for Android, suitable for accessing the sentry-native SDK from Java. See the [NDK Readme] for more details about this topic. + [cmake]: https://cmake.org/cmake/help/latest/ [cmake guide]: https://developer.android.com/ndk/guides/cmake +[NDK Readme]: ndk/README.md **MinGW**: diff --git a/ndk/README.md b/ndk/README.md new file mode 100644 index 0000000000..190b51251f --- /dev/null +++ b/ndk/README.md @@ -0,0 +1,75 @@ +# Android NDK support for sentry-native + +| Package | Maven Central | Minimum Android API Level | Supported ABIs | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------- | +| `io.sentry:sentry-native-ndk` | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-native-ndk/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-native-ndk) | 19 | "x86", "armeabi-v7a", "x86_64", "arm64-v8a" | + +## Resources + +- [SDK Documentation](https://docs.sentry.io/platforms/native/) +- [Discord](https://discord.gg/ez5KZN7) server for project discussions +- Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates + +## About + +The sub-project aims to automatically bundle pre-built `sentry-native` binaries together with a Java JNI layer into an Android friendly `.aar` package. + +The `.aar` package also provides [prefab](https://developer.android.com/build/native-dependencies?buildsystem=cmake) support, giving you the possibility to consume the native `sentry.h` APIs from your native app code. + +If you're using the [Sentry Android SDK](https://docs.sentry.io/platforms/android/), this package is included by default already. + +Besides the main package in `ndk/lib`, a simple Android app for for testing purposes is provided in the `ndk/sample` folder. + +## Building and Installation + +The `ndk` project uses the Gradle build system in combination with CMake. You can either use a suitable IDE (e.g. Android Studio) or the command line to build it. + +## Testing and consuming a local package version + +1. Set a custom `versionName` in the `ndk/gradle.properties` file +2. Publish the package locally + +```shell +cd ndk +./gradlew :sentry-native-ndk:publishToMavenLocal +``` + +3. Consume the build in your app + +``` +// usually settings.gradle +allprojects { + repositories { + mavenLocal() + } +} + +// usually app/build.gradle +android { + buildFeatures { + prefab = true + } +} + +dependencies { + implementation("io.sentry:sentry-native-ndk:") +} +``` + +4. Link the pre-built packages with your native code + +```cmake +# usually app/CMakeLists.txt + +find_package(sentry-native-ndk REQUIRED CONFIG) + +target_link_libraries( PRIVATE + ${LOG_LIB} + sentry-native-ndk::sentry-android + sentry-native-ndk::sentry +) +``` + +## Development + +Please see the [contribution guide](../CONTRIBUTING.md). diff --git a/ndk/build.gradle.kts b/ndk/build.gradle.kts new file mode 100644 index 0000000000..4e9ded0342 --- /dev/null +++ b/ndk/build.gradle.kts @@ -0,0 +1,207 @@ +import com.diffplug.spotless.LineEnding +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.MavenPublishPlugin +import com.vanniktech.maven.publish.MavenPublishPluginExtension +import groovy.util.Node +import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +plugins { + `java-library` + id("com.diffplug.spotless") version "6.11.0" apply true + id("io.gitlab.arturbosch.detekt") version "1.19.0" + `maven-publish` + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.0" + +} + +buildscript { + repositories { + google() + } + dependencies { + classpath("com.android.tools.build:gradle:7.4.2") + classpath(kotlin("gradle-plugin", version = "1.8.0")) + classpath("com.vanniktech:gradle-maven-publish-plugin:0.18.0") + // dokka is required by gradle-maven-publish-plugin. + classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.10") + classpath("net.ltgt.gradle:gradle-errorprone-plugin:3.0.1") + + // legacy pre-prefab support + // https://github.com/howardpang/androidNativeBundle + classpath("io.github.howardpang:androidNativeBundle:1.1.4") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } + group = "io.sentry" + version = properties["versionName"].toString() + description = "SDK for sentry.io" + tasks { + withType { + testLogging.showStandardStreams = true + testLogging.exceptionFormat = TestExceptionFormat.FULL + testLogging.events = setOf( + TestLogEvent.SKIPPED, + TestLogEvent.PASSED, + TestLogEvent.FAILED + ) + maxParallelForks = Runtime.getRuntime().availableProcessors() / 2 + + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" + dependsOn("cleanTest") + } + withType { + options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Werror", "-Xlint:-classfile", "-Xlint:-processing")) + } + } +} + +subprojects { + plugins.withId("io.gitlab.arturbosch.detekt") { + configure { + buildUponDefaultConfig = true + allRules = true + config.setFrom("${rootProject.rootDir}/detekt.yml") + } + } + + if (!this.name.contains("sample")) { + apply() + + val sep = File.separator + + configure { + + this.getByName("main").contents { + // non android modules + from("build${sep}libs") + from("build${sep}publications${sep}maven") + // android modules + from("build${sep}outputs${sep}aar") { + include("*-release*") + } + from("build${sep}publications${sep}release") + } + + // craft only uses zip archives + this.forEach { dist -> + if (dist.name == DistributionPlugin.MAIN_DISTRIBUTION_NAME) { + tasks.getByName("distTar").enabled = false + } else { + tasks.getByName(dist.name + "DistTar").enabled = false + } + } + } + + tasks.named("distZip").configure { + this.dependsOn("publishToMavenLocal") + this.doLast { + val distributionFilePath = + "${this.project.buildDir}${sep}distributions${sep}${this.project.name}-${this.project.version}.zip" + val file = File(distributionFilePath) + if (!file.exists()) throw IllegalStateException("Distribution file: $distributionFilePath does not exist") + if (file.length() == 0L) throw IllegalStateException("Distribution file: $distributionFilePath is empty") + } + } + + afterEvaluate { + apply() + + configure { + // signing is done when uploading files to MC + // via gpg:sign-and-deploy-file (release.kts) + releaseSigningEnabled = false + } + + @Suppress("UnstableApiUsage") + configure { + assignAarTypes() + } + } + } +} + +spotless { + lineEndings = LineEnding.UNIX + java { + target("**/*.java") + removeUnusedImports() + googleJavaFormat() + targetExclude("**/generated/**", "**/vendor/**") + } + kotlin { + target("**/*.kt") + ktlint() + } + kotlinGradle { + target("**/*.kts") + ktlint() + } +} + +private val androidLibs = setOf( + "lib" +) + +private val androidXLibs = listOf( + "androidx.core:core" +) + +/* + * Adapted from https://github.com/androidx/androidx/blob/c799cba927a71f01ea6b421a8f83c181682633fb/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt#L524-L549 + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Workaround for https://github.com/gradle/gradle/issues/3170 +@Suppress("UnstableApiUsage") +fun MavenPublishBaseExtension.assignAarTypes() { + pom { + withXml { + val dependencies = asNode().children().find { + it is Node && it.name().toString().endsWith("dependencies") + } as Node? + + dependencies?.children()?.forEach { dep -> + if (dep !is Node) { + return@forEach + } + val group = dep.children().firstOrNull { + it is Node && it.name().toString().endsWith("groupId") + } as? Node + val groupValue = group?.children()?.firstOrNull() as? String + + val artifactId = dep.children().firstOrNull { + it is Node && it.name().toString().endsWith("artifactId") + } as? Node + val artifactIdValue = artifactId?.children()?.firstOrNull() as? String + + if (artifactIdValue in androidLibs) { + dep.appendNode("type", "aar") + } else if ("$groupValue:$artifactIdValue" in androidXLibs) { + dep.appendNode("type", "aar") + } + } + } + } +} diff --git a/ndk/debug.keystore b/ndk/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..7da7480dd09f15fe47e9dffa9a506bf4ab7103f9 GIT binary patch literal 2473 zcmY+EcQ_l08pcB+_NrAYB=#s#wRbfYwJU0GQKYe{Jwmx?sXa?@N>y6asMIRK(PQUW zMeQ0@6jdtJb?ZhX31_&-p=|8eSbR zq-Z$tw~ZWIY=ZDsy|3RaH4s2D2jM^i4fj(teMl8!&Q-RW81xQej6zScfK)Y&II_hw zH0Iq-)CY=-2nS)l!}#8md@~}O@!ZqUNdAaLpG-AB&xb{@(*;MC1{9D$4w#H!$)bi# z1L$CE=|bXCHNV*}9iHS>@4Rhc>X~@%TiC)&|C;=GAh13~EE=a(jVHMRq(MywM=Hf?dt-63z9Je)XB#m~xYh%*&xbr_kwnHu@L;QSmao@kCyRRBxx$i+N%rdOAL zd^IaXem+OBfVgaBGP;rLOag+CR9cFb+oD;8{r4?%2GeoY*pZO zs0TG!Zl{WUNW0;jvThga40!Q==g0QS24gT8uc()A;^OaYi4)bj@H=z-p+Dk0$X)Ic za0GfLnZ^CC4(%#yQPs}yzzuMJ*~G?KSG!Io_Xs(^@_*Y!L`eRrHzW12hZd$7e^29n zVroa7ZlYqz17%JGSzk)J>ATH{@#KxkXxDZVlq#K?@0-(O8xqp88R$Oi%3HD*+o8^S z*2`g|Awp?0XFUKhS-)y$KN8OG+M%}CR7QmFw+B76c72rNd8**zH{#jUzwRD7u5cHA z?Jrc)b1Dc!AOKf@FTev32=D`VU#bTH4R8a50PX`Yms>a-`gfg6ffIBE<&8xPD<~`7c>IDKZ#faW zUmbPxz**tH%#FhV{rQICCx$(#;x>YKD?>=j5%~fJ7xk|WlQrLB($FCfQKVd@b&4wz zKcg(6dNWlea8Ou%Q%iB$^CV2Vd~OsXiCcm(Z?aevN>0*au5yUazaA7z@~n{$U72W| z97c_@j3LC{R~M~5We6^v7#p|Zr4tQlRpE^ozW@?jpH?qRp4iEh>8hIdwq*K?TIx2i z|B4D^;P7iIcQzyRuQ3XU2Ef(_WZZb^BmL92+BEnXI6Z%=IXny}w7)0V_KQ!pP;Oxi zh<3Z#J_vtr?slCr!7u&B>~6XZpY8 zs3q}`i&REO1$8#$xb6*Yrc1uw!nRy5h>@L9Mv`}!debwz&)z^yFW7UkE0gJ_IRZv+ z-mXN=%P?6PMs5BnH6Gr37aIe0IkN=4iwnB?$}4G=5CJjJ4Rgm`@kr7xhqb2E__mf` zb!##&(B9BAH6j$#X^h>lVz*{(SCdcXyg}nWMxxPJVEfU`6>TV(#>`J>Si}e((1Bdz zGH_33V%oH}eY-aNgjlePJv}SD+N%Lo$rA|Kc&;p%C!|K`Zk`dpZubkrYc`E_(F2bR zg-E}OJC*y+T}vbi^^gX3MVKZXHt=;$~ey>ipc))L6fA6(nE!N65#VwrF!) zm75CYfOrcXc#4dZLGCm z3HeGeezrq}K|U2b@8r_i@QKnfj?(F>OA*V8n?bvmgcSOby+~bGh&997+_4nj(J(Co zlJ$gWdQ$hT3gwuf)7ZUIGk(RKT02H{2!dz(t-t=04Sz1V+}S%3=}G_MPtq~$M3a^{ zZjByUd=Nv3wwli(8+^*zh6Pq;HT5Kye;&;lEbrHDmqO5HZB}cbr=155KXPoI>Fqs{ zPiq}>+NRA~^${+bK1EKskh_GDIwer$5`swE8WH~o1(2Ds0{nU6?W1e7u1I_*zj({W z>0J7=-@Fw~;bhOp){BH`Fa26yD9%nv4_Wi+ytov&D1VVVUtGWrPq4!tdpyz;?7gml zp>)Wvd0vBS!=Z3`FpUfgkeZJM0OAa~6GUuHO|m&+Iqg?sCy6}a)>;B_<_+dvB%bbC W#z5= 3.6.x +android.useAndroidX=true + +# Required by AGP >= 8.0.x +android.defaults.buildfeatures.buildconfig=true + +# Release information, used for maven publishing +versionName=0.7.2 + +# disable renderscript, it's enabled by default +android.defaults.buildfeatures.renderscript=false + +# disable shader compilation, it's enabled by default +android.defaults.buildfeatures.shaders=false + +# disable aidl files, it's enabled by default +android.defaults.buildfeatures.aidl=false + +# disable Resource Values generation +android.defaults.buildfeatures.resvalues=false + +# disable automatically adding Kotlin stdlib to compile dependencies +kotlin.stdlib.default.dependency=false + +# TODO: Enable Prefab https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html +# android.enablePrefab=true +# android.prefabVersion=1.0.0 + +# publication pom properties +POM_NAME=Sentry SDK +POM_DESCRIPTION=SDK for sentry.io +POM_URL=https://github.com/getsentry/sentry-native +POM_SCM_URL=https://github.com/getsentry/sentry-native +POM_SCM_CONNECTION=scm:git:git://github.com/getsentry/sentry-native.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/getsentry/sentry-native.git + +POM_LICENCE_NAME=MIT +POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php + +POM_DEVELOPER_ID=getsentry +POM_DEVELOPER_NAME=Sentry Team and Contributors +POM_DEVELOPER_URL=https://github.com/getsentry/ + +POM_ARTIFACT_ID=sentry-native-ndk + +systemProp.org.gradle.internal.http.socketTimeout=120000 + +android.nonTransitiveRClass=true diff --git a/ndk/gradle/wrapper/gradle-wrapper.jar b/ndk/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/ndk/gradle/wrapper/gradle-wrapper.properties b/ndk/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..3fa8f862f7 --- /dev/null +++ b/ndk/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ndk/gradlew b/ndk/gradlew new file mode 100755 index 0000000000..1aa94a4269 --- /dev/null +++ b/ndk/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/ndk/gradlew.bat b/ndk/gradlew.bat new file mode 100644 index 0000000000..6689b85bee --- /dev/null +++ b/ndk/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ndk/lib/CMakeLists.txt b/ndk/lib/CMakeLists.txt new file mode 100644 index 0000000000..b9f8f4a4ba --- /dev/null +++ b/ndk/lib/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) +project(Sentry-Android LANGUAGES C CXX) + +# Add sentry-android shared library +add_library(sentry-android SHARED src/main/jni/sentry.c) + +# make sure that we build it as a shared lib instead of a static lib +set(BUILD_SHARED_LIBS ON) +set(SENTRY_BUILD_SHARED_LIBS ON) + +# Adding sentry-native project +add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build) + +# Link to sentry-native +target_link_libraries(sentry-android PRIVATE + $ +) diff --git a/ndk/lib/api/sentry-android-ndk.api b/ndk/lib/api/sentry-android-ndk.api new file mode 100644 index 0000000000..e8f838ce8b --- /dev/null +++ b/ndk/lib/api/sentry-android-ndk.api @@ -0,0 +1,29 @@ +public final class io/sentry/android/ndk/BuildConfig { + public static final field BUILD_TYPE Ljava/lang/String; + public static final field DEBUG Z + public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; + public static final field VERSION_NAME Ljava/lang/String; + public fun ()V +} + +public final class io/sentry/android/ndk/DebugImagesLoader : io/sentry/android/core/IDebugImagesLoader { + public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/ndk/NativeModuleListLoader;)V + public fun clearDebugImages ()V + public fun loadDebugImages ()Ljava/util/List; +} + +public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObserverAdapter { + public fun (Lio/sentry/SentryOptions;)V + public fun addBreadcrumb (Lio/sentry/Breadcrumb;)V + public fun removeExtra (Ljava/lang/String;)V + public fun removeTag (Ljava/lang/String;)V + public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V + public fun setTag (Ljava/lang/String;Ljava/lang/String;)V + public fun setUser (Lio/sentry/protocol/User;)V +} + +public final class io/sentry/android/ndk/SentryNdk { + public static fun close ()V + public static fun init (Lio/sentry/android/core/SentryAndroidOptions;)V +} + diff --git a/ndk/lib/build.gradle.kts b/ndk/lib/build.gradle.kts new file mode 100644 index 0000000000..f14ce9a72f --- /dev/null +++ b/ndk/lib/build.gradle.kts @@ -0,0 +1,93 @@ +plugins { + id("com.android.library") + kotlin("android") + id("com.ydq.android.gradle.native-aar.export") +} + +var sentryNativeSrc: String = "${project.projectDir}/../.." + +android { + compileSdk = 34 + namespace = "io.sentry.ndk" + + defaultConfig { + minSdk = 19 + + externalNativeBuild { + cmake { + arguments.add(0, "-DANDROID_STL=c++_shared") + arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc") + } + } + + ndk { + abiFilters.addAll(listOf("x86", "armeabi-v7a", "x86_64", "arm64-v8a")) + } + } + + // we use the default NDK and CMake versions based on the AGP's version + // https://developer.android.com/studio/projects/install-ndk#apply-specific-version + externalNativeBuild { + cmake { + path("CMakeLists.txt") + } + } + + buildTypes { + getByName("debug") + getByName("release") { + consumerProguardFiles("proguard-rules.pro") + } + } + + buildFeatures { + prefabPublishing = true + } + + // creates + // lib.aar/prefab/modules/sentry-android/libs//.so + // lib.aar/prefab/modules/sentry-android/include/sentry.h + prefab { + create("sentry-android") {} + create("sentry") { + headers = "../../include" + } + } + + // legacy pre-prefab support + // https://github.com/howardpang/androidNativeBundle + // creates + // lib.aar/jni//.so + // lib.aar/jni/include/sentry.h + nativeBundleExport { + headerDir = "../../include" + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + testOptions { + animationsDisabled = true + unitTests.apply { + isReturnDefaultValues = true + isIncludeAndroidResources = true + } + } + + lint { + warningsAsErrors = true + checkDependencies = true + checkReleaseBuilds = true + } + + variantFilter { + if (System.getenv("CI")?.toBoolean() == true && buildType.name == "debug") { + ignore = true + } + } +} + +dependencies { + compileOnly("org.jetbrains:annotations:23.0.0") +} diff --git a/ndk/lib/proguard-rules.pro b/ndk/lib/proguard-rules.pro new file mode 100644 index 0000000000..a6d1d5f150 --- /dev/null +++ b/ndk/lib/proguard-rules.pro @@ -0,0 +1,22 @@ +##---------------Begin: proguard configuration for NDK ---------- + +# The Android SDK checks at runtime if this class is available via Class.forName +-keep class io.sentry.ndk.SentryNdk { *; } + +# The JNI layer uses this class through reflection +-keep class io.sentry.ndk.NdkOptions { *; } +-keep class io.sentry.ndk.DebugImage { *; } + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames,includedescriptorclasses class * { + native ; +} + +# don't warn jetbrains annotations +-dontwarn org.jetbrains.annotations.** + +# To ensure that stack traces is unambiguous +# https://developer.android.com/studio/build/shrink-code#decode-stack-trace +-keepattributes LineNumberTable,SourceFile + +##---------------End: proguard configuration for NDK ---------- diff --git a/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java b/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java new file mode 100644 index 0000000000..e06b6e2b49 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/DebugImage.java @@ -0,0 +1,199 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public final class DebugImage { + + /** + * The unique UUID of the image. + * + *

UUID computed from the file contents, assigned by the Java SDK. + */ + private @Nullable String uuid; + + private @Nullable String type; + /** + * Unique debug identifier of the image. + * + *

- `elf`: Debug identifier of the dynamic library or executable. If a code identifier is + * available, the debug identifier is the little-endian UUID representation of the first 16-bytes + * of that identifier. Spaces are inserted for readability, note the byte order of the first + * fields: + * + *

```text code id: f1c3bcc0 2798 65fe 3058 404b2831d9e6 4135386c debug id: + * c0bcc3f1-9827-fe65-3058-404b2831d9e6 ``` + * + *

If no code id is available, the debug id should be computed by XORing the first 4096 bytes + * of the `.text` section in 16-byte chunks, and representing it as a little-endian UUID (again + * swapping the byte order). + * + *

- `pe`: `signature` and `age` of the PDB file. Both values can be read from the CodeView + * PDB70 debug information header in the PE. The value should be represented as little-endian + * UUID, with the age appended at the end. Note that the byte order of the UUID fields must be + * swapped (spaces inserted for readability): + * + *

```text signature: f1c3bcc0 2798 65fe 3058 404b2831d9e6 age: 1 debug_id: + * c0bcc3f1-9827-fe65-3058-404b2831d9e6-1 ``` + * + *

- `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` + * load command in the Mach header, formatted as UUID. + */ + private @Nullable String debugId; + + /** + * Path and name of the debug companion file. + * + *

- `elf`: Name or absolute path to the file containing stripped debug information for this + * image. This value might be _required_ to retrieve debug files from certain symbol servers. + * + *

- `pe`: Name of the PDB file containing debug information for this image. This value is + * often required to retrieve debug files from specific symbol servers. + * + *

- `macho`: Name or absolute path to the dSYM file containing debug information for this + * image. This value might be required to retrieve debug files from certain symbol servers. + */ + private @Nullable String debugFile; + /** + * Optional identifier of the code file. + * + *

- `elf`: If the program was compiled with a relatively recent compiler, this should be the + * hex representation of the `NT_GNU_BUILD_ID` program header (type `PT_NOTE`), or the value of + * the `.note.gnu.build-id` note section (type `SHT_NOTE`). Otherwise, leave this value empty. + * + *

Certain symbol servers use the code identifier to locate debug information for ELF images, + * in which case this field should be included if possible. + * + *

- `pe`: Identifier of the executable or DLL. It contains the values of the `time_date_stamp` + * from the COFF header and `size_of_image` from the optional header formatted together into a hex + * string using `%08x%X` (note that the second value is not padded): + * + *

```text time_date_stamp: 0x5ab38077 size_of_image: 0x9000 code_id: 5ab380779000 ``` + * + *

The code identifier should be provided to allow server-side stack walking of binary crash + * reports, such as Minidumps. + * + *

+ * + *

- `macho`: Identifier of the dynamic library or executable. It is the value of the `LC_UUID` + * load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is + * equivalent to the debug identifier. + */ + private @Nullable String codeId; + /** + * Path and name of the image file (required). + * + *

The absolute path to the dynamic library or executable. This helps to locate the file if it + * is missing on Sentry. + * + *

- `pe`: The code file should be provided to allow server-side stack walking of binary crash + * reports, such as Minidumps. + */ + private @Nullable String codeFile; + /** + * Starting memory address of the image (required). + * + *

Memory address, at which the image is mounted in the virtual address space of the process. + * Should be a string in hex representation prefixed with `"0x"`. + */ + private @Nullable String imageAddr; + /** + * Size of the image in bytes (required). + * + *

The size of the image in virtual memory. If missing, Sentry will assume that the image spans + * up to the next image, which might lead to invalid stack traces. + */ + private @Nullable Long imageSize; + /** + * CPU architecture target. + * + *

Architecture of the module. If missing, this will be backfilled by Sentry. + */ + private @Nullable String arch; + + @SuppressWarnings("unused") + private @Nullable Map unknown; + + public @Nullable String getUuid() { + return uuid; + } + + public void setUuid(final @Nullable String uuid) { + this.uuid = uuid; + } + + public @Nullable String getType() { + return type; + } + + public void setType(final @Nullable String type) { + this.type = type; + } + + public @Nullable String getDebugId() { + return debugId; + } + + public void setDebugId(final @Nullable String debugId) { + this.debugId = debugId; + } + + public @Nullable String getDebugFile() { + return debugFile; + } + + public void setDebugFile(final @Nullable String debugFile) { + this.debugFile = debugFile; + } + + public @Nullable String getCodeFile() { + return codeFile; + } + + public void setCodeFile(final @Nullable String codeFile) { + this.codeFile = codeFile; + } + + public @Nullable String getImageAddr() { + return imageAddr; + } + + public void setImageAddr(final @Nullable String imageAddr) { + this.imageAddr = imageAddr; + } + + public @Nullable Long getImageSize() { + return imageSize; + } + + public void setImageSize(final @Nullable Long imageSize) { + this.imageSize = imageSize; + } + + /** + * Sets the image size. + * + * @param imageSize the image size. + */ + public void setImageSize(long imageSize) { + this.imageSize = imageSize; + } + + public @Nullable String getArch() { + return arch; + } + + public void setArch(final @Nullable String arch) { + this.arch = arch; + } + + public @Nullable String getCodeId() { + return codeId; + } + + public void setCodeId(final @Nullable String codeId) { + this.codeId = codeId; + } + +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java b/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java new file mode 100644 index 0000000000..4053929b37 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/INativeScope.java @@ -0,0 +1,18 @@ +package io.sentry.ndk; + +public interface INativeScope { + void setTag(String key, String value); + + void removeTag(String key); + + void setExtra(String key, String value); + + void removeExtra(String key); + + void setUser(String id, String email, String ipAddress, String username); + + void removeUser(); + + void addBreadcrumb( + String level, String message, String category, String type, String timestamp, String data); +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java b/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java new file mode 100644 index 0000000000..c6612cef79 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NativeModuleListLoader.java @@ -0,0 +1,18 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.Nullable; + +public final class NativeModuleListLoader { + + public static native DebugImage[] nativeLoadModuleList(); + + public static native void nativeClearModuleList(); + + public @Nullable DebugImage[] loadModuleList() { + return nativeLoadModuleList(); + } + + public void clearModuleList() { + nativeClearModuleList(); + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java b/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java new file mode 100644 index 0000000000..18df418f75 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NativeScope.java @@ -0,0 +1,55 @@ +package io.sentry.ndk; + +public final class NativeScope implements INativeScope { + public static native void nativeSetTag(String key, String value); + + public static native void nativeRemoveTag(String key); + + public static native void nativeSetExtra(String key, String value); + + public static native void nativeRemoveExtra(String key); + + public static native void nativeSetUser( + String id, String email, String ipAddress, String username); + + public static native void nativeRemoveUser(); + + public static native void nativeAddBreadcrumb( + String level, String message, String category, String type, String timestamp, String data); + + @Override + public void setTag(String key, String value) { + nativeSetTag(key, value); + } + + @Override + public void removeTag(String key) { + nativeRemoveTag(key); + } + + @Override + public void setExtra(String key, String value) { + nativeSetExtra(key, value); + } + + @Override + public void removeExtra(String key) { + nativeRemoveExtra(key); + } + + @Override + public void setUser(String id, String email, String ipAddress, String username) { + nativeSetUser(id, email, ipAddress, username); + } + + @Override + public void removeUser() { + nativeRemoveUser(); + } + + @Override + public void addBreadcrumb( + String level, String message, String category, String type, String timestamp, String data) { + nativeAddBreadcrumb(level, message, category, type, timestamp, data); + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java b/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java new file mode 100644 index 0000000000..c4ed14f409 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java @@ -0,0 +1,64 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class NdkOptions { + private final @NotNull String dsn; + private final boolean isDebug; + private final @NotNull String outboxPath; + private final @Nullable String release; + private final @Nullable String environment; + private final @Nullable String dist; + private final int maxBreadcrumbs; + private final @Nullable String sdkName; + + public NdkOptions(@NotNull String dsn, boolean isDebug, @NotNull String outboxPath, @Nullable String release, @Nullable String environment, @Nullable String dist, int maxBreadcrumbs, @Nullable String sdkName) { + this.dsn = dsn; + this.isDebug = isDebug; + this.outboxPath = outboxPath; + this.release = release; + this.environment = environment; + this.dist = dist; + this.maxBreadcrumbs = maxBreadcrumbs; + this.sdkName = sdkName; + } + + @NotNull + public String getDsn() { + return dsn; + } + + public boolean isDebug() { + return isDebug; + } + + @NotNull + public String getOutboxPath() { + return outboxPath; + } + + @Nullable + public String getRelease() { + return release; + } + + @Nullable + public String getEnvironment() { + return environment; + } + + @Nullable + public String getDist() { + return dist; + } + + public int getMaxBreadcrumbs() { + return maxBreadcrumbs; + } + + @Nullable + public String getSdkName() { + return sdkName; + } +} diff --git a/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java b/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java new file mode 100644 index 0000000000..58394188c7 --- /dev/null +++ b/ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java @@ -0,0 +1,42 @@ +package io.sentry.ndk; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Internal +public final class SentryNdk { + + static { + // On older Android versions, it was necessary to manually call "`System.loadLibrary` on all + // transitive dependencies before loading [the] main library." + // The dependencies of `libsentry.so` are currently `lib{c,m,dl,log}.so`. + // See + // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#changes-to-library-dependency-resolution + System.loadLibrary("log"); + System.loadLibrary("sentry"); + System.loadLibrary("sentry-android"); + } + + private SentryNdk() { + } + + private static native void initSentryNative(@NotNull final NdkOptions options); + + private static native void shutdown(); + + /** + * Init the NDK integration + * + * @param options the SentryAndroidOptions + */ + public static void init(@NotNull final NdkOptions options) { + initSentryNative(options); + } + + /** + * Closes the NDK integration + */ + public static void close() { + shutdown(); + } +} diff --git a/ndk/lib/src/main/jni/sentry.c b/ndk/lib/src/main/jni/sentry.c new file mode 100644 index 0000000000..ee412a01ab --- /dev/null +++ b/ndk/lib/src/main/jni/sentry.c @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include + +#define ENSURE(Expr) \ + if (!(Expr)) \ + return + +#define ENSURE_OR_FAIL(Expr) \ + if (!(Expr)) \ + goto fail + +static bool get_string_into(JNIEnv *env, jstring jstr, char *buf, size_t buf_len) { + jsize utf_len = (*env)->GetStringUTFLength(env, jstr); + if ((size_t) utf_len >= buf_len) { + return false; + } + + jsize j_len = (*env)->GetStringLength(env, jstr); + + (*env)->GetStringUTFRegion(env, jstr, 0, j_len, buf); + if ((*env)->ExceptionCheck(env) == JNI_TRUE) { + return false; + } + + buf[utf_len] = '\0'; + return true; +} + +static char *get_string(JNIEnv *env, jstring jstr) { + char *buf = NULL; + + jsize utf_len = (*env)->GetStringUTFLength(env, jstr); + size_t buf_len = (size_t) utf_len + 1; + buf = sentry_malloc(buf_len); + ENSURE_OR_FAIL(buf); + + ENSURE_OR_FAIL(get_string_into(env, jstr, buf, buf_len)); + + return buf; + + fail: + sentry_free(buf); + + return NULL; +} + +static char *call_get_string(JNIEnv *env, jobject obj, jmethodID mid) { + jstring j_str = (jstring) (*env)->CallObjectMethod(env, obj, mid); + ENSURE_OR_FAIL(j_str); + char *str = get_string(env, j_str); + (*env)->DeleteLocalRef(env, j_str); + + return str; + + fail: + return NULL; +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetTag( + JNIEnv *env, + jclass cls, + jstring key, + jstring value) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + const char *charValue = (*env)->GetStringUTFChars(env, value, 0); + + sentry_set_tag(charKey, charValue); + + (*env)->ReleaseStringUTFChars(env, key, charKey); + (*env)->ReleaseStringUTFChars(env, value, charValue); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveTag(JNIEnv *env, jclass cls, jstring key) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + + sentry_remove_tag(charKey); + + (*env)->ReleaseStringUTFChars(env, key, charKey); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetExtra( + JNIEnv *env, + jclass cls, + jstring key, + jstring value) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + const char *charValue = (*env)->GetStringUTFChars(env, value, 0); + + sentry_value_t sentryValue = sentry_value_new_string(charValue); + sentry_set_extra(charKey, sentryValue); + + (*env)->ReleaseStringUTFChars(env, key, charKey); + (*env)->ReleaseStringUTFChars(env, value, charValue); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveExtra(JNIEnv *env, jclass cls, jstring key) { + const char *charKey = (*env)->GetStringUTFChars(env, key, 0); + + sentry_remove_extra(charKey); + + (*env)->ReleaseStringUTFChars(env, key, charKey); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeSetUser( + JNIEnv *env, + jclass cls, + jstring id, + jstring email, + jstring ipAddress, + jstring username) { + sentry_value_t user = sentry_value_new_object(); + if (id) { + const char *charId = (*env)->GetStringUTFChars(env, id, 0); + sentry_value_set_by_key(user, "id", sentry_value_new_string(charId)); + (*env)->ReleaseStringUTFChars(env, id, charId); + } + if (email) { + const char *charEmail = (*env)->GetStringUTFChars(env, email, 0); + sentry_value_set_by_key( + user, "email", sentry_value_new_string(charEmail)); + (*env)->ReleaseStringUTFChars(env, email, charEmail); + } + if (ipAddress) { + const char *charIpAddress = (*env)->GetStringUTFChars(env, ipAddress, 0); + sentry_value_set_by_key( + user, "ip_address", sentry_value_new_string(charIpAddress)); + (*env)->ReleaseStringUTFChars(env, ipAddress, charIpAddress); + } + if (username) { + const char *charUsername = (*env)->GetStringUTFChars(env, username, 0); + sentry_value_set_by_key( + user, "username", sentry_value_new_string(charUsername)); + (*env)->ReleaseStringUTFChars(env, username, charUsername); + } + sentry_set_user(user); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeRemoveUser(JNIEnv *env, jclass cls) { + sentry_remove_user(); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeScope_nativeAddBreadcrumb( + JNIEnv *env, + jclass cls, + jstring level, + jstring message, + jstring category, + jstring type, + jstring timestamp, + jstring data) { + if (!level && !message && !category && !type) { + return; + } + const char *charMessage = NULL; + if (message) { + charMessage = (*env)->GetStringUTFChars(env, message, 0); + } + const char *charType = NULL; + if (type) { + charType = (*env)->GetStringUTFChars(env, type, 0); + } + sentry_value_t crumb = sentry_value_new_breadcrumb(charType, charMessage); + + if (charMessage) { + (*env)->ReleaseStringUTFChars(env, message, charMessage); + } + if (charType) { + (*env)->ReleaseStringUTFChars(env, type, charType); + } + + if (category) { + const char *charCategory = (*env)->GetStringUTFChars(env, category, 0); + sentry_value_set_by_key( + crumb, "category", sentry_value_new_string(charCategory)); + (*env)->ReleaseStringUTFChars(env, category, charCategory); + } + if (level) { + const char *charLevel = (*env)->GetStringUTFChars(env, level, 0); + sentry_value_set_by_key( + crumb, "level", sentry_value_new_string(charLevel)); + (*env)->ReleaseStringUTFChars(env, level, charLevel); + } + + if (timestamp) { + // overwrite timestamp that is already created on sentry_value_new_breadcrumb + const char *charTimestamp = (*env)->GetStringUTFChars(env, timestamp, 0); + sentry_value_set_by_key( + crumb, "timestamp", sentry_value_new_string(charTimestamp)); + (*env)->ReleaseStringUTFChars(env, timestamp, charTimestamp); + } + + if (data) { + const char *charData = (*env)->GetStringUTFChars(env, data, 0); + + // we create an object because the Java layer parses it as a Map + sentry_value_t dataObject = sentry_value_new_object(); + sentry_value_set_by_key(dataObject, "data", sentry_value_new_string(charData)); + + sentry_value_set_by_key(crumb, "data", dataObject); + + (*env)->ReleaseStringUTFChars(env, data, charData); + } + + sentry_add_breadcrumb(crumb); +} + +static void send_envelope(sentry_envelope_t *envelope, void *data) { + const char *outbox_path = (const char *) data; + char envelope_id_str[40]; + + sentry_uuid_t envelope_id = sentry_uuid_new_v4(); + sentry_uuid_as_string(&envelope_id, envelope_id_str); + + size_t outbox_len = strlen(outbox_path); + size_t final_len = outbox_len + 42; // "/" + envelope_id_str + "\0" = 42 + char *envelope_path = sentry_malloc(final_len); + ENSURE(envelope_path); + int written = snprintf(envelope_path, final_len, "%s/%s", outbox_path, envelope_id_str); + if (written > outbox_len && written < final_len) { + sentry_envelope_write_to_file(envelope, envelope_path); + } + + sentry_free(envelope_path); + sentry_envelope_free(envelope); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_SentryNdk_initSentryNative( + JNIEnv *env, + jclass cls, + jobject sentry_ndk_options) { + jclass options_cls = (*env)->GetObjectClass(env, sentry_ndk_options); + jmethodID outbox_path_mid = (*env)->GetMethodID(env, options_cls, "getOutboxPath", + "()Ljava/lang/String;"); + jmethodID dsn_mid = (*env)->GetMethodID(env, options_cls, "getDsn", "()Ljava/lang/String;"); + jmethodID is_debug_mid = (*env)->GetMethodID(env, options_cls, "isDebug", "()Z"); + jmethodID release_mid = (*env)->GetMethodID(env, options_cls, "getRelease", + "()Ljava/lang/String;"); + jmethodID environment_mid = (*env)->GetMethodID(env, options_cls, "getEnvironment", + "()Ljava/lang/String;"); + jmethodID dist_mid = (*env)->GetMethodID(env, options_cls, "getDist", "()Ljava/lang/String;"); + jmethodID max_crumbs_mid = (*env)->GetMethodID(env, options_cls, "getMaxBreadcrumbs", "()I"); + jmethodID native_sdk_name_mid = (*env)->GetMethodID(env, options_cls, "getSdkName", + "()Ljava/lang/String;"); + + (*env)->DeleteLocalRef(env, options_cls); + + char *outbox_path = NULL; + sentry_transport_t *transport = NULL; + bool transport_owns_path = false; + sentry_options_t *options = NULL; + bool options_owns_transport = false; + char *dsn_str = NULL; + char *release_str = NULL; + char *environment_str = NULL; + char *dist_str = NULL; + char *native_sdk_name_str = NULL; + + options = sentry_options_new(); + ENSURE_OR_FAIL(options); + + // session tracking is enabled by default, but the Android SDK already handles it + sentry_options_set_auto_session_tracking(options, 0); + + jboolean debug = (jboolean) (*env)->CallBooleanMethod(env, sentry_ndk_options, is_debug_mid); + sentry_options_set_debug(options, debug); + + jint max_crumbs = (jint) (*env)->CallIntMethod(env, sentry_ndk_options, max_crumbs_mid); + sentry_options_set_max_breadcrumbs(options, max_crumbs); + + outbox_path = call_get_string(env, sentry_ndk_options, outbox_path_mid); + ENSURE_OR_FAIL(outbox_path); + + transport = sentry_transport_new(send_envelope); + ENSURE_OR_FAIL(transport); + sentry_transport_set_state(transport, outbox_path); + sentry_transport_set_free_func(transport, sentry_free); + transport_owns_path = true; + + sentry_options_set_transport(options, transport); + options_owns_transport = true; + + // give sentry-native its own database path it can work with, next to the outbox + size_t outbox_len = strlen(outbox_path); + size_t final_len = outbox_len + 15; // len(".sentry-native\0") = 15 + char *database_path = sentry_malloc(final_len); + ENSURE_OR_FAIL(database_path); + strncpy(database_path, outbox_path, final_len); + char *dir = strrchr(database_path, '/'); + if (dir) { + strncpy(dir + 1, ".sentry-native", final_len - (dir + 1 - database_path)); + } + sentry_options_set_database_path(options, database_path); + sentry_free(database_path); + + dsn_str = call_get_string(env, sentry_ndk_options, dsn_mid); + ENSURE_OR_FAIL(dsn_str); + sentry_options_set_dsn(options, dsn_str); + sentry_free(dsn_str); + + release_str = call_get_string(env, sentry_ndk_options, release_mid); + if (release_str) { + sentry_options_set_release(options, release_str); + sentry_free(release_str); + } + + environment_str = call_get_string(env, sentry_ndk_options, environment_mid); + if (environment_str) { + sentry_options_set_environment(options, environment_str); + sentry_free(environment_str); + } + + dist_str = call_get_string(env, sentry_ndk_options, dist_mid); + if (dist_str) { + sentry_options_set_dist(options, dist_str); + sentry_free(dist_str); + } + + native_sdk_name_str = call_get_string(env, sentry_ndk_options, native_sdk_name_mid); + if (native_sdk_name_str) { + sentry_options_set_sdk_name(options, native_sdk_name_str); + sentry_free(native_sdk_name_str); + } + + sentry_init(options); + return; + + fail: + if (!transport_owns_path) { + sentry_free(outbox_path); + } + if (!options_owns_transport) { + sentry_transport_free(transport); + } + sentry_options_free(options); +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_NativeModuleListLoader_nativeClearModuleList(JNIEnv *env, jclass cls) { + sentry_clear_modulecache(); +} + +JNIEXPORT jobjectArray JNICALL +Java_io_sentry_ndk_NativeModuleListLoader_nativeLoadModuleList(JNIEnv *env, jclass cls) { + sentry_value_t image_list_t = sentry_get_modules_list(); + jobjectArray image_list = NULL; + + if (sentry_value_get_type(image_list_t) == SENTRY_VALUE_TYPE_LIST) { + size_t len_t = sentry_value_get_length(image_list_t); + + jclass image_class = (*env)->FindClass(env, "io/sentry/ndk/DebugImage"); + image_list = (*env)->NewObjectArray(env, len_t, image_class, NULL); + + jmethodID image_addr_method = (*env)->GetMethodID(env, image_class, "setImageAddr", + "(Ljava/lang/String;)V"); + + jmethodID image_size_method = (*env)->GetMethodID(env, image_class, "setImageSize", + "(J)V"); + + jmethodID code_file_method = (*env)->GetMethodID(env, image_class, "setCodeFile", + "(Ljava/lang/String;)V"); + + jmethodID image_addr_ctor = (*env)->GetMethodID(env, image_class, "", + "()V"); + + jmethodID type_method = (*env)->GetMethodID(env, image_class, "setType", + "(Ljava/lang/String;)V"); + + jmethodID debug_id_method = (*env)->GetMethodID(env, image_class, "setDebugId", + "(Ljava/lang/String;)V"); + + jmethodID code_id_method = (*env)->GetMethodID(env, image_class, "setCodeId", + "(Ljava/lang/String;)V"); + + jmethodID debug_file_method = (*env)->GetMethodID(env, image_class, "setDebugFile", + "(Ljava/lang/String;)V"); + + for (size_t i = 0; i < len_t; i++) { + sentry_value_t image_t = sentry_value_get_by_index(image_list_t, i); + + if (!sentry_value_is_null(image_t)) { + jobject image = (*env)->NewObject(env, image_class, image_addr_ctor); + + sentry_value_t image_addr_t = sentry_value_get_by_key(image_t, "image_addr"); + if (!sentry_value_is_null(image_addr_t)) { + + const char *value_v = sentry_value_as_string(image_addr_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, image_addr_method, value); + + // Local refs (eg NewStringUTF) are freed automatically when the native method + // returns, but if you're iterating a large array, it's recommended to release + // manually due to allocation limits (512) on Android < 8 or OOM. + // https://developer.android.com/training/articles/perf-jni.html#local-and-global-references + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t image_size_t = sentry_value_get_by_key(image_t, "image_size"); + if (!sentry_value_is_null(image_size_t)) { + + int32_t value_v = sentry_value_as_int32(image_size_t); + jlong value = (jlong) value_v; + + (*env)->CallVoidMethod(env, image, image_size_method, value); + } + + sentry_value_t code_file_t = sentry_value_get_by_key(image_t, "code_file"); + if (!sentry_value_is_null(code_file_t)) { + + const char *value_v = sentry_value_as_string(code_file_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, code_file_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t code_type_t = sentry_value_get_by_key(image_t, "type"); + if (!sentry_value_is_null(code_type_t)) { + + const char *value_v = sentry_value_as_string(code_type_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, type_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t debug_id_t = sentry_value_get_by_key(image_t, "debug_id"); + if (!sentry_value_is_null(code_type_t)) { + + const char *value_v = sentry_value_as_string(debug_id_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, debug_id_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + sentry_value_t code_id_t = sentry_value_get_by_key(image_t, "code_id"); + if (!sentry_value_is_null(code_id_t)) { + + const char *value_v = sentry_value_as_string(code_id_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, code_id_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + // not needed on Android, but keeping for forward compatibility + sentry_value_t debug_file_t = sentry_value_get_by_key(image_t, "debug_file"); + if (!sentry_value_is_null(debug_file_t)) { + + const char *value_v = sentry_value_as_string(debug_file_t); + jstring value = (*env)->NewStringUTF(env, value_v); + + (*env)->CallVoidMethod(env, image, debug_file_method, value); + + (*env)->DeleteLocalRef(env, value); + } + + (*env)->SetObjectArrayElement(env, image_list, i, image); + + (*env)->DeleteLocalRef(env, image); + } + } + + sentry_value_decref(image_list_t); + } + + return image_list; +} + +JNIEXPORT void JNICALL +Java_io_sentry_ndk_SentryNdk_shutdown(JNIEnv *env, jclass cls) { + sentry_close(); +} diff --git a/ndk/lib/src/main/res/values/public.xml b/ndk/lib/src/main/res/values/public.xml new file mode 100644 index 0000000000..788fdddc0c --- /dev/null +++ b/ndk/lib/src/main/res/values/public.xml @@ -0,0 +1,4 @@ + + + + diff --git a/ndk/sample/CMakeLists.txt b/ndk/sample/CMakeLists.txt new file mode 100644 index 0000000000..de05d5a020 --- /dev/null +++ b/ndk/sample/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) +project(sentry-native-ndk-sample LANGUAGES C CXX) + +set(BUILD_SHARED_LIBS ON) +set(SENTRY_BUILD_SHARED_LIBS ON) + +add_library(ndk-sample SHARED src/main/cpp/ndk-sample.cpp) + +# Adding sentry-native project +add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build) + +# Android logging library +find_library(LOG_LIB log) + +target_link_libraries(ndk-sample PRIVATE + ${LOG_LIB} + $ +) diff --git a/ndk/sample/build.gradle.kts b/ndk/sample/build.gradle.kts new file mode 100644 index 0000000000..5390793df4 --- /dev/null +++ b/ndk/sample/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + id("com.android.application") + kotlin("android") +} + +var sentryNativeSrc: String = "${project.projectDir}/../.." + +android { + compileSdk = 34 + namespace = "io.sentry.ndk.sample" + + defaultConfig { + applicationId = "io.sentry.ndk.sample" + minSdk = 19 + targetSdk = 34 + versionCode = 2 + versionName = project.version.toString() + + externalNativeBuild { + cmake { + arguments.add(0, "-DANDROID_STL=c++_shared") + arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc") + } + } + + ndk { + abiFilters.addAll(listOf("x86", "armeabi-v7a", "x86_64", "arm64-v8a")) + } + } + + externalNativeBuild { + cmake { + path("CMakeLists.txt") + } + } + + signingConfigs { + getByName("debug") { + storeFile = rootProject.file("debug.keystore") + storePassword = "android" + keyAlias = "androiddebugkey" + keyPassword = "android" + } + } + + buildTypes { + getByName("release") { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("debug") // to be able to run release mode + isShrinkResources = true + + addManifestPlaceholders( + mapOf( + "sentryDebug" to false, "sentryEnvironment" to "release" + ) + ) + } + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } +} + +dependencies { + implementation(project(":sentry-native-ndk")) +} diff --git a/ndk/sample/proguard-rules.pro b/ndk/sample/proguard-rules.pro new file mode 100644 index 0000000000..1165340c89 --- /dev/null +++ b/ndk/sample/proguard-rules.pro @@ -0,0 +1,34 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# To ensure that stack traces is unambiguous +# https://developer.android.com/studio/build/shrink-code#decode-stack-trace +-keepattributes LineNumberTable,SourceFile + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames,includedescriptorclasses class * { + native ; +} + +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/ndk/sample/src/main/AndroidManifest.xml b/ndk/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..882d9c1e80 --- /dev/null +++ b/ndk/sample/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/ndk/sample/src/main/cpp/ndk-sample.cpp b/ndk/sample/src/main/cpp/ndk-sample.cpp new file mode 100644 index 0000000000..d1f62bf0be --- /dev/null +++ b/ndk/sample/src/main/cpp/ndk-sample.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#define TAG "ndk-sample" + +extern "C" { + +JNIEXPORT void JNICALL Java_io_sentry_ndk_sample_NdkSample_crash(JNIEnv *env, jclass cls) { + __android_log_print(ANDROID_LOG_WARN, TAG, "About to crash."); + char *ptr = 0; + *ptr += 1; +} + +JNIEXPORT void JNICALL Java_io_sentry_ndk_sample_NdkSample_message(JNIEnv *env, jclass cls) { + __android_log_print(ANDROID_LOG_WARN, TAG, "Sending message."); + sentry_value_t event = sentry_value_new_message_event( + /* level */ SENTRY_LEVEL_INFO, + /* logger */ "custom", + /* message */ "It works!" + ); + sentry_capture_event(event); +} + +} diff --git a/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java new file mode 100644 index 0000000000..d8d42b3df7 --- /dev/null +++ b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java @@ -0,0 +1,54 @@ +package io.sentry.ndk.sample; + +import android.app.Activity; +import android.os.Bundle; + +import java.io.File; + +import io.sentry.ndk.NdkOptions; +import io.sentry.ndk.SentryNdk; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + findViewById(R.id.init_ndk_button).setOnClickListener(v -> initNdk()); + findViewById(R.id.trigger_native_crash_button).setOnClickListener(v -> NdkSample.crash()); + findViewById(R.id.capture_message_button).setOnClickListener(v -> NdkSample.message()); + } + + private void initNdk() { + final File outboxFolder = setupOutboxFolder(); + final NdkOptions options = new NdkOptions( + "https://1053864c67cc410aa1ffc9701bd6f93d@o447951.ingest.sentry.io/5428559", + BuildConfig.DEBUG, + outboxFolder.getAbsolutePath(), + "1.0.0", + "production", + BuildConfig.VERSION_NAME, + 100, + "sentry-native-jni" + ); + SentryNdk.init(options); + } + + private File setupOutboxFolder() { + // ensure we have a proper outbox directory + final File outboxDir = new File(getFilesDir(), "outbox"); + if (outboxDir.isFile()) { + final boolean deleteOk = outboxDir.delete(); + if (!deleteOk) { + throw new IllegalStateException("Failed to delete outbox file: " + outboxDir); + } + } + if (!outboxDir.exists()) { + final boolean mkdirOk = outboxDir.mkdirs(); + if (!mkdirOk) { + throw new IllegalStateException("Failed to create outbox directory: " + outboxDir); + } + } + return outboxDir; + } +} diff --git a/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java b/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java new file mode 100644 index 0000000000..27fdeaf5e0 --- /dev/null +++ b/ndk/sample/src/main/java/io/sentry/ndk/sample/NdkSample.java @@ -0,0 +1,11 @@ +package io.sentry.ndk.sample; + +public class NdkSample { + static { + System.loadLibrary("ndk-sample"); + } + + public static native void crash(); + + public static native void message(); +} diff --git a/ndk/sample/src/main/res/layout/activity_main.xml b/ndk/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..8045ad2590 --- /dev/null +++ b/ndk/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,36 @@ + + + + + +