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'> <item name='org.objectweb.asm.tree.MethodInsnNode desc'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </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;)'> <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'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </item>
@@ -338,6 +341,9 @@
<val name="value" val="{org.jetbrains.kannotator.controlFlow.builder.analysis.NullabilityKey.class}"/> <val name="value" val="{org.jetbrains.kannotator.controlFlow.builder.analysis.NullabilityKey.class}"/>
</annotation> </annotation>
</item> </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'> <item name='org.objectweb.asm.tree.MethodInsnNode void accept(org.objectweb.asm.MethodVisitor) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/> <annotation name='org.jetbrains.annotations.NotNull'/>
</item> </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.TypeInsnNode
import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.IincInsnNode 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) class UnsupportedByteCodeException(message: String) : RuntimeException(message)
@@ -32,11 +52,11 @@ trait Eval {
fun getStaticField(fieldDesc: String): Value fun getStaticField(fieldDesc: String): Value
fun setStaticField(fieldDesc: String, newValue: 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 getField(instance: Value, fieldDesc: String): Value
fun setField(instance: Value, fieldDesc: String, newValue: 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) { class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(ASM4) {
@@ -334,16 +354,15 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter<Value>(
} }
INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> {
val desc = (insn as MethodInsnNode).desc
eval.invokeMethod( eval.invokeMethod(
values[0], values[0],
desc, MethodDescription(insn as MethodInsnNode),
values.subList(1, values.size()), values.subList(1, values.size()),
insn.getOpcode() == INVOKESPECIAL 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") INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported")
else -> throw UnsupportedByteCodeException("$insn") 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.long: Long get() = (this as LongValue).value
val Value.float: Float get() = (this as FloatValue).value val Value.float: Float get() = (this as FloatValue).value
val Value.double: Double get() = (this as DoubleValue).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; 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 org.junit.Assert.*
import junit.framework.TestSuite import junit.framework.TestSuite
import junit.framework.TestCase import junit.framework.TestCase
import java.lang.reflect.Method
fun suite(): TestSuite { fun suite(): TestSuite {
val suite = TestSuite() val suite = TestSuite()
@@ -48,23 +49,12 @@ fun doTest(ownerClass: Class<TestData>, methodNode: MethodNode): TestCase? {
method.setAccessible(true) method.setAccessible(true)
val result = method.invoke(null) val result = method.invoke(null)
val returnType = Type.getType(method.getReturnType()!!) val returnType = Type.getType(method.getReturnType()!!)
expected = when (returnType.getSort()) { try {
Type.VOID -> ValueReturned(VOID_VALUE) expected = ValueReturned(objectToValue(result, returnType))
Type.BOOLEAN -> ValueReturned(boolean(result as Boolean)) }
Type.BYTE -> ValueReturned(byte(result as Byte)) catch (e: UnsupportedOperationException) {
Type.SHORT -> ValueReturned(short(result as Short)) println("Skipping $method: $e")
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
}
} }
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 { object REFLECTION_EVAL : Eval {
val lookup = ReflectionLookup(javaClass<ReflectionLookup>().getClassLoader()!!)
override fun loadClass(classType: Type): Value { override fun loadClass(classType: Type): Value {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
@@ -103,6 +112,7 @@ object REFLECTION_EVAL : Eval {
override fun isInsetanceOf(value: Value, targetType: Type): Boolean { override fun isInsetanceOf(value: Value, targetType: Type): Boolean {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun newArray(arrayType: Type, size: Int): Value { override fun newArray(arrayType: Type, size: Int): Value {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
@@ -118,22 +128,67 @@ object REFLECTION_EVAL : Eval {
override fun setArrayElement(array: Value, index: Value, newValue: Value) { override fun setArrayElement(array: Value, index: Value, newValue: Value) {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun getStaticField(fieldDesc: String): Value { override fun getStaticField(fieldDesc: String): Value {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun setStaticField(fieldDesc: String, newValue: Value) { override fun setStaticField(fieldDesc: String, newValue: Value) {
throw UnsupportedOperationException() 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 { override fun getField(instance: Value, fieldDesc: String): Value {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun setField(instance: Value, fieldDesc: String, newValue: Value) { override fun setField(instance: Value, fieldDesc: String, newValue: Value) {
throw UnsupportedOperationException() 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() 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)