diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index f66428094e3..924d69cca1f 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -329,6 +329,9 @@ + + + @@ -338,6 +341,9 @@ + + + diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index e6a75c4ffb1..528606163b5 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -13,7 +13,27 @@ import org.objectweb.asm.tree.MultiANewArrayInsnNode import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.IincInsnNode -import org.objectweb.asm.tree.LabelNode + +data class MethodDescription( + val ownerInternalName: String, + val name: String, + val desc: String, + val isStatic: Boolean +) + +fun MethodDescription(insn: MethodInsnNode): MethodDescription = + MethodDescription( + insn.owner, + insn.name, + insn.desc, + insn.getOpcode() == INVOKESTATIC + ) + +val MethodDescription.returnType: Type + get() = Type.getReturnType(desc) + +val MethodDescription.parameterTypes: List + get() = Type.getArgumentTypes(desc).toList() class UnsupportedByteCodeException(message: String) : RuntimeException(message) @@ -32,11 +52,11 @@ trait Eval { fun getStaticField(fieldDesc: String): Value fun setStaticField(fieldDesc: String, newValue: Value) - fun invokeStaticMethod(methodDesc: String, arguments: List): Value + fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value fun getField(instance: Value, fieldDesc: String): Value fun setField(instance: Value, fieldDesc: String, newValue: Value) - fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean = false): Value + fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean = false): Value } class SingleInstructionInterpreter(private val eval: Eval) : Interpreter(ASM4) { @@ -334,16 +354,15 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( } INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { - val desc = (insn as MethodInsnNode).desc eval.invokeMethod( values[0], - desc, + MethodDescription(insn as MethodInsnNode), values.subList(1, values.size()), insn.getOpcode() == INVOKESPECIAL ) } - INVOKESTATIC -> eval.invokeStaticMethod((insn as MethodInsnNode).desc, values) + INVOKESTATIC -> eval.invokeStaticMethod(MethodDescription(insn as MethodInsnNode), values) INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported") else -> throw UnsupportedByteCodeException("$insn") diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index bde6987d79c..c0f93054736 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -74,4 +74,4 @@ val Value.int: Int get() = (this as IntValue).value val Value.long: Long get() = (this as LongValue).value val Value.float: Float get() = (this as FloatValue).value val Value.double: Double get() = (this as DoubleValue).value -val Value.obj: Any? get() = (this as ObjectValue).value \ No newline at end of file +val Value.obj: Any? get() = (this as AbstractValue<*>).value \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 71a5acbd233..ca446e9ace7 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -144,4 +144,12 @@ class TestData { return i; } + static Object testCall() { + return Integer.valueOf(1); + } + + static Object testCallWithObject() { + return String.valueOf("str"); + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 5ea8a7f1099..158c3796cd9 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -8,6 +8,7 @@ import org.jetbrains.eval4j.* import org.junit.Assert.* import junit.framework.TestSuite import junit.framework.TestCase +import java.lang.reflect.Method fun suite(): TestSuite { val suite = TestSuite() @@ -48,23 +49,12 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { method.setAccessible(true) val result = method.invoke(null) val returnType = Type.getType(method.getReturnType()!!) - expected = when (returnType.getSort()) { - Type.VOID -> ValueReturned(VOID_VALUE) - Type.BOOLEAN -> ValueReturned(boolean(result as Boolean)) - Type.BYTE -> ValueReturned(byte(result as Byte)) - Type.SHORT -> ValueReturned(short(result as Short)) - Type.CHAR -> ValueReturned(char(result as Char)) - Type.INT -> ValueReturned(int(result as Int)) - Type.LONG -> ValueReturned(long(result as Long)) - Type.DOUBLE -> ValueReturned(double(result as Double)) - Type.FLOAT -> ValueReturned(float(result as Float)) - Type.OBJECT -> ValueReturned(if (result == null) NULL_VALUE else ObjectValue(result, returnType)) - else -> { - println("Unsupported result type: $returnType") - return null - } + try { + expected = ValueReturned(objectToValue(result, returnType)) + } + catch (e: UnsupportedOperationException) { + println("Skipping $method: $e") } - println("Testing $method") } } } @@ -88,7 +78,26 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { } } +fun objectToValue(obj: Any?, expectedType: Type): Value { + return when (expectedType.getSort()) { + Type.VOID -> VOID_VALUE + Type.BOOLEAN -> boolean(obj as Boolean) + Type.BYTE -> byte(obj as Byte) + Type.SHORT -> short(obj as Short) + Type.CHAR -> char(obj as Char) + Type.INT -> int(obj as Int) + Type.LONG -> long(obj as Long) + Type.DOUBLE -> double(obj as Double) + Type.FLOAT -> float(obj as Float) + Type.OBJECT -> if (obj == null) NULL_VALUE else ObjectValue(obj, expectedType) + else -> throw UnsupportedOperationException("Unsupported result type: $expectedType") + } +} + object REFLECTION_EVAL : Eval { + + val lookup = ReflectionLookup(javaClass().getClassLoader()!!) + override fun loadClass(classType: Type): Value { throw UnsupportedOperationException() } @@ -103,6 +112,7 @@ object REFLECTION_EVAL : Eval { override fun isInsetanceOf(value: Value, targetType: Type): Boolean { throw UnsupportedOperationException() } + override fun newArray(arrayType: Type, size: Int): Value { throw UnsupportedOperationException() } @@ -118,22 +128,67 @@ object REFLECTION_EVAL : Eval { override fun setArrayElement(array: Value, index: Value, newValue: Value) { throw UnsupportedOperationException() } + override fun getStaticField(fieldDesc: String): Value { throw UnsupportedOperationException() } override fun setStaticField(fieldDesc: String, newValue: Value) { throw UnsupportedOperationException() } - override fun invokeStaticMethod(methodDesc: String, arguments: List): Value { - throw UnsupportedOperationException() + + override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { + assertTrue(methodDesc.isStatic) + val owner = lookup.findClass(methodDesc.ownerInternalName) + assertNotNull("Class not found: ${methodDesc.ownerInternalName}", owner) + val method = owner!!.findMethod(methodDesc) + assertNotNull("Method not found: $methodDesc", method) + val result = method!!.invoke(null, *arguments.map { v -> v.obj }.copyToArray()) + return objectToValue(result, methodDesc.returnType) } + override fun getField(instance: Value, fieldDesc: String): Value { throw UnsupportedOperationException() } override fun setField(instance: Value, fieldDesc: String, newValue: Value) { throw UnsupportedOperationException() } - override fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean): Value { + override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { throw UnsupportedOperationException() } -} \ No newline at end of file +} + +class ReflectionLookup(val classLoader: ClassLoader) { + [suppress("UNCHECKED_CAST")] + fun findClass(internalName: String): Class? = classLoader.loadClass(internalName.replace('/', '.')) as Class +} + +[suppress("UNCHECKED_CAST")] +fun Class.findMethod(methodDesc: MethodDescription): Method? { + for (declared in getDeclaredMethods()) { + if (methodDesc.matches(declared)) return declared + } + + val fromSuperClass = (getSuperclass() as Class).findMethod(methodDesc) + if (fromSuperClass != null) return fromSuperClass + + for (supertype in getInterfaces()) { + val fromSuper = (supertype as Class).findMethod(methodDesc) + if (fromSuper != null) return fromSuper + } + + return null +} + +fun MethodDescription.matches(method: Method): Boolean { + if (name != method.getName()) return false + + val methodParams = method.getParameterTypes()!! + if (parameterTypes.size() != methodParams.size) return false + for ((i, p) in parameterTypes.withIndices()) { + if (!p.matches(methodParams[i])) return false + } + + return returnType.matches(method.getReturnType()!!) +} + +fun Type.matches(_class: Class<*>): Boolean = this == Type.getType(_class) \ No newline at end of file