3

I have a simple interface and its implementation:

interface Iface {
    fun doSomething(s: String)
}

class IfaceImpl : Iface {
    override fun doSomething(s: String) {
        println("Doing the job, s = $s")
    }
}

Also, there are two identical (at least I cannot spot the difference) invocation handlers, one in Java and one in Kotlin:

public class JavaHandler implements InvocationHandler {
    private final Iface target;

    public JavaHandler(Iface target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Java handler works");
        return method.invoke(target, args);
    }
}

class KotlinHandler(private val target: Iface) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
        println("Kotlin proxy works")
        return method!!.invoke(target, args)
    }
}

They both just output some string and then invoke the method on the target.

Finally, here is the code I run:

fun main(args: Array<String>) {
    val target = IfaceImpl()
    target.doSomething("one")

    val javaProxy = newProxy(JavaHandler(target))
    javaProxy.doSomething("two")

    val kotlinProxy = newProxy(KotlinHandler(target))
    kotlinProxy.doSomething("three")
}

fun newProxy(handler: InvocationHandler): Iface {
    return Proxy.newProxyInstance(Iface::class.java.classLoader, arrayOf(Iface::class.java), handler) as Iface
}

It creates two java proxies, using both invocation handlers, and tries to exercise them.

Java handler works fine, but Kotlin handler does not. The output follows:

Doing the job, s = one
Java handler works
Doing the job, s = two
Kotlin proxy works
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at KotlinHandler.invoke(KotlinHandler.kt:12)
    at com.sun.proxy.$Proxy0.doSomething(Unknown Source)
    at TestKt.main(Test.kt:17)

I can see with a debugger that in both cases args consists of 1 element, and it's a java.lang.Integer instance.

An interesting thing is that if the method has 0 parameters, the error message is different:

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

In such a case, null is passed as args parameter (which is allowed by javadocs for parameterless calls).

Do I do something wrong? Or is this a bug?

My build.gradle has the following:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.61'
}
1
  • 1
    I believe this should be a warning from the Kotlin compiler - really easy fix but really hard to detect Commented Aug 29, 2018 at 20:43

1 Answer 1

7

UPDATE: In newer versions of Kotlin you can use args.orEmpty() instead of args ?: emptyArray()

TL;DR

You cannot pass args but you need to use *(args ?: emptyArray()) because Method.invoke does not expect an array but a variadic parameter.

See this answer for more information

The way I found the actual issue

I looked at the generated bytecode, for Kotlin I get the following:

override fun invoke(proxy : Any?, method : Method, args : Array<Any>?) : Any?
{
    println("Kotlin proxy works")
    return method.invoke(target, args)
}
public java.lang.Object invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]);
Code:
   0: aload_2
   1: ldc           #12                 // String method
   3: invokestatic  #18                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
   6: ldc           #20                 // String Kotlin proxy works
   8: astore        4
  10: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
  13: aload         4
  15: invokevirtual #32                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  18: aload_2
  19: aload_0
  20: getfield      #36                 // Field target:LIface;
  23: iconst_1
  24: anewarray     #4                  // class java/lang/Object
  27: dup
  28: iconst_0
  29: aload_3
  30: aastore
  31: invokevirtual #41                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
  34: areturn

Now, as you can see, Kotlin does manipulate the args parameter - in fact it creates a new array. Java does not do this (also it skips null-checks):

public Object invoke(Object proxy, Method method, Object args[]) throws Throwable
{
    System.out.println("Java handler works");
    return method.invoke(target, args);
}
public java.lang.Object invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) throws java.lang.Throwable;
Code:
   0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: ldc           #4                  // String Java handler works
   5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8: aload_2
   9: aload_0
  10: getfield      #2                  // Field target:LIface;
  13: aload_3
  14: invokevirtual #6                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
  17: areturn

Now, let's intercept the actuall array. I created a method in Java code to act as an intermediary:

public static Object invoke0(Iface target, Method method, Object args[]) throws Throwable
{
    System.out.println("Invoking method with " + java.util.Arrays.toString(args));
    return method.invoke(target, args);
}

Execute that from both Java and Kotlin - and it works.

Now what is the difference? Right, we expect an Object[], but Method.invoke takes an Object....

Change our intermediary to take Object... and we get our error message, along with this output:

Invoking method with [[Ljava.lang.Object;@4b67cf4d]

So obviously, we aren't passing an Object[] but an Object[][], which means type mismatch!

Sign up to request clarification or add additional context in comments.

2 Comments

I played with it for a bit, hoped I could get vararg args: Any? to work - but it seems undoable since Java invocation passes null for the args which causes IllegarArgumentException to trip.
Same when you call KFunction.call(): You get IllegalArgumentException when you miss the spread operator *.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.