[runtime] Call mono_unhandled_exception to raise AppDomain.UnhandledException. (#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 https://github.com/dotnet/runtime/issues/102730 fixed first).
This commit is contained in:
Rolf Bjarne Kvinge 2024-05-29 20:14:47 +02:00 коммит произвёл GitHub
Родитель 1c7604cde2
Коммит a0b858ad47
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
19 изменённых файлов: 162 добавлений и 5 удалений

Просмотреть файл

@ -1174,4 +1174,11 @@ mono_string_new (MonoDomain *domain, const char *text)
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

Просмотреть файл

@ -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",

Просмотреть файл

@ -563,4 +563,11 @@ xamarin_enable_new_refcount ()
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

Просмотреть файл

@ -1143,6 +1143,16 @@ exception_handler (NSException *exc)
// 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 ();

Просмотреть файл

@ -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 ();

Просмотреть файл

@ -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")
{
}
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -0,0 +1,2 @@
TOP=../../..
include $(TOP)/tests/common/shared-dotnet-test.mk

Просмотреть файл

@ -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>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -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>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -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>

Просмотреть файл

@ -0,0 +1,3 @@
TOP=../../../..
TESTNAME=MySimpleApp
include $(TOP)/tests/common/shared-dotnet.mk

Просмотреть файл

@ -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>

Просмотреть файл

@ -0,0 +1 @@
include ../shared.mk

Просмотреть файл

@ -1764,5 +1764,31 @@ namespace Xamarin.Tests {
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);
}
}
}
}

Просмотреть файл

@ -327,28 +327,28 @@ namespace Xamarin.Tests {
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.");
@ -358,6 +358,10 @@ namespace Xamarin.Tests {
{ "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;