Refactor entire IrInterpreter to achieve better code quality

This commit is contained in:
Ivan Kylchik
2021-03-17 19:59:53 +03:00
committed by TeamCityServer
parent 3294af5e49
commit c8b268a789
20 changed files with 1459 additions and 1085 deletions
@@ -0,0 +1,436 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
import org.jetbrains.kotlin.ir.interpreter.builtins.evaluateIntrinsicAnnotation
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.hasAnnotation
internal fun unfoldInstruction(element: IrElement?, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
when (element) {
null -> return
is IrSimpleFunction -> unfoldFunction(element, environment)
is IrConstructor -> unfoldConstructor(element, callStack)
is IrCall -> unfoldCall(element, callStack)
is IrConstructorCall -> unfoldConstructorCall(element, callStack)
is IrEnumConstructorCall -> unfoldEnumConstructorCall(element, callStack)
is IrDelegatingConstructorCall -> unfoldDelegatingConstructorCall(element, callStack)
is IrInstanceInitializerCall -> unfoldInstanceInitializerCall(element, callStack)
is IrField -> unfoldField(element, callStack)
is IrBody -> unfoldBody(element, callStack)
is IrBlock -> unfoldBlock(element, callStack)
is IrReturn -> unfoldReturn(element, callStack)
is IrSetField -> unfoldSetField(element, callStack)
is IrGetField -> callStack.addInstruction(SimpleInstruction(element))
is IrGetValue -> unfoldGetValue(element, environment)
is IrGetObjectValue -> unfoldGetObjectValue(element, environment)
is IrGetEnumValue -> unfoldGetEnumValue(element, environment)
is IrEnumEntry -> unfoldEnumEntry(element, environment)
is IrConst<*> -> callStack.addInstruction(SimpleInstruction(element))
is IrVariable -> unfoldVariable(element, callStack)
is IrSetValue -> unfoldSetValue(element, callStack)
is IrTypeOperatorCall -> unfoldTypeOperatorCall(element, callStack)
is IrBranch -> unfoldBranch(element, callStack)
is IrWhileLoop -> unfoldWhileLoop(element, callStack)
is IrDoWhileLoop -> unfoldDoWhileLoop(element, callStack)
is IrWhen -> unfoldWhen(element, callStack)
is IrBreak -> unfoldBreak(element, callStack)
is IrContinue -> unfoldContinue(element, callStack)
is IrVararg -> unfoldVararg(element, callStack)
is IrSpreadElement -> callStack.addInstruction(CompoundInstruction(element.expression))
is IrTry -> unfoldTry(element, callStack)
is IrCatch -> unfoldCatch(element, callStack)
is IrThrow -> unfoldThrow(element, callStack)
is IrStringConcatenation -> unfoldStringConcatenation(element, environment)
is IrFunctionExpression -> callStack.addInstruction(SimpleInstruction(element))
is IrFunctionReference -> unfoldFunctionReference(element, callStack)
is IrPropertyReference -> unfoldPropertyReference(element, callStack)
is IrClassReference -> unfoldClassReference(element, callStack)
is IrComposite -> unfoldComposite(element, callStack)
else -> TODO("${element.javaClass} not supported")
}
}
private fun unfoldFunction(function: IrSimpleFunction, environment: IrInterpreterEnvironment) {
if (environment.callStack.getStackCount() >= IrInterpreterEnvironment.MAX_STACK)
return StackOverflowError().handleUserException(environment)
// SimpleInstruction with function is added in IrCall
// It will serve as endpoint for all possible calls, there we drop frame and copy result to new one
function.body?.let { environment.callStack.addInstruction(CompoundInstruction(it)) }
?: throw InterpreterError("Ir function must be with body")
}
private fun unfoldConstructor(constructor: IrConstructor, callStack: CallStack) {
// SimpleInstruction with function is added in constructor call
// It will serve as endpoint for all possible constructor calls, there we drop frame and return object
callStack.addInstruction(CompoundInstruction(constructor.body!!))
}
private fun unfoldCall(call: IrCall, callStack: CallStack) {
val function = call.symbol.owner
// new sub frame is used to store value arguments, in case then they are used in default args evaluation
callStack.newSubFrame(call, listOf())
callStack.addInstruction(SimpleInstruction(call))
unfoldValueParameters(call, callStack)
// must save receivers in memory in case then they are used in default args evaluation
call.extensionReceiver?.let {
callStack.addInstruction(SimpleInstruction(function.extensionReceiverParameter!!))
callStack.addInstruction(CompoundInstruction(it))
}
call.dispatchReceiver?.let {
callStack.addInstruction(SimpleInstruction(function.dispatchReceiverParameter!!))
callStack.addInstruction(CompoundInstruction(it))
}
}
private fun unfoldConstructorCall(constructorCall: IrFunctionAccessExpression, callStack: CallStack) {
val constructor = constructorCall.symbol.owner
callStack.newSubFrame(constructorCall, listOf()) // used to store value arguments, in case then they are use as default args
// this variable is used to create object once
callStack.addVariable(Variable(constructorCall.getThisReceiver(), Common(constructor.parentAsClass)))
callStack.addInstruction(SimpleInstruction(constructorCall))
unfoldValueParameters(constructorCall, callStack)
constructorCall.dispatchReceiver?.let {
callStack.addInstruction(SimpleInstruction(constructor.dispatchReceiverParameter!!))
callStack.addInstruction(CompoundInstruction(it))
}
}
private fun unfoldEnumConstructorCall(enumConstructorCall: IrEnumConstructorCall, callStack: CallStack) {
callStack.newSubFrame(enumConstructorCall, listOf()) // used to store value arguments, in case then they are use as default args
callStack.addInstruction(SimpleInstruction(enumConstructorCall))
unfoldValueParameters(enumConstructorCall, callStack)
}
private fun unfoldDelegatingConstructorCall(delegatingConstructorCall: IrFunctionAccessExpression, callStack: CallStack) {
callStack.newSubFrame(delegatingConstructorCall, listOf()) // used to store value arguments, in case then they are use as default args
callStack.addInstruction(SimpleInstruction(delegatingConstructorCall))
unfoldValueParameters(delegatingConstructorCall, callStack)
}
private fun unfoldValueParameters(expression: IrFunctionAccessExpression, callStack: CallStack) {
fun IrValueParameter.getDefault(): IrExpressionBody? {
return defaultValue
?: (this.parent as? IrSimpleFunction)?.overriddenSymbols
?.map { it.owner.valueParameters[this.index].getDefault() }
?.firstNotNullOfOrNull { it }
}
fun getDefaultForParameterAt(index: Int): IrExpression? {
return expression.symbol.owner.valueParameters[index].getDefault()?.expression
}
val irFunction = expression.symbol.owner
val valueParametersSymbols = irFunction.valueParameters.map { it.symbol }
val valueArguments = (0 until expression.valueArgumentsCount).map { expression.getValueArgument(it) }
for (i in valueParametersSymbols.indices.reversed()) {
callStack.addInstruction(SimpleInstruction(valueParametersSymbols[i].owner))
val arg = valueArguments[i] ?: getDefaultForParameterAt(i)
when {
arg != null -> callStack.addInstruction(CompoundInstruction(arg))
else ->
// case when value parameter is vararg and it is missing
expression.getVarargType(i)?.let {
callStack.addInstruction(SimpleInstruction(IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, it)))
}
}
}
// hack for extension receiver in lambda
if (expression.valueArgumentsCount != irFunction.valueParameters.size) {
val extensionReceiver = irFunction.getExtensionReceiver() ?: return
callStack.addInstruction(SimpleInstruction(extensionReceiver.owner))
valueArguments[0]?.let { callStack.addInstruction(CompoundInstruction(it)) }
}
}
private fun unfoldInstanceInitializerCall(instanceInitializerCall: IrInstanceInitializerCall, callStack: CallStack) {
val irClass = instanceInitializerCall.classSymbol.owner
// init blocks processing
val anonymousInitializer = irClass.declarations.filterIsInstance<IrAnonymousInitializer>().filter { !it.isStatic }
anonymousInitializer.reversed().forEach { callStack.addInstruction(CompoundInstruction(it.body)) }
// properties processing
val classProperties = irClass.declarations.filterIsInstance<IrProperty>()
classProperties.filter { it.backingField?.initializer?.expression != null }.reversed()
.forEach { callStack.addInstruction(CompoundInstruction(it.backingField)) }
}
private fun unfoldField(field: IrField, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(field))
callStack.addInstruction(CompoundInstruction(field.initializer?.expression))
}
private fun unfoldBody(body: IrBody, callStack: CallStack) {
unfoldStatements(body.statements, callStack)
}
private fun unfoldBlock(block: IrBlock, callStack: CallStack) {
callStack.newSubFrame(block, listOf())
callStack.addInstruction(SimpleInstruction(block))
unfoldStatements(block.statements, callStack)
}
private fun unfoldStatements(statements: List<IrStatement>, callStack: CallStack) {
for (i in statements.indices.reversed()) {
when (val statement = statements[i]) {
is IrClass -> if (!statement.isLocal) TODO("Only local classes are supported")
is IrFunction -> if (!statement.isLocal) TODO("Only local functions are supported")
else -> callStack.addInstruction(CompoundInstruction(statement))
}
}
}
private fun unfoldReturn(expression: IrReturn, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(expression)) //2
callStack.addInstruction(CompoundInstruction(expression.value)) //1
}
private fun unfoldSetField(expression: IrSetField, callStack: CallStack) {
// receiver is null, for example, for top level fields; cannot interpret set on top level var
if (expression.receiver.let { it == null || (it.type.classifierOrNull?.owner as? IrClass)?.isObject == true }) {
error("Cannot interpret set method on top level properties")
}
callStack.addInstruction(SimpleInstruction(expression))
callStack.addInstruction(CompoundInstruction(expression.value))
}
private fun unfoldGetValue(expression: IrGetValue, environment: IrInterpreterEnvironment) {
val expectedClass = expression.type.classOrNull?.owner
// used to evaluate constants inside object
if (expectedClass != null && expectedClass.isObject && expression.symbol.owner.origin == IrDeclarationOrigin.INSTANCE_RECEIVER) {
// TODO is this correct behaviour?
return unfoldGetObjectValue(IrGetObjectValueImpl(0, 0, expectedClass.defaultType, expectedClass.symbol), environment)
}
environment.callStack.pushState(environment.callStack.getVariable(expression.symbol).state)
}
private fun unfoldGetObjectValue(expression: IrGetObjectValue, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
val objectClass = expression.symbol.owner
environment.mapOfObjects[objectClass.symbol]?.let { return callStack.pushState(it) }
callStack.newSubFrame(expression, listOf(SimpleInstruction(expression)))
when {
!objectClass.hasAnnotation(evaluateIntrinsicAnnotation) -> {
val state = Common(objectClass)
environment.mapOfObjects[objectClass.symbol] = state // must set object's state here to avoid cyclic evaluation
callStack.addVariable(Variable(objectClass.thisReceiver!!.symbol, state))
val constructor = objectClass.constructors.first()
val constructorCall = IrConstructorCallImpl.fromSymbolOwner(constructor.returnType, constructor.symbol)
callStack.addInstruction(CompoundInstruction(constructorCall))
}
}
}
private fun unfoldGetEnumValue(expression: IrGetEnumValue, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
environment.mapOfEnums[expression.symbol]?.let { return callStack.pushState(it) }
callStack.addInstruction(SimpleInstruction(expression))
val enumEntry = expression.symbol.owner
val enumClass = enumEntry.symbol.owner.parentAsClass
enumClass.declarations.filterIsInstance<IrEnumEntry>().forEach {
if (enumClass.hasAnnotation(evaluateIntrinsicAnnotation)) return@forEach callStack.addInstruction(SimpleInstruction(it))
callStack.addInstruction(CompoundInstruction(it))
}
}
private fun unfoldEnumEntry(enumEntry: IrEnumEntry, environment: IrInterpreterEnvironment) {
val enumClass = enumEntry.symbol.owner.parentAsClass
val enumEntries = enumClass.declarations.filterIsInstance<IrEnumEntry>()
val enumSuperCall = (enumClass.primaryConstructor?.body?.statements?.firstOrNull() as? IrEnumConstructorCall)
if (enumEntries.isNotEmpty() && enumSuperCall != null) {
val valueArguments = listOf(
enumEntry.name.asString().toIrConst(environment.irBuiltIns.stringType),
enumEntries.indexOf(enumEntry).toIrConst(environment.irBuiltIns.intType)
)
valueArguments.forEachIndexed { index, irConst -> enumSuperCall.putValueArgument(index, irConst) }
}
val enumConstructorCall = enumEntry.initializerExpression?.expression as? IrEnumConstructorCall
?: throw InterpreterError("Initializer at enum entry ${enumEntry.fqNameWhenAvailable} is null")
val enumClassObject = Variable(enumConstructorCall.getThisReceiver(), Common(enumEntry.correspondingClass ?: enumClass))
environment.mapOfEnums[enumEntry.symbol] = enumClassObject.state as Complex
environment.callStack.newSubFrame(enumEntry, listOf(CompoundInstruction(enumConstructorCall), SimpleInstruction(enumEntry)))
environment.callStack.addVariable(enumClassObject)
}
private fun unfoldVariable(variable: IrVariable, callStack: CallStack) {
when (variable.initializer) {
null -> callStack.addVariable(Variable(variable.symbol))
else -> {
callStack.addInstruction(SimpleInstruction(variable))
callStack.addInstruction(CompoundInstruction(variable.initializer!!))
}
}
}
private fun unfoldSetValue(expression: IrSetValue, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(expression))
callStack.addInstruction(CompoundInstruction(expression.value))
}
private fun unfoldTypeOperatorCall(element: IrTypeOperatorCall, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(element))
callStack.addInstruction(CompoundInstruction(element.argument))
}
private fun unfoldBranch(branch: IrBranch, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(branch)) //2
callStack.addInstruction(CompoundInstruction(branch.condition)) //1
}
private fun unfoldWhileLoop(loop: IrWhileLoop, callStack: CallStack) {
callStack.newSubFrame(loop, listOf())
callStack.addInstruction(SimpleInstruction(loop))
callStack.addInstruction(CompoundInstruction(loop.condition))
}
private fun unfoldDoWhileLoop(loop: IrDoWhileLoop, callStack: CallStack) {
callStack.newSubFrame(loop, listOf())
callStack.addInstruction(SimpleInstruction(loop))
callStack.addInstruction(CompoundInstruction(loop.condition))
callStack.addInstruction(CompoundInstruction(loop.body))
}
private fun unfoldWhen(element: IrWhen, callStack: CallStack) {
// new sub frame to drop it after
callStack.newSubFrame(element, element.branches.map { CompoundInstruction(it) } + listOf(SimpleInstruction(element)))
}
private fun unfoldContinue(element: IrContinue, callStack: CallStack) {
callStack.unrollInstructionsForBreakContinue(element)
}
private fun unfoldBreak(element: IrBreak, callStack: CallStack) {
callStack.unrollInstructionsForBreakContinue(element)
}
private fun unfoldVararg(element: IrVararg, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(element))
element.elements.reversed().forEach { callStack.addInstruction(CompoundInstruction(it)) }
}
private fun unfoldTry(element: IrTry, callStack: CallStack) {
callStack.newSubFrame(element, listOf())
callStack.addInstruction(SimpleInstruction(element))
callStack.addInstruction(CompoundInstruction(element.tryResult))
}
private fun unfoldCatch(element: IrCatch, callStack: CallStack) {
val exceptionState = callStack.peekState() as? ExceptionState ?: return
if (exceptionState.isSubtypeOf(element.catchParameter.type)) {
callStack.popState()
val frameOwner = callStack.getCurrentFrameOwner() as IrTry
callStack.dropSubFrame() // drop other catch blocks
callStack.newSubFrame(element, listOf()) // new frame with IrTry instruction to interpret finally block at the end
callStack.addVariable(Variable(element.catchParameter.symbol, exceptionState))
callStack.addInstruction(SimpleInstruction(frameOwner))
callStack.addInstruction(CompoundInstruction(element.result))
}
}
private fun unfoldThrow(expression: IrThrow, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(expression))
callStack.addInstruction(CompoundInstruction(expression.value))
}
private fun unfoldStringConcatenation(expression: IrStringConcatenation, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
callStack.newSubFrame(expression, listOf())
callStack.addInstruction(SimpleInstruction(expression))
// this callback is used to check the need for an explicit toString call
val explicitToStringCheck = fun() {
when (val state = callStack.peekState()) {
is Common -> {
callStack.popState()
// TODO this check can be dropped after serialization introduction
// for now declarations in unsigned class don't have bodies and must be treated separately
if (state.irClass.defaultType.isUnsigned()) {
val result = when (val value = (state.fields.single().state as Primitive<*>).value) {
is Byte -> value.toUByte().toString()
is Short -> value.toUShort().toString()
is Int -> value.toUInt().toString()
else -> (value as Number).toLong().toULong().toString()
}
return callStack.pushState(result.toState(environment.irBuiltIns.stringType))
}
val toStringFun = state.getToStringFunction()
val receiver = toStringFun.dispatchReceiverParameter!!
val toStringCall = IrCallImpl.fromSymbolOwner(0, 0, environment.irBuiltIns.stringType, toStringFun.symbol)
toStringCall.dispatchReceiver = IrConstImpl.constNull(0, 0, receiver.type) // just stub receiver
callStack.newSubFrame(toStringCall, listOf(SimpleInstruction(toStringCall)))
callStack.pushState(state)
}
}
}
expression.arguments.reversed().forEach {
callStack.addInstruction(CustomInstruction(explicitToStringCheck))
callStack.addInstruction(CompoundInstruction(it))
}
}
private fun unfoldComposite(element: IrComposite, callStack: CallStack) {
when (element.origin) {
IrStatementOrigin.DESTRUCTURING_DECLARATION -> element.statements.reversed()
.forEach { callStack.addInstruction(CompoundInstruction(it)) }
null -> element.statements.reversed()
.forEach { callStack.addInstruction(CompoundInstruction(it)) } // is null for body of do while loop
else -> TODO("${element.origin} not implemented")
}
}
private fun unfoldFunctionReference(reference: IrFunctionReference, callStack: CallStack) {
val function = reference.symbol.owner
callStack.newSubFrame(reference, listOf())
callStack.addInstruction(SimpleInstruction(reference))
reference.extensionReceiver?.let {
callStack.addInstruction(SimpleInstruction(function.extensionReceiverParameter!!))
callStack.addInstruction(CompoundInstruction(it))
}
reference.dispatchReceiver?.let {
callStack.addInstruction(SimpleInstruction(function.dispatchReceiverParameter!!))
callStack.addInstruction(CompoundInstruction(it))
}
}
private fun unfoldPropertyReference(propertyReference: IrPropertyReference, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(propertyReference))
callStack.addInstruction(CompoundInstruction(propertyReference.dispatchReceiver))
}
private fun unfoldClassReference(classReference: IrClassReference, callStack: CallStack) {
callStack.addInstruction(SimpleInstruction(classReference))
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.state.Complex
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.isSubclassOf
internal class IrInterpreterEnvironment(val irBuiltIns: IrBuiltIns, val callStack: CallStack) {
val irExceptions = mutableListOf<IrClass>()
var mapOfEnums = mutableMapOf<IrSymbol, Complex>()
var mapOfObjects = mutableMapOf<IrSymbol, Complex>()
private constructor(environment: IrInterpreterEnvironment) : this(environment.irBuiltIns, CallStack()) {
irExceptions.addAll(environment.irExceptions)
mapOfEnums = environment.mapOfEnums
mapOfObjects = environment.mapOfObjects
}
constructor(irModule: IrModuleFragment) : this(irModule.irBuiltins, CallStack()) {
irExceptions.addAll(
irModule.files
.flatMap { it.declarations }
.filterIsInstance<IrClass>()
.filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) }
)
}
fun copyWithNewCallStack(): IrInterpreterEnvironment {
return IrInterpreterEnvironment(this)
}
companion object {
const val MAX_STACK = 10_000
const val MAX_COMMANDS = 1_000_000
}
}
@@ -1,89 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter
import org.jetbrains.kotlin.ir.interpreter.stack.Stack
import org.jetbrains.kotlin.ir.interpreter.state.Primitive
import org.jetbrains.kotlin.ir.interpreter.state.isSubtypeOf
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.render
enum class ReturnLabel {
REGULAR, RETURN, BREAK_LOOP, BREAK_WHEN, CONTINUE, EXCEPTION
}
open class ExecutionResult(val returnLabel: ReturnLabel, private val owner: IrElement? = null) {
fun getNextLabel(irElement: IrElement, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
return when (returnLabel) {
ReturnLabel.RETURN -> when (irElement) {
is IrCall, is IrReturnableBlock, is IrSimpleFunction -> if (owner == irElement) Next else this
else -> this
}
ReturnLabel.BREAK_WHEN -> when (irElement) {
is IrWhen -> Next
else -> this
}
ReturnLabel.BREAK_LOOP -> when (irElement) {
is IrWhileLoop, is IrDoWhileLoop -> if (owner == irElement) Next else this
else -> this
}
ReturnLabel.CONTINUE -> when (irElement) {
is IrWhileLoop -> if (owner == irElement) irElement.interpret() else this
is IrDoWhileLoop -> {
if (owner != irElement) return this
error("Continue must be handled inside DoWhile interpreter function")
}
else -> this
}
ReturnLabel.EXCEPTION -> Exception
ReturnLabel.REGULAR -> Next
}
}
fun addOwnerInfo(owner: IrElement): ExecutionResult {
return ExecutionResult(returnLabel, owner)
}
}
inline fun ExecutionResult.check(
vararg toCheckLabel: ReturnLabel = arrayOf(ReturnLabel.REGULAR), returnBlock: (ExecutionResult) -> Unit
): ExecutionResult {
if (this.returnLabel !in toCheckLabel) returnBlock(this)
return this
}
/**
* This method is analog of `checkcast` jvm bytecode operation. Throw exception whenever actual type is not a subtype of expected.
*/
internal fun ExecutionResult.implicitCastIfNeeded(expectedType: IrType, actualType: IrType, stack: Stack): ExecutionResult {
if (actualType.classifierOrNull !is IrTypeParameterSymbol) return this
if (expectedType.classifierOrFail is IrTypeParameterSymbol) return this
val actualState = stack.peekReturnValue()
if (actualState is Primitive<*> && actualState.value == null) return this // this is handled as NullPointerException
if (!actualState.isSubtypeOf(expectedType)) {
val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable
ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}").throwAsUserException()
}
return this
}
object Next : ExecutionResult(ReturnLabel.REGULAR)
object Return : ExecutionResult(ReturnLabel.RETURN)
object BreakLoop : ExecutionResult(ReturnLabel.BREAK_LOOP)
object BreakWhen : ExecutionResult(ReturnLabel.BREAK_WHEN)
object Continue : ExecutionResult(ReturnLabel.CONTINUE)
object Exception : ExecutionResult(ReturnLabel.EXCEPTION)
@@ -13,12 +13,13 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.interpreter.builtins.evaluateIntrinsicAnnotation
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.interpreter.state.reflection.KTypeState
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
@@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.lang.invoke.MethodType
import kotlin.math.min
@@ -53,6 +53,9 @@ internal fun State.toIrExpression(expression: IrExpression): IrExpression {
type.isPrimitiveType() || type.isString() -> this.value.toIrConst(type, start, end)
else -> expression // TODO support for arrays
}
is ExceptionState -> {
IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + this.getFullDescription())
}
is Complex -> {
val stateType = this.irClass.defaultType
when {
@@ -234,17 +237,51 @@ fun IrClass.internalName(): String {
return internalName.toString()
}
inline fun withExceptionHandler(block: () -> Any?): Any? {
internal inline fun withExceptionHandler(environment: IrInterpreterEnvironment, block: () -> Unit) {
try {
return block()
block()
// check if during proxy interpretation was an exception
val possibleException = environment.callStack.peekState() as? ExceptionState
if (possibleException != null) environment.callStack.dropFramesUntilTryCatch()
} catch (e: Throwable) {
e.throwAsUserException()
e.handleUserException(environment)
}
}
internal fun IrFunction.getArgsForMethodInvocation(interpreter: IrInterpreter, methodType: MethodType, args: List<Variable>): List<Any?> {
internal fun Throwable.handleUserException(environment: IrInterpreterEnvironment) {
val exceptionName = this::class.java.simpleName
val irExceptionClass = environment.irExceptions.firstOrNull { it.name.asString() == exceptionName }
?: environment.irBuiltIns.throwableClass.owner
environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace()))
environment.callStack.dropFramesUntilTryCatch()
}
/**
* This method is analog of `checkcast` jvm bytecode operation. Throw exception whenever actual type is not a subtype of expected.
*/
internal fun IrFunction?.checkCast(environment: IrInterpreterEnvironment): Boolean {
this ?: return true
val actualType = this.returnType
if (actualType.classifierOrNull !is IrTypeParameterSymbol) return true
// TODO expectedType can be missing for functions called as proxy
val expectedType = (environment.callStack.getVariable(this.symbol).state as? KTypeState)?.irType ?: return true
if (expectedType.classifierOrFail is IrTypeParameterSymbol) return true
val actualState = environment.callStack.peekState() ?: return true
if (actualState is Primitive<*> && actualState.value == null) return true // this is handled in checkNullability
if (!actualState.isSubtypeOf(expectedType)) {
val convertibleClassName = environment.callStack.popState().irClass.fqNameWhenAvailable
ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}").handleUserException(environment)
return false
}
return true
}
internal fun IrFunction.getArgsForMethodInvocation(interpreter: IrInterpreter, methodType: MethodType, args: List<State>): List<Any?> {
val argsValues = args
.mapIndexed { index, variable -> variable.state.wrap(interpreter, methodType.parameterType(index)) }
.mapIndexed { index, state -> state.wrap(interpreter, methodType.parameterType(index)) }
.toMutableList()
// TODO if vararg isn't last parameter
@@ -1,19 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter.exceptions
/**
* This class used to wrap 2 types of exceptions:
* 1. exceptions from JVM such as: ArithmeticException, StackOverflowError and others
* 2. exceptions defined by user
*/
class UserException(val exception: Throwable) : InterpreterException() {
override fun fillInStackTrace(): Throwable = this
}
fun Throwable.throwAsUserException(): Nothing {
throw UserException(this)
}
@@ -5,25 +5,24 @@
package org.jetbrains.kotlin.ir.interpreter.intrinsics
import org.jetbrains.kotlin.ir.interpreter.ExecutionResult
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundError
import org.jetbrains.kotlin.ir.interpreter.stack.Stack
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.interpreter.Instruction
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundError
internal class IntrinsicEvaluator {
fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
internal object IntrinsicEvaluator {
fun unwindInstructions(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return when {
EmptyArray.equalTo(irFunction) -> EmptyArray.evaluate(irFunction, stack, interpret)
ArrayOf.equalTo(irFunction) -> ArrayOf.evaluate(irFunction, stack, interpret)
ArrayOfNulls.equalTo(irFunction) -> ArrayOfNulls.evaluate(irFunction, stack, interpret)
EnumValues.equalTo(irFunction) -> EnumValues.evaluate(irFunction, stack, interpret)
EnumValueOf.equalTo(irFunction) -> EnumValueOf.evaluate(irFunction, stack, interpret)
EnumHashCode.equalTo(irFunction) -> EnumHashCode.evaluate(irFunction, stack, interpret)
JsPrimitives.equalTo(irFunction) -> JsPrimitives.evaluate(irFunction, stack, interpret)
ArrayConstructor.equalTo(irFunction) -> ArrayConstructor.evaluate(irFunction, stack, interpret)
SourceLocation.equalTo(irFunction) -> SourceLocation.evaluate(irFunction, stack, interpret)
AssertIntrinsic.equalTo(irFunction) -> AssertIntrinsic.evaluate(irFunction, stack, interpret)
EmptyArray.equalTo(irFunction) -> EmptyArray.unwind(irFunction, environment)
ArrayOf.equalTo(irFunction) -> ArrayOf.unwind(irFunction, environment)
ArrayOfNulls.equalTo(irFunction) -> ArrayOfNulls.unwind(irFunction, environment)
EnumValues.equalTo(irFunction) -> EnumValues.unwind(irFunction, environment)
EnumValueOf.equalTo(irFunction) -> EnumValueOf.unwind(irFunction, environment)
EnumHashCode.equalTo(irFunction) -> EnumHashCode.unwind(irFunction, environment)
JsPrimitives.equalTo(irFunction) -> JsPrimitives.unwind(irFunction, environment)
ArrayConstructor.equalTo(irFunction) -> ArrayConstructor.unwind(irFunction, environment)
SourceLocation.equalTo(irFunction) -> SourceLocation.unwind(irFunction, environment)
AssertIntrinsic.equalTo(irFunction) -> AssertIntrinsic.unwind(irFunction, environment)
else -> throw InterpreterMethodNotFoundError("Method ${irFunction.name} hasn't implemented")
}
}
@@ -5,15 +5,14 @@
package org.jetbrains.kotlin.ir.interpreter.intrinsics
import org.jetbrains.kotlin.ir.interpreter.*
import org.jetbrains.kotlin.ir.interpreter.stack.Stack
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrEnumEntry
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.interpreter.*
import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.interpreter.state.reflection.KFunctionState
import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState
import org.jetbrains.kotlin.ir.types.IrSimpleType
@@ -22,12 +21,20 @@ import org.jetbrains.kotlin.ir.types.impl.buildSimpleType
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.types.isArray
import org.jetbrains.kotlin.ir.types.isCharArray
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.types.Variance
internal sealed class IntrinsicBase {
abstract fun equalTo(irFunction: IrFunction): Boolean
abstract fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult
abstract fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment)
abstract fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction>
fun customEvaluateInstruction(irFunction: IrFunction, environment: IrInterpreterEnvironment): CustomInstruction {
return CustomInstruction {
evaluate(irFunction, environment)
environment.callStack.dropFrameAndCopyResult()
}
}
}
internal object EmptyArray : IntrinsicBase() {
@@ -36,13 +43,13 @@ internal object EmptyArray : IntrinsicBase() {
return fqName in setOf("kotlin.emptyArray", "kotlin.ArrayIntrinsicsKt.emptyArray")
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val typeArgument = irFunction.typeParameters.map { stack.getVariable(it.symbol) }.single().state as KTypeState
val returnType = (irFunction.returnType as IrSimpleType).buildSimpleType {
arguments = listOf(makeTypeProjection(typeArgument.irType, Variance.INVARIANT))
}
stack.pushReturnValue(emptyArray<Any?>().toState(returnType))
return Next
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val returnType = environment.callStack.getVariable(irFunction.symbol).state as KTypeState
environment.callStack.pushState(emptyArray<Any?>().toState(returnType.irType))
}
}
@@ -52,10 +59,14 @@ internal object ArrayOf : IntrinsicBase() {
return fqName == "kotlin.arrayOf"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val elementsVariable = irFunction.valueParameters.single().symbol
stack.pushReturnValue(stack.getVariable(elementsVariable).state)
return Next
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val elementsSymbol = irFunction.valueParameters.single().symbol
val varargVariable = environment.callStack.getVariable(elementsSymbol).state
environment.callStack.pushState(varargVariable)
}
}
@@ -65,15 +76,19 @@ internal object ArrayOfNulls : IntrinsicBase() {
return fqName == "kotlin.arrayOfNulls"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val size = stack.getVariable(irFunction.valueParameters.first().symbol).state.asInt()
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val size = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asInt()
val array = arrayOfNulls<Any?>(size)
val typeArgument = irFunction.typeParameters.map { stack.getVariable(it.symbol) }.single().state as KTypeState
val typeArgument = irFunction.typeParameters.map { environment.callStack.getVariable(it.symbol) }.single().state as KTypeState
val returnType = (irFunction.returnType as IrSimpleType).buildSimpleType {
arguments = listOf(makeTypeProjection(typeArgument.irType, Variance.INVARIANT))
}
stack.pushReturnValue(array.toState(returnType))
return Next
environment.callStack.pushState(array.toState(returnType))
}
}
@@ -83,19 +98,28 @@ internal object EnumValues : IntrinsicBase() {
return (fqName == "kotlin.enumValues" || fqName.endsWith(".values")) && irFunction.valueParameters.isEmpty()
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val enumClass = when (irFunction.fqNameWhenAvailable.toString()) {
private fun getEnumClass(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrClass {
return when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.enumValues" -> {
val kType = stack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState
val kType = environment.callStack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState
kType.irType.classOrNull!!.owner
}
else -> irFunction.parent as IrClass
}
}
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
val enumClass = getEnumClass(irFunction, environment)
val enumEntries = enumClass.declarations.filterIsInstance<IrEnumEntry>()
.map { entry -> entry.interpret().check { return it }.let { stack.popReturnValue() as Common } }
stack.pushReturnValue(enumEntries.toTypedArray().toState(irFunction.returnType))
return Next
return listOf(customEvaluateInstruction(irFunction, environment)) + enumEntries.reversed().map { CompoundInstruction(it) }
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val enumClass = getEnumClass(irFunction, environment)
val enumEntries = enumClass.declarations.filterIsInstance<IrEnumEntry>().map { environment.mapOfEnums[it.symbol] }
environment.callStack.pushState(enumEntries.toTypedArray().toState(irFunction.returnType))
}
}
@@ -105,20 +129,34 @@ internal object EnumValueOf : IntrinsicBase() {
return (fqName == "kotlin.enumValueOf" || fqName.endsWith(".valueOf")) && irFunction.valueParameters.size == 1
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val enumClass = when (irFunction.fqNameWhenAvailable.toString()) {
private fun getEnumClass(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrClass {
return when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.enumValueOf" -> {
val kType = stack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState
val kType = environment.callStack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState
kType.irType.classOrNull!!.owner
}
else -> irFunction.parent as IrClass
}
val enumEntryName = stack.getVariable(irFunction.valueParameters.first().symbol).state.asString()
val enumEntry = enumClass.declarations.filterIsInstance<IrEnumEntry>().singleOrNull { it.name.asString() == enumEntryName }
enumEntry?.interpret()?.check { return it }
?: IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName").throwAsUserException()
}
return Next
private fun getEnumEntryByName(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrEnumEntry? {
val enumClass = getEnumClass(irFunction, environment)
val enumEntryName = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asString()
val enumEntry = enumClass.declarations.filterIsInstance<IrEnumEntry>().singleOrNull { it.name.asString() == enumEntryName }
if (enumEntry == null) {
IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName").handleUserException(environment)
}
return enumEntry
}
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
val enumEntry = getEnumEntryByName(irFunction, environment) ?: return emptyList()
return listOf(customEvaluateInstruction(irFunction, environment), CompoundInstruction(enumEntry))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val enumEntry = getEnumEntryByName(irFunction, environment)!!
environment.callStack.pushState(environment.mapOfEnums[enumEntry.symbol]!!)
}
}
@@ -128,10 +166,13 @@ internal object EnumHashCode : IntrinsicBase() {
return fqName == "kotlin.Enum.hashCode"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val hashCode = stack.getAll().single().state.hashCode()
stack.pushReturnValue(hashCode.toState(irFunction.returnType))
return Next
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val hashCode = environment.callStack.getVariable(irFunction.dispatchReceiverParameter!!.symbol).state.hashCode()
environment.callStack.pushState(hashCode.toState(irFunction.returnType))
}
}
@@ -141,19 +182,22 @@ internal object JsPrimitives : IntrinsicBase() {
return fqName == "kotlin.Long.<init>" || fqName == "kotlin.Char.<init>"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.Long.<init>" -> {
val low = stack.getVariable(irFunction.valueParameters[0].symbol).state.asInt()
val high = stack.getVariable(irFunction.valueParameters[1].symbol).state.asInt()
stack.pushReturnValue((high.toLong().shl(32) + low).toState(irFunction.returnType))
val low = environment.callStack.getVariable(irFunction.valueParameters[0].symbol).state.asInt()
val high = environment.callStack.getVariable(irFunction.valueParameters[1].symbol).state.asInt()
environment.callStack.pushState((high.toLong().shl(32) + low).toState(irFunction.returnType))
}
"kotlin.Char.<init>" -> {
val value = stack.getVariable(irFunction.valueParameters[0].symbol).state.asInt()
stack.pushReturnValue(value.toChar().toState(irFunction.returnType))
val value = environment.callStack.getVariable(irFunction.valueParameters[0].symbol).state.asInt()
environment.callStack.pushState(value.toChar().toState(irFunction.returnType))
}
}
return Next
}
}
@@ -163,24 +207,36 @@ internal object ArrayConstructor : IntrinsicBase() {
return fqName.matches("kotlin\\.(Byte|Char|Short|Int|Long|Float|Double|Boolean|)Array\\.<init>".toRegex())
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
if (irFunction.valueParameters.size == 1) return listOf(customEvaluateInstruction(irFunction, environment))
val instructions = mutableListOf<Instruction>(customEvaluateInstruction(irFunction, environment))
val sizeDescriptor = irFunction.valueParameters[0].symbol
val size = stack.getVariable(sizeDescriptor).state.asInt()
val size = environment.callStack.getVariable(sizeDescriptor).state.asInt()
val initDescriptor = irFunction.valueParameters[1].symbol
val initLambda = environment.callStack.getVariable(initDescriptor).state as KFunctionState
environment.callStack.loadUpValues(initLambda)
val function = initLambda.irFunction as IrSimpleFunction
val index = initLambda.irFunction.valueParameters.single()
for (i in 0 until size) {
val call = IrCallImpl.fromSymbolOwner(0, 0, function.returnType, function.symbol)
call.putValueArgument(0, IrConstImpl.int(0, 0, index.type, i))
instructions += CompoundInstruction(call)
}
return instructions
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val sizeDescriptor = irFunction.valueParameters[0].symbol
val size = environment.callStack.getVariable(sizeDescriptor).state.asInt()
val arrayValue = MutableList<Any?>(size) { if (irFunction.returnType.isCharArray()) 0.toChar() else 0 }
if (irFunction.valueParameters.size == 2) {
val initDescriptor = irFunction.valueParameters[1].symbol
val initLambda = stack.getVariable(initDescriptor).state as KFunctionState
val index = initLambda.irFunction.valueParameters.single()
val nonLocalDeclarations = initLambda.extractNonLocalDeclarations()
for (i in 0 until size) {
val indexVar = listOf(Variable(index.symbol, i.toState(index.type)))
// TODO throw exception if label != RETURN
stack.newFrame(
asSubFrame = initLambda.irFunction.isLocal || initLambda.irFunction.isInline,
initPool = nonLocalDeclarations + indexVar
) { initLambda.irFunction.body!!.interpret() }.check(ReturnLabel.RETURN) { return it }
arrayValue[i] = stack.popReturnValue().let {
arrayValue[i] = environment.callStack.popState().let {
// TODO wrap
when (it) {
is Wrapper -> it.value
is Primitive<*> -> if (it.type.isArray() || it.type.isPrimitiveArray()) it else it.value
@@ -190,8 +246,8 @@ internal object ArrayConstructor : IntrinsicBase() {
}
}
stack.pushReturnValue(arrayValue.toPrimitiveStateArray(irFunction.parentAsClass.defaultType))
return Next
val type = environment.callStack.getVariable(irFunction.symbol).state as KTypeState
environment.callStack.pushState(arrayValue.toPrimitiveStateArray(type.irType))
}
}
@@ -201,9 +257,12 @@ internal object SourceLocation : IntrinsicBase() {
return fqName == "kotlin.experimental.sourceLocation" || fqName == "kotlin.experimental.SourceLocationKt.sourceLocation"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
stack.pushReturnValue(stack.getCurrentStackInfo().toState(irFunction.returnType))
return Next
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
return listOf(customEvaluateInstruction(irFunction, environment))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
environment.callStack.pushState(environment.callStack.getFileAndPositionInfo().toState(irFunction.returnType))
}
}
@@ -213,17 +272,22 @@ internal object AssertIntrinsic : IntrinsicBase() {
return fqName == "kotlin.PreconditionsKt.assert"
}
override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult {
val value = stack.getVariable(irFunction.valueParameters.first().symbol).state.asBoolean()
override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List<Instruction> {
if (irFunction.valueParameters.size == 1) return listOf(customEvaluateInstruction(irFunction, environment))
val messageLambda = environment.callStack.getVariable(irFunction.valueParameters.last().symbol).state as KFunctionState
val function = messageLambda.irFunction as IrSimpleFunction
val call = IrCallImpl.fromSymbolOwner(0, 0, function.returnType, function.symbol)
return listOf(customEvaluateInstruction(irFunction, environment), CompoundInstruction(call))
}
override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) {
val value = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asBoolean()
if (value) return
when (irFunction.valueParameters.size) {
1 -> if (!value) AssertionError("Assertion failed").throwAsUserException()
2 -> if (!value) {
val messageLambda = stack.getVariable(irFunction.valueParameters.last().symbol).state as KFunctionState
stack.newFrame(asSubFrame = true) { messageLambda.irFunction.body!!.interpret() }
.check(ReturnLabel.RETURN) { return it }
AssertionError(stack.popReturnValue().asString()).throwAsUserException()
}
1 -> AssertionError("Assertion failed").handleUserException(environment)
2 -> AssertionError(environment.callStack.popState().asString()).handleUserException(environment)
}
return Next
}
}
@@ -33,7 +33,7 @@ internal class CommonProxy private constructor(
equalsFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) }
valueArguments.add(Variable(equalsFun.valueParameters.single().symbol, other.state))
return with(interpreter) { equalsFun.interpret(valueArguments) } as Boolean
return with(interpreter) { equalsFun.proxyInterpret(valueArguments) } as Boolean
}
override fun hashCode(): Int {
@@ -42,7 +42,7 @@ internal class CommonProxy private constructor(
if (hashCodeFun.isFakeOverriddenFromAny() || calledFromBuiltIns) return System.identityHashCode(state)
hashCodeFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) }
return with(interpreter) { hashCodeFun.interpret(valueArguments) } as Int
return with(interpreter) { hashCodeFun.proxyInterpret(valueArguments) } as Int
}
override fun toString(): String {
@@ -53,7 +53,7 @@ internal class CommonProxy private constructor(
}
toStringFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) }
return with(interpreter) { toStringFun.interpret(valueArguments) } as String
return with(interpreter) { toStringFun.proxyInterpret(valueArguments) } as String
}
companion object {
@@ -79,7 +79,7 @@ internal class CommonProxy private constructor(
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) }
with(interpreter) { irFunction.proxyInterpret(valueArguments, method.returnType) }
}
}
}
@@ -48,7 +48,7 @@ internal class KFunctionProxy(
val extensionReceiver = state.irFunction.extensionReceiverParameter?.let { Variable(it.symbol, args[index++].toState(it.type)) }
val valueArguments = listOfNotNull(dispatchReceiver, extensionReceiver) +
state.irFunction.valueParameters.map { parameter -> Variable(parameter.symbol, args[index++].toState(parameter.type)) }
return with(interpreter) { state.irFunction.interpret(valueArguments) }
return with(interpreter) { state.irFunction.proxyInterpret(valueArguments) }
}
override fun callBy(args: Map<KParameter, Any?>): Any? {
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.ir.interpreter.proxy.reflection
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
import org.jetbrains.kotlin.ir.interpreter.proxy.wrap
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.reflection.KPropertyState
import org.jetbrains.kotlin.ir.interpreter.toState
@@ -24,7 +22,7 @@ internal open class KProperty1Proxy(
checkArguments(1, args.size)
val receiverParameter = getter.dispatchReceiverParameter ?: getter.extensionReceiverParameter
val receiver = Variable(receiverParameter!!.symbol, args[0].toState(receiverParameter.type))
return with(this@KProperty1Proxy.interpreter) { getter.interpret(listOf(receiver)) }
return with(this@KProperty1Proxy.interpreter) { getter.proxyInterpret(listOf(receiver)) }
}
override fun callBy(args: Map<KParameter, Any?>): Any? {
@@ -54,7 +52,7 @@ internal class KMutableProperty1Proxy(
val receiver = Variable(receiverParameter!!.symbol, args[0].toState(receiverParameter.type))
val valueParameter = setter.valueParameters.single()
val value = Variable(valueParameter.symbol, args[1].toState(valueParameter.type))
with(this@KMutableProperty1Proxy.interpreter) { setter.interpret(listOf(receiver, value)) }
with(this@KMutableProperty1Proxy.interpreter) { setter.proxyInterpret(listOf(receiver, value)) }
}
override fun callBy(args: Map<KParameter, Any?>) {
@@ -14,15 +14,15 @@ import kotlin.reflect.*
internal open class KProperty2Proxy(
override val state: KPropertyState, override val interpreter: IrInterpreter
) : AbstractKPropertyProxy(state, interpreter), KProperty2<Any?, Any?, Any?> {
override val getter: KProperty2.Getter<Any?, Any?, Any?> =
object : Getter(state.property.getter!!), KProperty2.Getter<Any?, Any?, Any?> {
override val getter: KProperty2.Getter<Any?, Any?, Any?>
get() = object : Getter(state.property.getter!!), KProperty2.Getter<Any?, Any?, Any?> {
override fun invoke(p1: Any?, p2: Any?): Any? = call(p1, p2)
override fun call(vararg args: Any?): Any? {
checkArguments(2, args.size)
val dispatch = Variable(getter.dispatchReceiverParameter!!.symbol, args[0].toState(getter.dispatchReceiverParameter!!.type))
val extension = Variable(getter.extensionReceiverParameter!!.symbol, args[1].toState(getter.extensionReceiverParameter!!.type))
return with(this@KProperty2Proxy.interpreter) { getter.interpret(listOf(dispatch, extension)) }
return with(this@KProperty2Proxy.interpreter) { getter.proxyInterpret(listOf(dispatch, extension)) }
}
override fun callBy(args: Map<KParameter, Any?>): Any? {
@@ -0,0 +1,201 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter.stack
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.CompoundInstruction
import org.jetbrains.kotlin.ir.interpreter.Instruction
import org.jetbrains.kotlin.ir.interpreter.SimpleInstruction
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.interpreter.state.StateWithClosure
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.fileOrNull
internal class CallStack {
private val frames = mutableListOf<Frame>()
private fun getCurrentFrame() = frames.last()
internal fun getCurrentFrameOwner() = frames.last().currentSubFrameOwner
fun newFrame(frameOwner: IrElement, instructions: List<Instruction>, irFile: IrFile? = null) {
val newFrame = SubFrame(instructions.toMutableList(), frameOwner)
frames.add(Frame(newFrame, irFile))
}
fun newFrame(frameOwner: IrFunction, instructions: List<Instruction>) {
val newFrame = SubFrame(instructions.toMutableList(), frameOwner)
frames.add(Frame(newFrame, frameOwner.fileOrNull))
}
fun newSubFrame(frameOwner: IrElement, instructions: List<Instruction>) {
val newFrame = SubFrame(instructions.toMutableList(), frameOwner)
getCurrentFrame().addSubFrame(newFrame)
}
fun dropFrame() {
frames.removeLast()
}
fun dropFrameAndCopyResult() {
val result = peekState() ?: return dropFrame()
popState()
dropFrame()
pushState(result)
}
fun dropSubFrame() {
getCurrentFrame().removeSubFrame()
}
fun returnFromFrameWithResult(irReturn: IrReturn) {
val result = popState()
var frameOwner = getCurrentFrameOwner()
while (frameOwner != irReturn.returnTargetSymbol.owner) {
when (frameOwner) {
is IrTry -> {
dropSubFrame()
pushState(result)
addInstruction(SimpleInstruction(irReturn))
addInstruction(CompoundInstruction(frameOwner.finallyExpression))
return
}
is IrCatch -> {
val tryBlock = getCurrentFrame().dropInstructions()!!.element as IrTry// last instruction in `catch` block is `try`
dropSubFrame()
pushState(result)
addInstruction(SimpleInstruction(irReturn))
addInstruction(CompoundInstruction(tryBlock.finallyExpression))
return
}
else -> {
dropSubFrame()
if (getCurrentFrame().hasNoSubFrames() && frameOwner != irReturn.returnTargetSymbol.owner) dropFrame()
frameOwner = getCurrentFrameOwner()
}
}
}
dropFrame()
// check that last frame is not a function itself; use case for proxyInterpret
if (frames.size == 0) newFrame(irReturn, emptyList()) // just stub frame
pushState(result)
}
fun unrollInstructionsForBreakContinue(breakOrContinue: IrBreakContinue) {
var frameOwner = getCurrentFrameOwner()
while (frameOwner != breakOrContinue.loop) {
when (frameOwner) {
is IrTry -> {
getCurrentFrame().removeSubFrameWithoutDataPropagation()
addInstruction(CompoundInstruction(breakOrContinue))
newSubFrame(frameOwner, listOf(SimpleInstruction(frameOwner))) // will be deleted when interpret 'try'
return
}
is IrCatch -> {
val tryInstruction = getCurrentFrame().dropInstructions()!! // last instruction in `catch` block is `try`
getCurrentFrame().removeSubFrameWithoutDataPropagation()
addInstruction(CompoundInstruction(breakOrContinue))
newSubFrame(tryInstruction.element!!, listOf(tryInstruction)) // will be deleted when interpret 'try'
return
}
else -> {
getCurrentFrame().removeSubFrameWithoutDataPropagation()
frameOwner = getCurrentFrameOwner()
}
}
}
when (breakOrContinue) {
is IrBreak -> getCurrentFrame().removeSubFrameWithoutDataPropagation() // drop loop
else -> if (breakOrContinue.loop is IrDoWhileLoop) {
addInstruction(SimpleInstruction(breakOrContinue.loop))
addInstruction(CompoundInstruction(breakOrContinue.loop.condition))
} else {
addInstruction(CompoundInstruction(breakOrContinue.loop))
}
}
}
fun dropFramesUntilTryCatch() {
val exception = popState()
var frameOwner = getCurrentFrameOwner()
while (frames.isNotEmpty()) {
val frame = getCurrentFrame()
while (!frame.hasNoSubFrames()) {
frameOwner = frame.currentSubFrameOwner
when (frameOwner) {
is IrTry -> {
dropSubFrame() // drop all instructions that left
newSubFrame(frameOwner, listOf())
addInstruction(SimpleInstruction(frameOwner)) // to evaluate finally at the end
frameOwner.catches.reversed().forEach { addInstruction(CompoundInstruction(it)) }
pushState(exception)
return
}
is IrCatch -> {
// in case of exception in catch, drop everything except of last `try` instruction
addInstruction(frame.dropInstructions()!!)
pushState(exception)
return
}
else -> frame.removeSubFrameWithoutDataPropagation()
}
}
dropFrame()
}
if (frames.size == 0) newFrame(frameOwner, emptyList()) // just stub frame
pushState(exception)
}
fun hasNoInstructions() = frames.isEmpty() || (frames.size == 1 && frames.first().hasNoInstructions())
fun addInstruction(instruction: Instruction) {
getCurrentFrame().addInstruction(instruction)
}
fun popInstruction(): Instruction {
return getCurrentFrame().popInstruction()
}
fun pushState(state: State) {
getCurrentFrame().pushState(state)
}
fun popState(): State = getCurrentFrame().popState()
fun peekState(): State? = getCurrentFrame().peekState()
fun addVariable(variable: Variable) {
getCurrentFrame().addVariable(variable)
}
fun getVariable(symbol: IrSymbol): Variable = getCurrentFrame().getVariable(symbol)
fun storeUpValues(state: StateWithClosure) {
// TODO save only necessary declarations
state.upValues.addAll(getCurrentFrame().getAll().toMutableList())
}
fun loadUpValues(state: StateWithClosure) {
state.upValues.forEach { addVariable(it) }
}
fun copyUpValuesFromPreviousFrame() {
frames[frames.size - 2].getAll().forEach { addVariable(it) }
}
fun getStackTrace(): List<String> {
return frames.map { it.toString() }.filter { it != Frame.NOT_DEFINED }
}
fun getFileAndPositionInfo(): String {
return frames[frames.size - 2].getFileAndPositionInfo()
}
fun getStackCount(): Int = frames.size
}
@@ -1,80 +1,143 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter.stack
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterEmptyReturnStackError
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.interpreter.Instruction
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
internal interface Frame {
fun addVar(variable: Variable)
fun addAll(variables: List<Variable>)
fun getVariable(symbol: IrSymbol): Variable?
fun getAll(): List<Variable>
fun contains(symbol: IrSymbol): Boolean
fun pushReturnValue(state: State)
fun pushReturnValue(frame: Frame) // TODO rename to getReturnValueFrom
fun peekReturnValue(): State
fun popReturnValue(): State
fun hasReturnValue(): Boolean
internal class Frame(subFrame: SubFrame, val irFile: IrFile? = null) {
private val innerStack = mutableListOf(subFrame)
private var currentInstruction: Instruction? = null
val currentSubFrameOwner: IrElement
get() = getCurrentFrame().owner
companion object {
const val NOT_DEFINED = "Not defined"
}
private fun getCurrentFrame() = innerStack.last()
fun addSubFrame(frame: SubFrame) {
innerStack.add(frame)
}
fun removeSubFrame() {
getCurrentFrame().peekState()?.let { if (innerStack.size > 1) innerStack[innerStack.size - 2].pushState(it) }
removeSubFrameWithoutDataPropagation()
}
fun removeSubFrameWithoutDataPropagation() {
innerStack.removeLast()
}
fun hasNoSubFrames() = innerStack.isEmpty()
fun hasNoInstructions() = hasNoSubFrames() || (innerStack.size == 1 && innerStack.first().isEmpty())
fun addInstruction(instruction: Instruction) {
getCurrentFrame().pushInstruction(instruction)
}
fun popInstruction(): Instruction {
return getCurrentFrame().popInstruction().apply { currentInstruction = this }
}
fun dropInstructions() = getCurrentFrame().dropInstructions()
fun pushState(state: State) {
getCurrentFrame().pushState(state)
}
fun popState(): State = getCurrentFrame().popState()
fun peekState(): State? = getCurrentFrame().peekState()
fun addVariable(variable: Variable) {
getCurrentFrame().addVariable(variable)
}
fun getVariable(symbol: IrSymbol): Variable {
return innerStack.firstNotNullResult { it.getVariable(symbol) }
?: throw InterpreterError("$symbol not found") // TODO better message
}
fun getAll(): List<Variable> = innerStack.flatMap { it.getAll() }
private fun getLineNumberForCurrentInstruction(): String {
irFile ?: return ""
val frameOwner = currentInstruction?.element
return when {
frameOwner is IrExpression || (frameOwner is IrDeclaration && frameOwner.origin == IrDeclarationOrigin.DEFINED) ->
":${irFile.fileEntry.getLineNumber(frameOwner.startOffset) + 1}"
else -> ""
}
}
fun getFileAndPositionInfo(): String {
irFile ?: return NOT_DEFINED
val lineNum = getLineNumberForCurrentInstruction()
return "${irFile.name}$lineNum"
}
override fun toString(): String {
irFile ?: return NOT_DEFINED
val fileNameCapitalized = irFile.name.replace(".kt", "Kt").capitalizeAsciiOnly()
val entryPoint = innerStack.firstOrNull { it.owner is IrFunction }?.owner as? IrFunction
val lineNum = getLineNumberForCurrentInstruction()
return "at $fileNameCapitalized.${entryPoint?.fqNameWhenAvailable ?: "<clinit>"}(${irFile.name}$lineNum)"
}
}
// TODO replace exceptions with InterpreterException
internal class InterpreterFrame(
private val pool: MutableList<Variable> = mutableListOf(),
private val typeArguments: List<Variable> = listOf()
) : Frame {
private val returnStack: MutableList<State> = mutableListOf()
internal class SubFrame(private val instructions: MutableList<Instruction>, val owner: IrElement) {
private val memory = mutableListOf<Variable>()
private val dataStack = DataStack()
override fun addVar(variable: Variable) {
pool.add(variable)
fun isEmpty() = instructions.isEmpty()
fun pushInstruction(instruction: Instruction) {
instructions.add(0, instruction)
}
override fun addAll(variables: List<Variable>) {
pool.addAll(variables)
fun popInstruction(): Instruction {
return instructions.removeFirst()
}
override fun getVariable(symbol: IrSymbol): Variable? {
return (if (symbol is IrTypeParameterSymbol) typeArguments else pool).firstOrNull { it.symbol == symbol }
fun dropInstructions() = instructions.lastOrNull()?.apply { instructions.clear() }
fun pushState(state: State) {
dataStack.push(state)
}
override fun getAll(): List<Variable> {
return pool
fun popState(): State = dataStack.pop()
fun peekState(): State? = if (!dataStack.isEmpty()) dataStack.peek() else null
fun addVariable(variable: Variable) {
memory += variable
}
override fun contains(symbol: IrSymbol): Boolean {
return (typeArguments + pool).any { it.symbol == symbol }
}
override fun pushReturnValue(state: State) {
returnStack += state
}
override fun pushReturnValue(frame: Frame) {
if (frame.hasReturnValue()) this.pushReturnValue(frame.popReturnValue())
}
override fun hasReturnValue(): Boolean {
return returnStack.isNotEmpty()
}
override fun peekReturnValue(): State {
if (returnStack.isNotEmpty()) {
return returnStack.last()
}
throw InterpreterEmptyReturnStackError()
}
override fun popReturnValue(): State {
if (returnStack.isNotEmpty()) {
val item = returnStack.last()
returnStack.removeAt(returnStack.size - 1)
return item
}
throw InterpreterEmptyReturnStackError()
}
fun getVariable(symbol: IrSymbol): Variable? = memory.firstOrNull { it.symbol == symbol }
fun getAll(): List<Variable> = memory
}
private class DataStack {
private val stack = mutableListOf<State>()
fun isEmpty() = stack.isEmpty()
fun push(state: State) {
stack.add(state)
}
fun pop(): State = stack.removeLast()
fun peek(): State = stack.last()
}
@@ -1,188 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter.stack
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.name
import org.jetbrains.kotlin.ir.interpreter.ExecutionResult
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.util.fileOrNull
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isLocal
internal interface Stack {
fun newFrame(asSubFrame: Boolean = false, initPool: List<Variable> = listOf(), block: () -> ExecutionResult): ExecutionResult
fun newFrame(irFunction: IrFunction, initPool: List<Variable> = listOf(), block: () -> ExecutionResult): ExecutionResult
fun newFrame(irFile: IrFile?, initPool: List<Variable> = listOf(), block: () -> ExecutionResult): ExecutionResult
fun fixCallEntryPoint(irExpression: IrExpression)
fun getStackTrace(): List<String>
fun getCurrentStackInfo(): String
fun getStackCount(): Int
fun clean(rootFile: IrFile?)
fun addVar(variable: Variable)
fun addAll(variables: List<Variable>)
fun getVariable(symbol: IrSymbol): Variable
fun getAll(): List<Variable>
fun contains(symbol: IrSymbol): Boolean
fun hasReturnValue(): Boolean
fun pushReturnValue(state: State)
fun popReturnValue(): State
fun peekReturnValue(): State
}
internal class StackImpl : Stack {
private val frameList = mutableListOf<FrameContainer>()
private fun getCurrentFrame() = frameList.last()
private var stackCount = 0
private fun addNewFrame(asSubFrame: Boolean, initPool: List<Variable>, irFunction: IrFunction? = null, irFile: IrFile? = null) {
val typeArgumentsPool = initPool.filter { it.symbol is IrTypeParameterSymbol }
val valueArguments = initPool.filter { it.symbol !is IrTypeParameterSymbol }
val newFrame = InterpreterFrame(valueArguments.toMutableList(), typeArgumentsPool)
when {
asSubFrame -> getCurrentFrame().addSubFrame(newFrame)
else -> frameList.add(FrameContainer(irFile, irFunction, newFrame))
}
}
private fun withStackIncrement(asSubFrame: Boolean, block: () -> ExecutionResult): ExecutionResult {
return try {
stackCount++
block()
} finally {
stackCount--
if (asSubFrame) getCurrentFrame().removeSubFrame() else removeLastFrame()
}
}
override fun newFrame(asSubFrame: Boolean, initPool: List<Variable>, block: () -> ExecutionResult): ExecutionResult {
addNewFrame(asSubFrame, initPool, null, null)
return withStackIncrement(asSubFrame, block)
}
override fun newFrame(irFunction: IrFunction, initPool: List<Variable>, block: () -> ExecutionResult): ExecutionResult {
val asSubFrame = irFunction.isInline || irFunction.isLocal
addNewFrame(asSubFrame, initPool, irFunction, irFunction.fileOrNull)
return withStackIncrement(asSubFrame, block)
}
override fun newFrame(irFile: IrFile?, initPool: List<Variable>, block: () -> ExecutionResult): ExecutionResult {
addNewFrame(false, initPool, null, irFile)
return withStackIncrement(false, block)
}
private fun removeLastFrame() {
if (frameList.size > 1 && getCurrentFrame().hasReturnValue()) frameList[frameList.lastIndex - 1].pushReturnValue(getCurrentFrame())
frameList.removeAt(frameList.lastIndex)
}
override fun fixCallEntryPoint(irExpression: IrExpression) {
val fileEntry = getCurrentFrame().irFile?.fileEntry ?: return
val lineNum = fileEntry.getLineNumber(irExpression.startOffset) + 1
getCurrentFrame().lineNumber = lineNum
}
override fun getStackTrace(): List<String> {
// TODO implement some sort of cache
return frameList.map { it.toString() }
}
override fun getCurrentStackInfo(): String {
val frame = getCurrentFrame()
frame.irFile ?: return "Not defined"
val lineNum = if (frame.lineNumber != -1) ":${frame.lineNumber}" else ""
return "${frame.irFile.name}$lineNum"
}
override fun getStackCount(): Int = stackCount
override fun clean(rootFile: IrFile?) {
stackCount = 0
frameList.clear()
frameList.add(FrameContainer(rootFile))
}
override fun addVar(variable: Variable) {
getCurrentFrame().addVar(variable)
}
override fun addAll(variables: List<Variable>) {
getCurrentFrame().addAll(variables)
}
override fun getVariable(symbol: IrSymbol): Variable {
return getCurrentFrame().getVariable(symbol)
}
override fun getAll(): List<Variable> {
return getCurrentFrame().getAll()
}
override fun contains(symbol: IrSymbol): Boolean {
return getCurrentFrame().contains(symbol)
}
override fun hasReturnValue(): Boolean {
return getCurrentFrame().hasReturnValue()
}
override fun pushReturnValue(state: State) {
getCurrentFrame().pushReturnValue(state)
}
override fun popReturnValue(): State {
return getCurrentFrame().popReturnValue()
}
override fun peekReturnValue(): State {
return getCurrentFrame().peekReturnValue()
}
}
private class FrameContainer(val irFile: IrFile? = null, private val entryPoint: IrFunction? = null, current: Frame = InterpreterFrame()) {
var lineNumber: Int = -1
private val innerStack = mutableListOf(current)
private fun getTopFrame() = innerStack.first()
fun addSubFrame(frame: Frame) {
innerStack.add(0, frame)
}
fun removeSubFrame() {
if (getTopFrame().hasReturnValue() && innerStack.size > 1) innerStack[1].pushReturnValue(getTopFrame())
innerStack.removeAt(0)
}
fun addVar(variable: Variable) = getTopFrame().addVar(variable)
fun addAll(variables: List<Variable>) = getTopFrame().addAll(variables)
fun getAll() = innerStack.flatMap { it.getAll() }
fun getVariable(symbol: IrSymbol): Variable {
return innerStack.firstNotNullOfOrNull { it.getVariable(symbol) }
?: throw InterpreterError("$symbol not found") // TODO better message
}
fun contains(symbol: IrSymbol) = innerStack.any { it.contains(symbol) }
fun hasReturnValue() = getTopFrame().hasReturnValue()
fun pushReturnValue(container: FrameContainer) = getTopFrame().pushReturnValue(container.getTopFrame())
fun pushReturnValue(state: State) = getTopFrame().pushReturnValue(state)
fun popReturnValue() = getTopFrame().popReturnValue()
fun peekReturnValue() = getTopFrame().peekReturnValue()
override fun toString(): String {
irFile ?: return "Not defined"
val fileNameCapitalized = irFile.name.replace(".kt", "Kt").capitalize()
val lineNum = if (lineNumber != -1) ":$lineNumber" else ""
return "at $fileNameCapitalized.${entryPoint?.fqNameWhenAvailable ?: "<clinit>"}(${irFile.name}$lineNum)"
}
}
@@ -12,7 +12,8 @@ import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
internal class Common private constructor(override val irClass: IrClass, override val fields: MutableList<Variable>) : Complex {
internal class Common private constructor(override val irClass: IrClass, override val fields: MutableList<Variable>) : Complex, StateWithClosure {
override val upValues: MutableList<Variable> = mutableListOf()
override var superWrapperClass: Wrapper? = null
override var outerClass: Variable? = null
@@ -17,8 +17,8 @@ import kotlin.math.min
internal class ExceptionState private constructor(
override val irClass: IrClass, override val fields: MutableList<Variable>, stackTrace: List<String>
) : Complex, Throwable() {
) : Complex, StateWithClosure, Throwable() {
override val upValues: MutableList<Variable> = mutableListOf()
override var superWrapperClass: Wrapper? = null
override var outerClass: Variable? = null
@@ -9,7 +9,8 @@ import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
import org.jetbrains.kotlin.ir.interpreter.handleUserException
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType
@@ -64,11 +65,12 @@ internal fun State.isSubtypeOf(other: IrType): Boolean {
* This method used to check if for not null parameter there was passed null argument.
*/
internal fun State.checkNullability(
irType: IrType?, throwException: () -> Nothing = { NullPointerException().throwAsUserException() }
): State {
irType: IrType?, environment: IrInterpreterEnvironment, exceptionToThrow: () -> Throwable = { NullPointerException() }
): State? {
if (irType !is IrSimpleType) return this
if (this.isNull() && !irType.isNullable()) {
throwException()
exceptionToThrow().handleUserException(environment)
return null
}
return this
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.interpreter.state
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
internal interface StateWithClosure {
val upValues: MutableList<Variable>
}
@@ -7,25 +7,23 @@ package org.jetbrains.kotlin.ir.interpreter.state.reflection
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.descriptors.IrAbstractFunctionFactory
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.getLastOverridden
import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KParameterProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KTypeParameterProxy
import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KTypeProxy
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.state.StateWithClosure
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.cast
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
internal class KFunctionState(val irFunction: IrFunction, override val irClass: IrClass) : ReflectionState() {
internal class KFunctionState(val irFunction: IrFunction, override val irClass: IrClass) : ReflectionState(), StateWithClosure {
override val upValues: MutableList<Variable> = mutableListOf()
override val fields: MutableList<Variable> = mutableListOf()
private var _parameters: List<KParameter>? = null
private var _returnType: KType? = null