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