test/jdk/com/sun/jdi/InterfaceMethodsTest.java
author amenkov
Fri, 21 Sep 2018 11:28:14 -0700
changeset 51837 46ca82c15f6c
parent 47216 71c04702a3d5
permissions -rw-r--r--
8210760: [TEST] rewrite com/sun/jdi shell tests to java version - step4 Reviewed-by: jcbeyler, sspitsyn, cjplummer

/*
 * 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;
    }
}