Eval4j: add exception type in ExceptionThrown result

This commit is contained in:
Natalia Ukhorskaya
2014-06-20 12:20:02 +04:00
parent 28cbe95e0c
commit 1687b9cbca
8 changed files with 75 additions and 24 deletions
@@ -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>?> {
+10 -7
View File
@@ -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)
}
} }
} }
} }