Skip to content

[Mono.Android] handle exceptions in RegisterNativeMembers #6672

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged

Conversation

jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Jan 26, 2022

Context: dotnet/maui#4262

dotnet new maui-blazor crashes at runtime, if you do:

dotnet build -t:Run -c Release

You can turn off the linker to solve this issue:

dotnet build -t:Run -c Release -p:AndroidLinkMode=None

(or PublishTrimmed=false)

The app crashes in way that you get a native crash:

backtrace:
#00 pc 000000000065d8fc  /apex/com.android.art/lib64/libart.so (void art::StackVisitor::WalkStack<(art::StackVisitor::CountTransitions)0>(bool)+156) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
#01 pc 000000000069b25d  /apex/com.android.art/lib64/libart.so (art::Thread::GetCurrentMethod(unsigned int*, bool, bool) const+157) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
#02 pc 0000000000430fed  /apex/com.android.art/lib64/libart.so (art::JNI<false>::FindClass(_JNIEnv*, char const*)+765) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
#03 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)
#04 pc 00000000000128ac  <anonymous:41640000>

After much investigation... We found that
Java.IO.InputStream.GetReadHandler() was linked away.

This is because:

  1. Mono.Android.dll has it's Java stubs precompiled into
    mono.android.jar and mono.android.dex.

  2. Since Mono.Android.dll doesn't go through GenerateJavaStubs,
    the linker has no way to know if the missing method is used -- it
    is only called from Java in the crashing app.

To make the crash better, we need to try-catch all exceptions in
RegisterNativeMembers() and pass them to
AndroidRuntime.RaisePendingException(). This is because
RegisterNativeMembers() is called directly from Java.

Now the crash shows:

01-26 12:20:20.290 29264 29264 I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
01-26 12:20:20.290 29264 29264 I MonoDroid:
01-26 12:20:20.290 29264 29264 I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
01-26 12:20:20.290 29264 29264 I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth
01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String )
01-26 12:20:20.290 29264 29264 I MonoDroid: --- End of stack trace from previous location ---
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.FindClass(String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.AllocObject(String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter..ctor(Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request)
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr )
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method)
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39)
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16)
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at org.chromium.android_webview.AwContentsBackgroundThreadClient.shouldInterceptRequestFromNative(chromium-TrichromeWebViewGoogle.apk-stable-410410686:2)
01-26 12:20:20.290 29264 29264 I MonoDroid:
01-26 12:20:20.290 29264 29264 I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
01-26 12:20:20.290 29264 29264 I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth
01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String )
01-26 12:20:20.290 29264 29264 I MonoDroid: --- End of stack trace from previous location ---
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.FindClass(String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.AllocObject(String )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter..ctor(Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream )
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request)
01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr )
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method)
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39)
01-26 12:20:20.290 29264 29264 I MonoDroid: 	at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16)
01-26 12:20:20.290 29264 29264 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.

A fix to the actual linker problem will be coming in another PR.

Context: dotnet/maui#4262

`dotnet new maui-blazor` crashes at runtime, if you do:

    dotnet build -t:Run -c Release

You can turn *off* the linker to solve this issue:

    dotnet build -t:Run -c Release -p:AndroidLinkMode=None

(or `PublishTrimmed=false`)

The app crashes in way that you get a native crash:

    backtrace:
    #00 pc 000000000065d8fc  /apex/com.android.art/lib64/libart.so (void art::StackVisitor::WalkStack<(art::StackVisitor::CountTransitions)0>(bool)+156) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
    #1 pc 000000000069b25d  /apex/com.android.art/lib64/libart.so (art::Thread::GetCurrentMethod(unsigned int*, bool, bool) const+157) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
    #2 pc 0000000000430fed  /apex/com.android.art/lib64/libart.so (art::JNI<false>::FindClass(_JNIEnv*, char const*)+765) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
    #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)
    #4 pc 00000000000128ac  <anonymous:41640000>

After much investigation... We found that
`Java.IO.InputStreamHandler.GetReadHandler()` was linked away.

This is because:

1. `Mono.Android.dll` has it's Java stubs precompiled into
   `mono.android.jar` and `mono.android.dex`.

