Rewrite exceptions handler in ir interpreter

For now there are 2 types of exceptions:
1. UserExceptions - express user and jvm like exceptions
2. InterpreterError - thrown only if something is wrong with interpreter
This commit is contained in:
Ivan Kylchik
2020-08-07 15:02:34 +03:00
committed by TeamCityServer
parent c3b0c9c6b2
commit f3d7dc5f22
14 changed files with 93 additions and 70 deletions
@@ -14,9 +14,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.interpreter.builtins.*
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterException
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundException
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterTimeOutException
import org.jetbrains.kotlin.ir.interpreter.exceptions.*
import org.jetbrains.kotlin.ir.interpreter.intrinsics.IntrinsicEvaluator
import org.jetbrains.kotlin.ir.interpreter.stack.StackImpl
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
@@ -70,7 +68,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
private fun incrementAndCheckCommands() {
commandCount++
if (commandCount >= MAX_COMMANDS) throw InterpreterTimeOutException()
if (commandCount >= MAX_COMMANDS) throw InterpreterTimeOutError()
}
fun interpret(expression: IrExpression): IrExpression {
@@ -133,13 +131,11 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
}
return executionResult.getNextLabel(this) { this@getNextLabel.interpret() }
} catch (e: InterpreterException) {
throw e
} catch (e: Throwable) {
// catch exception from JVM such as: ArithmeticException, StackOverflowError and others
val exceptionName = e::class.java.simpleName
} catch (e: UserException) {
// can handle only user exceptions, all others must be rethrown
val exceptionName = e.exception::class.java.simpleName
val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: irBuiltIns.throwableClass.owner
stack.pushReturnValue(ExceptionState(e, irExceptionClass, stack.getStackTrace()))
stack.pushReturnValue(ExceptionState(e.exception, irExceptionClass, stack.getStackTrace()))
return Exception
}
}
@@ -149,12 +145,12 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
if (irFunction.fileOrNull != null) stack.setCurrentFrameName(irFunction)
if (irFunction.body is IrSyntheticBody) return handleIntrinsicMethods(irFunction)
return irFunction.body?.interpret() ?: throw InterpreterException("Ir function must be with body")
return irFunction.body?.interpret() ?: throw InterpreterError("Ir function must be with body")
}
private fun MethodHandle?.invokeMethod(irFunction: IrFunction): ExecutionResult {
this ?: return handleIntrinsicMethods(irFunction)
val result = this.invokeWithArguments(irFunction.getArgsForMethodInvocation(stack.getAll()))
val result = withExceptionHandler { this.invokeWithArguments(irFunction.getArgsForMethodInvocation(stack.getAll())) }
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
return Next
@@ -197,26 +193,28 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
val signature = CompileTimeFunction(methodName, argsType.map { it.getOnlyName() })
// TODO replace unary, binary, ternary functions with vararg
val result = when (argsType.size) {
1 -> {
val function = unaryFunctions[signature]
?: throw InterpreterMethodNotFoundException("For given function $signature there is no entry in unary map")
function.invoke(argsValues.first())
}
2 -> {
val function = binaryFunctions[signature]
?: throw InterpreterMethodNotFoundException("For given function $signature there is no entry in binary map")
when (methodName) {
"rangeTo" -> return calculateRangeTo(irFunction.returnType)
else -> function.invoke(argsValues[0], argsValues[1])
val result = withExceptionHandler {
when (argsType.size) {
1 -> {
val function = unaryFunctions[signature]
?: throw InterpreterMethodNotFoundError("For given function $signature there is no entry in unary map")
function.invoke(argsValues.first())
}
2 -> {
val function = binaryFunctions[signature]
?: throw InterpreterMethodNotFoundError("For given function $signature there is no entry in binary map")
when (methodName) {
"rangeTo" -> return calculateRangeTo(irFunction.returnType)
else -> function.invoke(argsValues[0], argsValues[1])
}
}
3 -> {
val function = ternaryFunctions[signature]
?: throw InterpreterMethodNotFoundError("For given function $signature there is no entry in ternary map")
function.invoke(argsValues[0], argsValues[1], argsValues[2])
}
else -> throw InterpreterError("Unsupported number of arguments for invocation as builtin functions")
}
3 -> {
val function = ternaryFunctions[signature]
?: throw InterpreterMethodNotFoundException("For given function $signature there is no entry in ternary map")
function.invoke(argsValues[0], argsValues[1], argsValues[2])
}
else -> throw InterpreterException("Unsupported number of arguments")
}
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
@@ -259,7 +257,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
stack.peekReturnValue().checkNullability(valueParametersSymbols[i].owner.type) {
val method = irFunction.getCapitalizedFileName() + "." + irFunction.fqNameWhenAvailable
val parameter = valueParametersSymbols[i].owner.name
throw IllegalArgumentException("Parameter specified as non-null is null: method $method, parameter $parameter")
IllegalArgumentException("Parameter specified as non-null is null: method $method, parameter $parameter").throwAsUserException()
}
with(Variable(valueParametersSymbols[i], stack.popReturnValue())) {
@@ -637,7 +635,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
val executionResult = enumEntry.initializerExpression?.interpret()?.check { return it }
enumSuperCall?.apply { (0 until this.valueArgumentsCount).forEach { putValueArgument(it, null) } } // restore to null
return executionResult ?: throw InterpreterException("Initializer at enum entry ${enumEntry.fqNameWhenAvailable} is null")
return executionResult ?: throw InterpreterError("Initializer at enum entry ${enumEntry.fqNameWhenAvailable} is null")
}
private fun interpretTypeOperatorCall(expression: IrTypeOperatorCall): ExecutionResult {
@@ -653,7 +651,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> {
if (!isErased && !stack.peekReturnValue().isSubtypeOf(typeOperand)) {
val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable
throw ClassCastException("$convertibleClassName cannot be cast to ${typeOperand.render()}")
ClassCastException("$convertibleClassName cannot be cast to ${typeOperand.render()}").throwAsUserException()
}
}
IrTypeOperator.SAFE_CAST -> {
@@ -750,7 +748,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
is Common -> stack.pushReturnValue(ExceptionState(exception, stack.getStackTrace()))
is Wrapper -> stack.pushReturnValue(ExceptionState(exception, stack.getStackTrace()))
is ExceptionState -> stack.pushReturnValue(exception)
else -> throw InterpreterException("${exception::class} cannot be used as exception state")
else -> throw InterpreterError("${exception::class} cannot be used as exception state")
}
return Exception
}
@@ -778,7 +776,7 @@ class IrInterpreter(private val irBuiltIns: IrBuiltIns, private val bodyMap: Map
}
}
is Lambda -> state.toString()
else -> throw InterpreterException("${state::class.java} cannot be used in StringConcatenation expression")
else -> throw InterpreterError("${state::class.java} cannot be used in StringConcatenation expression")
}
stack.pushReturnValue(result.toState(irBuiltIns.stringType))
return Next
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrReturnableBlock
import org.jetbrains.kotlin.ir.expressions.IrWhen
import org.jetbrains.kotlin.ir.expressions.IrWhileLoop
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
@@ -72,7 +73,7 @@ internal fun ExecutionResult.implicitCastIfNeeded(expectedType: IrType, actualTy
if (!actualState.isSubtypeOf(expectedType)) {
val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable
throw ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}")
ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}").throwAsUserException()
}
return this
}
@@ -14,12 +14,10 @@ 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.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.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
@@ -240,3 +238,11 @@ fun IrClass.internalName(): String {
}
return internalName.toString()
}
inline fun withExceptionHandler(block: () -> Any?): Any? {
try {
return block()
} catch (e: Throwable) {
e.throwAsUserException()
}
}
@@ -0,0 +1,14 @@
/*
* 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
open class InterpreterError(override val message: String) : InterpreterException()
class InterpreterMethodNotFoundError(override val message: String) : InterpreterError(message)
class InterpreterTimeOutError : InterpreterError("Exceeded execution limit of constexpr expression")
class InterpreterEmptyReturnStackError : InterpreterError("Return values stack is empty")
@@ -5,5 +5,4 @@
package org.jetbrains.kotlin.ir.interpreter.exceptions
open class InterpreterException(override val message: String) : Exception(message) {
}
abstract class InterpreterException : Exception()
@@ -1,9 +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
class InterpreterMethodNotFoundException(override val message: String) : InterpreterException(message) {
}
@@ -1,10 +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
class InterpreterTimeOutException : InterpreterException("Exceeded execution limit of constexpr expression") {
}
@@ -0,0 +1,19 @@
/*
* 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)
}
@@ -6,7 +6,7 @@
package org.jetbrains.kotlin.ir.interpreter.intrinsics
import org.jetbrains.kotlin.ir.interpreter.ExecutionResult
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundException
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
@@ -23,7 +23,7 @@ internal class IntrinsicEvaluator {
EnumHashCode.equalTo(irFunction) -> EnumHashCode.evaluate(irFunction, stack, interpret)
JsPrimitives.equalTo(irFunction) -> JsPrimitives.evaluate(irFunction, stack, interpret)
ArrayConstructor.equalTo(irFunction) -> ArrayConstructor.evaluate(irFunction, stack, interpret)
else -> throw InterpreterMethodNotFoundException("Method ${irFunction.name} hasn't implemented")
else -> throw InterpreterMethodNotFoundError("Method ${irFunction.name} hasn't implemented")
}
}
}
@@ -13,6 +13,7 @@ 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.types.classOrNull
import org.jetbrains.kotlin.ir.util.*
@@ -96,7 +97,7 @@ internal object EnumValueOf : IntrinsicBase() {
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 }
?: throw IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName")
?: IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName").throwAsUserException()
return Next
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.ir.interpreter.stack
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterEmptyReturnStackError
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
@@ -65,7 +66,7 @@ internal class InterpreterFrame(
if (returnStack.isNotEmpty()) {
return returnStack.last()
}
throw NoSuchElementException("Return values stack is empty")
throw InterpreterEmptyReturnStackError()
}
override fun popReturnValue(): State {
@@ -74,6 +75,6 @@ internal class InterpreterFrame(
returnStack.removeAt(returnStack.size - 1)
return item
}
throw NoSuchElementException("Return values stack is empty")
throw InterpreterEmptyReturnStackError()
}
}
@@ -6,11 +6,11 @@
package org.jetbrains.kotlin.ir.interpreter.stack
import org.jetbrains.kotlin.ir.interpreter.ExecutionResult
import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterException
import org.jetbrains.kotlin.ir.interpreter.getCapitalizedFileName
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.name
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.file
@@ -132,7 +132,7 @@ private class FrameContainer(current: Frame = InterpreterFrame()) {
fun getAll() = innerStack.flatMap { it.getAll() }
fun getVariable(symbol: IrSymbol): Variable {
return innerStack.firstNotNullOfOrNull { it.getVariable(symbol) }
?: throw InterpreterException("$symbol not found") // TODO better message
?: throw InterpreterError("$symbol not found") // TODO better message
}
fun contains(symbol: IrSymbol) = innerStack.any { it.contains(symbol) }
@@ -9,6 +9,7 @@ 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.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType
@@ -60,7 +61,9 @@ 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 = { throw NullPointerException() }): State {
internal fun State.checkNullability(
irType: IrType?, throwException: () -> Nothing = { NullPointerException().throwAsUserException() }
): State {
if (irType !is IrSimpleType) return this
if (this.isNull() && !irType.isNullable()) {
throwException()
@@ -56,7 +56,7 @@ internal class Wrapper(val value: Any, override val irClass: IrClass) : Complex(
fun getCompanionObject(irClass: IrClass): Wrapper {
val objectName = irClass.getEvaluateIntrinsicValue()!!
val objectValue = companionObjectValue[objectName] ?: throw AssertionError("Companion object $objectName cannot be interpreted")
val objectValue = companionObjectValue[objectName] ?: throw InternalError("Companion object $objectName cannot be interpreted")
return Wrapper(objectValue, irClass)
}