From 169a2f361c9c41b82d44cbdaacc41a926cb59bbc Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Wed, 5 Feb 2020 21:10:04 +0300 Subject: [PATCH] Add support for cause field in exceptions --- .../common/interpreter/IrInterpreter.kt | 2 +- .../backend/common/interpreter/Utils.kt | 14 +++-- .../backend/common/interpreter/stack/State.kt | 55 +++++++++++++++++-- 3 files changed, 59 insertions(+), 12 deletions(-) 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 51f64efa7bf..7b643930b90 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 @@ -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) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt index 91f38197c8f..5818d080ec1 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/Utils.kt @@ -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 { - 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 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 9e7958ed6e2..0d40c4e454a 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 @@ -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) : this(common.irClass, common.fields, stackTrace) - constructor(wrapper: Wrapper, stackTrace: List) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace) + constructor(common: Common, stackTrace: List) : 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) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace) { + setUpCauseIfNeeded(wrapper) + } constructor( exception: Throwable, irClass: IrClass, stackTrace: List @@ -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 = getState(messageProperty.descriptor) as Primitive + 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(null, causeProperty.getter!!.returnType))) + } + + fun getMessage(): Primitive = getState(messageProperty.descriptor) as Primitive + 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() } \ No newline at end of file