Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt
if (RuntimeFeature.IsMonoRuntime) {
MonoDroidUnhandledException (innerException ?? javaException);
} else if (RuntimeFeature.IsCoreClrRuntime) {
// TODO: what to do here?
ExceptionHandling.RaiseAppDomainUnhandledExceptionEvent (innerException ?? javaException);
} else {
throw new NotSupportedException ("Internal error: unknown runtime not supported");
}
Expand Down
9 changes: 9 additions & 0 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal struct JnienvInitializeArgs {
public bool marshalMethodsEnabled;
public IntPtr grefGCUserPeerable;
public bool managedMarshalMethodsLookupEnabled;
public IntPtr propagateUncaughtExceptionFn;
}
#pragma warning restore 0649

Expand All @@ -49,6 +50,12 @@ internal struct JnienvInitializeArgs {

internal static JniRuntime? androidRuntime;

[UnmanagedCallersOnly]
static void PropagateUncaughtException (IntPtr env, IntPtr javaThread, IntPtr javaException)
{
JNIEnv.PropagateUncaughtException (env, javaThread, javaException);
}

[UnmanagedCallersOnly]
static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len)
{
Expand Down Expand Up @@ -157,6 +164,8 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
xamarin_app_init (args->env, getFunctionPointer);
}

args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, IntPtr, void>)&PropagateUncaughtException;

SetSynchronizationContext ();
}

Expand Down
4 changes: 2 additions & 2 deletions src/native/clr/host/host-jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang,
}

JNIEXPORT void
JNICALL Java_mono_android_Runtime_propagateUncaughtException ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass klass, [[maybe_unused]] jobject javaThread, [[maybe_unused]] jthrowable javaException)
JNICALL Java_mono_android_Runtime_propagateUncaughtException (JNIEnv *env, [[maybe_unused]] jclass klass, jobject javaThread, jthrowable javaException)
{
// TODO: implement or remove
Host::propagate_uncaught_exception (env, javaThread, javaException);
}

JNIEXPORT void
Expand Down
14 changes: 14 additions & 0 deletions src/native/clr/host/host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@ void Host::Java_mono_android_Runtime_initInternal (
log_debug (LOG_DEFAULT, "Calling into managed runtime init"sv);
FastTiming::time_call ("JNIEnv.Initialize UCO"sv, initialize, &init);

// PropagateUncaughtException is returned from Initialize to avoid an extra create_delegate call
jnienv_propagate_uncaught_exception = init.propagateUncaughtExceptionFn;
abort_unless (jnienv_propagate_uncaught_exception != nullptr, "Failed to obtain unmanaged-callers-only function pointer to the PropagateUncaughtException method.");

if (FastTiming::enabled ()) [[unlikely]] {
internal_timing.end_event (); // native to managed
internal_timing.end_event (); // total init time
Expand Down Expand Up @@ -613,3 +617,13 @@ auto HostCommon::Java_JNI_OnLoad (JavaVM *vm, [[maybe_unused]] void *reserved) n
AndroidSystem::init_max_gref_count ();
return JNI_VERSION_1_6;
}

void Host::propagate_uncaught_exception (JNIEnv *env, jobject javaThread, jthrowable javaException) noexcept
{
if (jnienv_propagate_uncaught_exception == nullptr) {
log_warn (LOG_DEFAULT, "propagate_uncaught_exception called before JNIEnvInit.PropagateUncaughtException was initialized"sv);
return;
}

jnienv_propagate_uncaught_exception (env, javaThread, javaException);
}
2 changes: 2 additions & 0 deletions src/native/clr/include/host/host.hh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace xamarin::android {
jstring runtimeNativeLibDir, jobjectArray appDirs, jint localDateTimeOffset, jobject loader,
jobjectArray assembliesJava, jboolean isEmulator, jboolean haveSplitApks) noexcept;
static void Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, jclass nativeClass, jstring methods) noexcept;
static void propagate_uncaught_exception (JNIEnv *env, jobject javaThread, jthrowable javaException) noexcept;

static auto get_timing () -> std::shared_ptr<Timing>
{
Expand Down Expand Up @@ -53,6 +54,7 @@ namespace xamarin::android {
static inline std::shared_ptr<Timing> _timing{};
static inline bool found_assembly_store = false;
static inline jnienv_register_jni_natives_fn jnienv_register_jni_natives = nullptr;
static inline jnienv_propagate_uncaught_exception_fn jnienv_propagate_uncaught_exception = nullptr;

static inline jclass java_TimeZone = nullptr;

Expand Down
3 changes: 3 additions & 0 deletions src/native/common/include/managed-interface.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace xamarin::android {
Signals = 0x08,
};

using jnienv_propagate_uncaught_exception_fn = void (*)(JNIEnv *env, jobject javaThread, jthrowable javaException);

// NOTE: Keep this in sync with managed side in src/Mono.Android/Android.Runtime/JNIEnvInit.cs
struct JnienvInitializeArgs {
JavaVM *javaVm;
Expand All @@ -33,6 +35,7 @@ namespace xamarin::android {
bool marshalMethodsEnabled;
jobject grefGCUserPeerable;
bool managedMarshalMethodsLookupEnabled;
jnienv_propagate_uncaught_exception_fn propagateUncaughtExceptionFn;
};

// Keep the enum values in sync with those in src/Mono.Android/AndroidRuntime/BoundExceptionType.cs
Expand Down
14 changes: 7 additions & 7 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,15 +213,9 @@ void Button_ViewTreeObserver_GlobalLayout (object sender, EventArgs e)
}, Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log"), 60), $"Output did not contain {expectedLogcatOutput}!");
}

// TODO: check if AppDomain.CurrentDomain.UnhandledException even works in CoreCLR
[Test]
public void SubscribeToAppDomainUnhandledException ([Values (AndroidRuntime.MonoVM, AndroidRuntime.CoreCLR)] AndroidRuntime runtime)
{
if (runtime == AndroidRuntime.CoreCLR) {
Assert.Ignore ("AppDomain.CurrentDomain.UnhandledException doesn't work in CoreCLR");
return;
}

proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
};
Expand All @@ -243,7 +237,13 @@ public void SubscribeToAppDomainUnhandledException ([Values (AndroidRuntime.Mono
Assert.IsTrue (builder.Install (proj), "Install should have succeeded.");
RunProjectAndAssert (proj, builder);

string expectedLogcatOutput = "# Unhandled Exception: sender=System.Object; e.IsTerminating=True; e.ExceptionObject=System.Exception: CRASH";
string? expectedSender = runtime switch
{
AndroidRuntime.MonoVM => "System.Object", // MonoVM passes the current domain as the sender
AndroidRuntime.CoreCLR => null, // CoreCLR explicitly passes a `null` sender
_ => throw new NotImplementedException($"Test does not support runtime {runtime}"),
};
string expectedLogcatOutput = $"# Unhandled Exception: sender={expectedSender}; e.IsTerminating=True; e.ExceptionObject=System.Exception: CRASH";
Assert.IsTrue (
MonitorAdbLogcat (CreateLineChecker (expectedLogcatOutput),
logcatFilePath: Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log"), timeout: 60),
Expand Down
Loading