ikvmnet / ikvm

A Java Virtual Machine and Bytecode-to-IL Converter for .NET
Other
1.22k stars 111 forks source link

Method.invoke on methods with covariant return type overrides gives System.Reflection.AmbiguousMatchException #360

Closed TheLastRar closed 1 year ago

TheLastRar commented 1 year ago

IKVM version : IKVM-8.5.2-image-netcoreapp3.1-win7-x64

Where a method is overridden with a covariant return type, both the original and new signature will be present in the array returned from getMethods() (IKVM matching JVM here)

When such a method is retrieved (either for the original or overridden return type). and then invoked, IKVM will encounter a AmbiguousMatchException.

Given Java code

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class invokeTest {

    public static void main(String[] args) throws Exception {
     Method[] methods = baz.class.getMethods();
     Method bar = null;
     for(Method method : methods)
     {
         Class<?>[] params = method.getParameterTypes();
         List<String> paramStr = new ArrayList<String>();
         for(Class<?> param : params)
         {
             paramStr.add(param.getName());
         }
         System.out.println(method.getReturnType().getTypeName() + " " + method.getName() + "(" + String.join(",", paramStr) + ")");
         if (method.getName() == "bar" && bar == null)
         {
             System.out.println("Storing above method");
             bar = method;
         }
     }

     baz b = new baz();
     System.out.println("Calling stored method");
     bar.invoke(b);
     System.out.println("Done");
    }
}

class foo
{
    public Object bar()
    {
     return null;
    }
}

class baz extends foo
{
    @Override
    public String bar()
    {
     return null;
    }
}

JVM produces this

java.lang.String bar()
Storing above method
java.lang.Object bar()
void wait(long,int)
void wait(long)
void wait()
boolean equals(java.lang.Object)
java.lang.String toString()
int hashCode()
java.lang.Class getClass()
void notify()
void notifyAll()
Calling stored method
Done

IKVM produces this

java.lang.String bar()
Storing above method
java.lang.Object bar()
void notifyAll()
void notify()
void wait()
void wait(long)
void wait(long,int)
java.lang.Class getClass()
java.lang.String toString()
int hashCode()
boolean equals(java.lang.Object)
Calling stored method
Exception in thread "Thread-0" cli.System.Reflection.AmbiguousMatchException: Ambiguous match found.
        at cli.System.DefaultBinder.SelectMethod(Unknown Source)
        at cli.System.RuntimeType.GetMethodImplCommon(Unknown Source)
        at cli.System.RuntimeType.GetMethodImpl(Unknown Source)
        at cli.System.Type.GetMethod(Unknown Source)
        at IKVM.Internal.MethodWrapper.ResolveMethod(MethodWrapper.cs:493)
        at IKVM.Java.Externs.sun.reflect.ReflectionFactory+FastMethodAccessorImpl.<init>(ReflectionFactory.cs:554)
        at IKVM.Java.Externs.sun.reflect.ReflectionFactory.newMethodAccessor(ReflectionFactory.cs:2102)
        at sun.reflect.ReflectionFactory.newMethodAccessor(Native Method)
        at java.lang.reflect.Method.acquireMethodAccessor(Method.java:552)
        at java.lang.reflect.Method.invoke(Method.java:484)
        at jnitest.Main.main(Main.java:31)
        at java.lang.reflect.Method.invoke(Method.java:486)
        at IKVM.Runtime.Accessors.Java.Lang.Reflect.MethodAccessor.InvokeInvoke(MethodAccessor.cs:35)
wasabii commented 1 year ago

Try as I might, I can't get this to reproduce in 8.5.2. Do you have any special compilation steps or anything?

wasabii commented 1 year ago

Built a test case on a branch to give it a go:

https://github.com/ikvmnet/ikvm/blob/bugfix/360/src/IKVM.Java.Tests/java/lang/reflect/MethodTests.java

That's statically compiled, and runs fine. But it's nested classes.

Tried with non nested.

Tried with command line with java.exe.

TheLastRar commented 1 year ago

A jar is run on command line with java.exe.

Code is compiled and exported into a jar using eclipse configured with OpenJDK 8, I've now compiled the class file directly with command line, with no extra arguments, running the resulting class file produces the same result, so eclipse isn't doing something funky here.

Looking at the code pointed to by the stack trace, I see different code is used in ResolveMethod between net461 and netcore3.1, so I retested both versions

The net461 build of 8.5.2 works correctly the netcore3.1 build of 8.5.2 fails and produces the stack trace I got above.

TheLastRar commented 1 year ago

Are you still unable to reproduce?

wasabii commented 1 year ago

I did get it to reproduce. Just been busy on other tihngs.