Skip to content

Commit

Permalink
[runtime] Call mono_unhandled_exception to raise AppDomain.UnhandledE…
Browse files Browse the repository at this point in the history
…xception. (#20656)

Call mono_unhandled_exception to raise AppDomain.UnhandledException when
managed exceptions are unhandled.

Partial fix for #15252 (for MonoVM, still pending for CoreCLR, which
needs dotnet/runtime#102730 fixed first).
  • Loading branch information
rolfbjarne authored May 29, 2024
1 parent 1c7604c commit a0b858a
Show file tree
Hide file tree
Showing 19 changed files with 162 additions and 5 deletions.
7 changes: 7 additions & 0 deletions runtime/coreclr-bridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -1174,4 +1174,11 @@
return rv;
}

void
xamarin_bridge_raise_unhandled_exception_event (GCHandle exception_gchandle)
{
// There's no way to raise the AppDomain.UnhandledException event.
// https://github.com/dotnet/runtime/issues/102730
}

#endif // CORECLR_RUNTIME
6 changes: 6 additions & 0 deletions runtime/exports.t4
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@
XamarinRuntime = RuntimeMode.MonoVM,
},

new Export ("void", "mono_unhandled_exception",
"MonoObject *", "ex"
) {
XamarinRuntime = RuntimeMode.MonoVM,
},

new Export ("char*", "mono_array_addr_with_size",
"MonoArray *", "array",
"int", "size",
Expand Down
7 changes: 7 additions & 0 deletions runtime/monovm-bridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,11 @@
mono_profiler_install_gc (gc_event_callback, NULL);
}

void
xamarin_bridge_raise_unhandled_exception_event (GCHandle exception_gchandle)
{
MonoObject *exc = xamarin_gchandle_get_target (exception_gchandle);
mono_unhandled_exception (exc);
}

#endif // !CORECLR_RUNTIME
10 changes: 10 additions & 0 deletions runtime/runtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,16 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
// COOP: We won't get here in coop-mode, because we don't set the uncaught objc exception handler in that case.
LOG (PRODUCT ": Received unhandled ObjectiveC exception: %@ %@", [exc name], [exc reason]);

XamarinGCHandle* exc_handle = [[exc userInfo] objectForKey: @"XamarinManagedExceptionHandle"];
if (exc_handle != NULL) {
GCHandle exception_gchandle = [exc_handle getHandle];
if (exception_gchandle != INVALID_GCHANDLE) {
xamarin_bridge_raise_unhandled_exception_event (exception_gchandle);
PRINT ("Received unhandled Objective-C exception that was marshalled from a managed exception: %@", exc);
abort ();
}
}

if (xamarin_handling_unhandled_exceptions == 1) {
PRINT ("Detected recursion when handling uncaught Objective-C exception: %@", exc);
abort ();
Expand Down
1 change: 1 addition & 0 deletions runtime/xamarin/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ void xamarin_bridge_call_runtime_initialize (struct InitializationOptions* opt
void xamarin_bridge_register_product_assembly (GCHandle* exception_gchandle);
MonoMethod * xamarin_bridge_get_mono_method (MonoReflectionMethod *method);
void xamarin_bridge_free_mono_signature (MonoMethodSignature **signature);
void xamarin_bridge_raise_unhandled_exception_event (GCHandle exception_gchandle); // the GCHandle is *not* freed. This method will return after raising the event.
bool xamarin_register_monoassembly (MonoAssembly *assembly, GCHandle *exception_gchandle);
void xamarin_install_nsautoreleasepool_hooks ();
void xamarin_enable_new_refcount ();
Expand Down
44 changes: 44 additions & 0 deletions tests/dotnet/ExceptionalTestApp/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Runtime.InteropServices;

using Foundation;

namespace MySimpleApp {
public class Program {
static int Main (string [] args)
{
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly

var testCaseString = Environment.GetEnvironmentVariable ("EXCEPTIONAL_TEST_CASE");
if (string.IsNullOrEmpty (testCaseString)) {
Console.WriteLine ($"The environment variable EXCEPTIONAL_TEST_CASE wasn't set.");
return 2;
}
var testCase = int.Parse (testCaseString);
switch (testCase) {
case 1:
AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => {
if (e.ExceptionObject is TestCaseException) {
Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));
} else {
Console.WriteLine ($"Unexpected exception type: {e.ExceptionObject?.GetType ()}");
}
Environment.Exit (0);
};
throw new TestCaseException ();
default:
Console.WriteLine ($"Unknown test case: {testCase}");
return 3;
}

return 1;
}
}
}

