/* * Copyright (c) 2009, 2019, 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. * */ /* * INVOKE_INTERFACE EXPECTED RESULTS * * Let C be the class of objectref. The actual method to be invoked is selected * by the following lookup procedure: * - If C contains a declaration for an instance method with the same name * and descriptor as the resolved method, then this is the method to be * invoked, and the lookup procedure terminates. * * - Otherwise, if C has a superclass, this same lookup procedure is * performed recursively using the direct superclass of C; the method to be * invoked is the result of the recursive invocation of this lookup * procedure. * * Otherwise, if the class of objectref does not implement the resolved * interface, invokeinterface throws an IncompatibleClassChangeError?. * * Otherwise, if no method matching the resolved name and descriptor is * selected, invokeinterface throws an AbstractMethodError?. * * Otherwise, if the selected method is not public, invokeinterface throws an * IllegalAccessError. Note that it cannot be private because private methods * are ignored when searching for an interface method. * * My translation: * 1. Resolve compile-time class/method. * 2. Look up runtime class C, if it contains a name/signature match, * and it is not private, invoke it. * 3. If it does not, recursively lookup direct superclass of C. * 4. If selected method is not public, throw IllegalAccessError * * InvokeInterface Results: * - A interface class, declares A.m * - A compile-time resolved class * - C runtime resolved class * - InvokeInterface will ALWAYS invoke C.m if C.m exists and is not private, * regardless of overriding or accessibility * - InvokeInterface will invoke a non-private B.m if C.m does not exist, * regardless of overriding or accessibility * * Note: assuming Interface is public * * TODO: member interfaces can be protected and private and have special hiding * rules (JLS 8.5) */ package invokeinterface; import static jdk.internal.org.objectweb.asm.Opcodes.*; import shared.AbstractGenerator; import shared.AccessType; import shared.Utils; import java.util.HashMap; import java.util.Map; public class Generator extends AbstractGenerator { public Generator(String[] args) { super(args); } protected Checker getChecker(Class paramClass, Class targetClass) { return new Checker(paramClass, targetClass); } public static void main (String[] args) throws Exception { new Generator(args).run(); } private void run() throws Exception { // Specify package names String pkg1 = "a."; String pkg2 = "b."; String pkg3 = "c."; String pkgIntf = "i."; String[] packages = new String[] { "", pkg1, pkg2, pkg3, pkgIntf }; int testNum = 0; boolean isPassed = true; // Hierarchy // The following triples will be used during further // hierarchy construction and will specify packages for A, B and C String[][] packageSets = new String[][] { { "", "", "", ""} , { "", "", "", pkgIntf } , { "", pkg1, pkg1, "" } , { "", pkg1, pkg1, pkg1 } , { "", pkg1, pkg1, pkgIntf } , { "", pkg1, pkg2, "" } , { "", pkg1, pkg2, pkg1} , { "", pkg1, pkg2, pkg2} , { "", pkg1, pkg2, pkgIntf} , { pkg1, pkg1, pkg1, pkg1 } , { pkg1, pkg1, pkg1, pkgIntf } , { pkg1, pkg1, pkg2, pkg1 } , { pkg1, pkg1, pkg2, pkg2 } , { pkg1, pkg1, pkg2, pkgIntf } , { pkg1, pkg2, pkg1, pkg1 } , { pkg1, pkg2, pkg1, pkg2 } , { pkg1, pkg2, pkg1, pkgIntf } , { pkg1, pkg2, pkg2, pkg1 } , { pkg1, pkg2, pkg2, pkg2 } , { pkg1, pkg2, pkg2, pkgIntf } }; String [] header = new String[] { String.format("%30s %68s %25s", "Method access modifiers", "Call site location", "Status") , String.format("%5s %-12s %-12s %-12s %-12s %7s %7s %7s %7s %7s %7s %7s" , " # " , "A.m()" , "B.m()" , "C.m()" , "I.m()" , " C " , "pkgC " , " B " , " pkgB" , " A " , "pkgA" , "Intf" ) , "--------------------------------------------------------------------------------------------------------------------" }; for (String aHeader : header) { System.out.println(aHeader); } for (String[] pkgSet : packageSets) { String packageA = pkgSet[0]; String packageB = pkgSet[1]; String packageC = pkgSet[2]; String packageIntf = pkgSet[3]; String classNameA = packageA + "A"; String classNameB = packageB + "B"; String classNameC = packageC + "C"; String classNameIntf = packageIntf + "I"; // For all possible access modifier combinations for (AccessType accessA : AccessType.values()) { for (AccessType accessB : AccessType.values()) { for (AccessType accessC : AccessType.values()) { for (AccessType accessIntf : AccessType.values()) { if (accessIntf == AccessType.UNDEF) { continue; } for (int I = 0; I < 4; I++) { boolean isAbstractA = ((I & 1) != 0); boolean isAbstractB = ((I & 2) != 0); testNum++; Map classes = new HashMap(); // TODO: add non-PUBLIC interfaces, then particular call sites will affect the results // Generate interface Intf classes.put( classNameIntf , new ClassGenerator( classNameIntf , "java.lang.Object" , ACC_ABSTRACT | ACC_INTERFACE | accessIntf.value()) .addTargetMethod(AccessType.PUBLIC) .getClassFile() ); // Generate class A classes.put( classNameA , new ClassGenerator( classNameA , "java.lang.Object" , ACC_PUBLIC | ( isAbstractA ? ACC_ABSTRACT : 0)) .addTargetMethod(accessA) .addCaller(classNameIntf) .getClassFile() ); // Generate class B classes.put( classNameB , new ClassGenerator( classNameB , classNameA , ACC_PUBLIC | ( isAbstractB ? ACC_ABSTRACT : 0) , new String[] { Utils.getInternalName(classNameIntf) }) .addTargetMethod(accessB) .addCaller(classNameIntf) .getClassFile() ); // Generate class C classes.put( classNameC , new ClassGenerator( classNameC, classNameB ) .addTargetMethod(accessC) .addCaller(classNameIntf) .getClassFile() ); // Generate package callers for (String pkg : packages) { classes.put( pkg+"Caller" , new ClassGenerator(pkg+"Caller") .addCaller(classNameIntf) .getClassFile() ); } String caseDescription = String.format("%-12s %-12s %-12s %-12s| " , (isAbstractA ? "! " : " ") + classNameA + " " + accessA , (isAbstractB ? "! " : " ") + classNameB + " " + accessB , classNameC + " " + accessC , accessIntf + " " + classNameIntf ); String[] callSites = new String[] { classNameC , packageC+"Caller" , classNameB , packageB+"Caller" , classNameA , packageA+"Caller" , packageIntf+"Caller" }; boolean result = exec(classes, caseDescription, classNameIntf, classNameC, callSites); isPassed = isPassed && result; } } } } } } // Print footer for (int i = header.length-1; i >= 0; i--) { System.out.println(header[i]); } if (executeTests) { System.out.printf("\nEXECUTION STATUS: %s\n", (isPassed? "PASSED" : "FAILED")); } } }