Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
134 changes: 87 additions & 47 deletions samples/Sentry.Samples.Ios/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using ObjCRuntime;

namespace Sentry.Samples.Ios;

[Register("AppDelegate")]
Expand All @@ -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;
}
}
76 changes: 38 additions & 38 deletions samples/Sentry.Samples.Ios/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,43 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Sentry.Samples.Ios</string>
<key>CFBundleIdentifier</key>
<string>io.sentry.dotnet.samples.ios</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>CFBundleDisplayName</key>
<string>Sentry.Samples.Ios</string>
<key>CFBundleIdentifier</key>
<string>io.sentry.dotnet.samples.ios</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>15.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
</dict>
</plist>
6 changes: 4 additions & 2 deletions samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-ios17.0</TargetFramework>
<TargetFramework>net9.0-ios18.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SupportedOSPlatformVersion>11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
<SelfContained>true</SelfContained>
<PublishAot>true</PublishAot>
<!-- TODO: Remove once this issue has been addressed: https://github.com/dotnet/runtime/pull/109186 -->
<WarningsNotAsErrors>IL3050;IL3053</WarningsNotAsErrors>
</PropertyGroup>

<PropertyGroup>
Expand Down
29 changes: 29 additions & 0 deletions src/Sentry/Platforms/Cocoa/RuntimeAdapter.cs
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
Copy link
Member

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?

Copy link
Collaborator Author

@jamescrosswell jamescrosswell Jan 31, 2025

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):

// Workaround for https://github.com/xamarin/xamarin-macios/issues/15252
ObjCRuntime.Runtime.MarshalManagedException += (_, args) =>
{
args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode;
};

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.

/// 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)
{
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.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not certain that it did crash?

Copy link
Collaborator Author

@jamescrosswell jamescrosswell Jan 31, 2025

Choose a reason for hiding this comment

The 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 AppDomain.Current.UnhandledException because Runtime.MarshalManagedException fires even in AOT compiled applications but MarshalManagedExceptionEventArgs has no equivalent of UnhandledExceptionEventArgs.IsTerminating.

I've asked the question here:

Copy link
Member

Choose a reason for hiding this comment

The 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();
}
}
}
Loading
Loading