Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 3fee05c

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 29f9707 commit 3fee05c

9 files changed

Lines changed: 25 additions & 15 deletions

File tree

‎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
publicpartialclassJniPeerMembers{
99

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

30-
JniPeerMembers(stringjniPeerTypeName,TypemanagedPeerType,boolcheckManagedPeerType)
37+
JniPeerMembers(stringjniPeerTypeName,TypemanagedPeerType,boolcheckManagedPeerType,boolisInterface=false)
3138
{
3239
if(jniPeerTypeName==null)
3340
thrownewArgumentNullException(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=newJniInstanceMethods(this);
5564
instanceFields=newJniInstanceFields(this);
5665
staticMethods=newJniStaticMethods(this);
@@ -140,7 +149,7 @@ protected virtual bool UsesVirtualDispatch (IJavaPeerable value, Type declaringT
140149

141150
protectedvirtualJniPeerMembersGetPeerMembers(IJavaPeerablevalue)
142151
{
143-
returnvalue.JniPeerMembers;
152+
returnisInterface?this:value.JniPeerMembers;
144153
}
145154

146155
internalstaticvoidAssertSelf(IJavaPeerableself)

‎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
internaloverridevoidWriteClassHandle(ClassGentype,stringindent,boolrequireNew)
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
internaloverridevoidWriteClassHandle(InterfaceGentype,stringindent,stringdeclaringType)
5757
{
58-
WritePeerMembers(indent,false,true,type.RawJniName,declaringType);
58+
WritePeerMembers(indent,false,true,type.RawJniName,declaringType,type.Name==declaringType);
5959
}
6060

6161
internaloverridevoidWriteClassInvokerHandle(ClassGentype,stringindent,stringdeclaringType)
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
internaloverridevoidWriteInterfaceInvokerHandle(InterfaceGentype,stringindent,stringdeclaringType)
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-
voidWritePeerMembers(stringindent,boolisInternal,boolisNew,stringrawJniType,stringdeclaringType)
265+
voidWritePeerMembers(stringindent,boolisInternal,boolisNew,stringrawJniType,stringdeclaringType,boolisInterface)
266266
{
267267
varsignature=$"{(isInternal?"internal ":"")}static {(isNew?"new ":"")}readonly JniPeerMembers _members = ";
268-
vartype=$"new {GetPeerMembersType()} (\"{rawJniType}\", typeof ({declaringType}));";
268+
vartype=$"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)