Skip to content

Commit 9ad492a

Browse files
authored
[NativeAOT] Add DefaultUncaughtExceptionHandler (#9994)
Context: dotnet/runtime#102730 Context: 1aa0ea7 What should happen when an exception is thrown and not caught? partial class MainActivity { protected override void OnCreate(Bundle? savedInstanceState) => throw new Exception("Uncaught exception"); } What *previously* happened is that the app would exit, with an `AndroidRuntime` tag containing the Java-side exception, which will contain *some* managed info courtesy of 1aa0ea7. I ActivityManager: Start proc 6911:net.dot.hellonativeaot/u0a205 for top-activity {net.dot.hellonativeaot/my.MainActivity} … D NativeAotRuntimeProvider: NativeAotRuntimeProvider() D NativeAotRuntimeProvider: NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()… D JavaInteropRuntime: Loading NativeAOT.so... I JavaInteropRuntime: JNI_OnLoad() … D NativeAotRuntimeProvider: NativeAotRuntimeProvider.onCreate() D NativeAOT: Application..ctor(7fff01a958, DoNotTransfer) D NativeAOT: Application.OnCreate() … D NativeAOT: MainActivity.OnCreate() … D NativeAOT: MainActivity.OnCreate() ColorStateList: ColorStateList{mThemeAttrs=nullmChangingConfigurations=0mStateSpecs=[[0, 1]]mColors=[0, 1]mDefaultColor=0} D AndroidRuntime: Shutting down VM E AndroidRuntime: FATAL EXCEPTION: main E AndroidRuntime: Process: net.dot.hellonativeaot, PID: 6911 E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidOperationException: What happened? E AndroidRuntime: at NativeAOT.MainActivity.OnCreate(Bundle savedInstanceState) + 0x2f4 E AndroidRuntime: at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + 0xc8 E AndroidRuntime: at my.MainActivity.n_onCreate(Native Method) E AndroidRuntime: at my.MainActivity.onCreate(MainActivity.java:28) E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8595) E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8573) E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456) E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3805) E AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3963) E AndroidRuntime: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139) E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96) E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2484) E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:205) E AndroidRuntime: at android.os.Looper.loop(Looper.java:294) E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8225) E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573) E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049) W ActivityTaskManager: Force finishing activity net.dot.hellonativeaot/my.MainActivity What *won't* happen is that the [`AppDomain.UnhandledException`][0] event will ***not*** be raised This is less than ideal, and will cause the `InstallAndRunTests.SubscribeToAppDomainUnhandledException()` test to fail, once that test is enabled for NativeAOT. *Begin* to address this by setting `Java.Lang.Thread.DefaultUncaughtExceptionHandler` to a `Thread.IUncaughtExceptionHandler` instance which at least prints the exception to `adb logcat`. Update `samples/NativeAOT` to demonstrate this, by using a new boolean `thrown` extra; if true, then `OnCreate()` throws: adb shell am start --ez throw 1 net.dot.hellonativeaot/my.MainActivity `adb logcat` continues to have the `FATAL EXCEPTION` message from `AndroidRuntime`, as shown above, and `adb logcat` now also contains: F DOTNET : FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: What happened? F DOTNET : at NativeAOT.MainActivity.OnCreate(Bundle savedInstanceState) + 0x2f4 F DOTNET : at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + 0xc8 which prints out the managed exception that we would expect to be raised by `AppDomain.UnhandledException`, once that integration works. TODO: once dotnet/runtime#102730 is fixed, update `UncaughtExceptionMarshaler` to do whatever it needs to do to cause the `AppDomain.UnhandledException` event to be raised. Update `JavaInteropRuntime.init()` to marshal excpetions back to Java, so that the process appropriately terminates if `init()` fails. [0]: https://learn.microsoft.com/dotnet/api/system.appdomain.unhandledexception?view=net-9.0
1 parent 196d7a0 commit 9ad492a

File tree

3 files changed

+33
-0
lines changed

3 files changed

+33
-0
lines changed

samples/NativeAOT/MainActivity.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ protected override void OnCreate(Bundle? savedInstanceState)
2222
// An example of an Android API that uses a Java array
2323
var list = new ColorStateList (new int[][] { [ 0, 1 ]}, [0, 1]);
2424
Log.Debug ("NativeAOT", "MainActivity.OnCreate() ColorStateList: " + list);
25+
26+
var t = Intent?.Extras?.GetBoolean ("throw") ?? false;
27+
if (t) {
28+
throw new InvalidOperationException ("What happened?");
29+
}
2530
}
2631
}

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
3333
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
3434
static void init (IntPtr jnienv, IntPtr klass)
3535
{
36+
JniTransition transition = default;
3637
try {
3738
var settings = new DiagnosticSettings ();
3839
settings.AddDebugDotnetLog ();
@@ -50,9 +51,16 @@ static void init (IntPtr jnienv, IntPtr klass)
5051

5152
// Entry point into Mono.Android.dll
5253
JNIEnvInit.InitializeJniRuntime (runtime);
54+
55+
transition = new JniTransition (jnienv);
56+
57+
var handler = Java.Lang.Thread.DefaultUncaughtExceptionHandler;
58+
Java.Lang.Thread.DefaultUncaughtExceptionHandler = new UncaughtExceptionMarshaler (handler);
5359
}
5460
catch (Exception e) {
5561
AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}");
62+
transition.SetPendingException (e);
5663
}
64+
transition.Dispose ();
5765
}
5866
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Java.Interop;
2+
3+
namespace Microsoft.Android.Runtime;
4+
5+
class UncaughtExceptionMarshaler (Java.Lang.Thread.IUncaughtExceptionHandler? OriginalHandler)
6+
: Java.Lang.Object, Java.Lang.Thread.IUncaughtExceptionHandler
7+
{
8+
public void UncaughtException (Java.Lang.Thread thread, Java.Lang.Throwable exception)
9+
{
10+
var e = (JniEnvironment.Runtime.ValueManager.PeekValue (exception.PeerReference) as System.Exception)
11+
?? exception;
12+
13+
AndroidLog.Print (AndroidLogLevel.Fatal, "DOTNET", $"FATAL UNHANDLED EXCEPTION: {e}");
14+
15+
// TODO: https://github.com/dotnet/runtime/issues/102730
16+
// ExceptionHandling.RaiseUnhandledExceptionEvent(e);
17+
18+
OriginalHandler?.UncaughtException (thread, exception);
19+
}
20+
}

0 commit comments

Comments
 (0)