diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae68fdbfdc..d43f2d28ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
### Fixes
+- Prevent Native EXC_BAD_ACCESS signal errors from being captured when managed NullRefrenceExceptions occur ([#3909](https://github.com/getsentry/sentry-dotnet/pull/3909))
- Fixed duplicate SentryMauiEventProcessors ([#3905](https://github.com/getsentry/sentry-dotnet/pull/3905))
### Dependencies
diff --git a/samples/Sentry.Samples.Ios/AppDelegate.cs b/samples/Sentry.Samples.Ios/AppDelegate.cs
index 8ba822292f..80e9ea09a5 100644
--- a/samples/Sentry.Samples.Ios/AppDelegate.cs
+++ b/samples/Sentry.Samples.Ios/AppDelegate.cs
@@ -1,3 +1,5 @@
+using ObjCRuntime;
+
namespace Sentry.Samples.Ios;
[Register("AppDelegate")]
@@ -16,6 +18,7 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l
{
options.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537";
options.Debug = true;
+ options.SampleRate = 1.0F;
options.TracesSampleRate = 1.0;
options.ProfilesSampleRate = 1.0;
@@ -23,77 +26,114 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l
// https://docs.sentry.io/platforms/apple/guides/ios/configuration/
// Enable Native iOS SDK App Hangs detection
options.Native.EnableAppHangTracking = true;
+
+ options.CacheDirectoryPath = Path.GetTempPath();
});
// create a new window instance based on the screen size
Window = new UIWindow(UIScreen.MainScreen.Bounds);
- // determine the background color for the view (SystemBackground requires iOS >= 13.0)
+ // determine control colours (SystemBackground requires iOS >= 13.0)
var backgroundColor = UIDevice.CurrentDevice.CheckSystemVersion(13, 0)
#pragma warning disable CA1416
? UIColor.SystemBackground
#pragma warning restore CA1416
: UIColor.White;
+ var buttonConfig = UIButtonConfiguration.TintedButtonConfiguration;
+ var terminalButtonConfig = UIButtonConfiguration.TintedButtonConfiguration;
+ terminalButtonConfig.BaseBackgroundColor = UIColor.SystemRed;
- // create a UIViewController with a single UILabel
var vc = new UIViewController();
- vc.View!.AddSubview(new UILabel(Window!.Frame)
+
+ var label = new UILabel
{
BackgroundColor = backgroundColor,
TextAlignment = UITextAlignment.Center,
Text = "Hello, iOS!",
+ AutoresizingMask = UIViewAutoresizing.All
+ };
+
+ // UIButton for a managed exception that we'll catch and handle (won't crash the app)
+ var managedCrashButton = new UIButton(UIButtonType.RoundedRect)
+ {
AutoresizingMask = UIViewAutoresizing.All,
- });
- Window.RootViewController = vc;
+ Configuration = buttonConfig
+ };
+ managedCrashButton.SetTitle("Managed Crash", UIControlState.Normal);
+ managedCrashButton.TouchUpInside += delegate
+ {
+ Console.WriteLine("Managed Crash button clicked!");
+ try
+ {
+ throw new Exception("Catch this!");
+ }
+ catch (Exception e)
+ {
+ SentrySdk.CaptureException(e);
+ }
+ };
- // make the window visible
- Window.MakeKeyAndVisible();
+ // UIButton for unhandled managed exception
+ var unhandledCrashButton = new UIButton(UIButtonType.RoundedRect)
+ {
+ AutoresizingMask = UIViewAutoresizing.All,
+ Configuration = terminalButtonConfig
+ };
+ unhandledCrashButton.SetTitle("Unhandled Crash", UIControlState.Normal);
+ unhandledCrashButton.TouchUpInside += delegate
+ {
+ Console.WriteLine("Unhandled Crash button clicked!");
+ string s = null!;
+ // This will cause a NullReferenceException that will crash the app before Sentry can send the event.
+ // Since we're using a caching transport though, the exception will be written to disk and sent the
+ // next time the app is launched.
+ Console.WriteLine("Length: {0}", s.Length);
+ };
+ // UIButton for native crash
+ var nativeCrashButton = new UIButton(UIButtonType.System)
+ {
+ Configuration = terminalButtonConfig
+ };
+ nativeCrashButton.SetTitle("Native Crash", UIControlState.Normal);
+ nativeCrashButton.TouchUpInside += delegate
+ {
+ Console.WriteLine("Native Crash button clicked!");
+#pragma warning disable CS0618 // Type or member is obsolete
+ // This will cause a native crash that will crash the application before
+ // Sentry gets a chance to send the event. Since we've enabled caching however,
+ // the event will be written to disk and sent the next time the app is launched.
+ SentrySdk.CauseCrash(CrashType.Native);
+#pragma warning restore CS0618 // Type or member is obsolete
+ };
- // Try out the Sentry SDK
- SentrySdk.CaptureMessage("From iOS");
+ // create a UIStackView to hold the label and buttons
+ var stackView = new UIStackView(new UIView[] { label, managedCrashButton, unhandledCrashButton, nativeCrashButton })
+ {
+ Axis = UILayoutConstraintAxis.Vertical,
+ Distribution = UIStackViewDistribution.FillEqually,
+ Alignment = UIStackViewAlignment.Center,
+ Spacing = 10,
+ TranslatesAutoresizingMaskIntoConstraints = false,
+ };
- // Uncomment to try these
- // throw new Exception("Test Unhandled Managed Exception");
- // SentrySdk.CauseCrash(CrashType.Native);
+ // add the stack view to the view controller's view
+ vc.View!.BackgroundColor = backgroundColor;
+ vc.View.AddSubview(stackView);
- {
- var tx = SentrySdk.StartTransaction("app", "run");
- var count = 10;
- for (var i = 0; i < count; i++)
- {
- FindPrimeNumber(100000);
- }
+ // set constraints for the stack view
+ NSLayoutConstraint.ActivateConstraints([
+ stackView.CenterXAnchor.ConstraintEqualTo(vc.View.CenterXAnchor),
+ stackView.CenterYAnchor.ConstraintEqualTo(vc.View.CenterYAnchor),
+ stackView.WidthAnchor.ConstraintEqualTo(vc.View.WidthAnchor, 0.8f),
+ stackView.HeightAnchor.ConstraintEqualTo(vc.View.HeightAnchor, 0.5f)
+ ]);
- tx.Finish();
- }
+ Window.RootViewController = vc;
- return true;
- }
+ // make the window visible
+ Window.MakeKeyAndVisible();
- private static long FindPrimeNumber(int n)
- {
- int count = 0;
- long a = 2;
- while (count < n)
- {
- long b = 2;
- int prime = 1;// to check if found a prime
- while (b * b <= a)
- {
- if (a % b == 0)
- {
- prime = 0;
- break;
- }
- b++;
- }
- if (prime > 0)
- {
- count++;
- }
- a++;
- }
- return (--a);
+ return true;
}
}
diff --git a/samples/Sentry.Samples.Ios/Info.plist b/samples/Sentry.Samples.Ios/Info.plist
index fb0738075a..38f2e8adc8 100644
--- a/samples/Sentry.Samples.Ios/Info.plist
+++ b/samples/Sentry.Samples.Ios/Info.plist
@@ -2,43 +2,43 @@
- CFBundleDisplayName
- Sentry.Samples.Ios
- CFBundleIdentifier
- io.sentry.dotnet.samples.ios
- CFBundleShortVersionString
- 1.0
- MinimumOSVersion
- 11.0
- CFBundleVersion
- 1.0
- LSRequiresIPhoneOS
-
- UIDeviceFamily
-
- 1
- 2
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- XSAppIconAssets
- Assets.xcassets/AppIcon.appiconset
+ CFBundleDisplayName
+ Sentry.Samples.Ios
+ CFBundleIdentifier
+ io.sentry.dotnet.samples.ios
+ CFBundleShortVersionString
+ 1.0
+ MinimumOSVersion
+ 15.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
diff --git a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj
index 9ecfce8114..76b122d042 100644
--- a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj
+++ b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj
@@ -1,13 +1,15 @@
- net8.0-ios17.0
+ net9.0-ios18.0
Exe
enable
true
- 11.0
+ 15.0
true
true
+
+ IL3050;IL3053
diff --git a/src/Sentry/Platforms/Cocoa/RuntimeAdapter.cs b/src/Sentry/Platforms/Cocoa/RuntimeAdapter.cs
new file mode 100644
index 0000000000..9bc7551cd4
--- /dev/null
+++ b/src/Sentry/Platforms/Cocoa/RuntimeAdapter.cs
@@ -0,0 +1,29 @@
+using ObjCRuntime;
+
+namespace Sentry.Cocoa;
+
+internal interface IRuntime
+{
+ internal event MarshalManagedExceptionHandler MarshalManagedException;
+ internal event MarshalObjectiveCExceptionHandler MarshalObjectiveCException;
+}
+
+internal sealed class RuntimeAdapter : IRuntime
+{
+ public static RuntimeAdapter Instance { get; } = new();
+
+ private RuntimeAdapter()
+ {
+ Runtime.MarshalManagedException += OnMarshalManagedException;
+ Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException;
+ }
+
+ public event MarshalManagedExceptionHandler? MarshalManagedException;
+ public event MarshalObjectiveCExceptionHandler? MarshalObjectiveCException;
+
+ [SecurityCritical]
+ private void OnMarshalManagedException(object sender, MarshalManagedExceptionEventArgs e) => MarshalManagedException?.Invoke(this, e);
+
+ [SecurityCritical]
+ private void OnMarshalObjectiveCException(object sender, MarshalObjectiveCExceptionEventArgs e) => MarshalObjectiveCException?.Invoke(this, e);
+}
diff --git a/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs b/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs
new file mode 100644
index 0000000000..396221cb79
--- /dev/null
+++ b/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs
@@ -0,0 +1,48 @@
+using ObjCRuntime;
+using Sentry.Extensibility;
+using Sentry.Integrations;
+
+namespace Sentry.Cocoa;
+
+///
+/// When AOT Compiling iOS applications, the AppDomain UnhandledExceptionHandler doesn't fire. So instead we intercept
+/// the Runtime.RuntimeMarshalManagedException event.
+///
+internal class RuntimeMarshalManagedExceptionIntegration : ISdkIntegration
+{
+ private readonly IRuntime _runtime;
+ private IHub? _hub;
+ private SentryOptions? _options;
+
+ internal RuntimeMarshalManagedExceptionIntegration(IRuntime? runtime = null)
+ => _runtime = runtime ?? RuntimeAdapter.Instance;
+
+ public void Register(IHub hub, SentryOptions options)
+ {
+ _hub = hub;
+ _options = options;
+ _runtime.MarshalManagedException += Handle;
+ }
+
+ // Internal for testability
+ [SecurityCritical]
+ internal void Handle(object sender, MarshalManagedExceptionEventArgs e)
+ {
+ _options?.LogDebug("Runtime Marshal Managed Exception mode {0}", e.ExceptionMode.ToString("G"));
+
+ if (e.Exception is { } ex)
+ {
+ ex.SetSentryMechanism(
+ "Runtime.MarshalManagedException",
+ "This exception was caught by the .NET Runtime Marshal Managed Exception global error handler. " +
+ "The application may have crashed as a result of this exception.",
+ handled: false);
+
+ // Call the internal implementation, so that we still capture even if the hub has been disabled.
+ _hub?.CaptureExceptionInternal(ex);
+
+ // This is likely a terminal exception so try to send the crash report before shutting down
+ _hub?.Flush();
+ }
+ }
+}
diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs
index c7b438f12d..dcc8e48006 100644
--- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs
+++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs
@@ -10,11 +10,6 @@ public static partial class SentrySdk
private static void InitSentryCocoaSdk(SentryOptions options)
{
options.LogDebug("Initializing native SDK");
- // Workaround for https://github.com/xamarin/xamarin-macios/issues/15252
- ObjCRuntime.Runtime.MarshalManagedException += (_, args) =>
- {
- args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode;
- };
// Set default release and distribution
options.Release ??= GetDefaultReleaseString();
@@ -153,10 +148,6 @@ private static void InitSentryCocoaSdk(SentryOptions options)
// When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native.
// The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK
// But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK.
- // This is partially due to our setting ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode above.
- // Thankfully, we can see Xamarin's unhandled exception handler on the stack trace, so we can filter them out.
- // Here is the function that calls abort(), which we will use as a filter:
- // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122
nativeOptions.BeforeSend = evt =>
{
// There should only be one exception on the event in this case
@@ -164,10 +155,25 @@ private static void InitSentryCocoaSdk(SentryOptions options)
{
// It will match the following characteristics
var ex = evt.Exceptions[0];
+
+ // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter
+ // them out. Here is the function that calls abort(), which we will use as a filter:
+ // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122
if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" &&
ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true)
{
- // Don't sent it
+ // Don't send it
+ options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value);
+ return null!;
+ }
+
+ // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the
+ // exception is managed code (compiled to native) or original native code though.
+ // See: https://github.com/getsentry/sentry-dotnet/issues/3776
+ if (ex.Type == "EXC_BAD_ACCESS")
+ {
+ // Don't send it
+ options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value);
return null!;
}
}
diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs
index de8d930066..7c99988acf 100644
--- a/src/Sentry/SentryOptions.cs
+++ b/src/Sentry/SentryOptions.cs
@@ -18,6 +18,11 @@
using Sentry.Android.AssemblyReader;
#endif
+#if IOS || MACCATALYST
+using ObjCRuntime;
+using Sentry.Cocoa;
+#endif
+
namespace Sentry;
///
@@ -162,10 +167,17 @@ internal IEnumerable Integrations
yield return new AutoSessionTrackingIntegration();
}
+#if IOS || MACCATALYST
+ if ((_defaultIntegrations & DefaultIntegrations.RuntimeMarshalManagedExceptionIntegration) != 0)
+ {
+ yield return new RuntimeMarshalManagedExceptionIntegration();
+ }
+#else
if ((_defaultIntegrations & DefaultIntegrations.AppDomainUnhandledExceptionIntegration) != 0)
{
yield return new AppDomainUnhandledExceptionIntegration();
}
+#endif
if ((_defaultIntegrations & DefaultIntegrations.AppDomainProcessExitIntegration) != 0)
{
@@ -1239,7 +1251,11 @@ public SentryOptions()
_integrations = new();
_defaultIntegrations = DefaultIntegrations.AutoSessionTrackingIntegration |
+#if IOS || MACCATALYST
+ DefaultIntegrations.RuntimeMarshalManagedExceptionIntegration |
+#else
DefaultIntegrations.AppDomainUnhandledExceptionIntegration |
+#endif
DefaultIntegrations.AppDomainProcessExitIntegration |
DefaultIntegrations.AutoSessionTrackingIntegration |
DefaultIntegrations.UnobservedTaskExceptionIntegration
@@ -1663,11 +1679,19 @@ public void ApplyDefaultTags(IHasTags hasTags)
public void DisableDuplicateEventDetection()
=> RemoveEventProcessor();
+#if IOS || MACCATALYST
+ ///
+ /// Disables the capture of errors through .
+ ///
+ public void DisableRuntimeMarshalManagedExceptionCapture() =>
+ RemoveDefaultIntegration(DefaultIntegrations.RuntimeMarshalManagedExceptionIntegration);
+#else
///
/// Disables the capture of errors through .
///
public void DisableAppDomainUnhandledExceptionCapture() =>
RemoveDefaultIntegration(DefaultIntegrations.AppDomainUnhandledExceptionIntegration);
+#endif
#if HAS_DIAGNOSTIC_INTEGRATION
///
@@ -1725,7 +1749,11 @@ public void DisableSystemDiagnosticsMetricsIntegration()
internal enum DefaultIntegrations
{
AutoSessionTrackingIntegration = 1 << 0,
+#if IOS || MACCATALYST
+ RuntimeMarshalManagedExceptionIntegration = 1 << 1,
+#else
AppDomainUnhandledExceptionIntegration = 1 << 1,
+#endif
AppDomainProcessExitIntegration = 1 << 2,
UnobservedTaskExceptionIntegration = 1 << 3,
#if NETFRAMEWORK
diff --git a/test/Sentry.Tests/Platforms/iOS/RuntimeMarshalManagedExceptionIntegrationTests.cs b/test/Sentry.Tests/Platforms/iOS/RuntimeMarshalManagedExceptionIntegrationTests.cs
new file mode 100644
index 0000000000..5ba83b0c28
--- /dev/null
+++ b/test/Sentry.Tests/Platforms/iOS/RuntimeMarshalManagedExceptionIntegrationTests.cs
@@ -0,0 +1,70 @@
+#if IOS
+using ObjCRuntime;
+using Sentry.Cocoa;
+
+namespace Sentry.Tests.Platforms.iOS;
+
+public class RuntimeMarshalManagedExceptionIntegrationTests
+{
+ private class Fixture
+ {
+ public IHub Hub { get; } = Substitute.For();
+ public IRuntime Runtime { get; } = Substitute.For();
+
+ public RuntimeMarshalManagedExceptionIntegration GetSut() => new(Runtime);
+ }
+
+ private readonly Fixture _fixture = new();
+ private SentryOptions SentryOptions { get; } = new();
+
+ [Fact]
+ public void Handle_WithException_CaptureEvent()
+ {
+ var sut = _fixture.GetSut();
+ sut.Register(_fixture.Hub, SentryOptions);
+
+ sut.Handle(this, new MarshalManagedExceptionEventArgs { Exception = new Exception() });
+
+ _fixture.Hub.Received(1).CaptureEvent(Arg.Any());
+ }
+
+ [Fact]
+ public void Handle_WithException_IsHandledFalse()
+ {
+ var sut = _fixture.GetSut();
+ sut.Register(_fixture.Hub, SentryOptions);
+
+ var exception = new Exception();
+ sut.Handle(this, new MarshalManagedExceptionEventArgs { Exception = exception });
+ Assert.Equal(false, exception.Data[Mechanism.HandledKey]);
+ Assert.True(exception.Data.Contains(Mechanism.MechanismKey));
+
+ var stackTraceFactory = Substitute.For();
+ var exceptionProcessor = new MainExceptionProcessor(SentryOptions, () => stackTraceFactory);
+ var @event = new SentryEvent(exception);
+
+ exceptionProcessor.Process(exception, @event);
+ Assert.NotNull(@event.SentryExceptions?.ToList().Single(p => p.Mechanism?.Handled == false));
+ }
+
+ [Fact]
+ public void Handle_NoException_NoCaptureEvent()
+ {
+ var sut = _fixture.GetSut();
+ sut.Register(_fixture.Hub, SentryOptions);
+
+ sut.Handle(this, new MarshalManagedExceptionEventArgs());
+
+ _fixture.Hub.DidNotReceive().CaptureEvent(Arg.Any());
+ }
+
+ [Fact]
+ public void Register_UnhandledException_Subscribes()
+ {
+ var sut = _fixture.GetSut();
+ sut.Register(_fixture.Hub, SentryOptions);
+
+ _fixture.Runtime.Received().MarshalManagedException += sut.Handle;
+ }
+}
+#endif
diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs
index cd20a5c7d3..7a12849443 100644
--- a/test/Sentry.Tests/SentryOptionsTests.cs
+++ b/test/Sentry.Tests/SentryOptionsTests.cs
@@ -1,6 +1,8 @@
namespace Sentry.Tests;
#if NETFRAMEWORK
using Sentry.PlatformAbstractions;
+#elif IOS || MACCATALYST
+using Sentry.Cocoa;
#endif
public partial class SentryOptionsTests
{
@@ -285,6 +287,16 @@ public void DisableNetFxInstallationsEventProcessor_RemovesDisableNetFxInstallat
}
#endif
+#if IOS || MACCATALYST
+ [Fact]
+ public void DisableRuntimeMarshalManagedExceptionCapture_RemovesRuntimeMarshalManagedExceptionIntegration()
+ {
+ var sut = new SentryOptions();
+ sut.DisableRuntimeMarshalManagedExceptionCapture();
+ Assert.DoesNotContain(sut.Integrations,
+ p => p is RuntimeMarshalManagedExceptionIntegration);
+ }
+#else
[Fact]
public void DisableAppDomainUnhandledExceptionCapture_RemovesAppDomainUnhandledExceptionIntegration()
{
@@ -293,6 +305,7 @@ public void DisableAppDomainUnhandledExceptionCapture_RemovesAppDomainUnhandledE
Assert.DoesNotContain(sut.Integrations,
p => p is AppDomainUnhandledExceptionIntegration);
}
+#endif
[Fact]
public void DisableTaskUnobservedTaskExceptionCapture_UnobservedTaskExceptionIntegration()
@@ -581,12 +594,21 @@ public void GetAllEventProcessors_NoAdding_FirstReturned_DuplicateDetectionProce
_ = Assert.IsType(sut.GetAllEventProcessors().First());
}
+#if IOS || MACCATALYST
+ [Fact]
+ public void Integrations_Includes_RuntimeMarshalManagedExceptionIntegration()
+ {
+ var sut = new SentryOptions();
+ Assert.Contains(sut.Integrations, i => i.GetType() == typeof(RuntimeMarshalManagedExceptionIntegration));
+ }
+#else
[Fact]
public void Integrations_Includes_AppDomainUnhandledExceptionIntegration()
{
var sut = new SentryOptions();
Assert.Contains(sut.Integrations, i => i.GetType() == typeof(AppDomainUnhandledExceptionIntegration));
}
+#endif
[Fact]
public void Integrations_Includes_AppDomainProcessExitIntegration()