-
-
Notifications
You must be signed in to change notification settings - Fork 226
Prevent Native EXC_BAD_ACCESS signal for NullRefrenceExceptions #3909
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
Changes from all commits
c8cf715
90d41c3
37900d7
892719f
2b35173
f630cea
b8672d2
8c97561
1543cea
96845d1
341aa83
6be8cfb
69d0d07
316e75a
2d22ca3
1497b61
6c20eb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| using ObjCRuntime; | ||
|
|
||
| namespace Sentry.Samples.Ios; | ||
|
|
||
| [Register("AppDelegate")] | ||
|
|
@@ -16,84 +18,122 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l | |
| { | ||
| options.Dsn = "https://[email protected]/5428537"; | ||
| options.Debug = true; | ||
| options.SampleRate = 1.0F; | ||
| options.TracesSampleRate = 1.0; | ||
| options.ProfilesSampleRate = 1.0; | ||
|
|
||
| // All the native iOS SDK options are available below | ||
| // 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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| using ObjCRuntime; | ||
| using Sentry.Extensibility; | ||
| using Sentry.Integrations; | ||
|
|
||
| namespace Sentry.Cocoa; | ||
|
|
||
| /// <summary> | ||
| /// When AOT Compiling iOS applications, the AppDomain UnhandledExceptionHandler doesn't fire. So instead we intercept | ||
| /// the Runtime.RuntimeMarshalManagedException event. | ||
| /// </summary> | ||
| 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) | ||
jamescrosswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| 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.", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not certain that it did crash?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about that no. We're using this event instead of I've asked the question here:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But regardless of that bool, if that fires (on your tests) the app crashes? If so, I'd indicate that's the case. But keeping it vague in here is fine, I was just wondering how this works |
||
| 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(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh we've been missing out on a category of errors?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially, yes. Matt created this workaround to the problem (after this conversation):
sentry-dotnet/src/Sentry/Platforms/Cocoa/SentrySdk.cs
Lines 13 to 17 in c20d162
However that marshalling mode isn't supported when AOT compiling iOS apps. It causes xamarin to throw an assertion error, which is unrelated to the error from the user's code.