dimanche 31 juillet 2022

IllegalArgumentException on passing Proxy object as constructor argument in Kotlin/JVM

I am trying to instantiate instances generically for a (de)serialization library I am building. This sometimes requires object stubs to be generated automatically (they are later replaced by real instances).

When trying to do so, I call the constructor as a KFunction, which works for classes that only take preexisting values or plain values like Int, Long ... for which I have predefined stubs. When I as the library provider don't know a type I need a stub for, I want to proxy that type and later inject the actual value (the latter works perfectly). However whenever I try to call the constructor with the proxy provided, the constructor call throws an IllegalArgumentException.

I debugged to DelegatingConstructorAccessorImpl.newInstance where I cannot go any further as things start to get native. I am pretty certain, that the arguments are in the right order and all, because if I replace the value with a real instance it works, however the proxy is not identified as the expected type.

My proxies are generated like this:

Proxy.newProxyInstance(
    ClassLoader.getSystemClassLoader(),
    type.jvmErasure.java.interfaces,
    InvocationHandler { proxy, method, args ->
        if (method.name == "getClass") {
            println("PROXY: #getClass was called on proxy of type $type.")
            return@InvocationHandler type.jvmErasure.java

        } else if (method.name == "toString" && method.parameters.isEmpty()
            && method.returnType == String::class.java
        ) {
            return@InvocationHandler "Proxy<$type>"

        } else if (method.name == "equals" && method.returnType == Boolean::class.java) {
            println("PROXY: #equals was called on proxy of type $type.")
            return@InvocationHandler proxy === args[0]

        } else {
            throw UnsupportedOperationException("This method should not have been called: $method")
        }
    }
)

In the beginning I thought my method calls failed, and I am still unsure, if this is not the case. However the debugging of the JVM does not stop on any invokation, which suggests to me, that I am doing something else wrong with the proxy.

How can I make the proxy fit the expected constructor argument?

My constructor call is made like this:

private fun <T> newInstanceCatching(
    className: String,
    calledConstructor: KFunction<T>,
    params: Map<KParameter, Any?>
): T = try {
    calledConstructor.callBy(params)
} catch (e: Exception) {
    val readableParams = params.map { "\n${it.key.name}: ${it.key.type} = ${it.value}" }
    println("Failed to instantiate: $className by $calledConstructor with parameters: $readableParams")
    throw e
}

As mentioned, this works for actual instances as constructor parameters, just not when at least one parameter is a proxy.





Aucun commentaire:

Enregistrer un commentaire