Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Starting in Xamarin.Android 10.3, "System.MemberAccessException: Cannot create an instance of ... `1[T]" in Release configuration if JavaTypeScanner.GetJavaTypes() happens to list a generic subclass of a binding before the binding itself #4660

Closed
brendanzagaeski opened this issue May 7, 2020 · 4 comments · Fixed by #4656
Labels
Area: App Runtime Issues in `libmonodroid.so`. regression

Comments

@brendanzagaeski
Copy link
Contributor

brendanzagaeski commented May 7, 2020

Context: #4596 (comment)

Steps to reproduce

  1. Download and unzip the attached test case.
  2. Ensure that a hardware device is attached and that adb is running.
  3. msbuild -restore -p:Configuration=Release -t:Install AndroidSingleViewApp1\AndroidSingleViewApp1.csproj
    
  4. Launch the app by tapping the app icon on the target device.

Test case: JavaToManagedDuplicatesRelease.zip

Test case details

The test case uses a bindings library for a single Java class. The original Java class is defined as:

package com.contoso.javalibrary1;

public class Class1 {
    public static Class1 Create()
    {
        return new Class1();
    }
}

The library adds a generic subclass of the binding in C#:

[Register("com/contoso/javalibrary1/Class1", DoNotGenerateAcw = true)]
public partial class Class0<T> : Class1
{
    public Class0(IntPtr handle, JniHandleOwnership transfer)
        : base(handle, transfer)
    {
    }
}

This subclass intentionally uses 0 in the name to try to make JavaTypeScanner.GetJavaTypes() list it first.

Expected behavior

When built with Xamarin.Android 10.2, the application launches successfully and displays Hello World! in the center of the view.

Actual behavior

When built with Xamarin.Android 10.3, the application exits during launch:

FATAL EXCEPTION: main
Process: com.companyname.androidsingleviewapp1, PID: 24068
android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00054] in <78d6f78df71a498abab079fd455ad967>:0
  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <78d6f78df71a498abab079fd455ad967>:0
  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters) [0x00000] in <78d6f78df71a498abab079fd455ad967>:0
  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x0001b] in <2ccd59583214493fb4df51106833c4d9>:0
  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType) [0x00111] in <2ccd59583214493fb4df51106833c4d9>:0
  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type) [0x00023] in <2ccd59583214493fb4df51106833c4d9>:0
  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x00017] in <2ccd59583214493fb4df51106833c4d9>:0
  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) [0x00000] in <2ccd59583214493fb4df51106833c4d9>:0
  at Com.Contoso.Javalibrary1.Class1.Create () [0x0001e] in <8dad429b6bb14f3ab7952159824bc769>:0
  at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState) [0x00012] in <dc89185e1acd4e22bcef03b0baa45e56>:0
  at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState) [0x0000f] in <2ccd59583214493fb4df51106833c4d9>:0
  at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
       at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
       at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
       at android.app.Activity.performCreate(Activity.java:7144)
       at android.app.Activity.performCreate(Activity.java:7135)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6718)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Preliminary investigation

It appears the issue is that JavaTypeScanner.GetJavaTypes() returns Class0<T> earlier in the list than Class1 during <GenerateJavaStubs/>:

https://github.com/xamarin/xamarin-android/blob/0fe28fb28c403b6c4addce2e9b728f077128c48f/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs#L173

Here's an excerpt of the values I see in allJavaTypes in my local debugging session:

