Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suppress Native SIGSEGV signal errors on Android #3903

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions src/Sentry/Platforms/Android/AndroidOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,21 @@ public class AndroidOptions
/// </summary>
/// <seealso cref="LogCatIntegration" />
public int LogCatMaxLines { get; set; } = 1000;

/// <summary>
/// <para>
/// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </summary>
public bool SuppressSegfaults { get; set; } = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
26 changes: 22 additions & 4 deletions src/Sentry/Platforms/Android/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -172,6 +170,26 @@ private static void InitSentryAndroidSdk(SentryOptions options)
// TODO: Pause/Resume
}

internal static Func<SentryEvent, SentryHint, SentryEvent?> 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.";
Expand Down
116 changes: 116 additions & 0 deletions test/Sentry.Tests/Platforms/Android/SentrySdkTests.cs
Original file line number Diff line number Diff line change
@@ -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<Func<SentryEvent, SentryHint, SentryEvent>>();
beforeSend.Invoke(Arg.Any<SentryEvent>(), Arg.Any<SentryHint>()).Returns(callInfo => callInfo.Arg<SentryEvent>());

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<SentryEvent>(), Arg.Any<SentryHint>());
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<Func<SentryEvent, SentryHint, SentryEvent>>();
beforeSend.Invoke(Arg.Any<SentryEvent>(), Arg.Any<SentryHint>()).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<SentryEvent>(), Arg.Any<SentryHint>());
result.Should().Be(evt);
}
}
#endif
Loading