Support static method calls
This commit is contained in:
@@ -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<org.objectweb.asm.tree.LabelNode,org.objectweb.asm.tree.LabelNode>)'>
|
||||
<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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user