Skip to content
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