Skip to content

Commit a017561

Browse files
authored
[typemaps] Handle Java to managed type maps properly (#4656)
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
1 parent 314e48b commit a017561

File tree

9 files changed

+118
-21
lines changed

9 files changed

+118
-21
lines changed

src/Mono.Android/Test/Android.OS/BundleTest.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public void TestBundleIntegerArrayList()
1919
Assert.NotNull (list, "'key' doesn't refer to a list of integers");
2020
var obj = b.Get ("key");
2121
Assert.NotNull (obj, "Missing 'key' in bundle");
22+
Assert.IsTrue (obj is global::Android.Runtime.JavaList, "`obj` should be a JavaList!");
2223
try {
2324
list = b.GetIntegerArrayList ("key");
2425
Assert.NotNull (list, "'key' doesn't refer to a list of integers after non-generic call");

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void Run (DirectoryAssemblyResolver res)
174174

175175
// Step 2 - Generate type maps
176176
// Type mappings need to use all the assemblies, always.
177-
WriteTypeMappings (allJavaTypes);
177+
WriteTypeMappings (allJavaTypes, cache);
178178

179179
var javaTypes = new List<TypeDefinition> ();
180180
foreach (TypeDefinition td in allJavaTypes) {
@@ -399,10 +399,10 @@ void SaveResource (string resource, string filename, string destDir, Func<string
399399
MonoAndroidHelper.CopyIfStringChanged (template, Path.Combine (destDir, filename));
400400
}
401401

402-
void WriteTypeMappings (List<TypeDefinition> types)
402+
void WriteTypeMappings (List<TypeDefinition> types, TypeDefinitionCache cache)
403403
{
404404
var tmg = new TypeMapGenerator ((string message) => Log.LogDebugMessage (message), SupportedAbis);
405-
if (!tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState))
405+
if (!tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, types, cache, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState))
406406
throw new XamarinAndroidException (4308, Properties.Resources.XA4308);
407407
GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray ();
408408
BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: false);

src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs

+83-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Text;
66

7+
using Java.Interop.Tools.Cecil;
78
using Mono.Cecil;
89

910
namespace Xamarin.Android.Tasks
@@ -14,6 +15,7 @@ class TypeMapGenerator
1415
const string TypeMapIndexMagicString = "XATI"; // Xamarin Android Typemap Index
1516
const uint TypeMapFormatVersion = 2; // Keep in sync with the value in src/monodroid/jni/xamarin-app.hh
1617
const string TypemapExtension = ".typemap";
18+
const uint InvalidJavaToManagedMappingIndex = UInt32.MaxValue;
1719

1820
internal sealed class ModuleUUIDArrayComparer : IComparer<ModuleReleaseData>
1921
{
@@ -52,6 +54,7 @@ internal sealed class TypeMapReleaseEntry
5254
public uint Token;
5355
public int AssemblyNameIndex = -1;
5456
public int ModuleIndex = -1;
57+
public bool SkipInJavaToManaged;
5558
}
5659

5760
internal sealed class ModuleReleaseData
@@ -76,6 +79,8 @@ internal sealed class TypeMapDebugEntry
7679
public string ManagedLabel;
7780
public int JavaIndex;
7881
public int ManagedIndex;
82+
public TypeDefinition TypeDefinition;
83+
public bool SkipInJavaToManaged;
7984
}
8085

8186
// Widths include the terminating nul character but not the padding!
@@ -87,6 +92,7 @@ internal sealed class ModuleDebugData
8792
public List<TypeMapDebugEntry> JavaToManagedMap;
8893
public List<TypeMapDebugEntry> ManagedToJavaMap;
8994
public string OutputFilePath;
95+
public string ModuleName;
9096
public byte[] ModuleNameBytes;
9197
}
9298

@@ -125,7 +131,7 @@ void UpdateApplicationConfig (TypeDefinition javaType, ApplicationConfigTaskStat
125131
}
126132
}
127133

