Add support for cause field in exceptions

This commit is contained in:
Ivan Kylchik
2020-02-05 21:10:04 +03:00
parent 6af47ad7b3
commit 169a2f361c
3 changed files with 59 additions and 12 deletions
@@ -146,7 +146,7 @@ class IrInterpreter(irModule: IrModuleFragment) {
if (irFunction.fileOrNull != null) {
val fileName = irFunction.file.name
val lineNum = irFunction.fileEntry.getLineNumber(irFunction.startOffset) + 1
stackTrace += "at ${fileName.replace(".kt", "Kt")}.${irFunction.fqNameForIrSerialization}($fileName:$lineNum)"
stackTrace += "at ${fileName.replace(".kt", "Kt").capitalize()}.${irFunction.fqNameForIrSerialization}($fileName:$lineNum)"
}
if (stackTrace.size == MAX_STACK_SIZE) {
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.backend.common.interpreter
import org.jetbrains.kotlin.backend.common.interpreter.builtins.evaluateIntrinsicAnnotation
import org.jetbrains.kotlin.backend.common.interpreter.stack.Frame
import org.jetbrains.kotlin.backend.common.interpreter.stack.Primitive
import org.jetbrains.kotlin.backend.common.interpreter.stack.State
import org.jetbrains.kotlin.backend.common.interpreter.stack.Wrapper
import org.jetbrains.kotlin.backend.common.interpreter.stack.*
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
@@ -173,7 +170,14 @@ fun IrType.getFqName(): String? {
}
fun IrFunction.getArgsForMethodInvocation(data: Frame): List<Any?> {
val argsValues = data.getAll().map { (it.state as? Wrapper)?.value ?: (it.state as Primitive<*>).value }.toMutableList()
val argsValues = data.getAll().map {
when (val state = it.state) {
is ExceptionState -> state.getThisAsCauseForException()
is Wrapper -> state.value
is Primitive<*> -> state.value
else -> throw AssertionError("${state::class} is unsupported as argument for wrapper method invocation")
}
}.toMutableList()
// TODO if vararg isn't last parameter
// must convert vararg array into separated elements for correct invoke
@@ -300,10 +300,21 @@ class ExceptionState private constructor(
instance = this
this.stackTrace = stackTrace.reversed()
if (!this::exceptionFqName.isInitialized) this.exceptionFqName = irClass.fqNameForIrSerialization.asString()
if (fields.none { it.descriptor.equalTo(messageProperty.descriptor) }) {
setMessage()
}
}
constructor(common: Common, stackTrace: List<String>) : this(common.irClass, common.fields, stackTrace)
constructor(wrapper: Wrapper, stackTrace: List<String>) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace)
constructor(common: Common, stackTrace: List<String>) : this(common.irClass, common.fields, stackTrace) {
var wrapperSuperType: Complex? = common
while (wrapperSuperType != null && wrapperSuperType !is Wrapper) wrapperSuperType = (wrapperSuperType as Common).superType
setUpCauseIfNeeded(wrapperSuperType as? Wrapper)
}
constructor(wrapper: Wrapper, stackTrace: List<String>) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace) {
setUpCauseIfNeeded(wrapper)
}
constructor(
exception: Throwable, irClass: IrClass, stackTrace: List<String>
@@ -318,6 +329,15 @@ class ExceptionState private constructor(
}
}
private fun setUpCauseIfNeeded(wrapper: Wrapper?) {
val cause = (wrapper?.value as? Throwable)?.cause as? ExceptionData
setCause(cause?.state)
if (getMessage().value == null && cause != null) {
val causeMessage = cause.state.getMessage().value?.let { ": $it" } ?: ""
setMessage("$exceptionFqName$causeMessage")
}
}
fun isSubtypeOf(ancestor: IrClass): Boolean {
if (exceptionHierarchy.isNotEmpty()) {
return exceptionHierarchy.any { it.contains(ancestor.name.asString()) }
@@ -325,18 +345,32 @@ class ExceptionState private constructor(
return irClass.isSubclassOf(ancestor)
}
fun getMessage(): Primitive<String> = getState(messageProperty.descriptor) as Primitive<String>
private fun setMessage(messageValue: String? = null) {
setState(Variable(messageProperty.descriptor, Primitive(messageValue, messageProperty.getter!!.returnType)))
}
fun getCause(): ExceptionState = getState(causeProperty.descriptor) as ExceptionState
private fun setCause(causeValue: State?) {
setState(Variable(causeProperty.descriptor, causeValue ?: Primitive<Throwable?>(null, causeProperty.getter!!.returnType)))
}
fun getMessage(): Primitive<String?> = getState(messageProperty.descriptor) as Primitive<String?>
fun getMessageWithName(): String = getMessage().value?.let { "$exceptionFqName: $it" } ?: exceptionFqName
fun getCause(): ExceptionState? = getState(causeProperty.descriptor)?.let { if (it is ExceptionState) it else null }
fun getFullDescription(): String {
val message = getMessage().value.let { if (it.isNotEmpty()) ": $it" else ""}
// TODO remainder of the stack trace with "..."
val message = getMessage().value.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 = getCause()?.getFullDescription()?.let { "\nCaused by: $it" } ?: ""
return "Exception $exceptionFqName$message" +
stackTrace.subList(0, min(stackTrace.size, 10)).joinToString(separator = "\n\t", prefix = prefix, postfix = postfix)
stackTrace.subList(0, min(stackTrace.size, 10)).joinToString(separator = "\n\t", prefix = prefix, postfix = postfix) +
causeMessage
}
fun getThisAsCauseForException() = ExceptionData(this)
override fun copy(): State {
return ExceptionState(irClass, fields, stackTrace).apply { this@apply.instance = this@ExceptionState.instance }
}
@@ -358,4 +392,13 @@ class ExceptionState private constructor(
return listOfNotNull(messageVar, causeVar).toMutableList()
}
}
}
// TODO remove this data class and make ExceptionState a child of Throwable
// this is possible by converting Complex to an interface
data class ExceptionData(val state: ExceptionState) : Throwable() {
override val message: String? = state.getMessage().value
override fun fillInStackTrace() = this
override fun toString(): String = state.getMessageWithName()
}