diff --git a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt index d868bb8b16b..e01ef0e1ed6 100644 --- a/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -20,10 +20,12 @@ import com.sun.jdi.* import org.jetbrains.eval4j.* import org.jetbrains.eval4j.Value import org.jetbrains.org.objectweb.asm.Type +import java.lang.reflect.AccessibleObject import com.sun.jdi.Type as jdi_Type import com.sun.jdi.Value as jdi_Value val CLASS = Type.getType(javaClass>()) +val OBJECT = Type.getType(Any::class.java) val BOOTSTRAP_CLASS_DESCRIPTORS = setOf("Ljava/lang/String;", "Ljava/lang/ClassLoader;", "Ljava/lang/Class;") public class JDIEval( @@ -224,6 +226,11 @@ public class JDIEval( val args = mapArguments(arguments, method.safeArgumentTypes()) args.disableCollection() + + if (shouldInvokeMethodWithReflection(method, args)) { + return invokeMethodWithReflection(_class.asType(), NULL_VALUE, args, methodDesc) + } + val result = mayThrow { _class.invokeMethod(thread, method, args, invokePolicy) } args.enableCollection() return result.asValue() @@ -290,6 +297,11 @@ public class JDIEval( fun doInvokeMethod(obj: ObjectReference, method: Method, policy: Int): Value { val args = mapArguments(arguments, method.safeArgumentTypes()) args.disableCollection() + + if (shouldInvokeMethodWithReflection(method, args)) { + return invokeMethodWithReflection(instance.asmType, instance, args, methodDesc) + } + val result = mayThrow { obj.invokeMethod(thread, method, args, policy) } args.enableCollection() return result.asValue() @@ -306,6 +318,54 @@ public class JDIEval( } } + private fun shouldInvokeMethodWithReflection(method: Method, args: List): Boolean { + return !method.isVarArgs && args.zip(method.argumentTypes()).any { isArrayOfInterfaces(it.first?.type(), it.second) } + } + + private fun isArrayOfInterfaces(valueType: jdi_Type?, expectedType: jdi_Type?): Boolean { + return (valueType as? ArrayType)?.componentType() is InterfaceType && (expectedType as? ArrayType)?.componentType() == OBJECT.asReferenceType() + } + + private fun invokeMethodWithReflection(ownerType: Type, instance: Value, args: List, methodDesc: MethodDescription): Value { + val methodToInvoke = invokeMethod( + loadClass(ownerType), + MethodDescription( + CLASS.internalName, + "getDeclaredMethod", + "(Ljava/lang/String;[L${CLASS.internalName};)Ljava/lang/reflect/Method;", + true + ), + listOf(vm.mirrorOf(methodDesc.name).asValue(), *methodDesc.parameterTypes.map { loadClass(it) }.toTypedArray()) + ) + + invokeMethod( + methodToInvoke, + MethodDescription( + Type.getType(AccessibleObject::class.java).internalName, + "setAccessible", + "(Z)V", + true + ), + listOf(vm.mirrorOf(true).asValue()) + ) + + val invocationResult = invokeMethod( + methodToInvoke, + MethodDescription( + methodToInvoke.asmType.internalName, + "invoke", + "(L${OBJECT.internalName};[L${OBJECT.internalName};)L${OBJECT.internalName};", + true + ), + listOf(instance, *args.map { it.asValue() }.toTypedArray()) + ) + + if (methodDesc.returnType.sort != Type.OBJECT && methodDesc.returnType.sort != Type.ARRAY && methodDesc.returnType.sort != Type.VOID) { + return unboxType(invocationResult, methodDesc.returnType) + } + return invocationResult + } + private fun List.disableCollection() { forEach { (it as? ObjectReference)?.disableCollection() } } diff --git a/eval4j/test/org/jetbrains/eval4j/test/TestData.java b/eval4j/test/org/jetbrains/eval4j/test/TestData.java index 30bef2c577f..0fc558eaefc 100644 --- a/eval4j/test/org/jetbrains/eval4j/test/TestData.java +++ b/eval4j/test/org/jetbrains/eval4j/test/TestData.java @@ -784,6 +784,41 @@ class TestData extends BaseTestData { return b[1]; } + @IgnoreInReflectionTests + public static void invokeMethodWithArrayOfInterfaces() { + BaseToArray[] c = new BaseToArray[1]; + + TestInvokeWithObjectArray.invokeStaticFun(c); + TestInvokeWithObjectArray.invokeStaticPrivateFun(c); + + TestInvokeWithObjectArray myObj = new TestInvokeWithObjectArray(); + myObj.invokeMemberFun(c); + myObj.invokeMemberPrivateFun(c); + + int i = TestInvokeWithObjectArray.primitiveReturnValue(c) + 1; + } + + private static class TestInvokeWithObjectArray { + public static void invokeStaticFun(Object[] o) { + } + + public static void invokeStaticPrivateFun(Object[] o) { + } + + public void invokeMemberFun(Object[] o) { + } + + public void invokeMemberPrivateFun(Object[] o) { + } + + public static int primitiveReturnValue(Object[] o) { + return 1; + } + } + + private interface BaseToArray { + } + public TestData() { } }