Skip to content

Commit f0c28a4

Browse files
jpobstjonpryor
authored andcommitted
[Java.Interop, generator] Fix GetPeerMembers for interfaces (#486)
Context: dotnet/android#3533 Consider the following Java interface which contains a default interface method: // Java package com.xamarin.android; public interface DefaultMethodsInterface { default int foo () { return 0; } } This is bound as: // C# Binding public partial interface IDefaultMethodsInterface : IJavaPeerable { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface)); [Register ("foo", "()V", "…")] virtual unsafe int Foo () { return _members.InstanceMethods.InvokeVirtualInt32Method ("foo.()V", this, null); } } If we "implement" the interface from C# *without* implementing `IDefaultMethodsInterface.Foo()`: // C# class ManagedOverrideDefault : Java.Lang.Object, IDefaultMethodsInterface { } Then invoke the default interface method: var over = new ManagedOverrideDefault (); (over as IDefaultMethodsInterface).Foo (); The attempt to invoke the default implementation of `IDefaultMethodsInterface.Foo()` causes a crash: Java.Lang.NoSuchMethodError : no non-static method "Ljava/lang/Object;.foo()I" The cause of the crash is as follows: `IDefaultMethodsInterface.Foo()` calls `JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method()` tries to determine if the method is overridden, and if the method is *not* overridden -- as is the case here -- then we hit the non-virtual invocation path: // src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs var j = Members.GetPeerMembers (self); var n = j.InstanceMethods.GetMethodInfo (encodedMember); return JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); `JniInstanceMethods.Members` will be the `IDefaultMethodsInterface._members` field, and `Members.GetPeerMembers()` would return `self.JniPeerMembers`. Because this is a C# class which inherits `Java.Lang.Object`, `Java.Lang.Object._members` is returned. We then attempt to resolve `foo()` from `java.lang.Object`, which *does not exist*, which is why the `NoSuchMethodError` is thrown. The fix is to "intercept" the `Members.GetPeerMembers()` invocation so that `j` refers to `IDefaultMethodsInterface._members`, *not* `Java.Lang.Object._members`. This would allow `n` to refer to `DefaultMethodsInterface.foo()`, which *does* exist, and *can* be invoked via `JniEnvironment.InstanceMethods.CallNonvirtualIntMethod()`. Add the following `JniPeerMembers` constructor overload: partial class JniPeerMembers { public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface); } Update `JniPeerMembers.GetPeerMembers()` so that if `isInterface` is true, we return `this` (the *interface*s `JniPeerMembers` instance). Update `generator` so that `_members` construction within interfaces sets the `isInterface` parameter to true, resulting in: partial interface IDefaultMethodsInterface { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface), isInterface: true); } This allows `(over as IDefaultMethodsInterface).Foo()` to work without an exception being thrown.
1 parent 75b1189 commit f0c28a4

File tree

9 files changed

+25
-15
lines changed

9 files changed

+25
-15
lines changed

gendarme-ignore.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ T: Java.Interop.JniInt32ArrayElements
351351
T: Java.Interop.JniInt64ArrayElements
352352
T: Java.Interop.JniLocationException
353353
T: Java.Interop.JniMethodInfo
354+
T: Java.Interop.JniPeerMembers
354355
T: Java.Interop.JniSByteArrayElements
355356
T: Java.Interop.JniSingleArrayElements
356357
T: Java.Interop.JniType

src/Java.Interop/Java.Interop/JniPeerMembers.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ namespace Java.Interop {
77

88
public partial class JniPeerMembers {
99

10+
private bool isInterface;
11+
12+
public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface)
13+
: this (jniPeerTypeName, managedPeerType, checkManagedPeerType: true, isInterface: isInterface)
14+
{
15+
}
16+
1017
public JniPeerMembers (string jniPeerTypeName, Type managedPeerType)
11-
: this (jniPeerTypeName, managedPeerType, checkManagedPeerType: true)
18+
: this (jniPeerTypeName, managedPeerType, checkManagedPeerType: true, isInterface: false)
1219
{
1320
if (managedPeerType == null)
1421
throw new ArgumentNullException ("managedPeerType");
@@ -27,7 +34,7 @@ public JniPeerMembers (string jniPeerTypeName, Type managedPeerType)
2734
ManagedPeerType = managedPeerType;
2835
}
2936

30-
JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType)
37+
JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
3138
{
3239
if (jniPeerTypeName == null)
3340
throw new ArgumentNullException (nameof (jniPeerTypeName));
@@ -51,6 +58,8 @@ public JniPeerMembers (string jniPeerTypeName, Type managedPeerType)
5158
JniPeerTypeName = jniPeerTypeName;
5259
ManagedPeerType = managedPeerType;
5360

61+
this.isInterface = isInterface;
62+
5463
instanceMethods = new JniInstanceMethods (this);
5564
instanceFields = new JniInstanceFields (this);
5665
staticMethods = new JniStaticMethods (this);
@@ -140,7 +149,7 @@ protected virtual bool UsesVirtualDispatch (IJavaPeerable value, Type declaringT
140149

141150
protected virtual JniPeerMembers GetPeerMembers (IJavaPeerable value)
142151
{
143-
return value.JniPeerMembers;
152+
return isInterface ? this : value.JniPeerMembers;
144153
}
145154

146155
internal static void AssertSelf (IJavaPeerable self)

tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ static string GetInvokeType (string type)
2929

3030
internal override void WriteClassHandle (ClassGen type, string indent, bool requireNew)
3131
{
32-
WritePeerMembers (indent + '\t', true, requireNew, type.RawJniName, type.Name);
32+
WritePeerMembers (indent + '\t', true, requireNew, type.RawJniName, type.Name, false);
3333

3434
writer.WriteLine ("{0}\tinternal static {1}IntPtr class_ref {{", indent, requireNew ? "new " : string.Empty);
3535
writer.WriteLine ("{0}\t\tget {{", indent);
@@ -55,12 +55,12 @@ internal override void WriteClassHandle (ClassGen type, string indent, bool requ
5555

5656
internal override void WriteClassHandle (InterfaceGen type, string indent, string declaringType)
5757
{
58-
WritePeerMembers (indent, false, true, type.RawJniName, declaringType);
58+
WritePeerMembers (indent, false, true, type.RawJniName, declaringType, type.Name == declaringType);
5959
}
6060

6161
internal override void WriteClassInvokerHandle (ClassGen type, string indent, string declaringType)
6262
{
63-
WritePeerMembers (indent, true, true, type.RawJniName, declaringType);
63+
WritePeerMembers (indent, true, true, type.RawJniName, declaringType, false);
6464

6565
writer.WriteLine ();
6666
writer.WriteLine ("{0}public override global::Java.Interop.JniPeerMembers JniPeerMembers {{", indent);
@@ -75,7 +75,7 @@ internal override void WriteClassInvokerHandle (ClassGen type, string indent, st
7575

7676
internal override void WriteInterfaceInvokerHandle (InterfaceGen type, string indent, string declaringType)
7777
{
78-
WritePeerMembers (indent, true, true, type.RawJniName, declaringType);
78+
WritePeerMembers (indent, true, true, type.RawJniName, declaringType, false);
7979

8080
writer.WriteLine ();
8181
writer.WriteLine ("{0}static IntPtr java_class_ref {{", indent);
@@ -262,10 +262,10 @@ internal override void WriteFieldSetBody (Field field, string indent, GenBase ty
262262
writer.WriteLine ("{0}}}", indent);
263263
}
264264

265-
void WritePeerMembers (string indent, bool isInternal, bool isNew, string rawJniType, string declaringType)
265+
void WritePeerMembers (string indent, bool isInternal, bool isNew, string rawJniType, string declaringType, bool isInterface)
266266
{
267267
var signature = $"{(isInternal ? "internal " : "")}static {(isNew ? "new " : "")}readonly JniPeerMembers _members = ";
268-
var type = $"new {GetPeerMembersType ()} (\"{rawJniType}\", typeof ({declaringType}));";
268+
var type = $"new {GetPeerMembersType ()} (\"{rawJniType}\", typeof ({declaringType}){(isInterface ? ", isInterface: true" : string.Empty)});";
269269

270270
writer.WriteLine ($"{indent}{signature}{type}");
271271
}

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultMethod.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_DoSomething;
77
#pragma warning disable 0169

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultProperty.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_get_Value;
77
#pragma warning disable 0169

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new JniPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_get_Value;
77
#pragma warning disable 0169

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_DoSomething;
77
#pragma warning disable 0169

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_get_Value;
77
#pragma warning disable 0169

tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Metadata.xml XPath interface reference: path="/api/package[@name='java.code']/interface[@name='IMyInterface']"
22
[Register ("java/code/IMyInterface", "", "java.code.IMyInterfaceInvoker")]
33
public partial interface IMyInterface : IJavaObject, IJavaPeerable {
4-
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface));
4+
static new readonly JniPeerMembers _members = new XAPeerMembers ("java/code/IMyInterface", typeof (IMyInterface), isInterface: true);
55

66
static Delegate cb_get_Value;
77
#pragma warning disable 0169

0 commit comments

Comments
 (0)