class TestCaseException : Exception {
public TestCaseException ()
: base ("Testing, testing")
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
1 change: 1 addition & 0 deletions tests/dotnet/ExceptionalTestApp/MacCatalyst/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
2 changes: 2 additions & 0 deletions tests/dotnet/ExceptionalTestApp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TOP=../../..
include $(TOP)/tests/common/shared-dotnet-test.mk
7 changes: 7 additions & 0 deletions tests/dotnet/ExceptionalTestApp/iOS/ExceptionalTestApp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-ios</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
1 change: 1 addition & 0 deletions tests/dotnet/ExceptionalTestApp/iOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-macos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
1 change: 1 addition & 0 deletions tests/dotnet/ExceptionalTestApp/macOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
15 changes: 15 additions & 0 deletions tests/dotnet/ExceptionalTestApp/shared.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<OutputType>Exe</OutputType>

<ApplicationTitle>ExceptionalTestApp</ApplicationTitle>
<ApplicationId>com.xamarin.exceptionaltestapp</ApplicationId>
</PropertyGroup>

<Import Project="../../common/shared-dotnet.csproj" />

<ItemGroup>
<Compile Include="../*.cs" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions tests/dotnet/ExceptionalTestApp/shared.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TOP=../../../..
TESTNAME=MySimpleApp
include $(TOP)/tests/common/shared-dotnet.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net$(BundledNETCoreAppTargetFrameworkVersion)-tvos</TargetFramework>
</PropertyGroup>
<Import Project="..\shared.csproj" />
</Project>
1 change: 1 addition & 0 deletions tests/dotnet/ExceptionalTestApp/tvOS/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../shared.mk
26 changes: 26 additions & 0 deletions tests/dotnet/UnitTests/ProjectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1764,5 +1764,31 @@ public void SourcelinkTest (ApplePlatform platform, string runtimeIdentifiers, s

Assert.AreEqual ($"sourcelink test passed: {pdbFile}", test.StandardOutput.ToString ().TrimEnd ('\n'));
}


[Test]
// [TestCase (ApplePlatform.iOS)] // Skipping because we're not executing tvOS apps anyway (but it should work)
// [TestCase (ApplePlatform.TVOS)] // Skipping because we're not executing tvOS apps anyway (but it should work)
[TestCase (ApplePlatform.MacOSX)] // https://github.com/dotnet/runtime/issues/102730
[TestCase (ApplePlatform.MacCatalyst)]
public void RaisesAppDomainUnhandledExceptionEvent (ApplePlatform platform)
{
var project = "ExceptionalTestApp";
Configuration.IgnoreIfIgnoredPlatform (platform);

var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform);
var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath);
Clean (project_path);
var properties = GetDefaultProperties ();
DotNet.AssertBuild (project_path, properties);

if (CanExecute (platform, runtimeIdentifiers)) {
var env = new Dictionary<string, string?> {
{ "EXCEPTIONAL_TEST_CASE", "1" },
};
var appExecutable = GetNativeExecutable (platform, appPath);
var output = ExecuteWithMagicWordAndAssert (appExecutable, env);
}
}
}
}
14 changes: 9 additions & 5 deletions tests/dotnet/UnitTests/TestBaseClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,28 +327,28 @@ protected string GenerateProject (ApplePlatform platform, string name, string ru
return csproj;
}

protected string ExecuteWithMagicWordAndAssert (ApplePlatform platform, string runtimeIdentifiers, string executable)
protected string ExecuteWithMagicWordAndAssert (ApplePlatform platform, string runtimeIdentifiers, string executable, Dictionary<string, string?>? environment = null)
{
if (!CanExecute (platform, runtimeIdentifiers))
return string.Empty;

return ExecuteWithMagicWordAndAssert (executable);
return ExecuteWithMagicWordAndAssert (executable, environment);
}

protected string ExecuteWithMagicWordAndAssert (string executable)
protected string ExecuteWithMagicWordAndAssert (string executable, Dictionary<string, string?>? environment = null)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
Console.WriteLine ($"Not executing '{executable}' because we're on Windows.");
return string.Empty;
}

var rv = Execute (executable, out var output, out string magicWord);
var rv = Execute (executable, out var output, out string magicWord, environment);
Assert.That (output.ToString (), Does.Contain (magicWord), "Contains magic word");
Assert.AreEqual (0, rv.ExitCode, "ExitCode");
return output.ToString ();
}

protected Execution Execute (string executable, out StringBuilder output, out string magicWord)
protected Execution Execute (string executable, out StringBuilder output, out string magicWord, Dictionary<string, string?>? environment = null)
{
if (!File.Exists (executable))
throw new FileNotFoundException ($"The executable '{executable}' does not exists.");
Expand All @@ -358,6 +358,10 @@ protected Execution Execute (string executable, out StringBuilder output, out st
{ "MAGIC_WORD", magicWord },
{ "DYLD_FALLBACK_LIBRARY_PATH", null }, // VSMac might set this, which may cause tests to crash.
};
if (environment is not null) {
foreach (var kvp in environment)
env [kvp.Key] = kvp.Value;
}

output = new StringBuilder ();
return Execution.RunWithStringBuildersAsync (executable, Array.Empty<string> (), environment: env, standardOutput: output, standardError: output, timeout: TimeSpan.FromSeconds (15)).Result;
Expand Down

0 comments on commit a0b858a

Please sign in to comment.