Eval4j: add exception type in ExceptionThrown result
This commit is contained in:
@@ -194,7 +194,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(
|
|||||||
ObjectValue(value.obj(), targetType)
|
ObjectValue(value.obj(), targetType)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throwEvalException(ClassCastException("Value '$value' cannot be cast to $targetType"))
|
throwEvalException(ClassCastException("${value.asmType.getClassName()} cannot be cast to ${targetType.getClassName()}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,20 @@ import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
|||||||
import org.jetbrains.org.objectweb.asm.util.Printer
|
import org.jetbrains.org.objectweb.asm.util.Printer
|
||||||
import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode
|
import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
import org.jetbrains.eval4j.ExceptionThrown.ExceptionKind
|
||||||
|
|
||||||
trait InterpreterResult {
|
trait InterpreterResult {
|
||||||
override fun toString(): String
|
override fun toString(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExceptionThrown(val exception: Value): InterpreterResult {
|
class ExceptionThrown(val exception: Value, val kind: ExceptionKind): InterpreterResult {
|
||||||
override fun toString(): String = "Thrown $exception"
|
override fun toString(): String = "Thrown $exception: $kind"
|
||||||
|
|
||||||
|
enum class ExceptionKind {
|
||||||
|
FROM_EVALUATED_CODE
|
||||||
|
FROM_EVALUATOR
|
||||||
|
BROKEN_CODE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ValueReturned(val result: Value): InterpreterResult {
|
data class ValueReturned(val result: Value): InterpreterResult {
|
||||||
@@ -60,10 +67,13 @@ trait InterpretationEventHandler {
|
|||||||
fun exceptionCaught(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult?
|
fun exceptionCaught(currentState: Frame<Value>, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult?
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThrownFromEvalException(cause: Throwable): RuntimeException(cause) {
|
abstract class ThrownFromEvalExceptionBase(cause: Throwable): RuntimeException(cause) {
|
||||||
override fun toString(): String = "Thrown by evaluator: ${getCause()}"
|
override fun toString(): String = "Thrown by evaluator: ${getCause()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BrokenCode(cause: Throwable): ThrownFromEvalExceptionBase(cause)
|
||||||
|
class ThrownFromEvalException(cause: Throwable): ThrownFromEvalExceptionBase(cause)
|
||||||
|
|
||||||
class ThrownFromEvaluatedCodeException(val exception: Value): RuntimeException() {
|
class ThrownFromEvaluatedCodeException(val exception: Value): RuntimeException() {
|
||||||
override fun toString(): String = "Thrown from evaluated code: $exception"
|
override fun toString(): String = "Thrown from evaluated code: $exception"
|
||||||
}
|
}
|
||||||
@@ -206,7 +216,7 @@ fun interpreterLoop(
|
|||||||
val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue)
|
val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue)
|
||||||
if (handled != null) return handled
|
if (handled != null) return handled
|
||||||
if (exceptionCaught(exceptionValue)) continue
|
if (exceptionCaught(exceptionValue)) continue
|
||||||
return ExceptionThrown(exceptionValue)
|
return ExceptionThrown(exceptionValue, ExceptionKind.FROM_EVALUATED_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise!
|
// Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise!
|
||||||
@@ -216,20 +226,22 @@ fun interpreterLoop(
|
|||||||
try {
|
try {
|
||||||
frame.execute(currentInsn, interpreter)
|
frame.execute(currentInsn, interpreter)
|
||||||
}
|
}
|
||||||
catch (e: ThrownFromEvalException) {
|
catch (e: ThrownFromEvalExceptionBase) {
|
||||||
val exception = e.getCause()!!
|
val exception = e.getCause()!!
|
||||||
val exceptionValue = ObjectValue(exception, Type.getType(exception.javaClass))
|
val exceptionValue = ObjectValue(exception, Type.getType(exception.javaClass))
|
||||||
val handled = handler.exceptionThrown(frame, currentInsn,
|
val handled = handler.exceptionThrown(frame, currentInsn,
|
||||||
exceptionValue)
|
exceptionValue)
|
||||||
if (handled != null) return handled
|
if (handled != null) return handled
|
||||||
if (exceptionFromEvalCaught(exception, exceptionValue)) continue
|
if (exceptionFromEvalCaught(exception, exceptionValue)) continue
|
||||||
return ExceptionThrown(exceptionValue)
|
|
||||||
|
val exceptionType = if (e is BrokenCode) ExceptionKind.BROKEN_CODE else ExceptionKind.FROM_EVALUATOR
|
||||||
|
return ExceptionThrown(exceptionValue, exceptionType)
|
||||||
}
|
}
|
||||||
catch (e: ThrownFromEvaluatedCodeException) {
|
catch (e: ThrownFromEvaluatedCodeException) {
|
||||||
val handled = handler.exceptionThrown(frame, currentInsn, e.exception)
|
val handled = handler.exceptionThrown(frame, currentInsn, e.exception)
|
||||||
if (handled != null) return handled
|
if (handled != null) return handled
|
||||||
if (exceptionCaught(e.exception)) continue
|
if (exceptionCaught(e.exception)) continue
|
||||||
return ExceptionThrown(e.exception)
|
return ExceptionThrown(e.exception, ExceptionKind.FROM_EVALUATED_CODE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +257,7 @@ fun interpreterLoop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T: Value> Frame<T>.getStackTop(i: Int = 0) = this.getStack(this.getStackSize() - 1 - i) ?: throwEvalException(IllegalArgumentException("Couldn't get value with index = $i from top of stack"))
|
private fun <T: Value> Frame<T>.getStackTop(i: Int = 0) = this.getStack(this.getStackSize() - 1 - i) ?: throwBrokenCodeException(IllegalArgumentException("Couldn't get value with index = $i from top of stack"))
|
||||||
|
|
||||||
// Copied from org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer.analyze()
|
// Copied from org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer.analyze()
|
||||||
fun computeHandlers(m: MethodNode): Array<out List<TryCatchBlockNode>?> {
|
fun computeHandlers(m: MethodNode): Array<out List<TryCatchBlockNode>?> {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class JDIEval(
|
|||||||
val _class = fieldDesc.ownerType.asReferenceType()
|
val _class = fieldDesc.ownerType.asReferenceType()
|
||||||
val field = _class.fieldByName(fieldDesc.name)
|
val field = _class.fieldByName(fieldDesc.name)
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
throwEvalException(NoSuchFieldError("Field not found: $fieldDesc"))
|
throwBrokenCodeException(NoSuchFieldError("Field not found: $fieldDesc"))
|
||||||
}
|
}
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ class JDIEval(
|
|||||||
private fun findStaticField(fieldDesc: FieldDescription): jdi.Field {
|
private fun findStaticField(fieldDesc: FieldDescription): jdi.Field {
|
||||||
val field = findField(fieldDesc)
|
val field = findField(fieldDesc)
|
||||||
if (!field.isStatic()) {
|
if (!field.isStatic()) {
|
||||||
throwEvalException(NoSuchFieldError("Field is not static: $fieldDesc"))
|
throwBrokenCodeException(NoSuchFieldError("Field is not static: $fieldDesc"))
|
||||||
}
|
}
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
@@ -160,12 +160,12 @@ class JDIEval(
|
|||||||
val field = findStaticField(fieldDesc)
|
val field = findStaticField(fieldDesc)
|
||||||
|
|
||||||
if (field.isFinal()) {
|
if (field.isFinal()) {
|
||||||
throwEvalException(NoSuchFieldError("Can't modify a final field: $field"))
|
throwBrokenCodeException(NoSuchFieldError("Can't modify a final field: $field"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val _class = field.declaringType()
|
val _class = field.declaringType()
|
||||||
if (_class !is jdi.ClassType) {
|
if (_class !is jdi.ClassType) {
|
||||||
throwEvalException(NoSuchFieldError("Can't a field in a non-class: $field"))
|
throwBrokenCodeException(NoSuchFieldError("Can't a field in a non-class: $field"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val jdiValue = newValue.asJdiValue(vm, field.`type`().asType())
|
val jdiValue = newValue.asJdiValue(vm, field.`type`().asType())
|
||||||
@@ -181,7 +181,7 @@ class JDIEval(
|
|||||||
else -> _class.methodsByName(methodDesc.name, methodDesc.desc)
|
else -> _class.methodsByName(methodDesc.name, methodDesc.desc)
|
||||||
}
|
}
|
||||||
if (method.isEmpty()) {
|
if (method.isEmpty()) {
|
||||||
throwEvalException(NoSuchMethodError("Method not found: $methodDesc"))
|
throwBrokenCodeException(NoSuchMethodError("Method not found: $methodDesc"))
|
||||||
}
|
}
|
||||||
return method[0]
|
return method[0]
|
||||||
}
|
}
|
||||||
@@ -189,10 +189,10 @@ class JDIEval(
|
|||||||
override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value {
|
override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value {
|
||||||
val method = findMethod(methodDesc)
|
val method = findMethod(methodDesc)
|
||||||
if (!method.isStatic()) {
|
if (!method.isStatic()) {
|
||||||
throwEvalException(NoSuchMethodError("Method is not static: $methodDesc"))
|
throwBrokenCodeException(NoSuchMethodError("Method is not static: $methodDesc"))
|
||||||
}
|
}
|
||||||
val _class = method.declaringType()
|
val _class = method.declaringType()
|
||||||
if (_class !is jdi.ClassType) throwEvalException(NoSuchMethodError("Static method is a non-class type: $method"))
|
if (_class !is jdi.ClassType) throwBrokenCodeException(NoSuchMethodError("Static method is a non-class type: $method"))
|
||||||
|
|
||||||
val args = mapArguments(arguments, method.safeArgumentTypes())
|
val args = mapArguments(arguments, method.safeArgumentTypes())
|
||||||
val result = mayThrow { _class.invokeMethod(thread, method, args, invokePolicy) }
|
val result = mayThrow { _class.invokeMethod(thread, method, args, invokePolicy) }
|
||||||
@@ -277,4 +277,7 @@ fun <T> mayThrow(f: () -> T): T {
|
|||||||
catch (e: jdi.InvocationException) {
|
catch (e: jdi.InvocationException) {
|
||||||
throw ThrownFromEvaluatedCodeException(e.exception().asValue())
|
throw ThrownFromEvaluatedCodeException(e.exception().asValue())
|
||||||
}
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
throwBrokenCodeException(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -129,4 +129,8 @@ fun <T: Any> T?.checkNull(): T {
|
|||||||
|
|
||||||
fun throwEvalException(e: Throwable): Nothing {
|
fun throwEvalException(e: Throwable): Nothing {
|
||||||
throw ThrownFromEvalException(e)
|
throw ThrownFromEvalException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun throwBrokenCodeException(e: Throwable): Nothing {
|
||||||
|
throw BrokenCode(e)
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ import java.io.File
|
|||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.eval4j.test.getTestName
|
import org.jetbrains.eval4j.test.getTestName
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
|
||||||
val DEBUGEE_CLASS = javaClass<Debugee>()
|
val DEBUGEE_CLASS = javaClass<Debugee>()
|
||||||
|
|
||||||
@@ -133,14 +134,14 @@ fun suite(): TestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (value is ExceptionThrown) {
|
|
||||||
val str = value.exception.jdiObj.callToString()
|
|
||||||
System.err.println("Exception: $str")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expected is ValueReturned && value is ValueReturned && value.result is ObjectValue) {
|
if (expected is ValueReturned && value is ValueReturned && value.result is ObjectValue) {
|
||||||
assertEquals(expected.result.obj().toString(), value.result.jdiObj.callToString())
|
assertEquals(expected.result.obj().toString(), value.result.jdiObj.callToString())
|
||||||
}
|
}
|
||||||
|
else if (expected is ExceptionThrown && value is ExceptionThrown) {
|
||||||
|
val valueObj = value.exception.obj()
|
||||||
|
val actual = if (valueObj is ObjectReference) valueObj.callToString() else valueObj.toString()
|
||||||
|
assertEquals(expected.exception.obj().toString(), actual)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
assertEquals(expected, value)
|
assertEquals(expected, value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -710,6 +710,27 @@ class TestData extends BaseTestData {
|
|||||||
|
|
||||||
private String invokeSpecialPrivateFun(String s) { return "Base"; }
|
private String invokeSpecialPrivateFun(String s) { return "Base"; }
|
||||||
|
|
||||||
|
static Throwable exception1() {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exception2() {
|
||||||
|
new ExceptionsTest().f1();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exceptionClassCast() {
|
||||||
|
ExceptionsTest.Derived test = (ExceptionsTest.Derived) new ExceptionsTest.Base();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ExceptionsTest {
|
||||||
|
void f1() {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Base {}
|
||||||
|
static class Derived extends Base {}
|
||||||
|
}
|
||||||
|
|
||||||
public TestData() {
|
public TestData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,13 @@ fun suite(): TestSuite = buildTestSuite {
|
|||||||
),
|
),
|
||||||
REFLECTION_EVAL
|
REFLECTION_EVAL
|
||||||
)
|
)
|
||||||
assertEquals(expected, value)
|
|
||||||
|
if (expected is ExceptionThrown && value is ExceptionThrown) {
|
||||||
|
assertEquals(expected.exception.toString(), value.exception.toString())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEquals(expected.toString(), value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,14 +72,18 @@ fun buildTestCase(ownerClass: Class<TestData>,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
method.setAccessible(true)
|
method.setAccessible(true)
|
||||||
val result = method.invoke(if (isStatic) null else ownerClass.newInstance())
|
|
||||||
val returnType = Type.getType(method.getReturnType()!!)
|
|
||||||
try {
|
try {
|
||||||
|
val result = method.invoke(if (isStatic) null else ownerClass.newInstance())
|
||||||
|
val returnType = Type.getType(method.getReturnType()!!)
|
||||||
expected = ValueReturned(objectToValue(result, returnType))
|
expected = ValueReturned(objectToValue(result, returnType))
|
||||||
}
|
}
|
||||||
catch (e: UnsupportedOperationException) {
|
catch (e: UnsupportedOperationException) {
|
||||||
println("Skipping $method: $e")
|
println("Skipping $method: $e")
|
||||||
}
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
val cause = e.getCause() ?: e
|
||||||
|
expected = ExceptionThrown(objectToValue(cause, Type.getType(cause.javaClass)), ExceptionThrown.ExceptionKind.FROM_EVALUATOR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user