[198]	{Com.Contoso.Javalibrary1.Class0`1}	Mono.Cecil.TypeDefinition
[199]	{Com.Contoso.Javalibrary1.BuildConfig}	Mono.Cecil.TypeDefinition
[200]	{Com.Contoso.Javalibrary1.Class1}	Mono.Cecil.TypeDefinition

The types are passed in this same order to TypeMapGenerator.GenerateRelease(), so the first match is added to moduleData.TypesScratch, while any other matches are added to moduleData.DuplicateTypes:

https://github.com/xamarin/xamarin-android/blob/0fe28fb28c403b6c4addce2e9b728f077128c48f/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs#L343-L350

Eventually this means that TypeMappingReleaseNativeAssemblyGenerator.WriteJavaMap() writes the MetadataToken of Class0<T> into typemaps.arm64-v8a.s instead of the MetadataToken of Class1

Version information

  • Xamarin.Android SDK 10.3.0.80 (d16-6/0fe28fb)
    • Java.Interop: xamarin/java.interop/d16-6@2cab35c

The issue also occurs when using the fixes from the candidate .vsix installer for #4656:

  • Xamarin.Android SDK 10.4.100.6 (remotes/pull/4656/merge/5266fab)
    • Java.Interop: xamarin/java.interop/master@377c4c7

This is expected because the changes in #4656 were only targeted for Debug configuration builds. (And indeed those fixes seemed to be effective for the DrawableTinting sample in the Debug configuration in my local environment.)

VS bug #1119273

@brendanzagaeski brendanzagaeski added Area: App Runtime Issues in `libmonodroid.so`. regression labels May 7, 2020
@brendanzagaeski brendanzagaeski added this to the d16-6 milestone May 7, 2020
@grendello
Copy link
Contributor

grendello commented May 7, 2020

Good test case @brendanzagaeski, thanks! However, the issue here is not one of duplication but rather of an open generic type being placed in the map, only to attempt to instantiate it later during the runtime and throw the exception you show. The fix here would be to go up the inheritance tree and put Class1 MVID:TokenID in the map for the open generic type instead. @jonpryor, would that be the correct course of action here?

@grendello
Copy link
Contributor

The old typemap generator from Java.Interop skips types with generic parameters altogether. I'll update #4656 to do the same.

grendello added a commit to grendello/xamarin-android that referenced this issue May 7, 2020
Fixes: dotnet#4596
Fixes: dotnet#4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when  using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three* bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

  java/util/ArrayList
  Android.Runtime.JavaList, Mono.Android
  java/util/ArrayList
  Android.Runtime.JavaList`1, Mono.Android
  java/util/ArrayList
  Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks` so that
all the duplicate Java type names will point to the same managed type
name.  Additionally, make sure we select the managed type in the same
fashion the old typemap generator in `Java.Interop` worked - prefer base
types to the derived ones if the type is an interface or an abstract
class.  The effect of this change is that no matter which entry
`binary_search` ends up selecting it will always return the same managed
type name for all the aliased Java types.

~~ Release mode ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

   FATAL EXCEPTION: main
    Process: com.companyname.androidsingleviewapp1, PID: 24068
    android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
      at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
      at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
      at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
      at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
      at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
      at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
      at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
      at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
      at Com.Contoso.Javalibrary1.Class1.Create ()
      at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
      at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
      at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
           at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
           at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
           at android.app.Activity.performCreate(Activity.java:7144)
           at android.app.Activity.performCreate(Activity.java:7135)
           at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
           at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
           at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
           at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
           at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
           at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
           at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
           at android.os.Handler.dispatchMessage(Handler.java:106)
           at android.os.Looper.loop(Looper.java:193)
           at android.app.ActivityThread.main(ActivityThread.java:6718)
           at java.lang.reflect.Method.invoke(Native Method)
           at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode. We
must not simply skip outputting entries for such types because we need
to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types is
to output a type token ID of `0` instead of the actual one.  This will
cause the runtime code to fail to find a mapping, thus allowing the
managed code to deal with such a type properly.

Additionally, update `TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
grendello added a commit to grendello/xamarin-android that referenced this issue May 7, 2020
Fixes: dotnet#4596
Fixes: dotnet#4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when  using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three* bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

  java/util/ArrayList
  Android.Runtime.JavaList, Mono.Android
  java/util/ArrayList
  Android.Runtime.JavaList`1, Mono.Android
  java/util/ArrayList
  Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks` so that
all the duplicate Java type names will point to the same managed type
name.  Additionally, make sure we select the managed type in the same
fashion the old typemap generator in `Java.Interop` worked - prefer base
types to the derived ones if the type is an interface or an abstract
class.  The effect of this change is that no matter which entry
`binary_search` ends up selecting it will always return the same managed
type name for all the aliased Java types.

~~ Release mode ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode. We
must not simply skip outputting entries for such types because we need
to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types is
to output a type token ID of `0` instead of the actual one.  This will
cause the runtime code to fail to find a mapping, thus allowing the
managed code to deal with such a type properly.

Additionally, update `TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
@jonpryor jonpryor added the vs-sync For internal use only; creates a VSTS "mirror" issue. label May 7, 2020
jonpryor added a commit to grendello/xamarin-android that referenced this issue May 7, 2020
Context: dotnet#4660

Add a unit test for Issue dotnet#4660.

It's very cute. :-)

We sort the Java-to-managed type name table, so it can be binary
searched to find the Managed type.

Additionally, the "preferred" managed type is the *first* type in
`monodis --typedef` output; the first type that we encounter when
going through all types in an assembly.

Brenden's cute way of provoking the bug is to add a new *generic* type
which will come *before* the "normally preferred" (and correct!) type.
Thus, even if/when binary search returns the first entry, that'll
still be the *wrong entry*, because that'll be a generic type!

Very cute.
grendello added a commit to grendello/xamarin-android that referenced this issue May 7, 2020
Fixes: dotnet#4596
Fixes: dotnet#4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when  using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three* bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

  java/util/ArrayList
  Android.Runtime.JavaList, Mono.Android
  java/util/ArrayList
  Android.Runtime.JavaList`1, Mono.Android
  java/util/ArrayList
  Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks` so that
all the duplicate Java type names will point to the same managed type
name.  Additionally, make sure we select the managed type in the same
fashion the old typemap generator in `Java.Interop` worked - prefer base
types to the derived ones if the type is an interface or an abstract
class.  The effect of this change is that no matter which entry
`binary_search` ends up selecting it will always return the same managed
type name for all the aliased Java types.

~~ Release mode ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode. We
must not simply skip outputting entries for such types because we need
to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types is
to output a type token ID of `0` instead of the actual one.  This will
cause the runtime code to fail to find a mapping, thus allowing the
managed code to deal with such a type properly.

~~ Test Updates ~~

Update `TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

Add a unit test for issue dotnet#4660 (by @brendanzagaeski)

It's very cute. :-)

We sort the Java-to-managed type name table, so it can be binary
searched to find the Managed type.

Additionally, the "preferred" managed type is the *first* type in
`monodis --typedef` output; the first type that we encounter when
going through all types in an assembly.

Brendan's cute way of provoking the bug is to add a new *generic* type
which will come *before* the "normally preferred" (and correct!) type.
Thus, even if/when binary search returns the first entry, that'll
still be the *wrong entry*, because that'll be a generic type!

Very cute.

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
grendello added a commit to grendello/xamarin-android that referenced this issue May 8, 2020
Fixes: dotnet#4596
Fixes: dotnet#4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when  using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three* bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

  java/util/ArrayList
  Android.Runtime.JavaList, Mono.Android
  java/util/ArrayList
  Android.Runtime.JavaList`1, Mono.Android
  java/util/ArrayList
  Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks` so that
all the duplicate Java type names will point to the same managed type
name.  Additionally, make sure we select the managed type in the same
fashion the old typemap generator in `Java.Interop` worked - prefer base
types to the derived ones if the type is an interface or an abstract
class.  The effect of this change is that no matter which entry
`binary_search` ends up selecting it will always return the same managed
type name for all the aliased Java types.

~~ Release mode ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode. We
must not simply skip outputting entries for such types because we need
to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types is
to output a type token ID of `0` instead of the actual one.  This will
cause the runtime code to fail to find a mapping, thus allowing the
managed code to deal with such a type properly.

~~ Test Updates ~~

Update `TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

Add a unit test for issue dotnet#4660 (by @brendanzagaeski)

It's very cute. :-)

We sort the Java-to-managed type name table, so it can be binary
searched to find the Managed type.

Additionally, the "preferred" managed type is the *first* type in
`monodis --typedef` output; the first type that we encounter when
going through all types in an assembly.

Brendan's cute way of provoking the bug is to add a new *generic* type
which will come *before* the "normally preferred" (and correct!) type.
Thus, even if/when binary search returns the first entry, that'll
still be the *wrong entry*, because that'll be a generic type!

Very cute.

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
jonpryor pushed a commit that referenced this issue May 8, 2020
Fixes: #4596
Fixes: #4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three*
bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

	java/util/ArrayList
	Android.Runtime.JavaList, Mono.Android
	java/util/ArrayList
	Android.Runtime.JavaList`1, Mono.Android
	java/util/ArrayList
	Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks.dll` so
that all the duplicate Java type names will point to the same managed
type name.  Additionally, make sure we select the managed type in the
same fashion the old typemap generator in `Java.Interop` worked: prefer
base types to the derived ones if the type is an interface or an
abstract class.  The effect of this change is that no matter which
entry `EmbeddedAssemblies::binary_search()` ends up selecting it will
always return the same managed type name for all aliased Java types.


~~ Release config apps ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode.
We must not simply skip outputting entries for such types because we
need to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types
is to output a type token ID of `0` instead of the actual one.  This
will cause the runtime code to fail to find a mapping, thus allowing
the managed code to deal with such a type properly.


~~ Test Updates ~~

Update `BundleTest.TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

Add a unit test for the Release config app bug in Issue #4660; thanks
to @brendanzagaeski.  It works by provoking the binary search bug by
"ensuring" that a *generic* type comes *before* the "normally
preferred" *non*-generic type.  Thus, even if/when binary search
returns the first entry, that would still be the *wrong entry*,
because that'll be a generic type!

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
jonpryor pushed a commit that referenced this issue May 8, 2020
Fixes: #4596
Fixes: #4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three*
bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

	java/util/ArrayList
	Android.Runtime.JavaList, Mono.Android
	java/util/ArrayList
	Android.Runtime.JavaList`1, Mono.Android
	java/util/ArrayList
	Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks.dll` so
that all the duplicate Java type names will point to the same managed
type name.  Additionally, make sure we select the managed type in the
same fashion the old typemap generator in `Java.Interop` worked: prefer
base types to the derived ones if the type is an interface or an
abstract class.  The effect of this change is that no matter which
entry `EmbeddedAssemblies::binary_search()` ends up selecting it will
always return the same managed type name for all aliased Java types.


~~ Release config apps ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode.
We must not simply skip outputting entries for such types because we
need to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types
is to output a type token ID of `0` instead of the actual one.  This
will cause the runtime code to fail to find a mapping, thus allowing
the managed code to deal with such a type properly.


~~ Test Updates ~~

Update `BundleTest.TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

Add a unit test for the Release config app bug in Issue #4660; thanks
to @brendanzagaeski.  It works by provoking the binary search bug by
"ensuring" that a *generic* type comes *before* the "normally
preferred" *non*-generic type.  Thus, even if/when binary search
returns the first entry, that would still be the *wrong entry*,
because that'll be a generic type!

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
jonpryor pushed a commit that referenced this issue May 8, 2020
Fixes: #4596
Fixes: #4660
Context: xamarin/monodroid@192b1f1
Context: https://github.com/xamarin/java.interop/blob/fc18c54b2ccf14f444fadac0a1e7e81f837cc470/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L149-L186
Context: https://github.com/xamarin/java.interop/blob/010161d1ef6625b762429334f02e7b15e1f7caae/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGenerator.cs#L155

~~ Debug Mode ~~

In some environments, `BundleTest.TestBundleIntegerArrayList2()` fails
when using the commercial shared runtime:

	Test 'Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2' failed: System.MemberAccessException : Cannot create an instance of Android.Runtime.JavaList`1[T] because Type.ContainsGenericParameters is true.
	  at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	  at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	  at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	  at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	  at Android.OS.Bundle.Get (System.String key)
	  at Xamarin.Android.RuntimeTests.BundleTest.TestBundleIntegerArrayList2 ()
	  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
	  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

The problem appears to be rooted in ce2bc68.  In a pre-ce2bc689 world,
there were two separate sets of "typemap" files that the application
could use:

  * Java-to-managed mappings, in `typemap.jm`
  * Managed-to-Java mappings, in `typemap.mj`.

These files did *not* need to have the same number of entries!

	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.jm
	% unzip /Library/Frameworks/Xamarin.Android.framework/Versions/10.2.0.100/lib/xamarin.android/xbuild/Xamarin/Android/Mono.Android.DebugRuntime-debug.apk typemap.mj
	% strings typemap.jm | wc -l
	   10318
	% strings typemap.mj | wc -l
	   11956

The reason why they have a different number of entries is *aliasing*:
there can be *multiple* bindings for a given Java type.  The
managed-to-Java mappings can contain *all* of them; the
Java-to-managed mapping *must* pick Only One.

For example, [`java.util.ArrayList`][0] contains (at least?) *three*
bindings:

  * [`Android.Runtime.JavaList`][1]
  * [`Android.Runtime.JavaList<T>`][2]
  * `Java.Util.ArrayList` (generated at build time)

In a pre-ce2bc689 world, this was "fine": we'd binary search each file
*separately* to find type mappings.

This changed in ce2bc68, as the typemap information was merged into a
*single* array in order to de-duplicate information.  This reduced
`.apk` size.

An unanticipated result of this is that the Java-to-managed mappings
can now contain *duplicate* keys, "as if" the original `typemap.jm`
had the contents:

	java/util/ArrayList
	Android.Runtime.JavaList, Mono.Android
	java/util/ArrayList
	Android.Runtime.JavaList`1, Mono.Android
	java/util/ArrayList
	Java.Util.ArrayLlist, Mono.Android

Whereas pre-ce2bc689 there would have only been the first entry.

"Sometimes" this duplication is "fine": the typemap search
"Just happens" to return the first entry, or the 3rd entry, and apps
continue to work as intended.

"Sometimes" it isn't: the typemap binary search finds the 2nd entry,
which is a generic type.  This results in attempting to instantiate a
generic type *without appropriate type parameters*, which results in
the aforementioned `MemberAccessException`.

Update typemap generation code in `Xamarin.Android.Build.Tasks.dll` so
that all the duplicate Java type names will point to the same managed
type name.  Additionally, make sure we select the managed type in the
same fashion the old typemap generator in `Java.Interop` worked: prefer
base types to the derived ones if the type is an interface or an
abstract class.  The effect of this change is that no matter which
entry `EmbeddedAssemblies::binary_search()` ends up selecting it will
always return the same managed type name for all aliased Java types.


~~ Release config apps ~~

Another issue introduced in ce2bc68 is that we no longer ignore
interface and generic types when generating the Java-to-managed maps.
This adversely affects the `Release` mode in which it is possible that
an attempt to instantiate an open generic type (or an interface) will
happen, leading to error similar to:

	FATAL EXCEPTION: main
	  Process: com.companyname.androidsingleviewapp1, PID: 24068
	  android.runtime.JavaProxyThrowable: System.MemberAccessException: Cannot create an instance of Com.Contoso.Javalibrary1.Class0`1[T] because Type.ContainsGenericParameters is true.
	    at System.Reflection.RuntimeConstructorInfo.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.RuntimeConstructorInfo.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
	    at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters)
	    at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType)
	    at Java.Lang.Object.GetObject (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type type)
	    at Java.Lang.Object._GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Java.Lang.Object.GetObject[T] (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer)
	    at Com.Contoso.Javalibrary1.Class1.Create ()
	    at AndroidSingleViewApp1.MainActivity.OnCreate (Android.OS.Bundle savedInstanceState)
	    at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_savedInstanceState)
	    at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.1(intptr,intptr,intptr)
	         at crc647d7dcab8da8ee782.MainActivity.n_onCreate(Native Method)
	         at crc647d7dcab8da8ee782.MainActivity.onCreate(MainActivity.java:29)
	         at android.app.Activity.performCreate(Activity.java:7144)
	         at android.app.Activity.performCreate(Activity.java:7135)
	         at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
	         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2931)
	         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
	         at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
	         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	         at android.os.Handler.dispatchMessage(Handler.java:106)
	         at android.os.Looper.loop(Looper.java:193)
	         at android.app.ActivityThread.main(ActivityThread.java:6718)
	         at java.lang.reflect.Method.invoke(Native Method)
	         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Update typemap generation code to also ignore generic types and
