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 exceptionName = e::class.java.simpleName
val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: irBuiltIns.throwableClass.owner 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 { val exceptionState = when {
// this block is used to replace jvm null pointer exception with common one // this block is used to replace jvm null pointer exception with common one
// TODO figure something better // TODO figure something better
irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException" -> irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException" ->
ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }, stackTrace) ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }, stackTrace + additionalStack)
else -> ExceptionState(e, irExceptionClass, stackTrace) else -> ExceptionState(e, irExceptionClass, stackTrace + additionalStack)
} }
data.pushReturnValue(exceptionState) data.pushReturnValue(exceptionState)
@@ -161,7 +185,6 @@ class IrInterpreter(irModule: IrModuleFragment) {
} }
private suspend fun MethodHandle.invokeMethod(irFunction: IrFunction, data: Frame): Code { private suspend fun MethodHandle.invokeMethod(irFunction: IrFunction, data: Frame): Code {
// TODO try catch
val result = this.invokeWithArguments(irFunction.getArgsForMethodInvocation(data)) val result = this.invokeWithArguments(irFunction.getArgsForMethodInvocation(data))
data.pushReturnValue(result.toState(result.getType(irFunction.returnType))) data.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
@@ -318,14 +318,13 @@ class ExceptionState private constructor(
constructor( constructor(
exception: Throwable, irClass: IrClass, stackTrace: List<String> exception: Throwable, irClass: IrClass, stackTrace: List<String>
) : this(irClass, evaluateFields(exception, irClass), stackTrace) { ) : this(irClass, evaluateFields(exception, irClass, stackTrace), stackTrace) {
if (irClass.name.asString() == "Throwable" && exception::class.java.name != "Throwable") { 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 // 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 } generateSequence(exception::class.java.superclass) { it.superclass }.forEach { exceptionHierarchy += it.name }
exceptionHierarchy.removeAt(exceptionHierarchy.lastIndex) // remove unnecessary java.lang.Object 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 val cause = (wrapper?.value as? Throwable)?.cause as? ExceptionData
setCause(cause?.state) setCause(cause?.state)
if (getMessage().value == null && cause != null) { if (getMessage().value == null && cause != null) {
val causeMessage = cause.state.getMessage().value?.let { ": $it" } ?: "" val causeMessage = cause.state.exceptionFqName + (cause.state.getMessage().value?.let { ": $it" } ?: "")
setMessage("$exceptionFqName$causeMessage") setMessage(causeMessage)
} }
} }
@@ -363,7 +362,7 @@ class ExceptionState private constructor(
val message = getMessage().value.let { if (it?.isNotEmpty() == true) ": $it" else "" } val message = getMessage().value.let { if (it?.isNotEmpty() == true) ": $it" else "" }
val prefix = if (stackTrace.isNotEmpty()) "\n\t" else "" val prefix = if (stackTrace.isNotEmpty()) "\n\t" else ""
val postfix = if (stackTrace.size > 10) "\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" + 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 causeMessage
@@ -382,13 +381,14 @@ class ExceptionState private constructor(
?: this.declarations.single { it.nameForIrSerialization.asString() == name } as IrProperty ?: 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 messageProperty = irClass.getPropertyByName("message")
val causeProperty = irClass.getPropertyByName("cause") val causeProperty = irClass.getPropertyByName("cause")
val messageVar = Variable(messageProperty.descriptor, exception.message.toState(messageProperty.getter!!.returnType)) val messageVar = Variable(messageProperty.descriptor, exception.message.toState(messageProperty.getter!!.returnType))
// TODO load stack trace from cause val causeVar = exception.cause?.let {
val causeVar = exception.cause?.let { Variable(causeProperty.descriptor, ExceptionState(it, irClass, listOf())) } Variable(causeProperty.descriptor, ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" }))
}
return listOfNotNull(messageVar, causeVar).toMutableList() return listOfNotNull(messageVar, causeVar).toMutableList()
} }
} }