diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt index bcf80e59f2d..3d3dec6a217 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt @@ -52,11 +52,7 @@ class IrInterpreter(irModule: IrModuleFragment) { return when (val code = expression.interpret(data)) { Code.NEXT -> data.popReturnValue().toIrExpression(expression) Code.EXCEPTION -> { - val message = when (val exception = data.popReturnValue()) { - is Common -> (exception.fields.first { it.descriptor.name.asString() == "message" }.state as Primitive<*>).value.toString() - is Wrapper -> (exception.value as Throwable).message.toString() - else -> TODO("not supported") - } + val message = (data.popReturnValue() as ExceptionState).getMessage().value IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, message) } else -> TODO("$code not supported as result of interpretation") @@ -118,14 +114,14 @@ class IrInterpreter(irModule: IrModuleFragment) { } catch (e: Exception) { // TODO catch Throwable // catch exception from JVM such as: ArithmeticException, StackOverflowError and others val exceptionName = e::class.java.simpleName - val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } + val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: irBuiltIns.throwableClass.owner - if (irExceptionClass == null && exceptionName == "KotlinNullPointerException") { + if (irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException") { // this block is used to replace jvm null pointer exception with common one // TODO figure something better - data.pushReturnValue(Wrapper(e, irExceptions.first { it.name.asString() == "NullPointerException" })) + data.pushReturnValue(ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" })) } else { - irExceptionClass?.let { data.pushReturnValue(Wrapper(e, it)) } ?: throw AssertionError("Cannot handle exception $e") + irExceptionClass.let { data.pushReturnValue(ExceptionState(e, it)) } } return Code.EXCEPTION @@ -164,9 +160,17 @@ class IrInterpreter(irModule: IrModuleFragment) { } private fun calculateOverridden(owner: IrFunctionImpl, data: Frame): Code { + fun IrFunctionImpl.getLastPossibleOverridden(): IrFunctionImpl { + // main usage: get throwable properties getter signature from exception classes + // then use that signatures to get right method from ir builtins map + var overridden = this + while (overridden.overriddenSymbols.isNotEmpty()) overridden = overridden.overriddenSymbols.first().owner as IrFunctionImpl + return overridden + } + val variableDescriptor = owner.symbol.getReceiver()!! val superQualifier = (data.getVariableState(variableDescriptor) as? Complex)?.superType - ?: return calculateBuiltIns(owner.overriddenSymbols.first().owner, data) + ?: return calculateBuiltIns(owner.getLastPossibleOverridden(), data) val overridden = owner.overriddenSymbols.first { it.getReceiver()?.equalTo(superQualifier.getReceiver()) == true } val newStates = InterpreterFrame(mutableListOf(Variable(overridden.getReceiver()!!, superQualifier))) @@ -478,7 +482,7 @@ class IrInterpreter(irModule: IrModuleFragment) { val convertibleClassName = data.popReturnValue().irClass.fqNameForIrSerialization val castClassName = expression.type.classOrNull?.owner?.fqNameForIrSerialization val message = "$convertibleClassName cannot be cast to $castClassName" - data.pushReturnValue(Wrapper(ClassCastException(message), classCastException)) + data.pushReturnValue(ExceptionState(ClassCastException(message), classCastException)) Code.EXCEPTION } else { code @@ -522,14 +526,15 @@ class IrInterpreter(irModule: IrModuleFragment) { private fun interpretTry(expression: IrTry, data: Frame): Code { var code = expression.tryResult.interpret(data) if (code == Code.EXCEPTION) { - val exception = data.peekReturnValue() + val exception = data.peekReturnValue() as ExceptionState for (catchBlock in expression.catches) { - if (exception.irClass.defaultType.isSubtypeOf(catchBlock.catchParameter.type, irBuiltIns)) { + if (exception.isSubtypeOf(catchBlock.catchParameter.type.classOrNull!!.owner)) { code = catchBlock.interpret(data) break } } } + // TODO check flow correctness; should I return finally result code if in catch there was an exception? return expression.finallyExpression?.interpret(data) ?: code } @@ -540,7 +545,13 @@ class IrInterpreter(irModule: IrModuleFragment) { } private fun interpretThrow(expression: IrThrow, data: Frame): Code { - expression.value.interpret(data) + expression.value.interpret(data).also { if (it != Code.NEXT) return it } + when (val exception = data.popReturnValue()) { + is Common -> data.pushReturnValue(ExceptionState(exception)) + is Wrapper -> data.pushReturnValue(ExceptionState(exception)) + is ExceptionState -> data.pushReturnValue(exception) + else -> throw AssertionError("${exception::class} cannot be used as exception state") + } return Code.EXCEPTION } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/State.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/State.kt index 271ee06bd3f..be6cd52e164 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/State.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/stack/State.kt @@ -18,9 +18,7 @@ import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.util.defaultType -import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization -import org.jetbrains.kotlin.ir.util.isTypeParameter +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqNameUnsafe import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles @@ -53,12 +51,14 @@ class Primitive(var value: T, val type: IrType) : State { override val irClass: IrClass = type.classOrNull!!.owner init { - val properties = irClass.declarations.filterIsInstance() - for (property in properties) { - val propertySignature = CompileTimeFunction(property.name.asString(), listOf(irClass.descriptor.defaultType.toString())) - val propertyValue = unaryFunctions[propertySignature]?.invoke(value) - ?: throw NoSuchMethodException("For given property $propertySignature there is no entry in unary map") - fields.add(Variable(property.descriptor, Primitive(propertyValue, property.backingField!!.type))) + if (value != null) { + val properties = irClass.declarations.filterIsInstance() + for (property in properties) { + val propertySignature = CompileTimeFunction(property.name.asString(), listOf(irClass.descriptor.defaultType.toString())) + val propertyValue = unaryFunctions[propertySignature]?.invoke(value) + ?: throw NoSuchMethodException("For given property $propertySignature there is no entry in unary map") + fields.add(Variable(property.descriptor, Primitive(propertyValue, property.backingField!!.type))) + } } } @@ -104,7 +104,7 @@ class Primitive(var value: T, val type: IrType) : State { } } -abstract class Complex(override var irClass: IrClass, override val fields: MutableList) : State { +abstract class Complex(override val irClass: IrClass, override val fields: MutableList) : State { var superType: Complex? = null var instance: Complex? = null @@ -117,14 +117,6 @@ abstract class Complex(override var irClass: IrClass, override val fields: Mutab return irClass.thisReceiver!!.descriptor } - override fun getIrFunction(descriptor: FunctionDescriptor): IrFunction? { - return irClass.declarations.filterIsInstance() - .filter { it.descriptor.name == descriptor.name } - .firstOrNull { it.descriptor.valueParameters.map { it.type } == descriptor.valueParameters.map { it.type } } - } -} - -class Common(override var irClass: IrClass, override val fields: MutableList) : Complex(irClass, fields) { override fun setState(newVar: Variable) { when (val oldState = fields.firstOrNull { it.descriptor == newVar.descriptor }) { null -> fields.add(newVar) // newVar isn't present in value list @@ -132,6 +124,14 @@ class Common(override var irClass: IrClass, override val fields: MutableList() + .filter { it.descriptor.name == descriptor.name } + .firstOrNull { it.descriptor.valueParameters.map { it.type } == descriptor.valueParameters.map { it.type } } + } +} + +class Common(override val irClass: IrClass, override val fields: MutableList) : Complex(irClass, fields) { fun getToStringFunction(): IrFunctionImpl { return irClass.declarations.filterIsInstance() .filter { it.descriptor.name.asString() == "toString" } @@ -150,7 +150,7 @@ class Common(override var irClass: IrClass, override val fields: MutableList) : Complex(irClass, fields) { + private val exceptionHierarchy = mutableListOf() + private val messageProperty = irClass.getPropertyByName("message") + private val causeProperty = irClass.getPropertyByName("cause") + + init { + instance = this + } + + constructor(common: Common) : this(common.irClass, common.fields) + constructor(wrapper: Wrapper) : this(wrapper.value as Throwable, wrapper.irClass) + + constructor(exception: Throwable, irClass: IrClass) : this(irClass, evaluateFields(exception, irClass)) { + if (irClass.name.asString() == "Throwable" && exception::class.java.name != "Throwable") { + // ir class wasn't found in classpath, a stub was passed => need to save java class hierarchy + exceptionHierarchy += exception::class.java.name + generateSequence(exception::class.java.superclass) { it.superclass }.forEach { exceptionHierarchy += it.name } + exceptionHierarchy.removeAt(exceptionHierarchy.lastIndex) + } + } + + fun isSubtypeOf(ancestor: IrClass): Boolean { + if (exceptionHierarchy.isNotEmpty()) { + return exceptionHierarchy.any { it.contains(ancestor.name.asString()) } + } + return irClass.isSubclassOf(ancestor) + } + + fun getMessage(): Primitive = getState(messageProperty.descriptor) as Primitive + + fun getCause(): ExceptionState = getState(causeProperty.descriptor) as ExceptionState + + override fun copy(): State { + return ExceptionState(irClass, fields).apply { this@apply.instance = this@ExceptionState.instance } + } + + companion object { + private fun IrClass.getPropertyByName(name: String): IrProperty { + val getPropertyFun = this.declarations.firstOrNull { it.nameForIrSerialization.asString().contains("get-$name") } + return (getPropertyFun as? IrFunctionImpl)?.correspondingPropertySymbol?.owner + ?: this.declarations.single { it.nameForIrSerialization.asString() == name } as IrProperty + } + + private fun evaluateFields(exception: Throwable, irClass: IrClass): MutableList { + val messageProperty = irClass.getPropertyByName("message") + val causeProperty = irClass.getPropertyByName("cause") + + val messageVar = Variable(messageProperty.descriptor, exception.message.toState(messageProperty.getter!!.returnType)) + val causeVar = exception.cause?.let { Variable(causeProperty.descriptor, ExceptionState(it, irClass)) } + return listOfNotNull(messageVar, causeVar).toMutableList() + } + } } \ No newline at end of file