| /* | |
| * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /** | |
| * @test | |
| * @bug 8031195 | |
| * @bug 8071657 | |
| * @bug 8165827 | |
| * @summary JDI: Add support for static, private and default methods in interfaces | |
| * | |
| * @run build TestScaffold VMConnection TargetListener TargetAdapter | |
| * @run build InterfaceMethodsTest | |
| * @run driver InterfaceMethodsTest | |
| */ | |
| import com.sun.jdi.*; | |
| import com.sun.jdi.event.*; | |
| import java.util.Collections; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| public class InterfaceMethodsTest extends TestScaffold { | |
| private static final int RESULT_A = 1; | |
| private static final int RESULT_B = 2; | |
| private static final int RESULT_TARGET = 3; | |
| static interface InterfaceA { | |
| static int staticMethodA() { | |
| System.out.println("-InterfaceA: static interface method A-"); | |
| return RESULT_A; | |
| } | |
| static int staticMethodB() { | |
| System.out.println("-InterfaceA: static interface method B-"); | |
| return RESULT_A; | |
| } | |
| default int defaultMethodA() { | |
| System.out.println("-InterfaceA: default interface method A-"); | |
| return RESULT_A; | |
| } | |
| default int defaultMethodB() { | |
| System.out.println("-InterfaceA: default interface method B-"); | |
| return RESULT_A; | |
| } | |
| default int defaultMethodC() { | |
| System.out.println("-InterfaceA: default interface method C-"); | |
| return RESULT_A; | |
| } | |
| private int privateMethodA() { | |
| System.out.println("-InterfaceA: private interface method A-"); | |
| return RESULT_A; | |
| } | |
| int implementedMethod(); | |
| } | |
| static interface InterfaceB extends InterfaceA { | |
| @Override | |
| default int defaultMethodC() { | |
| System.out.println("-InterfaceB: overridden default interface method C-"); | |
| return RESULT_B; | |
| } | |
| default int defaultMethodD() { | |
| System.out.println("-InterfaceB: default interface method D-"); | |
| return RESULT_B; | |
| } | |
| static int staticMethodB() { | |
| System.out.println("-InterfaceB: overridden static interface method B-"); | |
| return RESULT_B; | |
| } | |
| static int staticMethodC() { | |
| System.out.println("-InterfaceB: static interface method C-"); | |
| return RESULT_B; | |
| } | |
| private int privateMethodB() { | |
| System.out.println("-InterfaceB: private interface method B-"); | |
| return RESULT_B; | |
| } | |
| } | |
| final static class TargetClass implements InterfaceB { | |
| public int classMethod() { | |
| System.out.println("-TargetClass: class only method-"); | |
| return RESULT_TARGET; | |
| } | |
| @Override | |
| public int implementedMethod() { | |
| System.out.println("-TargetClass: implemented non-default interface method-"); | |
| return RESULT_TARGET; | |
| } | |
| @Override | |
| public int defaultMethodB() { | |
| System.out.println("-TargetClass: overridden default interface method B"); | |
| return RESULT_TARGET; | |
| } | |
| public static void main(String[] args) { | |
| TargetClass tc = new TargetClass(); | |
| tc.doTests(tc); | |
| } | |
| private void doTests(TargetClass ref) { | |
| // break | |
| } | |
| } | |
| public InterfaceMethodsTest(String[] args) { | |
| super(args); | |
| } | |
| public static void main(String[] args) throws Exception { | |
| new InterfaceMethodsTest(args).startTests(); | |
| } | |
| private static final String TEST_CLASS_NAME = InterfaceMethodsTest.class.getName().replace('.', '/'); | |
| private static final String TARGET_CLASS_NAME = TargetClass.class.getName().replace('.', '/'); | |
| private static final String INTERFACEA_NAME = InterfaceA.class.getName().replace('.', '/'); | |
| private static final String INTERFACEB_NAME = InterfaceB.class.getName().replace('.', '/'); | |
| protected void runTests() throws Exception { | |
| /* | |
| * Get to the top of main() | |
| * to determine targetClass and mainThread | |
| */ | |
| BreakpointEvent bpe = startToMain(TARGET_CLASS_NAME); | |
| bpe = resumeTo(TARGET_CLASS_NAME, "doTests", "(L" + TARGET_CLASS_NAME +";)V"); | |
| mainThread = bpe.thread(); | |
| StackFrame frame = mainThread.frame(0); | |
| ObjectReference thisObject = frame.thisObject(); | |
| ObjectReference ref = (ObjectReference)frame.getArgumentValues().get(0); | |
| ReferenceType targetClass = bpe.location().declaringType(); | |
| testImplementationClass(targetClass, thisObject); | |
| testInterfaceA(ref); | |
| testInterfaceB(ref); | |
| /* | |
| * resume the target listening for events | |
| */ | |
| listenUntilVMDisconnect(); | |
| /* | |
| * deal with results of test | |
| * if anything has called failure("foo") testFailed will be true | |
| */ | |
| if (!testFailed) { | |
| println("InterfaceMethodsTest: passed"); | |
| } else { | |
| throw new Exception("InterfaceMethodsTest: failed"); | |
| } | |
| } | |
| private void testInterfaceA(ObjectReference ref) { | |
| ReferenceType ifaceClass = (ReferenceType)vm().classesByName(INTERFACEA_NAME).get(0); | |
| /* Private method calls */ | |
| Method m = testLookup(ifaceClass, "privateMethodA", "()I", true, null); // should succeed | |
| testInvokePos(m, ref, vm().mirrorOf(RESULT_A), false); | |
| testInvokePos(m, ref, vm().mirrorOf(RESULT_A), true); | |
| // Test non-virtual calls on InterfaceA | |
| /* Default method calls */ | |
| // invoke the InterfaceA's "defaultMethodA" | |
| testInvokePos(ifaceClass, ref, "defaultMethodA", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoke the InterfaceA's "defaultMethodB" | |
| testInvokePos(ifaceClass, ref, "defaultMethodB", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoke the InterfaceA's "defaultMethodC" | |
| testInvokePos(ifaceClass, ref, "defaultMethodC", "()I", vm().mirrorOf(RESULT_A)); | |
| // "defaultMethodD" from InterfaceB is not accessible from here | |
| testInvokeNeg(ifaceClass, ref, "defaultMethodD", "()I", vm().mirrorOf(RESULT_B), | |
| "Attempted to invoke non-existing method"); | |
| // non-virtual invoke of the abstract method "implementedMethod" fails | |
| testInvokeNeg(ifaceClass, ref, "implementedMethod", "()I", vm().mirrorOf(TARGET_CLASS_NAME), | |
| "Invocation of abstract methods is not supported"); | |
| /* Static method calls */ | |
| // invoke static interface method A | |
| testInvokePos(ifaceClass, null, "staticMethodA", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoking static method A on the instance fails because static method A is | |
| // not inherited by TargetClass. | |
| testInvokeNeg(ifaceClass, ref, "staticMethodA", "()I", vm().mirrorOf(RESULT_A), | |
| "Invalid MethodID"); | |
| // invoke static interface method B | |
| testInvokePos(ifaceClass, null, "staticMethodB", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoking static method B on the instance fails because static method B is | |
| // not inherited by TargetClass. | |
| testInvokeNeg(ifaceClass, ref, "staticMethodB", "()I", vm().mirrorOf(RESULT_A), | |
| "Invalid MethodID"); | |
| // try to invoke a virtual method | |
| testInvokePos(ifaceClass, ref, "implementedMethod", "()I", vm().mirrorOf(RESULT_TARGET), true); | |
| } | |
| private void testInterfaceB(ObjectReference ref) { | |
| // Test non-virtual calls on InterfaceB | |
| ReferenceType ifaceClass = (ReferenceType)vm().classesByName(INTERFACEB_NAME).get(0); | |
| /* private method calls */ | |
| /* These should fail but won't because of JDK-8167416 | |
| testLookup(ifaceClass, "privateMethodA", "()I", true, NoSuchMethodError.class); // should fail | |
| testLookup(ifaceClass, "privateMethodA", "()I", false, NoSuchMethodError.class); // should fail | |
| */ | |
| Method m = testLookup(ifaceClass, "privateMethodB", "()I", true, null); // should succeed | |
| testInvokePos(m, ref, vm().mirrorOf(RESULT_B), false); | |
| testInvokePos(m, ref, vm().mirrorOf(RESULT_B), true); | |
| /* Default method calls */ | |
| // invoke the inherited "defaultMethodA" | |
| testInvokePos(ifaceClass, ref, "defaultMethodA", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoke the inherited "defaultMethodB" | |
| testInvokePos(ifaceClass, ref, "defaultMethodB", "()I", vm().mirrorOf(RESULT_A)); | |
| // invoke the inherited and overridden "defaultMethodC" | |
| testInvokePos(ifaceClass, ref, "defaultMethodC", "()I", vm().mirrorOf(RESULT_B)); | |
| // invoke InterfaceB only "defaultMethodD" | |
| testInvokePos(ifaceClass, ref, "defaultMethodD", "()I", vm().mirrorOf(RESULT_B)); | |
| // "implementedMethod" is not present in InterfaceB | |
| testInvokeNeg(ifaceClass, ref, "implementedMethod", "()I", vm().mirrorOf(RESULT_TARGET), | |
| "Invocation of non-default methods is not supported"); | |
| /* Static method calls*/ | |
| // "staticMethodA" must not be inherited by InterfaceB | |
| testInvokeNeg(ifaceClass, null, "staticMethodA", "()I", vm().mirrorOf(RESULT_A), | |
| "Static interface methods are not inheritable"); | |
| // "staticMethodA" is not inherited by InterfaceB even from an actual instance | |
| testInvokeNeg(ifaceClass, ref, "staticMethodA", "()I", vm().mirrorOf(RESULT_A), | |
| "Static interface methods are not inheritable"); | |
| // "staticMethodB" is re-defined in InterfaceB | |
| testInvokePos(ifaceClass, null, "staticMethodB", "()I", vm().mirrorOf(RESULT_B)); | |
| // the instance fails to invoke the re-defined form of "staticMethodB" from | |
| // InterfaceB because staticMethodB is not inherited by TargetClass | |
| testInvokeNeg(ifaceClass, ref, "staticMethodB", "()I", vm().mirrorOf(RESULT_B), | |
| "Invalid MethodID"); | |
| // "staticMethodC" is present only in InterfaceB | |
| testInvokePos(ifaceClass, null, "staticMethodC", "()I", vm().mirrorOf(RESULT_B)); | |
| // "staticMethodC" is not reachable from the instance because staticMethodC | |
| // is not inherited by TargetClass. | |
| testInvokeNeg(ifaceClass, ref, "staticMethodC", "()I", vm().mirrorOf(RESULT_B), | |
| "Invalid MethodID"); | |
| } | |
| private void testImplementationClass(ReferenceType targetClass, ObjectReference thisObject) { | |
| // Test invocations on the implementation object | |
| // Note: private interface calls have already been tested | |
| /* Default method calls */ | |
| // "defaultMethodA" is accessible and not overridden | |
| testInvokePos(targetClass, thisObject, "defaultMethodA", "()I", vm().mirrorOf(RESULT_A)); | |
| // "defaultMethodB" is accessible and overridden in TargetClass | |
| testInvokePos(targetClass, thisObject, "defaultMethodB", "()I", vm().mirrorOf(RESULT_TARGET)); | |
| // "defaultMethodC" is accessible and overridden in InterfaceB | |
| testInvokePos(targetClass, thisObject, "defaultMethodC", "()I", vm().mirrorOf(RESULT_B)); | |
| // "defaultMethodD" is accessible | |
| testInvokePos(targetClass, thisObject, "defaultMethodD", "()I", vm().mirrorOf(RESULT_B)); | |
| /* Non-default instance method calls */ | |
| // "classMethod" declared in TargetClass is accessible | |
| testInvokePos(targetClass, thisObject, "classMethod", "()I", vm().mirrorOf(RESULT_TARGET)); | |
| // the abstract "implementedMethod" has been implemented in TargetClass | |
| testInvokePos(targetClass, thisObject, "implementedMethod", "()I", vm().mirrorOf(RESULT_TARGET)); | |
| /* Static method calls */ | |
| // All the static methods declared by the interfaces are not reachable from the instance of the implementor class | |
| testInvokeNeg(targetClass, thisObject, "staticMethodA", "()I", vm().mirrorOf(RESULT_A), | |
| "Static interface methods are not inheritable"); | |
| testInvokeNeg(targetClass, thisObject, "staticMethodB", "()I", vm().mirrorOf(RESULT_B), | |
| "Static interface methods are not inheritable"); | |
| testInvokeNeg(targetClass, thisObject, "staticMethodC", "()I", vm().mirrorOf(RESULT_B), | |
| "Static interface methods are not inheritable"); | |
| // All the static methods declared by the interfaces are not reachable through the implementor class | |
| testInvokeNeg(targetClass, null, "staticMethodA", "()I", vm().mirrorOf(RESULT_A), | |
| "Static interface methods are not inheritable"); | |
| testInvokeNeg(targetClass, null, "staticMethodB", "()I", vm().mirrorOf(RESULT_B), | |
| "Static interface methods are not inheritable"); | |
| testInvokeNeg(targetClass, null, "staticMethodC", "()I", vm().mirrorOf(RESULT_B), | |
| "Static interface methods are not inheritable"); | |
| } | |
| // Non-virtual invocation | |
| private void testInvokePos(ReferenceType targetClass, ObjectReference ref, String methodName, | |
| String methodSig, Value value) { | |
| testInvokePos(targetClass, ref, methodName, methodSig, value, false); | |
| } | |
| // Lookup the named method in the targetClass and invoke on the given object (for instance methods) | |
| // using virtual, or non-virtual, invocation mode as specified, for instance methods. Verify the | |
| // expected return value. | |
| // Should succeed. | |
| private void testInvokePos(ReferenceType targetClass, ObjectReference ref, String methodName, | |
| String methodSig, Value value, boolean virtual) { | |
| logInvocation(ref, methodName, methodSig, targetClass); | |
| try { | |
| invoke(targetClass, ref, methodName, methodSig, value, virtual); | |
| System.err.println("--- PASSED"); | |
| } catch (Exception e) { | |
| System.err.println("--- FAILED"); | |
| failure("FAILED: Invocation failed with error message " + e.getLocalizedMessage()); | |
| } | |
| } | |
| // Invoke the given Method on the given object (for instance methods) | |
| // using virtual, or non-virtual, invocation mode as specified, for instance methods. Verify the | |
| // expected return value. | |
| // Should succeed. | |
| private void testInvokePos(Method method, ObjectReference ref, Value value, boolean virtual) { | |
| logInvocation(ref, method.name(), method.signature(), method.declaringType()); | |
| try { | |
| invoke(method.declaringType(), ref, method, value, virtual); | |
| System.err.println("--- PASSED"); | |
| } catch (Exception e) { | |
| System.err.println("--- FAILED"); | |
| failure("FAILED: Invocation failed with error message " + e.getLocalizedMessage()); | |
| } | |
| } | |
| // Non-virtual invocation - with lookup in targetClass | |
| private void testInvokeNeg(ReferenceType targetClass, ObjectReference ref, String methodName, | |
| String methodSig, Value value, String msg) { | |
| testInvokeNeg(targetClass, ref, methodName, methodSig, value, msg, false); | |
| } | |
| // Lookup the named method in the targetClass and invoke on the given object (for instance methods) | |
| // using virtual, or non-virtual, invocation mode as specified, for instance methods. Verify the | |
| // expected return value. | |
| // Should fail - with msg decribing why failure was expected | |
| private void testInvokeNeg(ReferenceType targetClass, ObjectReference ref, String methodName, | |
| String methodSig, Value value, String msg, boolean virtual) { | |
| logInvocation(ref, methodName, methodSig, targetClass); | |
| try { | |
| invoke(targetClass, ref, methodName, methodSig, value, virtual); | |
| System.err.println("--- FAILED"); | |
| failure("FAILED: " + msg); | |
| } catch (Exception e) { | |
| System.err.println("--- PASSED"); | |
| } | |
| } | |
| private void invoke(ReferenceType targetClass, ObjectReference ref, String methodName, | |
| String methodSig, Value value, boolean virtual) throws Exception { | |
| Method method = getMethod(targetClass, methodName, methodSig); | |
| if (method == null) { | |
| throw new Exception("Can't find method: " + methodName + " for class = " + targetClass); | |
| } | |
| invoke(targetClass, ref, method, value, virtual); | |
| } | |
| private void invoke(ReferenceType targetClass, ObjectReference ref, Method method, | |
| Value value, boolean virtual) throws Exception { | |
| println("Invoking " + (method.isAbstract() ? "abstract " : " ") + "method: " + method); | |
| println(method.declaringType().toString()); | |
| Value returnValue = null; | |
| if (ref != null) { | |
| if (virtual) { | |
| returnValue = invokeVirtual(ref, method); | |
| } else { | |
| returnValue = invokeNonVirtual(ref, method); | |
| } | |
| } else { | |
| returnValue = invokeStatic(targetClass, method); | |
| } | |
| println(" return val = " + returnValue); | |
| // It has to be the same value as what we passed in! | |
| if (returnValue.equals(value)) { | |
| println(" " + method.name() + " return value matches: " | |
| + value); | |
| } else { | |
| if (value != null) { | |
| throw new Exception(method.name() + " returned: " + returnValue + | |
| " expected: " + value ); | |
| } else { | |
| println(" " + method.name() + " return value : " + returnValue); | |
| } | |
| } | |
| } | |
| private Value invokeNonVirtual(ObjectReference ref, Method method) throws Exception { | |
| return ref.invokeMethod(mainThread, method, Collections.emptyList(), ObjectReference.INVOKE_NONVIRTUAL); | |
| } | |
| private Value invokeVirtual(ObjectReference ref, Method method) throws Exception { | |
| return ref.invokeMethod(mainThread, method, Collections.emptyList(), 0); | |
| } | |
| private Value invokeStatic(ReferenceType refType, Method method) throws Exception { | |
| if (refType instanceof ClassType) { | |
| return ((ClassType)refType).invokeMethod(mainThread, method, Collections.emptyList(), ObjectReference.INVOKE_NONVIRTUAL); | |
| } else { | |
| return ((InterfaceType)refType).invokeMethod(mainThread, method, Collections.emptyList(), ObjectReference.INVOKE_NONVIRTUAL); | |
| } | |
| } | |
| private Method getMethod(ReferenceType rt, String name, String signature) { | |
| if (rt == null) return null; | |
| Method m = findMethod(rt, name, signature); | |
| if (m == null) { | |
| if (rt instanceof ClassType) { | |
| for (Object ifc : ((ClassType)rt).interfaces()) { | |
| m = getMethod((ReferenceType)ifc, name, signature); | |
| if (m != null) { | |
| break; | |
| } | |
| } | |
| if (m == null) { | |
| m = getMethod(((ClassType)rt).superclass(), name, signature); | |
| } else { | |
| if (m.isStatic()) { | |
| // interface static methods are not inherited | |
| m = null; | |
| } | |
| } | |
| } else if (rt instanceof InterfaceType) { | |
| for(Object ifc : ((InterfaceType)rt).superinterfaces()) { | |
| m = getMethod((ReferenceType)ifc, name, signature); | |
| if (m != null) { | |
| if (m.isStatic()) { | |
| // interface static methods are not inherited | |
| m = null; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return m; | |
| } | |
| private void logInvocation(ObjectReference ref, String methodName, String methodSig, ReferenceType targetClass) { | |
| if (ref != null) { | |
| System.err.println("Invoking: " + ref.referenceType().name() + "." + | |
| methodName + methodSig + " with target of type " + | |
| targetClass.name()); | |
| } else { | |
| System.err.println("Invoking static : " + targetClass.name() + "." + | |
| methodName + methodSig); | |
| } | |
| } | |
| private Method testLookup(ReferenceType targetClass, String methodName, String methodSig, | |
| boolean declaredOnly, Class<?> expectedException) { | |
| System.err.println("Looking up " + targetClass.name() + "." + methodName + methodSig); | |
| try { | |
| Method m = declaredOnly ? | |
| lookupDeclaredMethod(targetClass, methodName, methodSig) : | |
| lookupMethod(targetClass, methodName, methodSig); | |
| if (expectedException == null) { | |
| System.err.println("--- PASSED"); | |
| return m; | |
| } | |
| else { | |
| System.err.println("--- FAILED"); | |
| failure("FAILED: lookup succeeded but expected exception " | |
| + expectedException.getSimpleName()); | |
| return null; | |
| } | |
| } | |
| catch (Throwable t) { | |
| if (t.getClass() != expectedException) { | |
| System.err.println("--- FAILED"); | |
| failure("FAILED: got exception " + t + " but expected exception " | |
| + expectedException.getSimpleName()); | |
| return null; | |
| } | |
| else { | |
| System.err.println("--- PASSED"); | |
| return null; | |
| } | |
| } | |
| } | |
| private Method lookupMethod(ReferenceType targetClass, String methodName, String methodSig) { | |
| List methods = targetClass.allMethods(); | |
| Iterator iter = methods.iterator(); | |
| while (iter.hasNext()) { | |
| Method method = (Method)iter.next(); | |
| if (method.name().equals(methodName) && | |
| method.signature().equals(methodSig)) { | |
| return method; | |
| } | |
| } | |
| throw new NoSuchMethodError(); | |
| } | |
| private Method lookupDeclaredMethod(ReferenceType targetClass, String methodName, String methodSig) { | |
| Method m = findMethod(targetClass, methodName, methodSig); | |
| if (m == null) | |
| throw new NoSuchMethodError(); | |
| return m; | |
| } | |
| } |