From 1687b9cbca649de8aa874ce7e72c5583a7452821 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Fri, 20 Jun 2014 12:20:02 +0400 Subject: [PATCH] Eval4j: add exception type in ExceptionThrown result --- .../src/org/jetbrains/eval4j/interpreter.kt | 2 +- .../org/jetbrains/eval4j/interpreterLoop.kt | 28 +++++++++++++------ .../src/org/jetbrains/eval4j/jdi/jdiEval.kt | 17 ++++++----- eval4j/src/org/jetbrains/eval4j/values.kt | 4 +++ .../org/jetbrains/eval4j/jdi/test/jdiTest.kt | 11 ++++---- .../org/jetbrains/eval4j/test/TestData.java | 21 ++++++++++++++ eval4j/test/org/jetbrains/eval4j/test/main.kt | 8 +++++- .../org/jetbrains/eval4j/test/suiteBuilder.kt | 8 ++++-- 8 files changed, 75 insertions(+), 24 deletions(-) diff --git a/eval4j/src/org/jetbrains/eval4j/interpreter.kt b/eval4j/src/org/jetbrains/eval4j/interpreter.kt index 90ade0434bc..c1d916f4fac 100644 --- a/eval4j/src/org/jetbrains/eval4j/interpreter.kt +++ b/eval4j/src/org/jetbrains/eval4j/interpreter.kt @@ -194,7 +194,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( ObjectValue(value.obj(), targetType) } else { - throwEvalException(ClassCastException("Value '$value' cannot be cast to $targetType")) + throwEvalException(ClassCastException("${value.asmType.getClassName()} cannot be cast to ${targetType.getClassName()}")) } } diff --git a/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt b/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt index 1f63176ab82..e6712a16519 100644 --- a/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -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.tree.TryCatchBlockNode import java.util.ArrayList +import org.jetbrains.eval4j.ExceptionThrown.ExceptionKind trait InterpreterResult { override fun toString(): String } -class ExceptionThrown(val exception: Value): InterpreterResult { - override fun toString(): String = "Thrown $exception" +class ExceptionThrown(val exception: Value, val kind: ExceptionKind): InterpreterResult { + override fun toString(): String = "Thrown $exception: $kind" + + enum class ExceptionKind { + FROM_EVALUATED_CODE + FROM_EVALUATOR + BROKEN_CODE + } } data class ValueReturned(val result: Value): InterpreterResult { @@ -60,10 +67,13 @@ trait InterpretationEventHandler { fun exceptionCaught(currentState: Frame, 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()}" } +class BrokenCode(cause: Throwable): ThrownFromEvalExceptionBase(cause) +class ThrownFromEvalException(cause: Throwable): ThrownFromEvalExceptionBase(cause) + class ThrownFromEvaluatedCodeException(val exception: Value): RuntimeException() { override fun toString(): String = "Thrown from evaluated code: $exception" } @@ -206,7 +216,7 @@ fun interpreterLoop( val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) if (handled != null) return handled 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! @@ -216,20 +226,22 @@ fun interpreterLoop( try { frame.execute(currentInsn, interpreter) } - catch (e: ThrownFromEvalException) { + catch (e: ThrownFromEvalExceptionBase) { val exception = e.getCause()!! val exceptionValue = ObjectValue(exception, Type.getType(exception.javaClass)) val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) if (handled != null) return handled 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) { val handled = handler.exceptionThrown(frame, currentInsn, e.exception) if (handled != null) return handled 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 Frame.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 Frame.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() fun computeHandlers(m: MethodNode): Array?> { diff --git a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 4f5a1df4e0b..1a00df6675f 100644 --- a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -138,7 +138,7 @@ class JDIEval( val _class = fieldDesc.ownerType.asReferenceType() val field = _class.fieldByName(fieldDesc.name) if (field == null) { - throwEvalException(NoSuchFieldError("Field not found: $fieldDesc")) + throwBrokenCodeException(NoSuchFieldError("Field not found: $fieldDesc")) } return field } @@ -146,7 +146,7 @@ class JDIEval( private fun findStaticField(fieldDesc: FieldDescription): jdi.Field { val field = findField(fieldDesc) if (!field.isStatic()) { - throwEvalException(NoSuchFieldError("Field is not static: $fieldDesc")) + throwBrokenCodeException(NoSuchFieldError("Field is not static: $fieldDesc")) } return field } @@ -160,12 +160,12 @@ class JDIEval( val field = findStaticField(fieldDesc) 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() 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()) @@ -181,7 +181,7 @@ class JDIEval( else -> _class.methodsByName(methodDesc.name, methodDesc.desc) } if (method.isEmpty()) { - throwEvalException(NoSuchMethodError("Method not found: $methodDesc")) + throwBrokenCodeException(NoSuchMethodError("Method not found: $methodDesc")) } return method[0] } @@ -189,10 +189,10 @@ class JDIEval( override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { val method = findMethod(methodDesc) if (!method.isStatic()) { - throwEvalException(NoSuchMethodError("Method is not static: $methodDesc")) + throwBrokenCodeException(NoSuchMethodError("Method is not static: $methodDesc")) } 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 result = mayThrow { _class.invokeMethod(thread, method, args, invokePolicy) } @@ -277,4 +277,7 @@ fun mayThrow(f: () -> T): T { catch (e: jdi.InvocationException) { throw ThrownFromEvaluatedCodeException(e.exception().asValue()) } + catch (e: Throwable) { + throwBrokenCodeException(e) + } } \ No newline at end of file diff --git a/eval4j/src/org/jetbrains/eval4j/values.kt b/eval4j/src/org/jetbrains/eval4j/values.kt index 4e15b5adda2..971aa6ad9f3 100644 --- a/eval4j/src/org/jetbrains/eval4j/values.kt +++ b/eval4j/src/org/jetbrains/eval4j/values.kt @@ -129,4 +129,8 @@ fun T?.checkNull(): T { fun throwEvalException(e: Throwable): Nothing { throw ThrownFromEvalException(e) +} + +fun throwBrokenCodeException(e: Throwable): Nothing { + throw BrokenCode(e) } \ No newline at end of file diff --git a/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt index 301830921a6..41968f8e2f6 100644 --- a/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt +++ b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt @@ -29,6 +29,7 @@ import java.io.File import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.eval4j.test.getTestName +import com.sun.jdi.ObjectReference val DEBUGEE_CLASS = javaClass() @@ -133,14 +134,14 @@ fun suite(): TestSuite { } 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) { 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 { assertEquals(expected, value) } diff --git a/eval4j/test/org/jetbrains/eval4j/test/TestData.java b/eval4j/test/org/jetbrains/eval4j/test/TestData.java index f154809d09b..0af5b48f738 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/TestData.java +++ b/eval4j/test/org/jetbrains/eval4j/test/TestData.java @@ -710,6 +710,27 @@ class TestData extends BaseTestData { 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() { } } diff --git a/eval4j/test/org/jetbrains/eval4j/test/main.kt b/eval4j/test/org/jetbrains/eval4j/test/main.kt index 58138bb6d94..43ba26d572a 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/main.kt +++ b/eval4j/test/org/jetbrains/eval4j/test/main.kt @@ -45,7 +45,13 @@ fun suite(): TestSuite = buildTestSuite { ), 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()) + } } } diff --git a/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt b/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt index ba08f6dac56..eecfeac02f1 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt +++ b/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt @@ -72,14 +72,18 @@ fun buildTestCase(ownerClass: Class, } else { method.setAccessible(true) - val result = method.invoke(if (isStatic) null else ownerClass.newInstance()) - val returnType = Type.getType(method.getReturnType()!!) try { + val result = method.invoke(if (isStatic) null else ownerClass.newInstance()) + val returnType = Type.getType(method.getReturnType()!!) expected = ValueReturned(objectToValue(result, returnType)) } catch (e: UnsupportedOperationException) { println("Skipping $method: $e") } + catch (e: Throwable) { + val cause = e.getCause() ?: e + expected = ExceptionThrown(objectToValue(cause, Type.getType(cause.javaClass)), ExceptionThrown.ExceptionKind.FROM_EVALUATOR) + } } } }