2. Since `Mono.Android.dll` doesn't go through `GenerateJavaStubs`,
   the linker has no way to know if the missing method is used -- it
   is only called from Java in the crashing app.

To make the crash better, we need to `try-catch` *all* exceptions in
`RegisterNativeMembers()` and pass them to
`AndroidRuntime.RaisePendingException()`. This is because
`RegisterNativeMembers()` is called directly from Java.

Now the crash shows:

    01-26 12:20:20.290 29264 29264 I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
    01-26 12:20:20.290 29264 29264 I MonoDroid:
    01-26 12:20:20.290 29264 29264 I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
    01-26 12:20:20.290 29264 29264 I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String )
    01-26 12:20:20.290 29264 29264 I MonoDroid: --- End of stack trace from previous location ---
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.FindClass(String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.AllocObject(String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter..ctor(Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request)
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr )
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method)
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39)
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16)
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at org.chromium.android_webview.AwContentsBackgroundThreadClient.shouldInterceptRequestFromNative(chromium-TrichromeWebViewGoogle.apk-stable-410410686:2)
    01-26 12:20:20.290 29264 29264 I MonoDroid:
    01-26 12:20:20.290 29264 29264 I MonoDroid:   --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
    01-26 12:20:20.290 29264 29264 I MonoDroid: android.runtime.JavaProxyThrowable: System.ArgumentException: Arg_DlgtTargMeth
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String , Boolean , Boolean )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at System.Delegate.CreateDelegate(Type , Type , String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType , Type , String )
    01-26 12:20:20.290 29264 29264 I MonoDroid: --- End of stack trace from previous location ---
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference , JniMethodInfo , JniArgumentValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr , IntPtr , JValue[] )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.FindClass(String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.AllocObject(String )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue* )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.JNIEnv.StartCreateInstance(String , String , JValue[] )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter..ctor(Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Runtime.InputStreamAdapter.ToLocalJniHandle(Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebResourceResponse..ctor(String , String , Int32 , String , IDictionary`2 , Stream )
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Microsoft.AspNetCore.Components.WebView.Maui.WebKitWebViewClient.ShouldInterceptRequest(WebView view, IWebResourceRequest request)
    01-26 12:20:20.290 29264 29264 I MonoDroid:    at Android.Webkit.WebViewClient.n_ShouldInterceptRequest_Landroid_webkit_WebView_Landroid_webkit_WebResourceRequest_(IntPtr , IntPtr , IntPtr , IntPtr )
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.n_shouldInterceptRequest(Native Method)
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at crc64d693e2d9159537db.WebKitWebViewClient.shouldInterceptRequest(WebKitWebViewClient.java:39)
    01-26 12:20:20.290 29264 29264 I MonoDroid: 	at Rr.a(chromium-TrichromeWebViewGoogle.apk-stable-410410686:16)
    01-26 12:20:20.290 29264 29264 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.

A fix to the actual linker problem will be coming in another PR.
@jonathanpeppers
Copy link
Member Author

The diff is easier to view if you hide whitespace:

https://github.com/xamarin/xamarin-android/pull/6672/files?w=1

@jonpryor
Copy link
Member

Context: https://github.com/dotnet/maui/issues/4262
Context: https://github.com/xamarin/xamarin-android/pull/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   : #01 pc 000000000069b25d  /apex/com.android.art/lib64/libart.so (art::Thread::GetCurrentMethod(unsigned int*, bool, bool) const+157) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
	F DEBUG   : #02 pc 0000000000430fed  /apex/com.android.art/lib64/libart.so (art::JNI<false>::FindClass(_JNIEnv*, char const*)+765) (BuildId: 7fbaf2a1a3317bd634b00eb90e32291e)
	F DEBUG   : #03 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   : #04 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
xamarin/xamarin-android#6675.

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

@jonpryor jonpryor merged commit b7a368a into dotnet:main Jan 27, 2022
jonathanpeppers added a commit that referenced this pull request Jan 27, 2022
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
@jonathanpeppers jonathanpeppers deleted the registernativemembers-handleexceptions branch January 27, 2022 20:35
@github-actions github-actions bot locked and limited conversation to collaborators Jan 24, 2024
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants