Support static method calls

This commit is contained in:
Andrey Breslav
2013-10-06 17:46:23 +04:00
parent cec2111c29
commit b7c169fc89
5 changed files with 115 additions and 27 deletions
@@ -329,6 +329,9 @@
<item name='org.objectweb.asm.tree.MethodInsnNode desc'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.objectweb.asm.tree.MethodInsnNode name'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.objectweb.asm.tree.MethodInsnNode org.objectweb.asm.tree.AbstractInsnNode clone(java.util.Map&lt;org.objectweb.asm.tree.LabelNode,org.objectweb.asm.tree.LabelNode&gt;)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -338,6 +341,9 @@
<val name="value" val="{org.jetbrains.kannotator.controlFlow.builder.analysis.NullabilityKey.class}"/>
</annotation>
</item>
<item name='org.objectweb.asm.tree.MethodInsnNode owner'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.objectweb.asm.tree.MethodInsnNode void accept(org.objectweb.asm.MethodVisitor) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
+25 -6
View File
@@ -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<Type>
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>): Value
fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): Value
fun getField(instance: Value, fieldDesc: String): Value
fun setField(instance: Value, fieldDesc: String, newValue: Value)
fun invokeMethod(instance: Value, methodDesc: String, arguments: List<Value>, invokespecial: Boolean = false): Value
fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean = false): Value
}
class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(ASM4) {
@@ -334,16 +354,15 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(
}
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")
+1 -1
View File
@@ -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
val Value.obj: Any? get() = (this as AbstractValue<*>).value
@@ -144,4 +144,12 @@ class TestData {
return i;
}
static Object testCall() {
return Integer.valueOf(1);
}
static Object testCallWithObject() {
return String.valueOf("str");
}
}
+75 -20
View File
@@ -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<TestData>, 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<TestData>, 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<ReflectionLookup>().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>): Value {
throw UnsupportedOperationException()
override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List<Value>): 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<Value>, invokespecial: Boolean): Value {
override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List<Value>, invokespecial: Boolean): Value {
throw UnsupportedOperationException()
}
}
}
class ReflectionLookup(val classLoader: ClassLoader) {
[suppress("UNCHECKED_CAST")]
fun findClass(internalName: String): Class<Any?>? = classLoader.loadClass(internalName.replace('/', '.')) as Class<Any?>
}
[suppress("UNCHECKED_CAST")]
fun Class<Any?>.findMethod(methodDesc: MethodDescription): Method? {
for (declared in getDeclaredMethods()) {
if (methodDesc.matches(declared)) return declared
}
val fromSuperClass = (getSuperclass() as Class<Any?>).findMethod(methodDesc)
if (fromSuperClass != null) return fromSuperClass
for (supertype in getInterfaces()) {
val fromSuper = (supertype as Class<Any?>).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)