128-
public bool Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState)
134+
public bool Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, TypeDefinitionCache cache, string outputDirectory, bool generateNativeAssembly, out ApplicationConfigTaskState appConfState)
129135
{
130136
if (String.IsNullOrEmpty (outputDirectory))
131137
throw new ArgumentException ("must not be null or empty", nameof (outputDirectory));
@@ -140,25 +146,26 @@ public bool Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAt
140146
string typemapsOutputDirectory = Path.Combine (outputDirectory, "typemaps");
141147

142148
if (debugBuild) {
143-
return GenerateDebug (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, typemapsOutputDirectory, generateNativeAssembly, appConfState);
149+
return GenerateDebug (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, cache, typemapsOutputDirectory, generateNativeAssembly, appConfState);
144150
}
145151

146152
return GenerateRelease (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, typemapsOutputDirectory, appConfState);
147153
}
148154

149-
bool GenerateDebug (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, string outputDirectory, bool generateNativeAssembly, ApplicationConfigTaskState appConfState)
155+
bool GenerateDebug (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, TypeDefinitionCache cache, string outputDirectory, bool generateNativeAssembly, ApplicationConfigTaskState appConfState)
150156
{
151157
if (generateNativeAssembly)
152-
return GenerateDebugNativeAssembly (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, outputDirectory, appConfState);
153-
return GenerateDebugFiles (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, outputDirectory, appConfState);
158+
return GenerateDebugNativeAssembly (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, cache, outputDirectory, appConfState);
159+
return GenerateDebugFiles (skipJniAddNativeMethodRegistrationAttributeScan, javaTypes, cache, outputDirectory, appConfState);
154160
}
155161

156-
bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState)
162+
bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, TypeDefinitionCache cache, string outputDirectory, ApplicationConfigTaskState appConfState)
157163
{
158164
var modules = new Dictionary<string, ModuleDebugData> (StringComparer.Ordinal);
159165
int maxModuleFileNameWidth = 0;
160166
int maxModuleNameWidth = 0;
161167

168+
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
162169
foreach (TypeDefinition td in javaTypes) {
163170
UpdateApplicationConfig (td, appConfState);
164171
string moduleName = td.Module.Assembly.Name.Name;
@@ -173,7 +180,8 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L
173180
JavaToManagedMap = new List<TypeMapDebugEntry> (),
174181
ManagedToJavaMap = new List<TypeMapDebugEntry> (),
175182
OutputFilePath = Path.Combine (outputDirectory, outputFileName),
176-
ModuleNameBytes = outputEncoding.GetBytes (moduleName)
183+
ModuleName = moduleName,
184+
ModuleNameBytes = outputEncoding.GetBytes (moduleName),
177185
};
178186

179187
if (module.ModuleNameBytes.Length > maxModuleNameWidth)
@@ -186,6 +194,7 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L
186194
}
187195

188196
TypeMapDebugEntry entry = GetDebugEntry (td);
197+
HandleDebugDuplicates (javaDuplicates, entry, td, cache);
189198
if (entry.JavaName.Length > module.JavaNameWidth)
190199
module.JavaNameWidth = (uint)entry.JavaName.Length + 1;
191200

@@ -195,6 +204,7 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L
195204
module.JavaToManagedMap.Add (entry);
196205
module.ManagedToJavaMap.Add (entry);
197206
}
207+
SyncDebugDuplicates (javaDuplicates);
198208

