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
|
||||||
import com.sun.jdi.ClassNotLoadedException
|
import com.sun.jdi.ClassNotLoadedException
|
||||||
import com.sun.tools.jdi.ReferenceTypeImpl
|
import com.sun.tools.jdi.ReferenceTypeImpl
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
import com.sun.jdi.Method
|
||||||
|
|
||||||
val CLASS = Type.getType(javaClass<Class<*>>())
|
val CLASS = Type.getType(javaClass<Class<*>>())
|
||||||
val BOOTSTRAP_CLASS_DESCRIPTORS = setOf("Ljava/lang/String;", "Ljava/lang/ClassLoader;", "Ljava/lang/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 {
|
override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean): Value {
|
||||||
if (invokespecial) {
|
if (invokespecial && methodDesc.name == "<init>") {
|
||||||
if (methodDesc.name == "<init>") {
|
// Constructor call
|
||||||
// Constructor call
|
val ctor = findMethod(methodDesc)
|
||||||
val ctor = findMethod(methodDesc)
|
val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType
|
||||||
val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType
|
val args = mapArguments(arguments, ctor.safeArgumentTypes())
|
||||||
val args = mapArguments(arguments, ctor.safeArgumentTypes())
|
val result = mayThrow { _class.newInstance(thread, ctor, args, invokePolicy) }
|
||||||
val result = mayThrow { _class.newInstance(thread, ctor, args, invokePolicy) }
|
instance.value = result
|
||||||
instance.value = result
|
return result.asValue()
|
||||||
return result.asValue()
|
}
|
||||||
}
|
|
||||||
else {
|
fun doInvokeMethod(obj: ObjectReference, method: Method, policy: Int): Value {
|
||||||
// TODO
|
val args = mapArguments(arguments, method.safeArgumentTypes())
|
||||||
throw UnsupportedOperationException("invokespecial is not suported yet")
|
val result = mayThrow { obj.invokeMethod(thread, method, args, policy) }
|
||||||
}
|
return result.asValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
val obj = instance.jdiObj.checkNull()
|
val obj = instance.jdiObj.checkNull()
|
||||||
val method = findMethod(methodDesc, instance.jdiObj!!.referenceType() ?: methodDesc.ownerType.asReferenceType())
|
if (invokespecial) {
|
||||||
val args = mapArguments(arguments, method.safeArgumentTypes())
|
val method = findMethod(methodDesc)
|
||||||
val result = mayThrow { obj.invokeMethod(thread, method, args, invokePolicy) }
|
return doInvokeMethod(obj, method, invokePolicy or ObjectReference.INVOKE_NONVIRTUAL)
|
||||||
return result.asValue()
|
}
|
||||||
|
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?> {
|
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> {
|
fun makeInitialFrame(methodNode: MethodNode, arguments: List<Value>): Frame<Value> {
|
||||||
val isStatic = (methodNode.access and ACC_STATIC) != 0
|
val isStatic = (methodNode.access and ACC_STATIC) != 0
|
||||||
assert(isStatic, "Instance methods are not supported: $methodNode")
|
|
||||||
|
|
||||||
val params = Type.getArgumentTypes(methodNode.desc)
|
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)
|
val frame = Frame<Value>(methodNode.maxLocals, methodNode.maxStack)
|
||||||
frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc)))
|
frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc)))
|
||||||
|
|||||||
@@ -21,15 +21,14 @@ import com.sun.jdi
|
|||||||
import junit.framework.TestSuite
|
import junit.framework.TestSuite
|
||||||
import org.jetbrains.eval4j.test.buildTestSuite
|
import org.jetbrains.eval4j.test.buildTestSuite
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import org.jetbrains.eval4j.interpreterLoop
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
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 org.jetbrains.eval4j.jdi.*
|
||||||
import java.io.File
|
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>()
|
val DEBUGEE_CLASS = javaClass<Debugee>()
|
||||||
|
|
||||||
@@ -98,15 +97,24 @@ fun suite(): TestSuite {
|
|||||||
val suite = buildTestSuite {
|
val suite = buildTestSuite {
|
||||||
methodNode, ownerClass, expected ->
|
methodNode, ownerClass, expected ->
|
||||||
remainingTests.incrementAndGet()
|
remainingTests.incrementAndGet()
|
||||||
object : TestCase("test" + methodNode.name.capitalize()) {
|
object : TestCase(getTestName(methodNode.name)) {
|
||||||
|
|
||||||
override fun runTest() {
|
override fun runTest() {
|
||||||
val eval = JDIEval(
|
val eval = JDIEval(vm, classLoader!!, thread!!, 0)
|
||||||
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(
|
val value = interpreterLoop(
|
||||||
methodNode,
|
methodNode,
|
||||||
makeInitialFrame(methodNode, listOf()),
|
makeInitialFrame(methodNode, args),
|
||||||
eval
|
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;
|
package org.jetbrains.eval4j.test;
|
||||||
|
|
||||||
class TestData {
|
class TestData extends BaseTestData {
|
||||||
static void returnVoid() {
|
static void returnVoid() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean returnBoolean() {
|
static boolean returnBoolean() {
|
||||||
@@ -691,6 +691,27 @@ class TestData {
|
|||||||
long l = 1;
|
long l = 1;
|
||||||
Long[] IFEQ_L = new Long[] { b ? 100L : 200L };
|
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 {
|
fun suite(): TestSuite = buildTestSuite {
|
||||||
methodNode, ownerClass, expected ->
|
methodNode, ownerClass, expected ->
|
||||||
object : TestCase("test" + methodNode.name.capitalize()) {
|
object : TestCase(getTestName(methodNode.name)) {
|
||||||
|
|
||||||
override fun runTest() {
|
override fun runTest() {
|
||||||
val value = interpreterLoop(
|
val value = interpreterLoop(
|
||||||
@@ -62,7 +62,9 @@ fun initFrame(
|
|||||||
var local = 0
|
var local = 0
|
||||||
if ((m.access and ACC_STATIC) == 0) {
|
if ((m.access and ACC_STATIC) == 0) {
|
||||||
val ctype = Type.getObjectType(owner)
|
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)
|
val args = Type.getArgumentTypes(m.desc)
|
||||||
@@ -265,7 +267,7 @@ object REFLECTION_EVAL : Eval {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO
|
// TODO
|
||||||
throw UnsupportedOperationException("invokespecial is not suported yet")
|
throw UnsupportedOperationException("invokespecial is not suported in reflection eval")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val obj = instance.obj().checkNull()
|
val obj = instance.obj().checkNull()
|
||||||
|
|||||||
@@ -63,15 +63,16 @@ fun buildTestCase(ownerClass: Class<TestData>,
|
|||||||
var expected: InterpreterResult? = null
|
var expected: InterpreterResult? = null
|
||||||
for (method in ownerClass.getDeclaredMethods()) {
|
for (method in ownerClass.getDeclaredMethods()) {
|
||||||
if (method.getName() == methodNode.name) {
|
if (method.getName() == methodNode.name) {
|
||||||
if ((method.getModifiers() and Modifier.STATIC) == 0) {
|
val isStatic = (method.getModifiers() and Modifier.STATIC) != 0
|
||||||
println("Skipping instance method: $method")
|
if (method.getParameterTypes()!!.size > 0) {
|
||||||
}
|
|
||||||
else if (method.getParameterTypes()!!.size > 0) {
|
|
||||||
println("Skipping method with parameters: $method")
|
println("Skipping method with parameters: $method")
|
||||||
}
|
}
|
||||||
|
else if (!isStatic && !method.getName()!!.startsWith("test")) {
|
||||||
|
println("Skipping instance method (should be started with 'test') : $method")
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
method.setAccessible(true)
|
method.setAccessible(true)
|
||||||
val result = method.invoke(null)
|
val result = method.invoke(if (isStatic) null else ownerClass.newInstance())
|
||||||
val returnType = Type.getType(method.getReturnType()!!)
|
val returnType = Type.getType(method.getReturnType()!!)
|
||||||
try {
|
try {
|
||||||
expected = ValueReturned(objectToValue(result, returnType))
|
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