Skip to content

Commit

Permalink
[monodroid] Handle managed -> Java duplicate type mapping correctly (d…
Browse files Browse the repository at this point in the history
…otnet#5459)

Fixes: dotnet#5409

Context: a017561
Context: bb55bb0

Yet Another Java :: Managed Type Aliasing Issue.

Type aliasing occurs when multiple managed types bind the same Java
type.  One common scenario for this is for `abstract` classes:

	// Java:
	public abstract class LayoutInflater {
	    // …
	}

	// C# Binding
	[Register ("android/view/LayoutInflater", DoNotGenerateAcw=true)]
	public abstract class LayoutInflater : Java.Lang.Object {
	    static readonly JniPeerMembers _members = new XAPeerMembers ("android/view/LayoutInflater", typeof (LayoutInflater));
	}

	// Used at runtime
	[Register ("android/view/LayoutInflater", DoNotGenerateAcw=true)]
	internal partial class LayoutInflaterInvoker : LayoutInflater {
	    static readonly JniPeerMembers _members = new XAPeerMembers ("android/view/LayoutInflater", typeof (LayoutInflaterInvoker));
	}

Both the C# `LayoutInflater` and `LayoutInflaterInvoker` types *alias*
the Java `LayoutInflater` type.

In a *Debug* configuration build, the mappings between the Java types
and Managed types is held within a per-assembly mapping (7117414),
and because of a017561 there is no explicit managed entry for
`LayoutInflaterInvoker`:

	DebugTypemap = {
	    .java_to_managed = {
	        { "android/view/LayoutInflater", 0 },   // Java LayoutInflater -> C# LayoutInflater
	        { "android/view/LayoutInflater", 0 },   // Java LayoutInflater -> C# LayoutInflater
	    },
	    .managed_to_java = {
	        { "Android.Views.LayoutInflater", 0 },  // C# LayoutInflater        -> Java LayoutInflater
	        { "Android.Views.LayoutInflater", 0 },  // C# LayoutInflaterInvoker -> Java LayoutInflater
	    },
	}

At *runtime* when the `LayoutInflaterInvoker` static constructor is
executed, we hit an [assert in the `JniPeerMembers` constructor][0]:

	app_process32: ---- DEBUG ASSERTION FAILED ----
	app_process32: ---- Assert Short Message ----
	app_process32: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof(Android.Views.LayoutInflaterInvoker)).JniTypeName="" != "android/view/LayoutInflater"
	app_process32: ---- Assert Long Message ----
	app_process32:
	app_process32:    at System.Diagnostics.DebugProvider.Fail(String message, String detailMessage)
	app_process32:    at System.Diagnostics.Debug.Fail(String message, String detailMessage)
	app_process32:    at System.Diagnostics.Debug.Assert(Boolean condition, String message, String detailMessage)
	app_process32:    at System.Diagnostics.Debug.Assert(Boolean condition, String message)
	app_process32:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	app_process32:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	app_process32:    at Android.Runtime.XAPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	app_process32:    at Android.Views.LayoutInflaterInvoker..cctor()
	app_process32:    at System.Reflection.RuntimeConstructorInfo.InternalInvoke(RuntimeConstruct
	        : CLR: Managed code called FailFast, saying "ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof(Android.Views.LayoutInflaterInvoker)).JniTypeName="" != "android/view/LayoutInflater"
	        :    at System.Diagnostics.DebugProvider.Fail(String message, String detailMessage)
	        :    at System.Diagnostics.Debug.Fail(String message, String detailMessage)
	        :    at System.Diagnostics.Debug.Assert(Boolean condition, String message, String detailMessage)
	        :    at System.Diagnostics.Debug.Assert(Boolean condition, String message)
	        :    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	        :    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	        :    at Android.Runtime.XAPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	        :    at Android.Views.LayoutInflaterInvoker..cctor()

The cause of the assertion failure is because the `.managed_to_java`
table doesn't contain an entry for `LayoutInflaterInvoker`.

The fix is to modify a017561 behavior and *retain* the `*Invoker`
types in the `.managed_to_java` table:

	DebugTypemap = {
	    .java_to_managed = {
	        { "android/view/LayoutInflater", 0 },   // Java LayoutInflater -> C# LayoutInflater
	        { "android/view/LayoutInflater", 0 },   // Java LayoutInflater -> C# LayoutInflater
	    },
	    .managed_to_java = {
	        { "Android.Views.LayoutInflater", 0 },          // C# LayoutInflater        -> Java LayoutInflater
	        { "Android.Views.LayoutInflaterInvoker", 0 },   // C# LayoutInflaterInvoker -> Java LayoutInflater
	    },
	}

This ensures that we can appropriately obtain a Java type name
for the `LayoutInflaterInvoker` type, fixing the assert.

[0]: https://github.com/xamarin/java.interop/blob/3894cd76f71f618949c8542f0edd95762e22881f/src/Java.Interop/Java.Interop/JniPeerMembers.cs#L29
  • Loading branch information
grendello authored Jan 7, 2021
1 parent 15269f6 commit 66f7206
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 5 deletions.
13 changes: 9 additions & 4 deletions src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ internal sealed class TypeMapDebugEntry
public int ManagedIndex;
public TypeDefinition TypeDefinition;
public bool SkipInJavaToManaged;
public TypeMapDebugEntry DuplicateForJavaToManaged;
}

