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 {