Support interpretation of Throwable constructor

For now every subclass of Throwable will be represented as
ExceptionState. This way it is easier to store stack trace and handle
`cause` property in exceptions.
This commit is contained in:
Ivan Kylchik
2021-06-16 22:59:14 +03:00
committed by TeamCityServer
parent c8cd000563
commit 1af1a3c84e
6 changed files with 68 additions and 56 deletions
@@ -86,9 +86,10 @@ internal class DefaultCallInterceptor(override val interpreter: IrInterpreter) :
when {
Wrapper.mustBeHandledWithWrapper(irClass) || irClass.fqNameWhenAvailable!!.startsWith(Name.identifier("java")) -> {
Wrapper.getConstructorMethod(irConstructor).invokeMethod(irConstructor, args)
when (constructorCall) {
is IrConstructorCall -> callStack.setState(constructorCall.getThisReceiver(), callStack.popState())
else -> (receiver as Common).superWrapperClass = callStack.popState() as Wrapper
when {
irClass.isSubclassOfThrowable() -> (receiver as ExceptionState).copyFieldsFrom(callStack.popState() as Wrapper)
constructorCall is IrConstructorCall -> callStack.setState(constructorCall.getThisReceiver(), callStack.popState())
else -> (receiver as Complex).superWrapperClass = callStack.popState() as Wrapper
}
}
irClass.defaultType.isArray() || irClass.defaultType.isPrimitiveArray() -> {
@@ -37,9 +37,9 @@ internal fun unfoldInstruction(element: IrElement?, environment: IrInterpreterEn
when (element) {
null -> return
is IrSimpleFunction -> unfoldFunction(element, environment)
is IrConstructor -> unfoldConstructor(element, environment)
is IrConstructor -> unfoldConstructor(element, callStack)
is IrCall -> unfoldCall(element, callStack)
is IrConstructorCall -> unfoldConstructorCall(element, callStack)
is IrConstructorCall -> unfoldConstructorCall(element, environment)
is IrEnumConstructorCall -> unfoldEnumConstructorCall(element, callStack)
is IrDelegatingConstructorCall -> unfoldDelegatingConstructorCall(element, callStack)
is IrInstanceInitializerCall -> unfoldInstanceInitializerCall(element, callStack)
@@ -88,15 +88,17 @@ private fun unfoldFunction(function: IrSimpleFunction, environment: IrInterprete
?: throw InterpreterError("Ir function must be with body")
}
private fun unfoldConstructor(constructor: IrConstructor, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
private fun unfoldConstructor(constructor: IrConstructor, callStack: CallStack) {
when (constructor.fqNameWhenAvailable?.asString()) {
"kotlin.Enum.<init>" -> {
"kotlin.Enum.<init>", "kotlin.Throwable.<init>" -> {
val irClass = constructor.parentAsClass
val receiver = irClass.thisReceiver!!.symbol
val receiverState = callStack.getState(receiver)
irClass.declarations.filterIsInstance<IrProperty>().forEachIndexed { index, property ->
receiverState.setField(Variable(property.symbol, callStack.getState(constructor.valueParameters[index].symbol)))
val receiverSymbol = irClass.thisReceiver!!.symbol
val receiverState = callStack.getState(receiverSymbol)
irClass.declarations.filterIsInstance<IrProperty>().forEach { property ->
val parameter = constructor.valueParameters.singleOrNull { it.name == property.name }
val state = parameter?.let { callStack.getState(it.symbol) } ?: Primitive.nullStateOfType(property.getter!!.returnType)
receiverState.setField(Variable(property.symbol, state))
}
}
else -> {
@@ -111,11 +113,15 @@ private fun unfoldCall(call: IrCall, callStack: CallStack) {
unfoldValueParameters(call, callStack)
}
private fun unfoldConstructorCall(constructorCall: IrFunctionAccessExpression, callStack: CallStack) {
private fun unfoldConstructorCall(constructorCall: IrFunctionAccessExpression, environment: IrInterpreterEnvironment) {
val callStack = environment.callStack
val constructor = constructorCall.symbol.owner
val irClass = constructor.parentAsClass
unfoldValueParameters(constructorCall, callStack)
// this variable is used to create object once
callStack.addVariable(Variable(constructorCall.getThisReceiver(), Common(constructor.parentAsClass)))
// this state is used to create object once
val state = if (irClass.isSubclassOfThrowable()) ExceptionState(irClass, callStack.getStackTrace()) else Common(irClass)
if (irClass.isObject) environment.mapOfObjects[irClass.symbol] = state // must set object's state here to avoid cyclic evaluation
callStack.addVariable(Variable(constructorCall.getThisReceiver(), state))
}
private fun unfoldEnumConstructorCall(enumConstructorCall: IrEnumConstructorCall, callStack: CallStack) {
@@ -385,18 +385,18 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
private fun interpretGetObjectValue(expression: IrGetObjectValue) {
callInterceptor.interceptGetObjectValue(expression) {
val objectClass = expression.symbol.owner
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))
// non compile time objects can be used in interpreter, for example, to evaluate const properties or in tests
if (!objectClass.hasAnnotation(compileTimeAnnotation) && !environment.configuration.createNonCompileTimeObjects)
val buildObject = objectClass.hasAnnotation(compileTimeAnnotation) || environment.configuration.createNonCompileTimeObjects
if (objectClass.constructors.none() || !buildObject) {
val state = Common(objectClass)
environment.mapOfObjects[objectClass.symbol] = state
return@interceptGetObjectValue callStack.pushState(state)
}
val constructor = objectClass.constructors.firstOrNull() ?: return@interceptGetObjectValue callStack.pushState(state)
val constructor = objectClass.constructors.first()
val constructorCall = IrConstructorCallImpl.fromSymbolOwner(constructor.returnType, constructor.symbol)
callStack.newSubFrame(constructorCall)
callStack.addInstruction(SimpleInstruction(constructorCall))
callStack.addInstruction(CompoundInstruction(constructorCall))
}
}
@@ -564,15 +564,8 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
element.finallyExpression?.handleAndDropResult(callStack)
}
@Suppress("UNUSED_PARAMETER")
private fun interpretThrow(expression: IrThrow) {
val exception = callStack.popState()
callStack.newSubFrame(expression) // temporary frame to get correct stack trace
when (exception) {
is Common -> callStack.pushState(ExceptionState(exception, callStack.getStackTrace()))
is Wrapper -> callStack.pushState(ExceptionState(exception, callStack.getStackTrace()))
is ExceptionState -> callStack.pushState(exception)
else -> throw InterpreterError("${exception::class} cannot be used as exception state")
}
callStack.dropFramesUntilTryCatch()
}
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import java.lang.invoke.MethodType
@@ -77,7 +76,7 @@ internal fun Any?.toState(irType: IrType): State {
is State -> this
is Boolean, is Char, is Byte, is Short, is Int, is Long, is String, is Float, is Double, is Array<*>, is ByteArray,
is CharArray, is ShortArray, is IntArray, is LongArray, is FloatArray, is DoubleArray, is BooleanArray -> Primitive(this, irType)
null -> Primitive(this, irType)
null -> Primitive.nullStateOfType(irType)
else -> irType.classOrNull?.owner?.let { Wrapper(this, it) } ?: Wrapper(this)
}
}
@@ -200,6 +199,12 @@ internal fun IrType.isFunction() = this.getClass()?.fqNameWhenAvailable?.asStrin
internal fun IrType.isKFunction() = this.getClass()?.fqNameWhenAvailable?.asString()?.startsWith("kotlin.reflect.KFunction") ?: false
internal fun IrType.isTypeParameter() = classifierOrNull is IrTypeParameterSymbol
internal fun IrType.isThrowable() = this.getClass()?.fqNameWhenAvailable?.asString() == "kotlin.Throwable"
internal fun IrClass.isSubclassOfThrowable(): Boolean {
return generateSequence(this) { irClass ->
if (irClass.defaultType.isAny()) return@generateSequence null
irClass.superTypes.mapNotNull { it.classOrNull?.owner }.singleOrNull { it.isClass }
}.any { it.defaultType.isThrowable() }
}
internal fun IrType.isUnsignedArray(): Boolean {
if (this !is IrSimpleType || classifier !is IrClassSymbol) return false
@@ -5,12 +5,12 @@
package org.jetbrains.kotlin.ir.interpreter.state
import org.jetbrains.kotlin.ir.interpreter.getLastOverridden
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.toState
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.interpreter.getLastOverridden
import org.jetbrains.kotlin.ir.interpreter.stack.Variable
import org.jetbrains.kotlin.ir.interpreter.toState
import org.jetbrains.kotlin.ir.util.isSubclassOf
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
import kotlin.math.min
@@ -24,8 +24,8 @@ internal class ExceptionState private constructor(
override val message: String?
get() = getField(messageProperty.symbol)?.asStringOrNull()
override val cause: Throwable?
get() = getField(causeProperty.symbol)?.let { if (it is ExceptionState) it else null }
override val cause: ExceptionState?
get() = getField(causeProperty.symbol) as? ExceptionState
private lateinit var exceptionFqName: String
private val exceptionHierarchy = mutableListOf<String>()
@@ -36,20 +36,11 @@ internal class ExceptionState private constructor(
init {
if (!this::exceptionFqName.isInitialized) this.exceptionFqName = irClassFqName()
if (fields.none { it.symbol == messageProperty.symbol }) {
setMessage()
}
if (fields.none { it.symbol == messageProperty.symbol }) setMessage(null)
if (fields.none { it.symbol == causeProperty.symbol }) setCause(null)
}
constructor(common: Common, stackTrace: List<String>) : this(common.irClass, common.fields, stackTrace) {
(common.superWrapperClass?.value as? Throwable)?.let { setMessage(it.message) }
setUpCauseIfNeeded(common.superWrapperClass)
}
constructor(wrapper: Wrapper, stackTrace: List<String>) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace) {
setUpCauseIfNeeded(wrapper)
}
constructor(irClass: IrClass, stackTrace: List<String>) : this(irClass, mutableListOf(), stackTrace)
constructor(
exception: Throwable, irClass: IrClass, stackTrace: List<String>
@@ -64,11 +55,21 @@ internal class ExceptionState private constructor(
}
}
private fun setUpCauseIfNeeded(wrapper: Wrapper?) {
val cause = (wrapper?.value as? Throwable)?.cause as? ExceptionState
setCause(cause)
fun copyFieldsFrom(wrapper: Wrapper) {
(wrapper.value as? Throwable)?.let {
setMessage(it.message)
setCause(it.cause as? ExceptionState)
}
}
override fun setField(newVar: Variable) {
super.setField(newVar)
recalculateCauseAndMessage()
}
private fun recalculateCauseAndMessage() {
if (message == null && cause != null) {
val causeMessage = cause.exceptionFqName + (cause.message?.let { ": $it" } ?: "")
val causeMessage = cause!!.exceptionFqName + (cause!!.message?.let { ": $it" } ?: "")
setMessage(causeMessage)
}
}
@@ -80,12 +81,12 @@ internal class ExceptionState private constructor(
return irClass.isSubclassOf(ancestor)
}
private fun setMessage(messageValue: String? = null) {
private fun setMessage(messageValue: String?) {
setField(Variable(messageProperty.symbol, Primitive(messageValue, messageProperty.getter!!.returnType)))
}
private fun setCause(causeValue: State?) {
setField(Variable(causeProperty.symbol, causeValue ?: Primitive<Throwable?>(null, causeProperty.getter!!.returnType)))
setField(Variable(causeProperty.symbol, causeValue ?: Primitive.nullStateOfType(causeProperty.getter!!.returnType)))
}
fun getFullDescription(): String {
@@ -93,7 +94,7 @@ internal class ExceptionState private constructor(
val message = message.let { if (it?.isNotEmpty() == true) ": $it" else "" }
val prefix = if (stackTrace.isNotEmpty()) "\n\t" else ""
val postfix = if (stackTrace.size > 10) "\n\t..." else ""
val causeMessage = (cause as? ExceptionState)?.getFullDescription()?.replaceFirst("Exception ", "\nCaused by: ") ?: ""
val causeMessage = cause?.getFullDescription()?.replaceFirst("Exception ", "\nCaused by: ") ?: ""
return "Exception $exceptionFqName$message" +
stackTrace.subList(0, min(stackTrace.size, 10)).joinToString(separator = "\n\t", prefix = prefix, postfix = postfix) +
causeMessage
@@ -29,4 +29,10 @@ internal class Primitive<T>(val value: T, val type: IrType) : State {
override fun toString(): String {
return "Primitive(value=$value, type=${irClass.defaultType})"
}
companion object {
fun nullStateOfType(irType: IrType): Primitive<*> {
return Primitive(null, irType)
}
}
}