Skip to content

Commit 44c48f6

Browse files
Prevent Native EXC_BAD_ACCESS signal for NullRefrenceExceptions (#3909)
1 parent 2f818b2 commit 44c48f6

File tree

10 files changed

+343
-97
lines changed

10 files changed

+343
-97
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
### Fixes
1515

16+
- Prevent Native EXC_BAD_ACCESS signal errors from being captured when managed NullRefrenceExceptions occur ([#3909](https://github.com/getsentry/sentry-dotnet/pull/3909))
1617
- Fixed duplicate SentryMauiEventProcessors ([#3905](https://github.com/getsentry/sentry-dotnet/pull/3905))
1718
- Fixed invalid string.Format index in Debug logs for the DiagnosticSource integration ([#3923](https://github.com/getsentry/sentry-dotnet/pull/3923))
1819

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using ObjCRuntime;
2+
13
namespace Sentry.Samples.Ios;
24

35
[Register("AppDelegate")]
@@ -16,84 +18,122 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l
1618
{
1719
options.Dsn = "https://[email protected]/5428537";
1820
options.Debug = true;
21+
options.SampleRate = 1.0F;
1922
options.TracesSampleRate = 1.0;
2023
options.ProfilesSampleRate = 1.0;
2124

2225
// All the native iOS SDK options are available below
2326
// https://docs.sentry.io/platforms/apple/guides/ios/configuration/
2427
// Enable Native iOS SDK App Hangs detection
2528
options.Native.EnableAppHangTracking = true;
29+
30+
options.CacheDirectoryPath = Path.GetTempPath();
2631
});
2732

2833
// create a new window instance based on the screen size
2934
Window = new UIWindow(UIScreen.MainScreen.Bounds);
3035

31-
// determine the background color for the view (SystemBackground requires iOS >= 13.0)
36+
// determine control colours (SystemBackground requires iOS >= 13.0)
3237
var backgroundColor = UIDevice.CurrentDevice.CheckSystemVersion(13, 0)
3338
#pragma warning disable CA1416
3439
? UIColor.SystemBackground
3540
#pragma warning restore CA1416
3641
: UIColor.White;
42+
var buttonConfig = UIButtonConfiguration.TintedButtonConfiguration;
43+
var terminalButtonConfig = UIButtonConfiguration.TintedButtonConfiguration;
44+
terminalButtonConfig.BaseBackgroundColor = UIColor.SystemRed;
3745

38-
// create a UIViewController with a single UILabel
3946
var vc = new UIViewController();
40-
vc.View!.AddSubview(new UILabel(Window!.Frame)
47+
48+
var label = new UILabel
4149
{
4250
BackgroundColor = backgroundColor,
4351
TextAlignment = UITextAlignment.Center,
4452
Text = "Hello, iOS!",
53+
AutoresizingMask = UIViewAutoresizing.All
54+
};
55+
56+
// UIButton for a managed exception that we'll catch and handle (won't crash the app)
57+
var managedCrashButton = new UIButton(UIButtonType.RoundedRect)
58+
{
4559
AutoresizingMask = UIViewAutoresizing.All,
46-
});
47-
Window.RootViewController = vc;
60+
Configuration = buttonConfig
61+
};
62+
managedCrashButton.SetTitle("Managed Crash", UIControlState.Normal);
63+
managedCrashButton.TouchUpInside += delegate
64+
{
65+
Console.WriteLine("Managed Crash button clicked!");
66+
try
67+
{
68+
throw new Exception("Catch this!");
69+
}
70+
catch (Exception e)
71+
{
72+
SentrySdk.CaptureException(e);
73+
}
74+
};
4875

49-
// make the window visible
50-
Window.MakeKeyAndVisible();
76+
// UIButton for unhandled managed exception
77+
var unhandledCrashButton = new UIButton(UIButtonType.RoundedRect)
78+
{
79+
AutoresizingMask = UIViewAutoresizing.All,
80+
Configuration = terminalButtonConfig
81+
};
82+
unhandledCrashButton.SetTitle("Unhandled Crash", UIControlState.Normal);
83+
unhandledCrashButton.TouchUpInside += delegate
84+
{
85+
Console.WriteLine("Unhandled Crash button clicked!");
86+
string s = null!;
87+
// This will cause a NullReferenceException that will crash the app before Sentry can send the event.
88+
// Since we're using a caching transport though, the exception will be written to disk and sent the
89+
// next time the app is launched.
90+
Console.WriteLine("Length: {0}", s.Length);
91+
};
5192

93+
// UIButton for native crash
94+
var nativeCrashButton = new UIButton(UIButtonType.System)
95+
{
96+
Configuration = terminalButtonConfig
97+
};
98+
nativeCrashButton.SetTitle("Native Crash", UIControlState.Normal);
99+
nativeCrashButton.TouchUpInside += delegate
100+
{
101+
Console.WriteLine("Native Crash button clicked!");
102+
#pragma warning disable CS0618 // Type or member is obsolete
103+
// This will cause a native crash that will crash the application before
104+
// Sentry gets a chance to send the event. Since we've enabled caching however,
105+
// the event will be written to disk and sent the next time the app is launched.
106+
SentrySdk.CauseCrash(CrashType.Native);
107+
#pragma warning restore CS0618 // Type or member is obsolete
108+
};
52109

53-
// Try out the Sentry SDK
54-
SentrySdk.CaptureMessage("From iOS");
110+
// create a UIStackView to hold the label and buttons
111+
var stackView = new UIStackView(new UIView[] { label, managedCrashButton, unhandledCrashButton, nativeCrashButton })
112+
{
113+
Axis = UILayoutConstraintAxis.Vertical,
114+
Distribution = UIStackViewDistribution.FillEqually,
115+
Alignment = UIStackViewAlignment.Center,
116+
Spacing = 10,
117+
TranslatesAutoresizingMaskIntoConstraints = false,
118+
};
55119

56-
// Uncomment to try these
57-
// throw new Exception("Test Unhandled Managed Exception");
58-
// SentrySdk.CauseCrash(CrashType.Native);
120+
// add the stack view to the view controller's view
121+
vc.View!.BackgroundColor = backgroundColor;
122+
vc.View.AddSubview(stackView);
59123

60-
{
61-
var tx = SentrySdk.StartTransaction("app", "run");
62-
var count = 10;
63-
for (var i = 0; i < count; i++)
64-
{
65-
FindPrimeNumber(100000);
66-
}
124+
// set constraints for the stack view
125+
NSLayoutConstraint.ActivateConstraints([
126+
stackView.CenterXAnchor.ConstraintEqualTo(vc.View.CenterXAnchor),
127+
stackView.CenterYAnchor.ConstraintEqualTo(vc.View.CenterYAnchor),
128+
stackView.WidthAnchor.ConstraintEqualTo(vc.View.WidthAnchor, 0.8f),
129+
stackView.HeightAnchor.ConstraintEqualTo(vc.View.HeightAnchor, 0.5f)
130+
]);
67131

68-
tx.Finish();
69-
}
132+
Window.RootViewController = vc;
70133

71-
return true;
72-
}
134+
// make the window visible
135+
Window.MakeKeyAndVisible();
73136

74-
private static long FindPrimeNumber(int n)
75-
{
76-
int count = 0;
77-
long a = 2;
78-
while (count < n)
79-
{
80-
long b = 2;
81-
int prime = 1;// to check if found a prime
82-
while (b * b <= a)
83-
{
84-
if (a % b == 0)
85-
{
86-
prime = 0;
87-
break;
88-
}
89-
b++;
90-
}
91-
if (prime > 0)
92-
{
93-
count++;
94-
}
95-
a++;
96-
}
97-
return (--a);
137+
return true;
98138
}
99139
}

samples/Sentry.Samples.Ios/Info.plist

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,43 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<key>CFBundleDisplayName</key>
6-
<string>Sentry.Samples.Ios</string>
7-
<key>CFBundleIdentifier</key>
8-
<string>io.sentry.dotnet.samples.ios</string>
9-
<key>CFBundleShortVersionString</key>
10-
<string>1.0</string>
11-
<key>MinimumOSVersion</key>
12-
<string>11.0</string>
13-
<key>CFBundleVersion</key>
14-
<string>1.0</string>
15-
<key>LSRequiresIPhoneOS</key>
16-
<true/>
17-
<key>UIDeviceFamily</key>
18-
<array>
19-
<integer>1</integer>
20-
<integer>2</integer>
21-
</array>
22-
<key>UILaunchStoryboardName</key>
23-
<string>LaunchScreen</string>
24-
<key>UIRequiredDeviceCapabilities</key>
25-
<array>
26-
<string>armv7</string>
27-
</array>
28-
<key>UISupportedInterfaceOrientations</key>
29-
<array>
30-
<string>UIInterfaceOrientationPortrait</string>
31-
<string>UIInterfaceOrientationLandscapeLeft</string>
32-
<string>UIInterfaceOrientationLandscapeRight</string>
33-
</array>
34-
<key>UISupportedInterfaceOrientations~ipad</key>
35-
<array>
36-
<string>UIInterfaceOrientationPortrait</string>
37-
<string>UIInterfaceOrientationPortraitUpsideDown</string>
38-
<string>UIInterfaceOrientationLandscapeLeft</string>
39-
<string>UIInterfaceOrientationLandscapeRight</string>
40-
</array>
41-
<key>XSAppIconAssets</key>
42-
<string>Assets.xcassets/AppIcon.appiconset</string>
5+
<key>CFBundleDisplayName</key>
6+
<string>Sentry.Samples.Ios</string>
7+
<key>CFBundleIdentifier</key>
8+
<string>io.sentry.dotnet.samples.ios</string>
9+
<key>CFBundleShortVersionString</key>
10+
<string>1.0</string>
11+
<key>MinimumOSVersion</key>
12+
<string>15.0</string>
13+
<key>CFBundleVersion</key>
14+
<string>1.0</string>
15+
<key>LSRequiresIPhoneOS</key>
16+
<true/>
17+
<key>UIDeviceFamily</key>
18+
<array>
19+
<integer>1</integer>
20+
<integer>2</integer>
21+
</array>
22+
<key>UILaunchStoryboardName</key>
23+
<string>LaunchScreen</string>
24+
<key>UIRequiredDeviceCapabilities</key>
25+
<array>
26+
<string>armv7</string>
27+
</array>
28+
<key>UISupportedInterfaceOrientations</key>
29+
<array>
30+
<string>UIInterfaceOrientationPortrait</string>
31+
<string>UIInterfaceOrientationLandscapeLeft</string>
32+
<string>UIInterfaceOrientationLandscapeRight</string>
33+
</array>
34+
<key>UISupportedInterfaceOrientations~ipad</key>
35+
<array>
36+
<string>UIInterfaceOrientationPortrait</string>
37+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
38+
<string>UIInterfaceOrientationLandscapeLeft</string>
39+
<string>UIInterfaceOrientationLandscapeRight</string>
40+
</array>
41+
<key>XSAppIconAssets</key>
42+
<string>Assets.xcassets/AppIcon.appiconset</string>
4343
</dict>
4444
</plist>

samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0-ios17.0</TargetFramework>
4+
<TargetFramework>net9.0-ios18.0</TargetFramework>
55
<OutputType>Exe</OutputType>
66
<Nullable>enable</Nullable>
77
<ImplicitUsings>true</ImplicitUsings>
8-
<SupportedOSPlatformVersion>11.0</SupportedOSPlatformVersion>
8+
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
99
<SelfContained>true</SelfContained>
1010
<PublishAot>true</PublishAot>
11+
<!-- TODO: Remove once this issue has been addressed: https://github.com/dotnet/runtime/pull/109186 -->
12+
<WarningsNotAsErrors>IL3050;IL3053</WarningsNotAsErrors>
1113
</PropertyGroup>
1214

1315
<PropertyGroup>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using ObjCRuntime;
2+
3+
namespace Sentry.Cocoa;
4+
5+
internal interface IRuntime
6+
{
7+
internal event MarshalManagedExceptionHandler MarshalManagedException;
8+
internal event MarshalObjectiveCExceptionHandler MarshalObjectiveCException;
9+
}
10+
11+
internal sealed class RuntimeAdapter : IRuntime
12+
{
13+
public static RuntimeAdapter Instance { get; } = new();
14+
15+
private RuntimeAdapter()
16+
{
17+
Runtime.MarshalManagedException += OnMarshalManagedException;
18+
Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException;
19+
}
20+
21+
public event MarshalManagedExceptionHandler? MarshalManagedException;
22+
public event MarshalObjectiveCExceptionHandler? MarshalObjectiveCException;
23+
24+
[SecurityCritical]
25+
private void OnMarshalManagedException(object sender, MarshalManagedExceptionEventArgs e) => MarshalManagedException?.Invoke(this, e);
26+
27+
[SecurityCritical]
28+
private void OnMarshalObjectiveCException(object sender, MarshalObjectiveCExceptionEventArgs e) => MarshalObjectiveCException?.Invoke(this, e);
29+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using ObjCRuntime;
2+
using Sentry.Extensibility;
3+
using Sentry.Integrations;
4+
5+
namespace Sentry.Cocoa;
6+
7+
/// <summary>
8+
/// When AOT Compiling iOS applications, the AppDomain UnhandledExceptionHandler doesn't fire. So instead we intercept
9+
/// the Runtime.RuntimeMarshalManagedException event.
10+
/// </summary>
11+
internal class RuntimeMarshalManagedExceptionIntegration : ISdkIntegration
12+
{
13+
private readonly IRuntime _runtime;
14+
private IHub? _hub;
15+
private SentryOptions? _options;
16+
17+
internal RuntimeMarshalManagedExceptionIntegration(IRuntime? runtime = null)
18+
=> _runtime = runtime ?? RuntimeAdapter.Instance;
19+
20+
public void Register(IHub hub, SentryOptions options)
21+
{
22+
_hub = hub;
23+
_options = options;
24+
_runtime.MarshalManagedException += Handle;
25+
}
26+
27+
// Internal for testability
28+
[SecurityCritical]
29+
internal void Handle(object sender, MarshalManagedExceptionEventArgs e)
30+
{
31+
_options?.LogDebug("Runtime Marshal Managed Exception mode {0}", e.ExceptionMode.ToString("G"));
32+
33+
if (e.Exception is { } ex)
34+
{
35+
ex.SetSentryMechanism(
36+
"Runtime.MarshalManagedException",
37+
"This exception was caught by the .NET Runtime Marshal Managed Exception global error handler. " +
38+
"The application may have crashed as a result of this exception.",
39+
handled: false);
40+
41+
// Call the internal implementation, so that we still capture even if the hub has been disabled.
42+
_hub?.CaptureExceptionInternal(ex);
43+
44+
// This is likely a terminal exception so try to send the crash report before shutting down
45+
_hub?.Flush();
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)