Eval4j: support invokespecial instruction
This commit is contained in:
@@ -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()}"
|
||||
Reference in New Issue
Block a user