Skip to content

Commit b7a368a

Browse files
[Mono.Android] Marshal exceptions in RegisterNativeMembers() (#6672)
Context: dotnet/maui#4262 Context: #6675 If you run the `maui-blazor` template in a Release build: dotnet build -t:Run -c Release it crashes at runtime: D monodroid-assembly: typemap: type with token 33555274 (0x200034a) in module {C7B4CC8F-7A03-4A3F-A34A-DC66EDC548B9} (Mono.Android) corresponds to Java type 'android/runtime/JavaProxyThrowable' … F DEBUG : backtrace: F DEBUG : #00 pc 000000000065d8fc /apex/com.android.art/lib64/libart.so (void art::StackVisitor::WalkStack<(art::StackVisitor::CountTransitions)0>(bool)+156) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e) F DEBUG : #1 pc 000000000069b25d /apex/com.android.art/lib64/libart.so (art::Thread::GetCurrentMethod(unsigned int*, bool, bool) const+157) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e) F DEBUG : #2 pc 0000000000430fed /apex/com.android.art/lib64/libart.so (art::JNI<false>::FindClass(_JNIEnv*, char const*)+765) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e) F DEBUG : #3 pc 0000000000047e5a /data/app/~~0Qm6D1S0sO3f1lwfakN0PA==/com.companyname.mauiapp2-08UokVCH5k_PlbZEH_hhkA==/split_config.x86_64.apk!libmono-android.release.so (offset 0x11e000) (java_interop_jnienv_find_class+26) (BuildId: 3d04f8b946590175e97b89aee2e3b19ceed4b524) F DEBUG : #4 pc 00000000000128ac <anonymous:41640000> The crash can be avoided by disabling the linker: dotnet build -t:Run -c Release -p:AndroidLinkMode=None # -or- dotnet build -t:Run -c Release -p:PublishTrimmed=false However, let us return to the crash: *why* is it crashing? This isn't a "good debugging experience"; we have no useful context. Lots of investigation later -- all hail printf debugging -- and we found that the cause of the crash was an unhandled exception: 1. `Mono.Android.dll` has it's Java Callable Wrappers generated from the *unlinked* assembly, into `mono.android.jar` and `mono.android.dex` files. The Java Callable Wrapper for `Android.Runtime.InputStreamAdapter` thus includes *all* `Read()` method overloads. 2. When the app is built in Release configuration, linking is enabled, and *some* of the `InputStreamAdapter.Read()` methods are removed by the linker, along with the `Java.IO.InputStream.Read()` methods that were overridden. 3. At runtime, we perform [Java Type Registration][0] for the `Android.Runtime.InputStreamAdapter` type, which eventually calls `AndroidTypeManager.RegisterNativeMembers()`, which eventually attempts to *effectively* do: Delegate.CreateDelegate ( typeof(Func<Delegate>), typeof(InputStreamAdapter), "GetReadHandler"); 4. Because of (2), `Java.IO.InputStream.GetReadHandler()` *does not exist*, and thus `Delegate.CreateDelegate()` throws an `ArgumentException`. So far, so reasonable, but… 5. `AndroidTypeManager.RegisterNativeMembers()` didn't catch any exceptions, nor did any other method between the original Java `Runtime.register()` invocation and `AndroidTypeManager.RegisterNativeMembers()`. The result is that a C# exception was "in flight", and Mono then proceeded to *tear down the stack frame* as it unwound the callstack looking for `catch` handlers. At this point, the process is toast: the runtime stack is FUBAR. This is also why the `backtrace:` is "rooted" in `JNIEnv::FindClass()`: `JNIEnv::FindClass()` invokes Java static constructors before returning, which is how the static constructor in the Java Callable Wrapper for `InputStreamAdapter` called `Runtime.register()` in the first place. All of this makes for a miserable debugging experience. Fixing the "original" linker issue will be done in #6675. This hasn't been an issue in "Classic" Xamarin.Android, presumably because the classic linker isn't as good as the net6 linker. What we want to do *here* is improve this debugging experience, by "wrapping" `AndroidTypeManager.RegisterNativeMembers()` in a `try`/`catch` block, which can then *marshal the thrown exception* back to Java. This *prevents* Mono from unwinding the callstack past a JNI boundary, and avoids the annoying-to-debug app crash. After this change, we get a much friendlier unhandled exception crash: I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth I MonoDroid: at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean ) I MonoDroid: at System.Delegate.CreateDelegate(Type , Type , String ) I MonoDroid: at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String ) I MonoDroid: --- End of stack trace from previous location --- I MonoDroid: at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* ) I MonoDroid: at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* ) I MonoDroid: at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] ) I MonoDroid: at Android.Runtime.JNIEnv.FindClass(String ) I MonoDroid: at Android.Runtime.JNIEnv.AllocObject(String ) I MonoDroid: at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* ) I MonoDroid: at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] ) I MonoDroid: at Android.Runtime.InputStreamAdapter..ctor(Stream ) I MonoDroid: at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream ) I MonoDroid: at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream ) I MonoDroid: at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request) I MonoDroid: at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr ) I MonoDroid: at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method) I MonoDroid: at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39) I MonoDroid: at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16) I MonoDroid: at org.chromium.android_webview.AwContentsBackgroundThreadClient.shouldInterceptRequestFromNative(chromium-TrichromeWebViewGoogle.apk-stable-410410686:2) I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth I MonoDroid: at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean ) I MonoDroid: at System.Delegate.CreateDelegate(Type , Type , String ) I MonoDroid: at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String ) I MonoDroid: --- End of stack trace from previous location --- I MonoDroid: at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* ) I MonoDroid: at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* ) I MonoDroid: at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] ) I MonoDroid: at Android.Runtime.JNIEnv.FindClass(String ) I MonoDroid: at Android.Runtime.JNIEnv.AllocObject(String ) I MonoDroid: at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* ) I MonoDroid: at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] ) I MonoDroid: at Android.Runtime.InputStreamAdapter..ctor(Stream ) I MonoDroid: at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream ) I MonoDroid: at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream ) I MonoDroid: at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request) I MonoDroid: at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr ) I MonoDroid: at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method) I MonoDroid: at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39) I MonoDroid: at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16) I MonoDroid: at org.chromium.android_webview.AwContentsBackgroundThreadClient.shouldInterceptRequestFromNative(chromium-TrichromeWebViewGoogle.apk-stable-410410686:2) This is much easier to reason about, and will save us time in the future. [0]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
1 parent f8ad887 commit b7a368a

File tree

1 file changed

+48
-44
lines changed

1 file changed

+48
-44
lines changed

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -378,57 +378,61 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu
378378

379379
public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods)
380380
{
381-
if (FastRegisterNativeMembers (nativeClass, type, methods))
382-
return;
381+
try {
382+
if (FastRegisterNativeMembers (nativeClass, type, methods))
383+
return;
383384

384-
if (string.IsNullOrEmpty (methods)) {
385-
if (jniAddNativeMethodRegistrationAttributePresent)
386-
base.RegisterNativeMembers (nativeClass, type, methods);
387-
return;
388-
}
385+
if (string.IsNullOrEmpty (methods)) {
386+
if (jniAddNativeMethodRegistrationAttributePresent)
387+
base.RegisterNativeMembers (nativeClass, type, methods);
388+
return;
389+
}
389390

390-
string[] members = methods!.Split ('\n');
391-
if (members.Length < 2) {
392-
if (jniAddNativeMethodRegistrationAttributePresent)
393-
base.RegisterNativeMembers (nativeClass, type, methods);
394-
return;
395-
}
391+
string[] members = methods!.Split ('\n');
392+
if (members.Length < 2) {
393+
if (jniAddNativeMethodRegistrationAttributePresent)
394+
base.RegisterNativeMembers (nativeClass, type, methods);
395+
return;
396+
}
396397

397-
JniNativeMethodRegistration[] natives = new JniNativeMethodRegistration [members.Length-1];
398-
for (int i = 0; i < members.Length; ++i) {
399-
string method = members [i];
400-
if (string.IsNullOrEmpty (method))
401-
continue;
402-
string[] toks = members [i].Split (new[]{':'}, 4);
403-
Delegate callback;
404-
if (toks [2] == "__export__") {
405-
var mname = toks [0].Substring (2);
406-
MethodInfo? minfo = null;
407-
foreach (var mi in type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
408-
if (mi.Name == mname && JavaNativeTypeManager.GetJniSignature (mi) == toks [1]) {
409-
minfo = mi;
410-
break;
398+
JniNativeMethodRegistration[] natives = new JniNativeMethodRegistration [members.Length-1];
399+
for (int i = 0; i < members.Length; ++i) {
400+
string method = members [i];
401+
if (string.IsNullOrEmpty (method))
402+
continue;
403+
string[] toks = members [i].Split (new[]{':'}, 4);
404+
Delegate callback;
405+
if (toks [2] == "__export__") {
406+
var mname = toks [0].Substring (2);
407+
MethodInfo? minfo = null;
408+
foreach (var mi in type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
409+
if (mi.Name == mname && JavaNativeTypeManager.GetJniSignature (mi) == toks [1]) {
410+
minfo = mi;
411+
break;
412+
}
413+
414+
if (minfo == null)
415+
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname, toks [1]));
416+
callback = CreateDynamicCallback (minfo);
417+
} else {
418+
Type callbackDeclaringType = type;
419+
if (toks.Length == 4) {
420+
callbackDeclaringType = Type.GetType (toks [3], throwOnError: true)!;
411421
}
412-
413-
if (minfo == null)
414-
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname, toks [1]));
415-
callback = CreateDynamicCallback (minfo);
416-
} else {
417-
Type callbackDeclaringType = type;
418-
if (toks.Length == 4) {
419-
callbackDeclaringType = Type.GetType (toks [3], throwOnError: true)!;
420-
}
421-
while (callbackDeclaringType.ContainsGenericParameters) {
422-
callbackDeclaringType = callbackDeclaringType.BaseType!;
422+
while (callbackDeclaringType.ContainsGenericParameters) {
423+
callbackDeclaringType = callbackDeclaringType.BaseType!;
424+
}
425+
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
426+
callbackDeclaringType, toks [2]);
427+
callback = connector ();
423428
}
424-
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
425-
callbackDeclaringType, toks [2]);
426-
callback = connector ();
429+
natives [i] = new JniNativeMethodRegistration (toks [0], toks [1], callback);
427430
}
428-
natives [i] = new JniNativeMethodRegistration (toks [0], toks [1], callback);
429-
}
430431

431-
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length);
432+
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length);
433+
} catch (Exception e) {
434+
JniEnvironment.Runtime.RaisePendingException (e);
435+
}
432436
}
433437
}
434438

0 commit comments

Comments
 (0)