Eval4j: support invokespecial instruction

This commit is contained in:
Natalia Ukhorskaya
2014-06-18 16:45:47 +04:00
parent 3edd6b52cf
commit fab770a346
8 changed files with 120 additions and 39 deletions
+24 -18
View File
@@ -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<Class<*>>())
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<Value>, invokespecial: Boolean): Value {
if (invokespecial) {
if (methodDesc.name == "<init>") {
// 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 == "<init>") {
// 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<Value>, expecetedTypes: List<jdi.Type>): List<jdi.Value?> {
@@ -25,10 +25,9 @@ import com.sun.jdi
fun makeInitialFrame(methodNode: MethodNode, arguments: List<Value>): Frame<Value> {
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<Value>(methodNode.maxLocals, methodNode.maxStack)
frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc)))
@@ -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<Debugee>()
@@ -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(), "<init>", "()V", false), listOf(), true)
listOf(thisValue)
}
else {
listOf()
}
val value = interpreterLoop(
methodNode,
makeInitialFrame(methodNode, listOf()),
makeInitialFrame(methodNode, args),
eval
)
@@ -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"; }
}
@@ -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() {
}
}
@@ -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, "<init>", "()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()
@@ -63,15 +63,16 @@ fun buildTestCase(ownerClass: Class<TestData>,
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))
@@ -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()}"