199209
foreach (ModuleDebugData module in modules.Values) {
200210
PrepareDebugMaps (module);
@@ -217,18 +227,22 @@ bool GenerateDebugFiles (bool skipJniAddNativeMethodRegistrationAttributeScan, L
217227
return true;
218228
}
219229

220-
bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState)
230+
bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, TypeDefinitionCache cache, string outputDirectory, ApplicationConfigTaskState appConfState)
221231
{
222232
var javaToManaged = new List<TypeMapDebugEntry> ();
223233
var managedToJava = new List<TypeMapDebugEntry> ();
224234

235+
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
225236
foreach (TypeDefinition td in javaTypes) {
226237
UpdateApplicationConfig (td, appConfState);
227238

228239
TypeMapDebugEntry entry = GetDebugEntry (td);
240+
HandleDebugDuplicates (javaDuplicates, entry, td, cache);
241+
229242
javaToManaged.Add (entry);
230243
managedToJava.Add (entry);
231244
}
245+
SyncDebugDuplicates (javaDuplicates);
232246

233247
var data = new ModuleDebugData {
234248
EntryCount = (uint)javaToManaged.Count,
@@ -246,6 +260,39 @@ bool GenerateDebugNativeAssembly (bool skipJniAddNativeMethodRegistrationAttribu
246260
return true;
247261
}
248262

263+
void SyncDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> javaDuplicates)
264+
{
265+
foreach (List<TypeMapDebugEntry> duplicates in javaDuplicates.Values) {
266+
if (duplicates.Count < 2) {
267+
continue;
268+
}
269+
270+
TypeMapDebugEntry template = duplicates [0];
271+
for (int i = 1; i < duplicates.Count; i++) {
272+
duplicates[i].TypeDefinition = template.TypeDefinition;
273+
duplicates[i].ManagedName = template.ManagedName;
274+
}
275+
}
276+
}
277+
278+
void HandleDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> javaDuplicates, TypeMapDebugEntry entry, TypeDefinition td, TypeDefinitionCache cache)
279+
{
280+
List<TypeMapDebugEntry> duplicates;
281+
282+
if (!javaDuplicates.TryGetValue (entry.JavaName, out duplicates)) {
283+
javaDuplicates.Add (entry.JavaName, new List<TypeMapDebugEntry> { entry });
284+
} else {
285+
duplicates.Add (entry);
286+
TypeMapDebugEntry oldEntry = duplicates[0];
287+
if (td.IsAbstract || td.IsInterface || oldEntry.TypeDefinition.IsAbstract || oldEntry.TypeDefinition.IsInterface) {
288+
if (td.IsAssignableFrom (oldEntry.TypeDefinition, cache)) {
289+
oldEntry.TypeDefinition = td;
290+
oldEntry.ManagedName = GetManagedTypeName (td);
291+
}
292+
}
293+
}
294+
}
295+
249296
void PrepareDebugMaps (ModuleDebugData module)
250297
{
251298
module.JavaToManagedMap.Sort ((TypeMapDebugEntry a, TypeMapDebugEntry b) => String.Compare (a.JavaName, b.JavaName, StringComparison.Ordinal));
@@ -261,6 +308,16 @@ void PrepareDebugMaps (ModuleDebugData module)
261308
}
262309

263310
TypeMapDebugEntry GetDebugEntry (TypeDefinition td)
311+
{
312+
return new TypeMapDebugEntry {
313+
JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td),
314+
ManagedName = GetManagedTypeName (td),
315+
TypeDefinition = td,
316+
SkipInJavaToManaged = ShouldSkipInJavaToManaged (td),
317+
};
318+
}
319+
320+
string GetManagedTypeName (TypeDefinition td)
264321
{
265322
// This is necessary because Mono runtime will return to us type name with a `.` for nested types (not a
266323
// `/` or a `+`. So, for instance, a type named `DefaultRenderer` found in the
@@ -277,17 +334,13 @@ TypeMapDebugEntry GetDebugEntry (TypeDefinition td)
277334
//
278335
string managedTypeName = td.FullName.Replace ('/', '+');
279336

280-
return new TypeMapDebugEntry {
281-
JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td),
282-
ManagedName = $"{managedTypeName}, {td.Module.Assembly.Name.Name}",
283-
};
337+
return $"{managedTypeName}, {td.Module.Assembly.Name.Name}";
284338
}
285339

