From 6411d095799a6803164f0e5611e15504fd3872a2 Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Thu, 8 Apr 2021 13:50:22 +0300 Subject: [PATCH] Rewrite wrap function to take into account function name This is needed to understand whenever arrays must be unwrapped or not. For now, in case of kotlin.Array.set and kotlin.Pair. calls it is considered to remain arrays as Primitive class to save their irType info. --- .../kotlin/ir/interpreter/CallInterceptor.kt | 4 +- .../jetbrains/kotlin/ir/interpreter/Utils.kt | 4 +- .../ir/interpreter/proxy/CommonProxy.kt | 41 ++++++++++--------- .../kotlin/ir/interpreter/proxy/Proxy.kt | 32 +++++++++++---- .../kotlin/ir/interpreter/stack/CallStack.kt | 1 + .../kotlin/ir/interpreter/stack/Frame.kt | 6 ++- 6 files changed, 55 insertions(+), 33 deletions(-) diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/CallInterceptor.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/CallInterceptor.kt index 86b6eebf369..339e7aa67a3 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/CallInterceptor.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/CallInterceptor.kt @@ -54,7 +54,7 @@ internal class DefaultCallInterceptor(override val interpreter: IrInterpreter) : return interpreter.withNewCallStack(irFunction) { this@withNewCallStack.environment.callStack.addInstruction(CompoundInstruction(irFunction)) valueArguments.forEach { this@withNewCallStack.environment.callStack.addVariable(it) } - }.wrap(this@DefaultCallInterceptor, expectedResultClass) + }.wrap(this@DefaultCallInterceptor, remainArraysAsIs = true, extendFrom = expectedResultClass) } override fun interceptCall(call: IrCall, irFunction: IrFunction, receiver: State?, args: List, defaultAction: () -> Unit) { @@ -150,7 +150,7 @@ internal class DefaultCallInterceptor(override val interpreter: IrInterpreter) : val receiverType = irFunction.dispatchReceiverParameter?.type val argsType = listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type } - val argsValues = args.map { it.wrap(this, calledFromBuiltIns = methodName !in setOf("plus", IrBuiltIns.OperatorNames.EQEQ)) } + val argsValues = args.wrap(this, irFunction) fun IrType.getOnlyName(): String { return when { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt index fdff0494ff3..5981888cdda 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt @@ -282,9 +282,7 @@ internal fun IrFunction?.checkCast(environment: IrInterpreterEnvironment): Boole internal fun IrFunction.getArgsForMethodInvocation( callInterceptor: CallInterceptor, methodType: MethodType, args: List ): List { - val argsValues = args - .mapIndexed { index, state -> state.wrap(callInterceptor, methodType.parameterType(index)) } - .toMutableList() + val argsValues = args.wrap(callInterceptor, this, methodType).toMutableList() // TODO if vararg isn't last parameter // must convert vararg array into separated elements for correct invoke diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt index fcd031fdb8d..96e7a6f4440 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.ir.interpreter.proxy +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.interpreter.* import org.jetbrains.kotlin.ir.interpreter.CallInterceptor import org.jetbrains.kotlin.ir.interpreter.getDispatchReceiver @@ -13,22 +14,28 @@ import org.jetbrains.kotlin.ir.interpreter.state.Common import org.jetbrains.kotlin.ir.interpreter.toState import org.jetbrains.kotlin.ir.util.isFakeOverriddenFromAny -/** - * calledFromBuiltIns - used to avoid cyclic calls. For example: - * override fun toString(): String { - * return super.toString() - * } - */ -internal class CommonProxy private constructor( - override val state: Common, override val callInterceptor: CallInterceptor, private val calledFromBuiltIns: Boolean = false -) : Proxy { +internal class CommonProxy private constructor(override val state: Common, override val callInterceptor: CallInterceptor) : Proxy { + private fun defaultEquals(other: Proxy): Boolean = this.state === other.state + private fun defaultHashCode(): Int = System.identityHashCode(state) + private fun defaultToString(): String = "${state.irClass.internalName()}@" + hashCode().toString(16).padStart(8, '0') + + /** + * This check used to avoid cyclic calls. For example: + * override fun toString(): String = super.toString() + */ + private fun IrFunction.wasAlreadyCalled(): Boolean { + val anyParameter = this.getLastOverridden().dispatchReceiverParameter!!.symbol + if (callInterceptor.environment.callStack.containsVariable(anyParameter)) return true + return this == callInterceptor.environment.callStack.getCurrentFrameOwner() + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Proxy) return false val valueArguments = mutableListOf() val equalsFun = state.getEqualsFunction() - if (equalsFun.isFakeOverriddenFromAny() || calledFromBuiltIns) return this.state === other.state + if (equalsFun.isFakeOverriddenFromAny() || equalsFun.wasAlreadyCalled()) return defaultEquals(other) equalsFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } valueArguments.add(Variable(equalsFun.valueParameters.single().symbol, other.state)) @@ -39,7 +46,7 @@ internal class CommonProxy private constructor( override fun hashCode(): Int { val valueArguments = mutableListOf() val hashCodeFun = state.getHashCodeFunction() - if (hashCodeFun.isFakeOverriddenFromAny() || calledFromBuiltIns) return System.identityHashCode(state) + if (hashCodeFun.isFakeOverriddenFromAny() || hashCodeFun.wasAlreadyCalled()) return defaultHashCode() hashCodeFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } return callInterceptor.interceptProxy(hashCodeFun, valueArguments) as Int @@ -48,24 +55,20 @@ internal class CommonProxy private constructor( override fun toString(): String { val valueArguments = mutableListOf() val toStringFun = state.getToStringFunction() - if (toStringFun.isFakeOverriddenFromAny() || calledFromBuiltIns) { - return "${state.irClass.internalName()}@" + hashCode().toString(16).padStart(8, '0') - } + if (toStringFun.isFakeOverriddenFromAny() || toStringFun.wasAlreadyCalled()) return defaultToString() toStringFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } return callInterceptor.interceptProxy(toStringFun, valueArguments) as String } companion object { - internal fun Common.asProxy( - callInterceptor: CallInterceptor, extendFrom: Class<*>? = null, calledFromBuiltIns: Boolean = false - ): Any { - val commonProxy = CommonProxy(this, callInterceptor, calledFromBuiltIns) - + internal fun Common.asProxy(callInterceptor: CallInterceptor, extendFrom: Class<*>? = null): Any { + val commonProxy = CommonProxy(this, callInterceptor) 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 { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/Proxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/Proxy.kt index 5d1c6bbec36..d33bb3bd199 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/Proxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/Proxy.kt @@ -5,12 +5,16 @@ package org.jetbrains.kotlin.ir.interpreter.proxy +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.interpreter.CallInterceptor -import org.jetbrains.kotlin.ir.interpreter.IrInterpreter +import org.jetbrains.kotlin.ir.interpreter.isPrimitiveArray import org.jetbrains.kotlin.ir.interpreter.proxy.CommonProxy.Companion.asProxy import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.ReflectionProxy.Companion.asProxy import org.jetbrains.kotlin.ir.interpreter.state.* import org.jetbrains.kotlin.ir.interpreter.state.reflection.ReflectionState +import org.jetbrains.kotlin.ir.types.isArray +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import java.lang.invoke.MethodType internal interface Proxy { val state: State @@ -21,20 +25,32 @@ internal interface Proxy { override fun toString(): String } -/** - * Prepare state object to be passed in outer world - */ -internal fun State.wrap(callInterceptor: CallInterceptor, extendFrom: Class<*>? = null, calledFromBuiltIns: Boolean = false): Any? { +internal fun State.wrap(callInterceptor: CallInterceptor, remainArraysAsIs: Boolean, extendFrom: Class<*>? = null): Any? { return when (this) { is ExceptionState -> this is Wrapper -> this.value - is Primitive<*> -> this.value - is Common -> this.asProxy(callInterceptor, extendFrom, calledFromBuiltIns) + is Primitive<*> -> when { + this.type.isArray() || this.type.isPrimitiveArray() -> if (remainArraysAsIs) this else this.value + else -> this.value + } + is Common -> this.asProxy(callInterceptor, extendFrom) is ReflectionState -> this.asProxy(callInterceptor) else -> throw AssertionError("${this::class} is unsupported as argument for wrap function") } } +/** + * Prepare state object to be passed in outer world + */ +internal fun List.wrap(callInterceptor: CallInterceptor, irFunction: IrFunction, methodType: MethodType? = null): List { + val name = irFunction.fqNameWhenAvailable?.asString() + return this.mapIndexed { index, state -> + // don't get arrays from Primitive in case of "set" and "Pair."; information about type will be lost + val unwrapArrays = name?.let { (it == "kotlin.Array.set" && index != 0) || it == "kotlin.Pair." } ?: false + state.wrap(callInterceptor, unwrapArrays, methodType?.parameterType(index)) + } +} + internal fun Class<*>.isObject(): Boolean { - return this.name == "java.lang.Object" + return this == java.lang.Object::class.java } \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt index 0b3cbc9e696..ab43a53232e 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt @@ -175,6 +175,7 @@ internal class CallStack { } fun getVariable(symbol: IrSymbol): Variable = getCurrentFrame().getVariable(symbol) + fun containsVariable(symbol: IrSymbol): Boolean = getCurrentFrame().containsVariable(symbol) fun storeUpValues(state: StateWithClosure) { // TODO save only necessary declarations diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt index cc8067f53c6..946532b1375 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt @@ -66,10 +66,14 @@ internal class Frame(subFrame: SubFrame, val irFile: IrFile? = null) { } fun getVariable(symbol: IrSymbol): Variable { - return innerStack.reversed().firstNotNullResult { it.getVariable(symbol) } + return tryToGetVariable(symbol) ?: throw InterpreterError("$symbol not found") // TODO better message } + fun containsVariable(symbol: IrSymbol): Boolean = tryToGetVariable(symbol) != null + + private fun tryToGetVariable(symbol: IrSymbol): Variable? = innerStack.reversed().firstNotNullResult { it.getVariable(symbol) } + fun getAll(): List = innerStack.flatMap { it.getAll() } private fun getLineNumberForCurrentInstruction(): String {