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)
|
||||
}
|
||||
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.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<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()}"
|
||||
}
|
||||
|
||||
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 <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()
|
||||
fun computeHandlers(m: MethodNode): Array<out List<TryCatchBlockNode>?> {
|
||||
|
||||
@@ -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>): 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 <T> mayThrow(f: () -> T): T {
|
||||
catch (e: jdi.InvocationException) {
|
||||
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 {
|
||||
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.Type
|
||||
import org.jetbrains.eval4j.test.getTestName
|
||||
import com.sun.jdi.ObjectReference
|
||||
|
||||
val DEBUGEE_CLASS = javaClass<Debugee>()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,14 +72,18 @@ fun buildTestCase(ownerClass: Class<TestData>,
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user