Open jonpryor opened 2 years ago
…and about that mono crash? With .NET 6:
% dotnet new android -n android-hw-net6
% cat <<EOF | patch -p1
diff --git a/MainActivity.cs b/MainActivity.cs
index 3beae44..043b2b3 100644
--- a/MainActivity.cs
+++ b/MainActivity.cs
@@ -3,6 +3,11 @@ namespace android_hw_net6;
[Activity(Label = "@string/app_name", MainLauncher = true)]
public class MainActivity : Activity
{
+ public MainActivity()
+ {
+ var cursor = ContentResolver.Query(Android.Net.Uri.Parse("content://mms-sms/conversations/"), new string[] { "*" }, null, null, "date DESC");
+ }
+
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
EOF
% dotnet build -t:Install
% adb shell setprop debug.mono.log gref
% dotnet build -t:Run
Expected behavior: the expected Unhandled Exception
"crash".
Actual behavior: mono aborts!
E android_hw_net: * Assertion: should not be reached at /__w/1/s/src/mono/mono/mini/mini-exceptions.c:456
…
I crash_dump64: performing dump of process 10718 (target tid = 10718)
E DEBUG : failed to read /proc/uptime: Permission denied
F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
F DEBUG : Build fingerprint: 'google/raven/raven:12/SQ3A.220705.004/8836240:user/release-keys'
F DEBUG : Revision: 'MP1.0'
F DEBUG : ABI: 'arm64'
F DEBUG : Timestamp: 2022-08-31 14:36:02.523194803-0400
F DEBUG : Process uptime: 0s
F DEBUG : Cmdline: com.companyname.android_hw_net6
F DEBUG : pid: 10718, tid: 10718, name: android_hw_net6 >>> com.companyname.android_hw_net6 <<<
F DEBUG : uid: 10278
F DEBUG : tagged_addr_ctrl: 0000000000000001
F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
F DEBUG : x0 0000000000000000 x1 00000000000029de x2 0000000000000006 x3 0000007ff1f3abb0
F DEBUG : x4 67626064711f6461 x5 67626064711f6461 x6 67626064711f6461 x7 7f7f7f7f7f7f7f7f
F DEBUG : x8 00000000000000f0 x9 000000711b0a30b0 x10 0000000000000000 x11 ffffff80fffffbdf
F DEBUG : x12 0000000000000001 x13 0000000000000059 x14 0000007ff1f39a50 x15 00001c0ca925bd07
F DEBUG : x16 000000711b140050 x17 000000711b11dbd0 x18 000000711fdf2000 x19 00000000000029de
F DEBUG : x20 00000000000029de x21 00000000ffffffff x22 0000007ff1f3bee0 x23 0000007ff1f3b6e0
F DEBUG : x24 0000000000000000 x25 0000007ff1f3b9f0 x26 b400006fbdecb430 x27 0000000000000000
F DEBUG : x28 0000000000000000 x29 0000007ff1f3ac30
F DEBUG : lr 000000711b0d072c sp 0000007ff1f3ab90 pc 000000711b0d075c pst 0000000000001000
F DEBUG : backtrace:
F DEBUG : #00 pc 000000000004f75c /apex/com.android.runtime/lib64/bionic/libc.so (abort+168) (BuildId: 53a228529316d67f22e241dd17ea9b9e)
F DEBUG : #01 pc 00000000000309c0 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonodroid.so (xamarin::android::internal::MonodroidRuntime::mono_log_handler(char const*, char const*, char const*, int, void*)+144) (BuildId: 0615bbc115b094c7682d18118b1ea0c19b27ba97)
F DEBUG : #02 pc 0000000000264074 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #03 pc 00000000002641a0 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #04 pc 0000000000264200 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #05 pc 00000000001e5d7c /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #06 pc 00000000001e5b18 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #07 pc 00000000001e6de4 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #08 pc 00000000001e6b54 /data/app/~~-9u-MryjEfwWJIeVGn07vQ==/com.companyname.android_hw_net6-LAyawSCT5cLDklg8Q_cmjg==/lib/arm64/libmonosgen-2.0.so (BuildId: ca0b0b9194e7cf921e407ed4dc579808ea4e788a)
F DEBUG : #09 pc 0000000000006198 <anonymous:710cc77000>
Apparent assert location is: https://github.com/dotnet/runtime/blob/release/6.0/src/mono/mono/mini/mini-exceptions.c#L456
This doesn't appear to happen on .NET 7.
Assume you have an
Activity
subclass:At build time, a Java Callable Wrapper is generated.
The constructor of the Java Callable Wrapper calls
TypeManager.Activate()
: https://github.com/xamarin/xamarin-android/blob/7c9c24b3614710614c5512d7a3b8272065270dc2/src/Mono.Android/java/mono/android/TypeManager.java#L5-L8…which eventually invokes
TypeManager.Activate()
in C#: https://github.com/xamarin/xamarin-android/blob/7c9c24b3614710614c5512d7a3b8272065270dc2/src/Mono.Android/Java.Interop/TypeManager.cs#L172-L192What
TypeManager.Activate()
does is create the corresponding managed-side type, and then invoke the appropriate managed constructor on that instance. This is how when Android creates theMainActivity
Java Callable Wrapper, an instance of the C#MainActivity
type is created and the default constructor is invoked.What happens if the constructor throws an exception?
This hits the
catch
block inTypeManager.Activate()
: https://github.com/xamarin/xamarin-android/blob/7c9c24b3614710614c5512d7a3b8272065270dc2/src/Mono.Android/Java.Interop/TypeManager.cs#L184-L191which does two things:
NotSupportedException
.The log message is written to
adb logcat
:The
NotSupportedException
is visible within the debugger (when debugging the app), and/or is eventually written toadb logcat
as an unhandled exception:So far so reasonable. (It's worked this way for ages.)
But…is wrapping every exception in
NotSupportedException
actually reasonable?Consider this slight variation:
Here we are "mis-using" the Android API, as we shall see. The resulting unhandled exception is:
This is "mis-use" of the API because we're trying to use Android APIs before a base context has been applied; see the docs for the [
ContextThemeWrapper
default constructor](https://developer.android.com/reference/android/view/ContextThemeWrapper#ContextThemeWrapper()):Meaning no
Activity
members can be safely invoked from theMainActivity
constructor. (CallingActivity
members from theOnCreate()
method override is fine, just not the constructor.)OK, so far so "fine", but… is this really fine?
The "topmost" exception is
NotSupportedException
mentioning that the handle couldn't be activated. While correct, it is also misleading, because not all context is immediately available:You need to expand quite a bit to get to the "source"
NullPointerException
:Additionally, no Call Stack is available, because
TypeManager.Activate()
always catches all exceptions, instead of using a "debugger aware exception filter" (e.g. 32cff4383232d5de156bd6c5d10292fcffa66d50), so there is no easy way to know that the C# constructor is the source of the crash.Suggestions for improvement:
Logger.Log()
calls withinTypeManager.Activate()
also printe.ToString()
? (Why? In case of mono crash; see later comment.)TypeManager.Activate()
, so that if a debugger is attached, a "first chance exception" will bring the developer to the originalContentResolver.Query()
call site.NotSupportedException
? Or should "Java native" exceptions be passed through? Related question here is what Java consumers should see. ForActivity
, current behavior is fine, but if Java code had an expectation that constructors could throwjava.lang.IllegalArgumentException
, it is not possible to satisfy that requirement. A C# constructor throwingnew Java.Lang.IllegalArgumentException(…)
will raise aJavaProxyThrowable
to the calling Java code, always.