From fab770a3465919347868e1efd7a4c97be5e2d804 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Wed, 18 Jun 2014 16:45:47 +0400 Subject: [PATCH] Eval4j: support invokespecial instruction --- .../src/org/jetbrains/eval4j/jdi/jdiEval.kt | 42 +++++++++++-------- .../src/org/jetbrains/eval4j/jdi/jdiValues.kt | 3 +- .../org/jetbrains/eval4j/jdi/test/jdiTest.kt | 26 ++++++++---- .../jetbrains/eval4j/test/BaseTestData.java | 25 +++++++++++ .../org/jetbrains/eval4j/test/TestData.java | 25 ++++++++++- eval4j/test/org/jetbrains/eval4j/test/main.kt | 8 ++-- .../org/jetbrains/eval4j/test/suiteBuilder.kt | 11 ++--- eval4j/test/org/jetbrains/eval4j/test/util.kt | 19 +++++++++ 8 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 eval4j/test/org/jetbrains/eval4j/test/BaseTestData.java create mode 100644 eval4j/test/org/jetbrains/eval4j/test/util.kt diff --git a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 6ca3861c850..4f5a1df4e0b 100644 --- a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -21,6 +21,8 @@ import org.jetbrains.org.objectweb.asm.Type import com.sun.jdi import com.sun.jdi.ClassNotLoadedException import com.sun.tools.jdi.ReferenceTypeImpl +import com.sun.jdi.ObjectReference +import com.sun.jdi.Method val CLASS = Type.getType(javaClass>()) val BOOTSTRAP_CLASS_DESCRIPTORS = setOf("Ljava/lang/String;", "Ljava/lang/ClassLoader;", "Ljava/lang/Class;") @@ -213,27 +215,31 @@ class JDIEval( } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { - if (invokespecial) { - if (methodDesc.name == "") { - // Constructor call - val ctor = findMethod(methodDesc) - val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType - val args = mapArguments(arguments, ctor.safeArgumentTypes()) - val result = mayThrow { _class.newInstance(thread, ctor, args, invokePolicy) } - instance.value = result - return result.asValue() - } - else { - // TODO - throw UnsupportedOperationException("invokespecial is not suported yet") - } + if (invokespecial && methodDesc.name == "") { + // Constructor call + val ctor = findMethod(methodDesc) + val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType + val args = mapArguments(arguments, ctor.safeArgumentTypes()) + val result = mayThrow { _class.newInstance(thread, ctor, args, invokePolicy) } + instance.value = result + return result.asValue() + } + + fun doInvokeMethod(obj: ObjectReference, method: Method, policy: Int): Value { + val args = mapArguments(arguments, method.safeArgumentTypes()) + val result = mayThrow { obj.invokeMethod(thread, method, args, policy) } + return result.asValue() } val obj = instance.jdiObj.checkNull() - val method = findMethod(methodDesc, instance.jdiObj!!.referenceType() ?: methodDesc.ownerType.asReferenceType()) - val args = mapArguments(arguments, method.safeArgumentTypes()) - val result = mayThrow { obj.invokeMethod(thread, method, args, invokePolicy) } - return result.asValue() + if (invokespecial) { + val method = findMethod(methodDesc) + return doInvokeMethod(obj, method, invokePolicy or ObjectReference.INVOKE_NONVIRTUAL) + } + else { + val method = findMethod(methodDesc, obj.referenceType() ?: methodDesc.ownerType.asReferenceType()) + return doInvokeMethod(obj, method, invokePolicy) + } } private fun mapArguments(arguments: List, expecetedTypes: List): List { diff --git a/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt index c155c691bac..d665acfd1a3 100644 --- a/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt @@ -25,10 +25,9 @@ import com.sun.jdi fun makeInitialFrame(methodNode: MethodNode, arguments: List): Frame { val isStatic = (methodNode.access and ACC_STATIC) != 0 - assert(isStatic, "Instance methods are not supported: $methodNode") val params = Type.getArgumentTypes(methodNode.desc) - assert(params.size == arguments.size(), "Wrong number of arguments for $methodNode: $arguments") + assert(arguments.size() == (if (isStatic) params.size else params.size + 1), "Wrong number of arguments for $methodNode: $arguments") val frame = Frame(methodNode.maxLocals, methodNode.maxStack) frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc))) diff --git a/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt index e1bf8a7dcbb..301830921a6 100644 --- a/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt +++ b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt @@ -21,15 +21,14 @@ import com.sun.jdi import junit.framework.TestSuite import org.jetbrains.eval4j.test.buildTestSuite import junit.framework.TestCase -import org.jetbrains.eval4j.interpreterLoop import org.junit.Assert.* import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger -import org.jetbrains.eval4j.ExceptionThrown -import org.jetbrains.eval4j.MethodDescription -import org.jetbrains.eval4j.ValueReturned import org.jetbrains.eval4j.jdi.* import java.io.File +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.eval4j.test.getTestName val DEBUGEE_CLASS = javaClass() @@ -98,15 +97,24 @@ fun suite(): TestSuite { val suite = buildTestSuite { methodNode, ownerClass, expected -> remainingTests.incrementAndGet() - object : TestCase("test" + methodNode.name.capitalize()) { + object : TestCase(getTestName(methodNode.name)) { override fun runTest() { - val eval = JDIEval( - vm, classLoader!!, thread!!, 0 - ) + val eval = JDIEval(vm, classLoader!!, thread!!, 0) + + val args = if ((methodNode.access and Opcodes.ACC_STATIC) == 0) { + // Instance method + val newInstance = eval.newInstance(Type.getType(ownerClass)) + val thisValue = eval.invokeMethod(newInstance, MethodDescription(ownerClass.getName(), "", "()V", false), listOf(), true) + listOf(thisValue) + } + else { + listOf() + } + val value = interpreterLoop( methodNode, - makeInitialFrame(methodNode, listOf()), + makeInitialFrame(methodNode, args), eval ) diff --git a/eval4j/test/org/jetbrains/eval4j/test/BaseTestData.java b/eval4j/test/org/jetbrains/eval4j/test/BaseTestData.java new file mode 100644 index 00000000000..514d24c0cf5 --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/test/BaseTestData.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.eval4j.test; + +class BaseTestData { + String superCall() { + return "Base"; + } + + private String invokeSpecialPrivateFun(String s) { return "Derived"; } +} \ No newline at end of file diff --git a/eval4j/test/org/jetbrains/eval4j/test/TestData.java b/eval4j/test/org/jetbrains/eval4j/test/TestData.java index 4efccd6967f..00bcc84c8c3 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/TestData.java +++ b/eval4j/test/org/jetbrains/eval4j/test/TestData.java @@ -16,8 +16,8 @@ package org.jetbrains.eval4j.test; -class TestData { - static void returnVoid() { +class TestData extends BaseTestData { + static void returnVoid() { } static boolean returnBoolean() { @@ -691,6 +691,27 @@ class TestData { long l = 1; Long[] IFEQ_L = new Long[] { b ? 100L : 200L }; } + + @Override + String superCall() { + return "Derived"; + } + + String testInvokeSpecialForSuperCall() { + return super.superCall(); + } + + static String testInvokeSpecial() { + TestData td = new TestData(); + return td.invokeSpecialPrivateFun(""); + } + + private String invokeSpecialPrivateFun(String s) { return "Base"; } + + public TestData() { + } } + + diff --git a/eval4j/test/org/jetbrains/eval4j/test/main.kt b/eval4j/test/org/jetbrains/eval4j/test/main.kt index 0ec942f1710..56ff0890ec0 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/main.kt +++ b/eval4j/test/org/jetbrains/eval4j/test/main.kt @@ -33,7 +33,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Frame fun suite(): TestSuite = buildTestSuite { methodNode, ownerClass, expected -> - object : TestCase("test" + methodNode.name.capitalize()) { + object : TestCase(getTestName(methodNode.name)) { override fun runTest() { val value = interpreterLoop( @@ -62,7 +62,9 @@ fun initFrame( var local = 0 if ((m.access and ACC_STATIC) == 0) { val ctype = Type.getObjectType(owner) - current.setLocal(local++, makeNotInitializedValue(ctype)) + val newInstance = REFLECTION_EVAL.newInstance(ctype) + val thisValue = REFLECTION_EVAL.invokeMethod(newInstance, MethodDescription(owner, "", "()V", false), listOf(), true) + current.setLocal(local++, thisValue) } val args = Type.getArgumentTypes(m.desc) @@ -265,7 +267,7 @@ object REFLECTION_EVAL : Eval { } else { // TODO - throw UnsupportedOperationException("invokespecial is not suported yet") + throw UnsupportedOperationException("invokespecial is not suported in reflection eval") } } val obj = instance.obj().checkNull() diff --git a/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt b/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt index 9de6ed4cd4a..ba08f6dac56 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt +++ b/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt @@ -63,15 +63,16 @@ fun buildTestCase(ownerClass: Class, var expected: InterpreterResult? = null for (method in ownerClass.getDeclaredMethods()) { if (method.getName() == methodNode.name) { - if ((method.getModifiers() and Modifier.STATIC) == 0) { - println("Skipping instance method: $method") - } - else if (method.getParameterTypes()!!.size > 0) { + val isStatic = (method.getModifiers() and Modifier.STATIC) != 0 + if (method.getParameterTypes()!!.size > 0) { println("Skipping method with parameters: $method") } + else if (!isStatic && !method.getName()!!.startsWith("test")) { + println("Skipping instance method (should be started with 'test') : $method") + } else { method.setAccessible(true) - val result = method.invoke(null) + val result = method.invoke(if (isStatic) null else ownerClass.newInstance()) val returnType = Type.getType(method.getReturnType()!!) try { expected = ValueReturned(objectToValue(result, returnType)) diff --git a/eval4j/test/org/jetbrains/eval4j/test/util.kt b/eval4j/test/org/jetbrains/eval4j/test/util.kt new file mode 100644 index 00000000000..5813cf5579b --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/test/util.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.eval4j.test + +fun getTestName(methodName: String) = if (methodName.startsWith("test")) methodName else "test${methodName.capitalize()}" \ No newline at end of file