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:
+26
-3
@@ -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)))
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user