286340
bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List<TypeDefinition> javaTypes, string outputDirectory, ApplicationConfigTaskState appConfState)
287341
{
288342
int assemblyId = 0;
289343
int maxJavaNameLength = 0;
290-
int maxModuleFileNameLength = 0;
291344
var knownAssemblies = new Dictionary<string, int> (StringComparer.Ordinal);
292345
var tempModules = new Dictionary<byte[], ModuleReleaseData> ();
293346
Dictionary <AssemblyDefinition, int> moduleCounter = null;
@@ -329,12 +382,18 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List
329382
}
330383

331384
string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td);
385+
// We will ignore generic types and interfaces when generating the Java to Managed map, but we must not
386+
// omit them from the table we output - we need the same number of entries in both java-to-managed and
387+
// managed-to-java tables. `SkipInJavaToManaged` set to `true` will cause the native assembly generator
388+
// to output `0` as the token id for the type, thus effectively causing the runtime unable to match such
389+
// a Java type name to a managed type. This fixes https://github.com/xamarin/xamarin-android/issues/4660
332390
var entry = new TypeMapReleaseEntry {
333391
JavaName = javaName,
334392
JavaNameLength = outputEncoding.GetByteCount (javaName),
335393
ManagedTypeName = td.FullName,
336394
Token = td.MetadataToken.ToUInt32 (),
337-
AssemblyNameIndex = knownAssemblies [assemblyName]
395+
AssemblyNameIndex = knownAssemblies [assemblyName],
396+
SkipInJavaToManaged = ShouldSkipInJavaToManaged (td),
338397
};
339398

340399
if (entry.JavaNameLength > maxJavaNameLength)
@@ -376,6 +435,11 @@ bool GenerateRelease (bool skipJniAddNativeMethodRegistrationAttributeScan, List
376435
return true;
377436
}
378437