interfaces when generating the Java to Managed map in `Release` mode.
We must not simply skip outputting entries for such types because we
need to maintain element count parity between java-to-managed and
managed-to-java lookup tables.  Therefore, the way we skip such types
is to output a type token ID of `0` instead of the actual one.  This
will cause the runtime code to fail to find a mapping, thus allowing
the managed code to deal with such a type properly.


~~ Test Updates ~~

Update `BundleTest.TestBundleIntegerArrayList2()` so that it asserts
the runtime *type* of `obj` returned, asserting that it is in fact a
`JavaList` and not e.g. `Java.Util.ArrayList` or something.
(The linker could otherwise "change" things, and this assertion will
ensure that `JavaList` won't be linked away…)

Add a unit test for the Release config app bug in Issue #4660; thanks
to @brendanzagaeski.  It works by provoking the binary search bug by
"ensuring" that a *generic* type comes *before* the "normally
preferred" *non*-generic type.  Thus, even if/when binary search
returns the first entry, that would still be the *wrong entry*,
because that'll be a generic type!

[0]: https://developer.android.com/reference/java/util/ArrayList
[1]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L11-L13
[2]: https://github.com/xamarin/xamarin-android/blob/61f09bf2ef0c8f09550e2d77eb97f417432b315b/src/Mono.Android/Android.Runtime/JavaList.cs#L667-L668
@brendanzagaeski
Copy link
Contributor Author

Release status update

A Preview version of Xamarin.Android has now been published on macOS that includes the fix for this item.

The fix is not yet available on Windows. I will update this item again when a version with the fix is available on Windows.

Fix included in Xamarin.Android 10.3.1.0.

Fix included on macOS in Visual Studio 2019 for Mac version 8.6 Preview 6. To try the Preview version that includes the fix, check for the latest updates on the Preview updater channel.

Fix not yet available on Windows.

@brendanzagaeski
Copy link
Contributor Author

Release status update

A new Release version of Xamarin.Android has now been published on both Windows and macOS that includes the fix for this item.

Fix included in Xamarin.Android 10.3.1.0.

Fix included on Windows in Visual Studio 2019 version 16.6. To get the new version that includes the fix, check for the latest updates or install the latest version from https://visualstudio.microsoft.com/downloads/.

Fix included on macOS in Visual Studio 2019 for Mac version 8.6. To get the new version that includes the fix, check for the latest updates on the Stable updater channel.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: App Runtime Issues in `libmonodroid.so`. regression
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants