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
34 changes: 16 additions & 18 deletions integration-test/ios.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ BeforeDiscovery {
$script:simulator = Get-IosSimulatorUdid -PreferredStates @('Booted')
}

Describe 'iOS app (<tfm>, <configuration>)' -ForEach @(
Describe 'iOS app (<tfm>, <configuration>, <runtime>)' -ForEach @(
# Note: we can't run against net10 and net9 becaus .NET 10 requires Xcode 26.2 and .NET 9 requires Xcode 26.0.
# The macOS GitHub Actions runners only have Xcode 26.1+ installed and no support for Xcode 26.2 is planned for
# net9.0-ios: https://github.com/dotnet/macios/issues/24199#issuecomment-3819021247
@{ tfm = "net10.0-ios26.2"; configuration = "Release" }
@{ tfm = "net10.0-ios26.2"; configuration = "Debug" }
#
# TODO: add coreclr when available
@{ tfm = "net10.0-ios26.2"; configuration = "Release"; runtime = "mono" }
@{ tfm = "net10.0-ios26.2"; configuration = "Debug"; runtime = "mono" }
) -Skip:(-not $script:simulator) {
BeforeAll {
. $PSScriptRoot/../scripts/device-test-utils.ps1
Expand All @@ -29,10 +31,12 @@ Describe 'iOS app (<tfm>, <configuration>)' -ForEach @(
$rid = "iossimulator-$arch"

Write-Host "::group::Build Sentry.Maui.Device.IntegrationTestApp.csproj"
$useMonoRuntime = if ($runtime -eq 'mono') { 'true' } else { 'false' }
dotnet build Sentry.Maui.Device.IntegrationTestApp.csproj `
--configuration $configuration `
--framework $tfm `
--runtime $rid
--runtime $rid `
-p:UseMonoRuntime=$useMonoRuntime
| ForEach-Object { Write-Host $_ }
Write-Host '::endgroup::'
$LASTEXITCODE | Should -Be 0
Expand Down Expand Up @@ -90,7 +94,7 @@ Describe 'iOS app (<tfm>, <configuration>)' -ForEach @(
UninstallIosApp
}

It 'captures managed crash (<configuration>)' {
It 'captures managed crash (<configuration>, <runtime>)' {
$result = Invoke-SentryServer {
param([string]$url)
RunIosApp -Dsn $url -TestArg "Managed"
Expand All @@ -99,25 +103,24 @@ Describe 'iOS app (<tfm>, <configuration>)' -ForEach @(

$result.HasErrors() | Should -BeFalse
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.ApplicationException`""
# TODO: fix redundant SIGABRT (#3954)
{ $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"SIGABRT`"" } | Should -Throw
{ $result.Envelopes() | Should -HaveCount 1 } | Should -Throw
$result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"(EXC_[A-Z_]+|SIG[A-Z]+)`""
$result.Envelopes() | Should -HaveCount 1
}

It 'captures native crash (<configuration>)' {
It 'captures native crash (<configuration>, <runtime>)' {
$result = Invoke-SentryServer {
param([string]$url)
RunIosApp -Dsn $url -TestArg "Native"
RunIosApp -Dsn $url
}

$result.HasErrors() | Should -BeFalse
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"EXC_[A-Z_]+`""
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"(EXC_[A-Z_]+|SIG[A-Z]+)`""
$result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"System.\w+Exception`""
$result.Envelopes() | Should -HaveCount 1
}

It 'captures null reference exception (<configuration>)' {
It 'captures null reference exception (<configuration>, <runtime>)' {
$result = Invoke-SentryServer {
param([string]$url)
RunIosApp -Dsn $url -TestArg "NullReferenceException"
Expand All @@ -126,12 +129,7 @@ Describe 'iOS app (<tfm>, <configuration>)' -ForEach @(

$result.HasErrors() | Should -BeFalse
$result.Envelopes() | Should -AnyElementMatch "`"type`":`"System.NullReferenceException`""
# TODO: fix redundant EXC_BAD_ACCESS in Release (#3954)
if ($configuration -eq 'Release') {
{ $result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"EXC_BAD_ACCESS`"" } | Should -Throw
} else {
$result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"EXC_BAD_ACCESS`""
$result.Envelopes() | Should -HaveCount 1
}
$result.Envelopes() | Should -Not -AnyElementMatch "`"type`":`"(EXC_[A-Z_]+|SIG[A-Z]+)`""
$result.Envelopes() | Should -HaveCount 1
}
}
9 changes: 6 additions & 3 deletions scripts/build-sentry-cocoa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,17 @@ xcodebuild archive -project Sentry.xcodeproj \
-sdk "$ios_sdk" \
-archivePath ./Carthage/output-ios.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) SENTRY_CRASH_MANAGED_RUNTIME=1'
./scripts/remove-architectures.sh ./Carthage/output-ios.xcarchive arm64e
xcodebuild archive -project Sentry.xcodeproj \
-scheme Sentry \
-configuration Release \
-sdk "$ios_simulator_sdk" \
-archivePath ./Carthage/output-iossimulator.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) SENTRY_CRASH_MANAGED_RUNTIME=1'
xcodebuild -create-xcframework \
-framework ./Carthage/output-ios.xcarchive/Products/Library/Frameworks/Sentry.framework \
-framework ./Carthage/output-iossimulator.xcarchive/Products/Library/Frameworks/Sentry.framework \
Expand All @@ -73,7 +75,8 @@ xcodebuild archive -project Sentry.xcodeproj \
-destination 'generic/platform=macOS,variant=Mac Catalyst' \
-archivePath ./Carthage/output-maccatalyst.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) SENTRY_CRASH_MANAGED_RUNTIME=1'
./scripts/remove-architectures.sh ./Carthage/output-maccatalyst.xcarchive arm64e
xcodebuild -create-xcframework \
-framework ./Carthage/output-maccatalyst.xcarchive/Products/Library/Frameworks/Sentry.framework \
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry/Platforms/Cocoa/RuntimeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ internal interface IRuntime
{
internal event MarshalManagedExceptionHandler MarshalManagedException;
internal event MarshalObjectiveCExceptionHandler MarshalObjectiveCException;
internal bool IsMono { get; }
internal void IgnoreNextSignal(int signal);
}

internal sealed class RuntimeAdapter : IRuntime
Expand All @@ -21,6 +23,10 @@ private RuntimeAdapter()
public event MarshalManagedExceptionHandler? MarshalManagedException;
public event MarshalObjectiveCExceptionHandler? MarshalObjectiveCException;

public bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;

public void IgnoreNextSignal(int signal) => SentryCocoaHybridSdk.IgnoreNextSignal(signal);

[SecurityCritical]
private void OnMarshalManagedException(object sender, MarshalManagedExceptionEventArgs e) => MarshalManagedException?.Invoke(this, e);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ internal void Handle(object sender, MarshalManagedExceptionEventArgs e)

// This is likely a terminal exception so try to send the crash report before shutting down
_hub?.Flush();

// Under Mono, Disable/UnwindNativeCode call mono_raise_exception which can unwind into
// a managed catch and never hit abort(). The thread-local ignore flag would then linger
// and silently swallow an unrelated later SIGABRT on this thread, so skip arming it. See
// https://github.com/dotnet/macios/blob/be8a2ca1057242f745ef58011a02ffe21326d180/runtime/runtime.m#L2215
if (_runtime.IsMono && e.ExceptionMode is MarshalManagedExceptionMode.Disable
or MarshalManagedExceptionMode.UnwindNativeCode)
{
return;
}

// Otherwise the runtime will call abort() after we return — directly via
// xamarin_assertion_message, or indirectly via the uncaught-NSException handler for
// ThrowObjectiveCException. Tell SentryCrash to ignore that SIGABRT so we don't emit a
// duplicate native crash for an exception we've already captured. See
// https://github.com/dotnet/macios/blob/be8a2ca1057242f745ef58011a02ffe21326d180/runtime/runtime.m#L2285
const int SIGABRT = 6;
_runtime.IgnoreNextSignal(SIGABRT);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,62 @@ public void Register_UnhandledException_Subscribes()

_fixture.Runtime.Received().MarshalManagedException += sut.Handle;
}

[Theory]
[InlineData(MarshalManagedExceptionMode.Default)]
[InlineData(MarshalManagedExceptionMode.ThrowObjectiveCException)]
[InlineData(MarshalManagedExceptionMode.Abort)]
public void Handle_Mono_AbortingMode_IgnoresSigabrt(MarshalManagedExceptionMode mode)
{
_fixture.Runtime.IsMono.Returns(true);
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

sut.Handle(this, new MarshalManagedExceptionEventArgs { Exception = new Exception(), ExceptionMode = mode });

_fixture.Runtime.Received(1).IgnoreNextSignal(6);
}

[Theory]
[InlineData(MarshalManagedExceptionMode.Disable)]
[InlineData(MarshalManagedExceptionMode.UnwindNativeCode)]
public void Handle_Mono_NonAbortingMode_DoesNotIgnoreSigabrt(MarshalManagedExceptionMode mode)
{
_fixture.Runtime.IsMono.Returns(true);
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

sut.Handle(this, new MarshalManagedExceptionEventArgs { Exception = new Exception(), ExceptionMode = mode });

_fixture.Runtime.DidNotReceive().IgnoreNextSignal(Arg.Any<int>());
}

[Theory]
[InlineData(MarshalManagedExceptionMode.Default)]
[InlineData(MarshalManagedExceptionMode.Disable)]
[InlineData(MarshalManagedExceptionMode.UnwindNativeCode)]
[InlineData(MarshalManagedExceptionMode.ThrowObjectiveCException)]
[InlineData(MarshalManagedExceptionMode.Abort)]
public void Handle_CoreCLR_AnyMode_IgnoresSigabrt(MarshalManagedExceptionMode mode)
{
_fixture.Runtime.IsMono.Returns(false);
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

sut.Handle(this, new MarshalManagedExceptionEventArgs { Exception = new Exception(), ExceptionMode = mode });

_fixture.Runtime.Received(1).IgnoreNextSignal(6);
}
Comment thread
cursor[bot] marked this conversation as resolved.

[Fact]
public void Handle_NoException_DoesNotIgnoreSigabrt()
{
var sut = _fixture.GetSut();
sut.Register(_fixture.Hub, SentryOptions);

sut.Handle(this, new MarshalManagedExceptionEventArgs());

_fixture.Runtime.DidNotReceive().IgnoreNextSignal(Arg.Any<int>());
}
}
#endif
Loading