// Widths include the terminating nul character but not the padding!
Expand Down Expand Up @@ -267,11 +268,13 @@ void SyncDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> javaDuplic
continue;
}

// Java duplicates must all point to the same managed type
// Managed types, however, must point back to the original Java type instead
// File/assembly generator use the `DuplicateForJavaToManaged` field to know to which managed type the
// duplicate Java type must be mapped.
TypeMapDebugEntry template = duplicates [0];
for (int i = 1; i < duplicates.Count; i++) {
duplicates[i].TypeDefinition = template.TypeDefinition;
duplicates[i].ManagedName = template.ManagedName;
duplicates[i].SkipInJavaToManaged = template.SkipInJavaToManaged;
duplicates[i].DuplicateForJavaToManaged = template;
}
}
}
Expand Down Expand Up @@ -571,7 +574,9 @@ void OutputModule (BinaryWriter bw, ModuleDebugData moduleData)
foreach (TypeMapDebugEntry entry in moduleData.JavaToManagedMap) {
bw.Write (outputEncoding.GetBytes (entry.JavaName));
PadField (bw, entry.JavaName.Length, (int)moduleData.JavaNameWidth);
bw.Write (entry.SkipInJavaToManaged ? InvalidJavaToManagedMappingIndex : (uint)entry.ManagedIndex);

TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry;
bw.Write (managedEntry.SkipInJavaToManaged ? InvalidJavaToManagedMappingIndex : (uint)managedEntry.ManagedIndex);
}

foreach (TypeMapDebugEntry entry in moduleData.ManagedToJavaMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ protected override void WriteSymbols (StreamWriter output)
if (haveJavaToManaged) {
foreach (TypeMapGenerator.TypeMapDebugEntry entry in data.JavaToManagedMap) {
size += WritePointer (output, entry.JavaLabel);
size += WritePointer (output, entry.SkipInJavaToManaged ? null : entry.ManagedLabel);

TypeMapGenerator.TypeMapDebugEntry managedEntry = entry.DuplicateForJavaToManaged != null ? entry.DuplicateForJavaToManaged : entry;
size += WritePointer (output, managedEntry.SkipInJavaToManaged ? null : managedEntry.ManagedLabel);
}
}
WriteStructureSize (output, JavaToManagedSymbol, size, alwaysWriteSize: true);
Expand Down

0 comments on commit 66f7206

Please sign in to comment.