Make stack trace from exceptions more precise

Additional information is retrieved from original stack trace when
exception happened during wrapper invocation.
This commit is contained in:
Ivan Kylchik
2020-02-07 20:13:54 +03:00
parent 18d29f7d98
commit 0a70277240
2 changed files with 37 additions and 14 deletions
@@ -126,12 +126,36 @@ class IrInterpreter(irModule: IrModuleFragment) {
val exceptionName = e::class.java.simpleName
val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: irBuiltIns.throwableClass.owner
// TODO do we really need this?... It will point to JVM stdlib
val additionalStack = mutableListOf<String>()
if (e.stackTrace.any { it.className == "java.lang.invoke.MethodHandle" }) {
for ((index, stackTraceElement) in e.stackTrace.withIndex()) {
if (stackTraceElement.methodName == "invokeWithArguments") {
additionalStack.addAll(e.stackTrace.slice(0 until index).reversed().map { "at $it" })
break
}
}
var cause = e.cause
val lastNeededValue = e.stackTrace.first().className + "." + e.stackTrace.first().methodName
while (cause != null) {
for ((causeStackIndex, causeStackTraceElement) in cause.stackTrace.withIndex()) {
val currentStackTraceValue = causeStackTraceElement.className + "." + causeStackTraceElement.methodName
if (currentStackTraceValue== lastNeededValue) {
cause.stackTrace = cause.stackTrace.sliceArray(0 until causeStackIndex).reversedArray()
break
}
}
cause = cause.cause
}
}
val exceptionState = when {
// this block is used to replace jvm null pointer exception with common one
// TODO figure something better
irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException" ->
ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }, stackTrace)
else -> ExceptionState(e, irExceptionClass, stackTrace)
ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }, stackTrace + additionalStack)
else -> ExceptionState(e, irExceptionClass, stackTrace + additionalStack)
}
data.pushReturnValue(exceptionState)
@@ -161,7 +185,6 @@ class IrInterpreter(irModule: IrModuleFragment) {
}
private suspend fun MethodHandle.invokeMethod(irFunction: IrFunction, data: Frame): Code {
// TODO try catch
val result = this.invokeWithArguments(irFunction.getArgsForMethodInvocation(data))
data.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
@@ -318,14 +318,13 @@ class ExceptionState private constructor(
constructor(
exception: Throwable, irClass: IrClass, stackTrace: List<String>
) : this(irClass, evaluateFields(exception, irClass), stackTrace) {
if (irClass.name.asString() == "Throwable" && exception::class.java.name != "Throwable") {
) : this(irClass, evaluateFields(exception, irClass, stackTrace), stackTrace) {
if (irClass.name.asString() != exception::class.java.simpleName) {
// ir class wasn't found in classpath, a stub was passed => need to save java class hierarchy
exceptionHierarchy += exception::class.java.name
this.exceptionFqName = exception::class.java.name
exceptionHierarchy += this.exceptionFqName
generateSequence(exception::class.java.superclass) { it.superclass }.forEach { exceptionHierarchy += it.name }
exceptionHierarchy.removeAt(exceptionHierarchy.lastIndex) // remove unnecessary java.lang.Object
this.exceptionFqName = exception::class.java.name
}
}
@@ -333,8 +332,8 @@ class ExceptionState private constructor(
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")
val causeMessage = cause.state.exceptionFqName + (cause.state.getMessage().value?.let { ": $it" } ?: "")
setMessage(causeMessage)
}
}
@@ -363,7 +362,7 @@ class ExceptionState private constructor(
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" } ?: ""
val causeMessage = getCause()?.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
@@ -382,13 +381,14 @@ class ExceptionState private constructor(
?: this.declarations.single { it.nameForIrSerialization.asString() == name } as IrProperty
}
private fun evaluateFields(exception: Throwable, irClass: IrClass): MutableList<Variable> {
private fun evaluateFields(exception: Throwable, irClass: IrClass, stackTrace: List<String>): MutableList<Variable> {
val messageProperty = irClass.getPropertyByName("message")
val causeProperty = irClass.getPropertyByName("cause")
val messageVar = Variable(messageProperty.descriptor, exception.message.toState(messageProperty.getter!!.returnType))
// TODO load stack trace from cause
val causeVar = exception.cause?.let { Variable(causeProperty.descriptor, ExceptionState(it, irClass, listOf())) }
val causeVar = exception.cause?.let {
Variable(causeProperty.descriptor, ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" }))
}
return listOfNotNull(messageVar, causeVar).toMutableList()
}
}