diff --git a/CHANGELOG.md b/CHANGELOG.md index af09754d95..882005d48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,12 @@ ### Fixes +- Native SIGSEGV errors resulting from managed NullReferenceExceptions are now suppressed on Android ([#3903](https://github.com/getsentry/sentry-dotnet/pull/3903)) - OTel activities that are marked as not recorded are no longer sent to Sentry ([#3890](https://github.com/getsentry/sentry-dotnet/pull/3890)) ## 5.1.0 ### Significant change in behavior - - The User.IpAddress is now only set to `{{auto}}` when `SendDefaultPii` is enabled. This change gives you control over IP address collection directly on the client ([#3893](https://github.com/getsentry/sentry-dotnet/pull/3893)) ### Features diff --git a/src/Sentry/Platforms/Android/AndroidOptions.cs b/src/Sentry/Platforms/Android/AndroidOptions.cs index f6dd210aaa..73e38ae20a 100644 --- a/src/Sentry/Platforms/Android/AndroidOptions.cs +++ b/src/Sentry/Platforms/Android/AndroidOptions.cs @@ -25,5 +25,21 @@ public class AndroidOptions /// /// public int LogCatMaxLines { get; set; } = 1000; + + /// + /// + /// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK. + /// + /// + /// When managed code results in a NullReferenceException, this also causes a SIGSEGV (Segfault). Duplicate + /// events (one managed and one native) can be prevented by suppressing native Segfaults, which may be + /// convenient. + /// + /// + /// Enabling this may prevent the capture of Segfault originating from native (not managed) code... so it may + /// prevent the capture of genuine native Segfault errors. + /// + /// + public bool SuppressSegfaults { get; set; } = false; } } diff --git a/src/Sentry/Platforms/Android/BindableAndroidSentryOptions.cs b/src/Sentry/Platforms/Android/BindableAndroidSentryOptions.cs index ea721bb7d4..0fa8264cb1 100644 --- a/src/Sentry/Platforms/Android/BindableAndroidSentryOptions.cs +++ b/src/Sentry/Platforms/Android/BindableAndroidSentryOptions.cs @@ -14,11 +14,13 @@ public class AndroidOptions { public LogCatIntegrationType? LogCatIntegration { get; set; } public int? LogCatMaxLines { get; set; } + public bool? SuppressSegfaults { get; set; } public void ApplyTo(SentryOptions.AndroidOptions options) { options.LogCatIntegration = LogCatIntegration ?? options.LogCatIntegration; options.LogCatMaxLines = LogCatMaxLines ?? options.LogCatMaxLines; + options.SuppressSegfaults = SuppressSegfaults ?? options.SuppressSegfaults; } } } diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index 607f4dee59..3cba5dac2c 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -3,6 +3,7 @@ using Sentry.Android; using Sentry.Android.Callbacks; using Sentry.Android.Extensions; +using Sentry.Extensibility; using Sentry.JavaSdk.Android.Core; // Don't let the Sentry Android SDK auto-init, as we do that manually in SentrySdk.Init @@ -99,10 +100,7 @@ private static void InitSentryAndroidSdk(SentryOptions options) } } - if (options.Native.EnableBeforeSend && options.BeforeSendInternal is { } beforeSend) - { - o.BeforeSend = new BeforeSendCallback(beforeSend, options, o); - } + o.BeforeSend = new BeforeSendCallback(BeforeSendWrapper(options), options, o); // These options are from SentryAndroidOptions o.AttachScreenshot = options.Native.AttachScreenshot; @@ -172,6 +170,26 @@ private static void InitSentryAndroidSdk(SentryOptions options) // TODO: Pause/Resume } + internal static Func BeforeSendWrapper(SentryOptions options) + { + return (evt, hint) => + { + // Suppress SIGSEGV errors. + // See: https://github.com/getsentry/sentry-dotnet/pull/3903 + if (options.Android.SuppressSegfaults + && evt.SentryExceptions?.FirstOrDefault() is { Type: "SIGSEGV", Value: "Segfault" }) + { + options.LogDebug("Suppressing SIGSEGV (this will be thrown as a managed exception instead)"); + return null; + } + + // Call the user defined BeforeSend callback, if it's defined - otherwise return the event as-is + return (options.Native.EnableBeforeSend && options.BeforeSendInternal is { } beforeSend) + ? beforeSend(evt, hint) + : evt; + }; + } + private static void AndroidEnvironment_UnhandledExceptionRaiser(object? _, RaiseThrowableEventArgs e) { var description = "This exception was caught by the Android global error handler."; diff --git a/test/Sentry.Tests/Platforms/Android/SentrySdkTests.cs b/test/Sentry.Tests/Platforms/Android/SentrySdkTests.cs new file mode 100644 index 0000000000..96bde8e62b --- /dev/null +++ b/test/Sentry.Tests/Platforms/Android/SentrySdkTests.cs @@ -0,0 +1,116 @@ +#if ANDROID +namespace Sentry.Tests.Platforms.Android; + +public class SentrySdkTests +{ + [Fact] + public void BeforeSendWrapper_Suppress_SIGSEGV_ReturnsNull() + { + // Arrange + var options = new SentryOptions(); + options.Android.SuppressSegfaults = true; + var evt = new SentryEvent + { + SentryExceptions = new[] + { + new SentryException + { + Type = "SIGSEGV", + Value = "Segfault" + } + } + }; + var hint = new SentryHint(); + + // Act + var result = SentrySdk.BeforeSendWrapper(options).Invoke(evt, hint); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void BeforeSendWrapper_DontSupress_SIGSEGV_ReturnsEvent() + { + // Arrange + var options = new SentryOptions(); + options.Android.SuppressSegfaults = false; + var evt = new SentryEvent + { + SentryExceptions = new[] + { + new SentryException + { + Type = "SIGSEGV", + Value = "Segfault" + } + } + }; + var hint = new SentryHint(); + + // Act + var result = SentrySdk.BeforeSendWrapper(options).Invoke(evt, hint); + + // Assert + result.Should().Be(evt); + } + + [Fact] + public void BeforeSendWrapper_BeforeSendCallbackDefined_CallsBeforeSend() + { + // Arrange + var beforeSend = Substitute.For>(); + beforeSend.Invoke(Arg.Any(), Arg.Any()).Returns(callInfo => callInfo.Arg()); + + var options = new SentryOptions(); + options.Native.EnableBeforeSend = true; + options.SetBeforeSend(beforeSend); + var evt = new SentryEvent(); + var hint = new SentryHint(); + + // Act + var result = SentrySdk.BeforeSendWrapper(options).Invoke(evt, hint); + + // Assert + beforeSend.Received(1).Invoke(Arg.Any(), Arg.Any()); + result.Should().Be(evt); + } + + [Fact] + public void BeforeSendWrapper_NoBeforeSendCallback_ReturnsEvent() + { + // Arrange + var options = new SentryOptions(); + options.Native.EnableBeforeSend = true; + var evt = new SentryEvent(); + var hint = new SentryHint(); + + // Act + var result = SentrySdk.BeforeSendWrapper(options).Invoke(evt, hint); + + // Assert + result.Should().Be(evt); + } + + [Fact] + public void BeforeSendWrapper_NativeBeforeSendDisabled_ReturnsEvent() + { + // Arrange + var beforeSend = Substitute.For>(); + beforeSend.Invoke(Arg.Any(), Arg.Any()).Returns(_ => null); + + var options = new SentryOptions(); + options.SetBeforeSend(beforeSend); + options.Native.EnableBeforeSend = false; + var evt = new SentryEvent(); + var hint = new SentryHint(); + + // Act + var result = SentrySdk.BeforeSendWrapper(options).Invoke(evt, hint); + + // Assert + beforeSend.DidNotReceive().Invoke(Arg.Any(), Arg.Any()); + result.Should().Be(evt); + } +} +#endif