438+
bool ShouldSkipInJavaToManaged (TypeDefinition td)
439+
{
440+
return td.IsInterface || td.HasGenericParameters;
441+
}
442+
379443
void GenerateNativeAssembly (Func<NativeAssemblerTargetProvider, bool, bool, NativeAssemblyGenerator> getGenerator)
380444
{
381445
NativeAssemblerTargetProvider asmTargetProvider;
@@ -491,6 +555,10 @@ void OutputModule (ModuleDebugData moduleData)
491555
//
492556
void OutputModule (BinaryWriter bw, ModuleDebugData moduleData)
493557
{
558+
if ((uint)moduleData.JavaToManagedMap.Count == InvalidJavaToManagedMappingIndex) {
559+
throw new InvalidOperationException ($"Too many types in module {moduleData.ModuleName}");
560+
}
561+
494562
bw.Write (moduleMagicString);
495563
bw.Write (TypeMapFormatVersion);
496564
bw.Write (moduleData.JavaToManagedMap.Count);
@@ -502,7 +570,7 @@ void OutputModule (BinaryWriter bw, ModuleDebugData moduleData)
502570
foreach (TypeMapDebugEntry entry in moduleData.JavaToManagedMap) {
503571
bw.Write (outputEncoding.GetBytes (entry.JavaName));
504572
PadField (bw, entry.JavaName.Length, (int)moduleData.JavaNameWidth);
505-
bw.Write (entry.ManagedIndex);
573+
bw.Write (entry.SkipInJavaToManaged ? InvalidJavaToManagedMappingIndex : (uint)entry.ManagedIndex);
506574
}
507575

508576
foreach (TypeMapDebugEntry entry in moduleData.ManagedToJavaMap) {

src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ protected override void WriteSymbols (StreamWriter output)
7070
if (haveJavaToManaged) {
7171
foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) {
7272
size += WritePointer (output, entry.JavaLabel);
73-
size += WritePointer (output, entry.ManagedLabel);
73+
size += WritePointer (output, entry.SkipInJavaToManaged ? null : entry.ManagedLabel);
7474
}
7575
}
7676
WriteStructureSize (output, JavaToManagedSymbol, size, alwaysWriteSize: true);

src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ uint WriteJavaMapEntry (StreamWriter output, TypeMapGenerator.TypeMapReleaseEntr
219219
size += WriteData (output, entry.ModuleIndex);
220220

221221
WriteCommentLine (output, "type_token_id");
222-
size += WriteData (output, entry.Token);
222+
size += WriteData (output, entry.SkipInJavaToManaged ? 0 : entry.Token);
223223

224224
WriteCommentLine (output, "java_name");
225225
size += WriteAsciiData (output, entry.JavaName, mappingData.JavaNameWidth);

src/monodroid/jni/embedded-assemblies.cc

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <libgen.h>
1212
#include <errno.h>
1313
#include <unistd.h>
14+
#include <climits>
1415

1516
#include <mono/metadata/assembly.h>
1617
#include <mono/metadata/image.h>
@@ -208,6 +209,10 @@ EmbeddedAssemblies::typemap_java_to_managed (const char *java_type_name)
208209
}
209210

210211
const char *managed_type_name = entry->to;
212+
if (managed_type_name == nullptr) {
213+
log_debug (LOG_ASSEMBLY, "typemap: Java type '%s' maps either to an open generic type or an interface type.");
214+
return nullptr;
215+
}
211216
log_debug (LOG_DEFAULT, "typemap: Java type '%s' corresponds to managed type '%s'", java_type_name, managed_type_name);
212217

213218
MonoType *type = mono_reflection_type_from_name (const_cast<char*>(managed_type_name), nullptr);
@@ -731,12 +736,18 @@ EmbeddedAssemblies::typemap_load_file (BinaryTypeMapHeader &header, const char *
731736
uint8_t *managed_pos = managed_start;
732737
TypeMapEntry *cur;
733738

739+
constexpr uint32_t INVALID_TYPE_INDEX = UINT32_MAX;
734740
for (size_t i = 0; i < module.entry_count; i++) {
735741
cur = &module.java_to_managed[i];
736742
cur->from = reinterpret_cast<char*>(java_pos);
737743

738744
uint32_t idx = *(reinterpret_cast<uint32_t*>(java_pos + header.java_name_width));
739-
cur->to = reinterpret_cast<char*>(managed_start + (managed_entry_size * idx));
745+
if (idx < INVALID_TYPE_INDEX) {
746+
cur->to = reinterpret_cast<char*>(managed_start + (managed_entry_size * idx));
747+
} else {
748+
// Ignore the type mapping
749+
cur->to = nullptr;
750+
}
740751
java_pos += java_entry_size;
741752

742753
cur = &module.managed_to_java[i];

tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ namespace Xamarin.Android.JcwGenTests {
1212
[TestFixture]
1313
public class BindingTests {
1414

15+
[Test]
16+
public void TestTimingCreateTimingIsCorrectType ()
17+
{
18+
var t = Com.Xamarin.Android.Timing.CreateTiming ();
19+
Assert.IsTrue (t is Com.Xamarin.Android.Timing);
20+
}
21+
1522
[Test]
1623
public void TestResourceId ()
1724
{

tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Timing.cs

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
namespace Com.Xamarin.Android {
1010

11+
// Provoke https://github.com/xamarin/xamarin-android/issues/4660
12+
// We need this type to appear *before* `Timing` in `monodis --typedef` output
13+
[Register ("com/xamarin/android/Timing", DoNotGenerateAcw = true)]
14+
public partial class Timinf<T> : Timing {
15+
}
16+
1117
public partial class Timing {
1218

1319
static IntPtr jonp_id_VirtualVoidMethod;

tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/Timing.java

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
public class Timing {
66

7+
public static Timing createTiming () {
8+
return new Timing ();
9+
}
10+
711
public static void StaticVoidMethod ()
812
{
913
}

0 commit comments

Comments
 (0)