Implement proxy for custom interfaces

This commit is contained in:
Ivan Kylchik
2020-08-30 21:06:36 +03:00
committed by TeamCityServer
parent 96a090f2ab
commit 1e5096294e
6 changed files with 114 additions and 57 deletions
@@ -16,10 +16,9 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.interpreter.builtins.*
import org.jetbrains.kotlin.ir.interpreter.exceptions.*
import org.jetbrains.kotlin.ir.interpreter.intrinsics.IntrinsicEvaluator
import org.jetbrains.kotlin.ir.interpreter.proxy.CommonProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.LambdaProxy.Companion.asProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.CommonProxy.Companion.asProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.proxy.unwrap
import org.jetbrains.kotlin.ir.interpreter.proxy.wrap
import org.jetbrains.kotlin.ir.interpreter.stack.StackImpl
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.*
@@ -93,42 +92,17 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
}
}
internal fun IrFunction.interpret(valueArguments: List<Variable>): Any? {
internal fun IrFunction.interpret(valueArguments: List<Variable>, expectedResultClass: Class<*> = Any::class.java): Any? {
val returnLabel = stack.newFrame(initPool = valueArguments) {
this@interpret.interpret()
}
return when (returnLabel.returnLabel) {
ReturnLabel.REGULAR -> stack.popReturnValue().unwrap(this@IrInterpreter)
ReturnLabel.REGULAR -> stack.popReturnValue().wrap(this@IrInterpreter, expectedResultClass)
ReturnLabel.EXCEPTION -> throw stack.popReturnValue() as ExceptionState
else -> TODO("$returnLabel not supported as result of interpretation")
}
}
internal fun State.wrapAsProxyIfNeeded(calledFromBuiltIns: Boolean = false): Any? {
return when (this) {
is ExceptionState -> this
is Wrapper -> this.value
is Primitive<*> -> this.value
is Common -> CommonProxy(this, this@IrInterpreter, calledFromBuiltIns)
is Lambda -> this.asProxy(this@IrInterpreter)
else -> throw AssertionError("${this::class} is unsupported as argument for wrap function")
}
}
internal fun IrFunction.getArgsForMethodInvocation(args: List<Variable>): List<Any?> {
val argsValues = args.map { it.state.wrapAsProxyIfNeeded() }.toMutableList()
// TODO if vararg isn't last parameter
// must convert vararg array into separated elements for correct invoke
if (this.valueParameters.lastOrNull()?.varargElementType != null) {
val varargValue = argsValues.last()
argsValues.removeAt(argsValues.size - 1)
argsValues.addAll(varargValue as Array<out Any?>)
}
return argsValues
}
private fun IrElement.interpret(): ExecutionResult {
try {
incrementAndCheckCommands()
@@ -192,7 +166,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
private fun MethodHandle?.invokeMethod(irFunction: IrFunction): ExecutionResult {
this ?: return handleIntrinsicMethods(irFunction)
val argsForMethodInvocation = irFunction.getArgsForMethodInvocation(stack.getAll())
val argsForMethodInvocation = irFunction.getArgsForMethodInvocation(this@IrInterpreter, this.type(), stack.getAll())
val result = withExceptionHandler { this.invokeWithArguments(argsForMethodInvocation) }
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
@@ -212,7 +186,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
val receiverType = irFunction.dispatchReceiverParameter?.type
val argsType = listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type }
val argsValues = args.map { it.wrapAsProxyIfNeeded(methodName !in setOf("plus", IrBuiltIns.OperatorNames.EQEQ)) }
val argsValues = args.map { it.wrap(this, calledFromBuiltIns = methodName !in setOf("plus", IrBuiltIns.OperatorNames.EQEQ)) }
fun IrType.getOnlyName(): String {
return when {
@@ -734,7 +708,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
is Primitive<*> -> arrayToList(result.value)
is Common -> when {
result.irClass.defaultType.isUnsignedArray() -> arrayToList((result.fields.single().state as Primitive<*>).value)
else -> listOf(CommonProxy(result, this))
else -> listOf(result.asProxy(this))
}
else -> listOf(result)
}
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.proxy.wrap
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
@@ -25,6 +26,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.lang.invoke.MethodType
internal fun IrFunction.getDispatchReceiver(): IrValueParameterSymbol? = this.dispatchReceiverParameter?.symbol
@@ -56,6 +58,9 @@ internal fun State.toIrExpression(expression: IrExpression): IrExpression {
}
}
/**
* Convert object from outer world to state
*/
internal fun Any?.toState(irType: IrType): State {
return when (this) {
is Proxy -> this.state
@@ -225,4 +230,20 @@ inline fun withExceptionHandler(block: () -> Any?): Any? {
} catch (e: Throwable) {
e.throwAsUserException()
}
}
internal fun IrFunction.getArgsForMethodInvocation(interpreter: IrInterpreter, methodType: MethodType, args: List<Variable>): List<Any?> {
val argsValues = args
.mapIndexed { index, variable -> variable.state.wrap(interpreter, methodType.parameterType(index)) }
.toMutableList()
// TODO if vararg isn't last parameter
// must convert vararg array into separated elements for correct invoke
if (this.valueParameters.lastOrNull()?.varargElementType != null) {
val varargValue = argsValues.last()
argsValues.removeAt(argsValues.size - 1)
argsValues.addAll(varargValue as Array<out Any?>)
}
return argsValues
}
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.ir.interpreter.getDispatchReceiver
import org.jetbrains.kotlin.ir.interpreter.internalName
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.Common
import org.jetbrains.kotlin.ir.interpreter.toState
import org.jetbrains.kotlin.ir.util.isFakeOverriddenFromAny
/**
@@ -18,12 +19,12 @@ import org.jetbrains.kotlin.ir.util.isFakeOverriddenFromAny
* return super.toString()
* }
*/
internal class CommonProxy(override val state: Common, override val interpreter: IrInterpreter, private val calledFromBuiltIns: Boolean = false): Proxy {
internal class CommonProxy private constructor(
override val state: Common, override val interpreter: IrInterpreter, private val calledFromBuiltIns: Boolean = false
) : Proxy {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CommonProxy
if (other !is Proxy) return false
val valueArguments = mutableListOf<Variable>()
val equalsFun = state.getEqualsFunction()
@@ -54,4 +55,34 @@ internal class CommonProxy(override val state: Common, override val interpreter:
toStringFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) }
return with(interpreter) { toStringFun.interpret(valueArguments) } as String
}
companion object {
internal fun Common.asProxy(interpreter: IrInterpreter, extendFrom: Class<*>? = null, calledFromBuiltIns: Boolean = false): Any {
val commonProxy = CommonProxy(this, interpreter, calledFromBuiltIns)
val interfaces = when (extendFrom) {
null, Object::class.java -> arrayOf(Proxy::class.java)
else -> arrayOf(extendFrom, Proxy::class.java)
}
return java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), interfaces)
{ /*proxy*/_, method, args ->
when {
method.declaringClass == Proxy::class.java && method.name == "getState" -> commonProxy.state
method.declaringClass == Proxy::class.java && method.name == "getInterpreter" -> commonProxy.interpreter
method.name == "equals" && method.parameterTypes.single().isObject() -> commonProxy.equals(args.single())
method.name == "hashCode" && method.parameterTypes.isEmpty() -> commonProxy.hashCode()
method.name == "toString" && method.parameterTypes.isEmpty() -> commonProxy.toString()
else -> {
val irFunction = commonProxy.state.getIrFunction(method)
?: throw AssertionError("Cannot find method $method in ${commonProxy.state}")
val valueArguments = mutableListOf<Variable>()
valueArguments += Variable(irFunction.getDispatchReceiver()!!, commonProxy.state)
valueArguments += irFunction.valueParameters
.mapIndexed { index, parameter -> Variable(parameter.symbol, args[index].toState(parameter.type)) }
with(interpreter) { irFunction.interpret(valueArguments, method.returnType) }
}
}
}
}
}
}
@@ -11,12 +11,12 @@ import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.Lambda
import org.jetbrains.kotlin.ir.interpreter.toState
internal class LambdaProxy(override val state: Lambda, override val interpreter: IrInterpreter): Proxy {
internal class LambdaProxy private constructor(
override val state: Lambda, override val interpreter: IrInterpreter
) : Proxy {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LambdaProxy
if (other !is Proxy) return false
return state == other.state
}
@@ -41,17 +41,19 @@ internal class LambdaProxy(override val state: Lambda, override val interpreter:
this.isKFunction -> Class.forName("kotlin.reflect.KFunction")
else -> throw InternalError("Cannot handle lambda $this as proxy")
}
return java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), arrayOf(functionClass))
return java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), arrayOf(functionClass, Proxy::class.java))
{ proxy, method, args ->
when {
method.name == "invoke" && method.parameterTypes.single().isObject() -> {
val valueArguments = this.irFunction.valueParameters
.mapIndexed { index, parameter -> Variable(parameter.symbol, args[index].toState(parameter.type)) }
with(interpreter) { lambdaProxy.state.irFunction.interpret(valueArguments) }
}
method.declaringClass == Proxy::class.java && method.name == "getState" -> lambdaProxy.state
method.declaringClass == Proxy::class.java && method.name == "getInterpreter" -> lambdaProxy.interpreter
method.name == "equals" && method.parameterTypes.single().isObject() -> lambdaProxy.equals(args.single())
method.name == "hashCode" && method.parameterTypes.isEmpty() -> lambdaProxy.hashCode()
method.name == "toString" && method.parameterTypes.isEmpty() -> lambdaProxy.toString()
method.name == "invoke" && method.parameterTypes.single().isObject() -> {
val valueArguments = this.irFunction.valueParameters
.mapIndexed { index, parameter -> Variable(parameter.symbol, args[index].toState(parameter.type)) }
with(interpreter) { lambdaProxy.state.irFunction.interpret(valueArguments, method.returnType) }
}
else -> throw InternalError("Cannot invoke method ${method.name} from lambda $this")
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.ir.interpreter.proxy
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.proxy.CommonProxy.Companion.asProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.LambdaProxy.Companion.asProxy
import org.jetbrains.kotlin.ir.interpreter.state.*
@@ -18,14 +19,17 @@ internal interface Proxy {
override fun toString(): String
}
internal fun State.unwrap(interpreter: IrInterpreter): Any? {
/**
* Prepare state object to be passed in outer world
*/
internal fun State.wrap(interpreter: IrInterpreter, extendFrom: Class<*>? = null, calledFromBuiltIns: Boolean = false): Any? {
return when (this) {
is Proxy -> this.state
is Primitive<*> -> this.value
is Common -> CommonProxy(this, interpreter)
is Lambda -> this.asProxy(interpreter)
is ExceptionState -> this
is Wrapper -> this.value
else -> throw AssertionError("Unsupported state for proxy unwrap: ${this::class.java}")
is Primitive<*> -> this.value
is Common -> this.asProxy(interpreter, extendFrom, calledFromBuiltIns)
is Lambda -> this.asProxy(interpreter)
else -> throw AssertionError("${this::class} is unsupported as argument for wrap function")
}
}
@@ -6,8 +6,13 @@
package org.jetbrains.kotlin.ir.interpreter.state
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.interpreter.getLastOverridden
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
internal class Common private constructor(override val irClass: IrClass, override val fields: MutableList<Variable>) : Complex {
override var superWrapperClass: Wrapper? = null
@@ -16,12 +21,32 @@ internal class Common private constructor(override val irClass: IrClass, overrid
constructor(irClass: IrClass) : this(irClass, mutableListOf())
override fun toString(): String {
return "Common(obj='${irClass.fqNameForIrSerialization}', values=$fields)"
}
fun copyFieldsFrom(state: Complex) {
this.fields.addAll(state.fields)
superWrapperClass = state.superWrapperClass ?: state as? Wrapper
}
// This method is used to get correct java method name
private fun getKotlinName(declaringClassName: String, methodName: String): String {
return when {
// TODO see specialBuiltinMembers.kt
//"kotlin.collections.Map.<get-entries>" -> "entrySet"
//"kotlin.collections.Map.<get-keys>" -> "keySet"
declaringClassName == "java.lang.CharSequence" && methodName == "charAt" -> "get"
//"kotlin.collections.MutableList.removeAt" -> "remove"
else -> methodName
}
}
fun getIrFunction(method: java.lang.reflect.Method): IrFunction? {
val methodName = getKotlinName(method.declaringClass.name, method.name)
return when (val declaration = irClass.declarations.singleOrNull { it.nameForIrSerialization.asString() == methodName }) {
is IrProperty -> declaration.getter
else -> declaration as? IrFunction
}
}
override fun toString(): String {
return "Common(obj='${irClass.fqNameForIrSerialization}', values=$fields)"
}
}