-
Notifications
You must be signed in to change notification settings - Fork 545
[typemap] Handle managed -> Java duplicate mapping correctly #5459
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
Fixes: dotnet#5409 Context: a017561 Context: bb55bb0 This is another commit in the series of properly handling the N:1 type mapping from Java to Managed types. a017561 implemented most of the handling properly, bb55bb0 fixed it up for generic types but neither of the two commits accounted for the situation when a Managed type map is requested that points back to a duplicate Java type. In fact, such Managed types were removed from the typemap unless they were the type pointed to from the Java duplicate. This meant that, effectively, all the `*Invoker` classes were not part of the mapping, leading to this error for Debug builds: 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() This commit modifies the mapping code to make sure that all duplicate Java types point to the **first** Managed type but that all the Managed types point back to their original Java type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be useful to have a test for that?
Definitely, but it would be nice if it was part of our XF integration app - I don't really know what elements in XF trigger it, so short of copying the .NET6 sample I don't know how to create the test |
We are also running tests with NET6 and XForms in https://github.com/xamarin/xamarin-android/blob/master/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs That would require knowing what causes the issue in the net6 sample, so that we can add relevant code there. Mentioning it just in case you find out later. |
That's the thing, though, the sample is mostly empty... I also don't think it's specific to .NET6, I suspect it's about the XF version used. XF 4.8 used by the .NET6 sample must be doing something differently than the 4.0 version we use in our XF integration test |
XF 4.0 would be using the old Android Support libraries and XF 4.6 and higher (with TFV 10.0+) uses AndroidX. It might just be a difference in using AndroidX. |
work-in-progress commit message; Fixes: https://github.com/xamarin/xamarin-android/issues/5409
Context: a017561b1e44c51a9af79fae0baaa50fe01c4123
Context: bb55bb064b967adf8ca975928736346694dceb02
This is another commit in the series of properly handling 1:N
type mappings from Java to Managed types.
a017561b implemented most of the handling properly, bb55bb064 fixed
it up for generic types, but neither of the two commits accounted for
the situation when a Managed type map is requested that points back
to a duplicate Java type.
The typical example is for the `*Invoker` classes:
// Java:
interface Runnable {
void run();
}
// C# Binding
[Register ("java/lang/Runnable", "", "Java.Lang.IRunnableInvoker")]
public interface IRunnable : IJavaObject, IJavaPeerable {
[Register (…)]
void Run();
}
// Used at runtime
[Register ("java/lang/Runnable", DoNotGenerateAcw=true)]
internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
public void Run() {…}
}
Here, both `IRunnable` and `IRunnableInvoker` have a `[Register]`
custom attribute for `java.lang.Runnable`, and thus *alias* the
`Runnable` type.
@grendello TODO: what is meant by "points back to a duplicate java type"?
In fact, such Managed types aliases were removed from the typemap
unless they were the type pointed to from the Java duplicate.
[I'm still not sure what this means? -@jonpryor]
This meant that, effectively, all the `*Invoker` classes were not part
of the mapping, leading to this error for Debug builds:
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()
Modify the mapping code to make sure that all duplicate Java types
point to the **first** Managed type but that all the Managed types
point back to their original Java type.
This ensures that `IRunnableInvoker` remains in the type map, and is
appropriately mapped to `java.lang.Runnable`. @grendello: I still have some questions/confusion in ^^; please review. :-) |
@jonpryor I think the confusion stems from a typo, the |
@grendello wrote:
Part of the confusion here is whether we're talking about types or entries. Consider the above example. This defines two C# types which alias the same Java type. Thus, for entries, we'd presumably have:
For Java-to-managed mappings, we'd have
For C#-to-Java mappings, we'd have
Fundamental Question #1: is All the above has been a mental exercise for me; what about the file format, as per 7117414, which is where the problem is? struct DebugTypemapFileHeader {
byte magic [4]; // "XATS"
uint32_t format_version; // 2
uint32_t entry_count;
uint32_t java_type_name_width;
uint32_t managed_type_name_width;
uint32_t assembly_name_size;
byte assembly_name [assembly_name_size];
DebugTypemapFileJavaToManagedEntry java_to_managed [entry_count];
DebugTypemapFileManagedToJavaEntry managed_to_java [entry_count];
}
struct DebugTypemapFileJavaToManagedEntry {
byte jni_name [DebugTypemapFileHeader::java_type_name_width];
uint32_t managed_index; // Index into DebugTypemapFileHeader::managed_to_java
};
struct DebugTypemapFileManagedToJavaEntry {
byte managed_name [DebugTypemapFileHeader::managed_type_name_width];
uint32_t jni_index; // Index into DebugTypemapFileHeader::java_to_managed
};
What I don't know/remember is whether those tables have duplicates. The current commit message suggests that there can be:
Thus, question #2: before this fix, what was the tabular structure? I think it would be: java_to_managed = {
{ "android/view/LayoutInflater", 0 },
{ "android/view/LayoutInflater", uint.MaxValue }, // invalid
}
managed_to_java = {
{ "Android.View.LayoutInflater", 0 },
{ "Android.View.LayoutInflaterInvoker", 0 },
} After this fix, what is the tabular structure? I think it will be: java_to_managed = {
{ "android/view/LayoutInflater", 0 },
{ "android/view/LayoutInflater", 0 },
}
managed_to_java = {
{ "Android.View.LayoutInflater", 0 },
{ "Android.View.LayoutInflaterInvoker", 0 },
} |
The Java to Managed tables have duplicates, yes. There can be X entries witth the same type name, and all of them pointing to a single Managed type. There are no duplicates in the Managed to Java table, but there's an equal number of entries - this comes from the fact that many different Managed types (as in your invoker example) can register the same Java type. This fix doesn't change where the Java to Managed entries point - all the duplicate Java types still point to the FIRST Managed type. However, previously all the Managed types were made equal to the FIRST Managed type - that's what caused the issue. With this fix, all the Managed types remain with their original names. And that's the only change here. So, where previously we didn't have a Managed to Java entry for |
Commit message take 2! Fixes: https://github.com/xamarin/xamarin-android/issues/5409
Context: a017561b1e44c51a9af79fae0baaa50fe01c4123
Context: bb55bb064b967adf8ca975928736346694dceb02
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 (7117414c),
and because of a017561b 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 a017561b 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 |
…5459) Fixes: #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
Fixes: #5409
Context: a017561
Context: bb55bb0
This is another commit in the series of properly handling the N:1 type
mapping from Java to Managed types.
a017561 implemented most of the handling properly, bb55bb0 fixed it
up for generic types but neither of the two commits accounted for the
situation when a Managed type map is requested that points back to a
duplicate Java type. In fact, such Managed types were removed from the
typemap unless they were the type pointed to from the Java duplicate.
This meant that, effectively, all the
*Invoker
classes were not partof the mapping, leading to this error for Debug builds:
This commit modifies the mapping code to make sure that all duplicate
Java types point to the first Managed type but that all the Managed
types point back to their original Java type.