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:
committed by
TeamCityServer
parent
c8cd000563
commit
1af1a3c84e
+4
-3
@@ -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() -> {
|
||||
|
||||
+18
-12
@@ -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) {
|
||||
|
||||
+8
-15
@@ -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
|
||||
|
||||
+25
-24
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user