Implement basic stack trace reporting if unhandled exception appear
This commit is contained in:
+24
-16
@@ -36,7 +36,7 @@ class IrInterpreter(irModule: IrModuleFragment) {
|
|||||||
.filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) }
|
.filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) }
|
||||||
private val classCastException = irExceptions.first { it.name.asString() == ClassCastException::class.java.simpleName }
|
private val classCastException = irExceptions.first { it.name.asString() == ClassCastException::class.java.simpleName }
|
||||||
|
|
||||||
private var stackSize = 0
|
private val stackTrace = mutableListOf<String>()
|
||||||
|
|
||||||
private fun Any?.getType(defaultType: IrType): IrType {
|
private fun Any?.getType(defaultType: IrType): IrType {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
@@ -60,8 +60,8 @@ class IrInterpreter(irModule: IrModuleFragment) {
|
|||||||
return@runBlocking when (val code = withContext(this.coroutineContext) { expression.interpret(data) }) {
|
return@runBlocking when (val code = withContext(this.coroutineContext) { expression.interpret(data) }) {
|
||||||
Code.NEXT -> data.popReturnValue().toIrExpression(expression)
|
Code.NEXT -> data.popReturnValue().toIrExpression(expression)
|
||||||
Code.EXCEPTION -> {
|
Code.EXCEPTION -> {
|
||||||
val message = (data.popReturnValue() as ExceptionState).getMessage().value
|
val message = (data.popReturnValue() as ExceptionState).getFullDescription()
|
||||||
IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, message)
|
IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + message)
|
||||||
}
|
}
|
||||||
else -> TODO("$code not supported as result of interpretation")
|
else -> TODO("$code not supported as result of interpretation")
|
||||||
}
|
}
|
||||||
@@ -125,29 +125,37 @@ 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
|
||||||
|
|
||||||
if (irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException") {
|
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
|
||||||
data.pushReturnValue(ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }))
|
irExceptionClass == irBuiltIns.throwableClass.owner && exceptionName == "KotlinNullPointerException" ->
|
||||||
} else {
|
ExceptionState(e, irExceptions.first { it.name.asString() == "NullPointerException" }, stackTrace)
|
||||||
irExceptionClass.let { data.pushReturnValue(ExceptionState(e, it)) }
|
else -> ExceptionState(e, irExceptionClass, stackTrace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.pushReturnValue(exceptionState)
|
||||||
return Code.EXCEPTION
|
return Code.EXCEPTION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is used to get stack trace after exception
|
// this method is used to get stack trace after exception
|
||||||
private suspend fun interpretFunction(irFunction: IrFunctionImpl, data: Frame): Code {
|
private suspend fun interpretFunction(irFunction: IrFunctionImpl, data: Frame): Code {
|
||||||
try {
|
return try {
|
||||||
yield()
|
yield()
|
||||||
stackSize++
|
|
||||||
if (stackSize == MAX_STACK_SIZE) {
|
if (irFunction.fileOrNull != null) {
|
||||||
throw StackOverflowError("Error!!!")
|
val fileName = irFunction.file.name
|
||||||
|
val lineNum = irFunction.fileEntry.getLineNumber(irFunction.startOffset) + 1
|
||||||
|
stackTrace += "at ${fileName.replace(".kt", "Kt")}.${irFunction.fqNameForIrSerialization}($fileName:$lineNum)"
|
||||||
}
|
}
|
||||||
return irFunction.body?.interpret(data) ?: throw AssertionError("Ir function must be with body")
|
|
||||||
|
if (stackTrace.size == MAX_STACK_SIZE) {
|
||||||
|
throw StackOverflowError("")
|
||||||
|
}
|
||||||
|
|
||||||
|
irFunction.body?.interpret(data) ?: throw AssertionError("Ir function must be with body")
|
||||||
} finally {
|
} finally {
|
||||||
stackSize--
|
if (irFunction.fileOrNull != null) stackTrace.removeAt(stackTrace.lastIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +506,7 @@ class IrInterpreter(irModule: IrModuleFragment) {
|
|||||||
val convertibleClassName = data.popReturnValue().irClass.fqNameForIrSerialization
|
val convertibleClassName = data.popReturnValue().irClass.fqNameForIrSerialization
|
||||||
val castClassName = expression.type.classOrNull?.owner?.fqNameForIrSerialization
|
val castClassName = expression.type.classOrNull?.owner?.fqNameForIrSerialization
|
||||||
val message = "$convertibleClassName cannot be cast to $castClassName"
|
val message = "$convertibleClassName cannot be cast to $castClassName"
|
||||||
data.pushReturnValue(ExceptionState(ClassCastException(message), classCastException))
|
data.pushReturnValue(ExceptionState(ClassCastException(message), classCastException, stackTrace))
|
||||||
Code.EXCEPTION
|
Code.EXCEPTION
|
||||||
} else {
|
} else {
|
||||||
code
|
code
|
||||||
@@ -563,8 +571,8 @@ class IrInterpreter(irModule: IrModuleFragment) {
|
|||||||
private suspend fun interpretThrow(expression: IrThrow, data: Frame): Code {
|
private suspend fun interpretThrow(expression: IrThrow, data: Frame): Code {
|
||||||
expression.value.interpret(data).also { if (it != Code.NEXT) return it }
|
expression.value.interpret(data).also { if (it != Code.NEXT) return it }
|
||||||
when (val exception = data.popReturnValue()) {
|
when (val exception = data.popReturnValue()) {
|
||||||
is Common -> data.pushReturnValue(ExceptionState(exception))
|
is Common -> data.pushReturnValue(ExceptionState(exception, stackTrace))
|
||||||
is Wrapper -> data.pushReturnValue(ExceptionState(exception))
|
is Wrapper -> data.pushReturnValue(ExceptionState(exception, stackTrace))
|
||||||
is ExceptionState -> data.pushReturnValue(exception)
|
is ExceptionState -> data.pushReturnValue(exception)
|
||||||
else -> throw AssertionError("${exception::class} cannot be used as exception state")
|
else -> throw AssertionError("${exception::class} cannot be used as exception state")
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-7
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.name.FqNameUnsafe
|
|||||||
import java.lang.invoke.MethodHandle
|
import java.lang.invoke.MethodHandle
|
||||||
import java.lang.invoke.MethodHandles
|
import java.lang.invoke.MethodHandles
|
||||||
import java.lang.invoke.MethodType
|
import java.lang.invoke.MethodType
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
val fields: MutableList<Variable>
|
val fields: MutableList<Variable>
|
||||||
@@ -285,24 +286,35 @@ class Lambda(val irFunction: IrFunction, override val irClass: IrClass) : Comple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExceptionState private constructor(override val irClass: IrClass, override val fields: MutableList<Variable>) : Complex(irClass, fields) {
|
class ExceptionState private constructor(
|
||||||
|
override val irClass: IrClass, override val fields: MutableList<Variable>, stackTrace: List<String>
|
||||||
|
) : Complex(irClass, fields) {
|
||||||
|
private lateinit var exceptionFqName: String
|
||||||
private val exceptionHierarchy = mutableListOf<String>()
|
private val exceptionHierarchy = mutableListOf<String>()
|
||||||
private val messageProperty = irClass.getPropertyByName("message")
|
private val messageProperty = irClass.getPropertyByName("message")
|
||||||
private val causeProperty = irClass.getPropertyByName("cause")
|
private val causeProperty = irClass.getPropertyByName("cause")
|
||||||
|
|
||||||
|
private val stackTrace: List<String>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
instance = this
|
instance = this
|
||||||
|
this.stackTrace = stackTrace.reversed()
|
||||||
|
if (!this::exceptionFqName.isInitialized) this.exceptionFqName = irClass.fqNameForIrSerialization.asString()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(common: Common) : this(common.irClass, common.fields)
|
constructor(common: Common, stackTrace: List<String>) : this(common.irClass, common.fields, stackTrace)
|
||||||
constructor(wrapper: Wrapper) : this(wrapper.value as Throwable, wrapper.irClass)
|
constructor(wrapper: Wrapper, stackTrace: List<String>) : this(wrapper.value as Throwable, wrapper.irClass, stackTrace)
|
||||||
|
|
||||||
constructor(exception: Throwable, irClass: IrClass) : this(irClass, evaluateFields(exception, irClass)) {
|
constructor(
|
||||||
|
exception: Throwable, irClass: IrClass, stackTrace: List<String>
|
||||||
|
) : this(irClass, evaluateFields(exception, irClass), stackTrace) {
|
||||||
if (irClass.name.asString() == "Throwable" && exception::class.java.name != "Throwable") {
|
if (irClass.name.asString() == "Throwable" && exception::class.java.name != "Throwable") {
|
||||||
// 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
|
exceptionHierarchy += exception::class.java.name
|
||||||
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)
|
exceptionHierarchy.removeAt(exceptionHierarchy.lastIndex) // remove unnecessary java.lang.Object
|
||||||
|
|
||||||
|
this.exceptionFqName = exception::class.java.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,8 +329,16 @@ class ExceptionState private constructor(override val irClass: IrClass, override
|
|||||||
|
|
||||||
fun getCause(): ExceptionState = getState(causeProperty.descriptor) as ExceptionState
|
fun getCause(): ExceptionState = getState(causeProperty.descriptor) as ExceptionState
|
||||||
|
|
||||||
|
fun getFullDescription(): String {
|
||||||
|
val message = getMessage().value.let { if (it.isNotEmpty()) ": $it" else ""}
|
||||||
|
val prefix = if (stackTrace.isNotEmpty()) "\n\t" else ""
|
||||||
|
val postfix = if (stackTrace.size > 10) "\n\t..." else ""
|
||||||
|
return "Exception $exceptionFqName$message" +
|
||||||
|
stackTrace.subList(0, min(stackTrace.size, 10)).joinToString(separator = "\n\t", prefix = prefix, postfix = postfix)
|
||||||
|
}
|
||||||
|
|
||||||
override fun copy(): State {
|
override fun copy(): State {
|
||||||
return ExceptionState(irClass, fields).apply { this@apply.instance = this@ExceptionState.instance }
|
return ExceptionState(irClass, fields, stackTrace).apply { this@apply.instance = this@ExceptionState.instance }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -333,7 +353,8 @@ class ExceptionState private constructor(override val irClass: IrClass, override
|
|||||||
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))
|
||||||
val causeVar = exception.cause?.let { Variable(causeProperty.descriptor, ExceptionState(it, irClass)) }
|
// TODO load stack trace from cause
|
||||||
|
val causeVar = exception.cause?.let { Variable(causeProperty.descriptor, ExceptionState(it, irClass, listOf())) }
|
||||||
return listOfNotNull(messageVar, causeVar).toMutableList()
|
return listOfNotNull(messageVar, causeVar).toMutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user