8224137: Analyze and port invocation tests to jtreg and co-locate to jdk repo
Summary: Add JTReg compatible main programs to run tests for various invoke* instructions
Reviewed-by: lfoltan, coleenp
--- a/test/hotspot/jtreg/TEST.groups Wed Jun 26 05:49:59 2019 +0000
+++ b/test/hotspot/jtreg/TEST.groups Wed Jun 26 09:06:32 2019 -0400
@@ -275,6 +275,7 @@
-runtime/ErrorHandling/ErrorHandler.java \
-runtime/ErrorHandling/TestHeapDumpOnOutOfMemoryError.java \
-runtime/ErrorHandling/TimeoutInErrorHandlingTest.java \
+ -runtime/InvocationTests \
-runtime/logging/MonitorMismatchTest.java \
-runtime/memory/ReserveMemory.java \
-runtime/memory/RunUnitTestsConcurrently.java \
@@ -384,6 +385,7 @@
serviceability/ \
-runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java \
-runtime/CompressedOops/UseCompressedOops.java \
+ -runtime/InvocationTests \
-runtime/Thread/TestThreadDumpMonitorContention.java \
-:tier1_runtime \
-:tier1_serviceability \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokeinterface/Checker.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ */
+
+package invokeinterface;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class Checker extends shared.Checker {
+ private Class interfaceClass;
+
+ public Checker(Class interfaceClass, Class dynamicTargetClass) {
+ super(interfaceClass, dynamicTargetClass);
+
+ if (staticTargetClass.isInterface()) {
+ this.interfaceClass = staticTargetClass;
+ } else {
+ throw new RuntimeException("Static target class should be an interface.");
+ }
+ }
+
+ public String check (Class callerClass) {
+ // Check access rights to interface for caller
+ if (!checkAccess(interfaceClass, callerClass)) {
+ return "java.lang.IllegalAccessError";
+ }
+
+ // NSME is thrown when interface doesn't declare the method
+ if (getDeclaredMethod(interfaceClass) == null) {
+ return "java.lang.NoSuchMethodError";
+ }
+
+ // 9.1.5 Access to Interface Member Names
+ // "All interface members are implicitly public. They are
+ // accessible outside the package where the interface is
+ // declared if the interface is also declared public or
+ // protected, in accordance with the rules of 6.6."
+
+ // Search for method declaration in the hierarchy
+ Class klass = dynamicTargetClass;
+
+ while (klass != Object.class) {
+ Method method = getDeclaredMethod(klass);
+
+ if (method != null) {
+ int modifiers = method.getModifiers();
+
+ // Check whether obtained method is public and isn't abstract
+ if ( Modifier.isPublic(modifiers))
+ {
+ if (Modifier.isAbstract(modifiers)) {
+ return "java.lang.AbstractMethodError";
+ } else {
+ return String.format("%s.%s"
+ , method.getDeclaringClass().getSimpleName()
+ , methodName
+ );
+ }
+ } else {
+ // IAE is thrown when located method isn't PUBLIC
+ // or private. Private methods are skipped when
+ // looking for an interface method.
+ if (!Modifier.isPrivate(modifiers)) {
+ return "java.lang.IllegalAccessError";
+ }
+ }
+ }
+
+ klass = klass.getSuperclass();
+ }
+
+ // No method declaration is found
+ return "java.lang.AbstractMethodError";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokeinterface/ClassGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ *
+ */
+
+package invokeinterface;
+
+import jdk.internal.org.objectweb.asm.Opcodes;
+import shared.GenericClassGenerator;
+
+/*******************************************************************/
+class ClassGenerator extends GenericClassGenerator<ClassGenerator> {
+ public ClassGenerator(String fullClassName) {
+ super(fullClassName);
+ }
+
+ public ClassGenerator(String fullClassName, String parentClassName) {
+ super(fullClassName, parentClassName);
+ }
+
+ public ClassGenerator(String fullClassName, String parentClassName, int flags) {
+ super(fullClassName, parentClassName, flags);
+ }
+
+ public ClassGenerator(String fullClassName, String parentClassName, int flags, String[] implementedInterfaces) {
+ super(fullClassName, parentClassName, flags, implementedInterfaces);
+ }
+
+ // Add target method call site into current class
+ public ClassGenerator addCaller(String targetClass) {
+ return super.addCaller(targetClass, Opcodes.INVOKEINTERFACE);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokeinterface/Generator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,279 @@
+/*
+ * 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<String, byte[]> classes = new HashMap<String, byte[]>();
+
+ // 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"));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokeinterfaceTests.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 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.
+ *
+ */
+
+/*
+ * @test
+ * @bug 8224137
+ * @summary Run invokeinterface invocation tests
+ * @library /test/lib
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * java.base/jdk.internal.misc
+ * @compile shared/AbstractGenerator.java shared/AccessCheck.java shared/AccessType.java
+ * shared/Caller.java shared/ExecutorGenerator.java shared/Utils.java
+ * shared/ByteArrayClassLoader.java shared/Checker.java shared/GenericClassGenerator.java
+ * @compile invokeinterface/Checker.java invokeinterface/ClassGenerator.java
+ * invokeinterface/Generator.java
+ *
+ * @run main/othervm/timeout=1800 invokeinterfaceTests
+ */
+
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.compiler.InMemoryJavaCompiler;
+
+public class invokeinterfaceTests {
+
+ public static void runTest(String classFileVersion, String option) throws Exception {
+ System.out.println("\ninvokeinterface invocation tests, option: " + option +
+ ", class file version: " + classFileVersion);
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false, "-Xmx128M", option,
+ "invokeinterface.Generator", "--classfile_version=" + classFileVersion);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ try {
+ output.shouldContain("EXECUTION STATUS: PASSED");
+ output.shouldHaveExitValue(0);
+ } catch (Throwable e) {
+ System.out.println(
+ "\nNote that an entry such as 'B.m/C.m' in the failure chart means that" +
+ " the test case failed because method B.m was invoked but the test " +
+ "expected method C.m to be invoked. Similarly, a result such as 'AME/C.m'" +
+ " means that an AbstractMethodError exception was thrown but the test" +
+ " case expected method C.m to be invoked.");
+ System.out.println(
+ "\nAlso note that passing --dump to invokeinterface.Generator will" +
+ " dump the generated classes (for debugging purposes).\n");
+
+ System.exit(1);
+ }
+ }
+
+ public static void main(String args[]) throws Throwable {
+ // Get current major class file version and test with it.
+ byte klassbuf[] = InMemoryJavaCompiler.compile("blah", "public class blah { }");
+ int major_version = klassbuf[6] << 8 | klassbuf[7];
+ runTest(String.valueOf(major_version), "-Xint");
+ runTest(String.valueOf(major_version), "-Xcomp");
+
+ // Test old class file version.
+ runTest("51", "-Xint"); // JDK-7
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokespecial/Checker.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ *
+ */
+
+package invokespecial;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class Checker extends shared.Checker {
+
+ public Checker(Class staticTargetClass, Class dynamicTargetClass) {
+ super(staticTargetClass, dynamicTargetClass);
+ }
+
+ public String check (Class callerClass) {
+ /*
+ * If objectref is null, the invokespecial instruction throws a NullPointerException.
+ */
+ if (dynamicTargetClass == null) {
+ return "java.lang.NullPointerException";
+ }
+
+ /*
+ * TODO: find a citiation from spec for this case
+ */
+ Method resolvedMethod;
+ try {
+ // May throw VerifyError
+ resolvedMethod = getMethodInHierarchy(staticTargetClass);
+ } catch (Throwable e) {
+ return e.getClass().getName();
+ }
+
+ if (resolvedMethod == null) {
+ return "java.lang.NoSuchMethodError";
+ }
+
+ /*
+ * If:
+ * - the resolved method is protected (4.7)
+ * - it is a member of a superclass of the current class
+ * - the method is not declared in the same run-time package (5.3) as the current class
+ * then:
+ * the class of objectref must be either the current class or a subclass of the
+ * current class.
+ */
+
+ if (Modifier.isProtected(resolvedMethod.getModifiers())) {
+ Method methodInSuperclass = getMethodInHierarchy(resolvedMethod.getDeclaringClass().getSuperclass());
+
+ if (methodInSuperclass != null) {
+ String resolvedMethodPkg = getClassPackageName(resolvedMethod.getDeclaringClass());
+ String methodInSuperclassPkg = getClassPackageName(methodInSuperclass.getDeclaringClass());
+
+ if (!resolvedMethodPkg.equals(methodInSuperclassPkg)) {
+ //TODO: clarify this
+// if (callerClass == methodInSuperclass.getDeclaringClass()) {
+// return "java.lang.IllegalAccessError";
+// }
+ }
+ }
+ }
+
+ /*
+ * The resolved method is selected for invocation unless all of
+ * the following conditions are true:
+ * * TODO: The ACC_SUPER flag (see Table 4.1, "Class access and property
+ * modifiers") is set for the current class.
+ * * The class of the resolved method is a superclass of the
+ * current class - assumed by construction procedure
+ *
+ * * The resolved method is not an instance initialization method (3.9).
+ */
+ if (!"<init>".equals(methodName)) {
+ /*
+ * Let C be the direct superclass of the current class:
+ * * If C contains a declaration for an instance method with the same
+ * name and descriptor as the resolved method, then this method will be
+ * invoked. 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, an AbstractMethodError is raised.
+ * TODO: so far, sometimes NSME is thrown
+ */
+ Class klass = dynamicTargetClass.getSuperclass();
+
+ while (klass != Object.class) {
+ Method method = getDeclaredMethod(klass);
+
+ if (method != null) {
+ /*
+ * If the resolved method is a class (static) method, the
+ * invokespecial instruction throws an IncompatibleClassChangeError.
+ */
+ if (Modifier.isStatic(method.getModifiers())) {
+ return "java.lang.IncompatibleClassChangeError";
+ }
+
+ // Check access rights
+ if ( checkAccess(method, callerClass)
+// && !(
+// Modifier.isProtected(method.getModifiers())
+// && (
+// staticTargetClass.isAssignableFrom(callerClass)
+// || getClassPackageName(staticTargetClass).equals(getClassPackageName(callerClass))
+// )
+//
+// )
+ )
+ {
+ return String.format("%s.%s"
+ , method.getDeclaringClass().getSimpleName()
+ , methodName
+ );
+ } else {
+ // IAE is thrown when located method can't be accessed from the call site
+ return "java.lang.IllegalAccessError";
+ }
+ }
+
+ klass = klass.getSuperclass();
+ }
+
+ return "java.lang.AbstractMethodError";
+ } else {
+ // The resolved method is an instance initialization method (3.9).
+ }
+
+ // TODO: change
+ return "---";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokespecial/ClassGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *
+ */
+
+package invokespecial;
+
+import jdk.internal.org.objectweb.asm.Opcodes;
+import shared.GenericClassGenerator;
+
+/*******************************************************************/
+class ClassGenerator extends GenericClassGenerator<ClassGenerator> {
+ public ClassGenerator(String fullClassName, String parentClassName, int flags) {
+ super(fullClassName, parentClassName, flags);
+ }
+
+ // Add target method call site into current class
+ public ClassGenerator addCaller(String targetClass) {
+ return super.addCaller(targetClass, Opcodes.INVOKESPECIAL);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokespecial/Generator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,452 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * INVOKESPECIAL EXPECTED RESULTS
+ *
+ * From JVMS 3rd edition: invokespecial instruction:
+ *
+ * Invoke instance method; special handling for superclass, private, and instance
+ * initialization method invocations
+ *
+ * The named method is resolved (5.4.3.3). Finally, if the resolved method is
+ * protected (4.7), and it is a member of a superclass of the current class, and
+ * the method is not declared in the same run-time package (5.3) as the current
+ * class, then the class of objectref must be either the current class or a
+ * subclass of the current class.
+ *
+ * Next, the resolved method is selected for invocation unless all of the
+ * following conditions are true:
+ * * The ACC_SUPER flag (see Table 4.1, "Class access and property modifiers") is set for the current class.
+ * * The class of the resolved method is a superclass of the current class.
+ * * The resolved method is not an instance initialization method (3.9).
+ *
+ * If the above conditions are true, the actual method to be invoked is selected
+ * by the following lookup procedure. Let C be the direct superclass of the
+ * current class:
+ * * If C contains a declaration for an instance method with the same name and
+ * descriptor as the resolved method, then this method will be invoked.
+ * 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, an AbstractMethodError? is raised.
+ *
+ * During resolution of the symbolic reference to the method, any of the
+ * exceptions pertaining to method resolution documented in Section 5.4.3.3 can be
+ * thrown.
+ *
+ * Otherwise, if the resolved method is an instance initialization method, and the
+ * class in which it is declared is not the class symbolically referenced by the
+ * instruction, a NoSuchMethodError? is thrown.
+ *
+ * Otherwise, if the resolved method is a class (static) method, the invokespecial
+ * instruction throws an IncompatibleClassChangeError?.
+ *
+ * Otherwise, if no method matching the resolved name and descriptor is selected,
+ * invokespecial throws an AbstractMethodError?.
+ *
+ * Otherwise, if the selected method is abstract, invokespecial throws an
+ * AbstractMethodError?.
+ *
+ * RUNTIME EXCEPTIONS
+ *
+ * Otherwise, if objectref is null, the invokespecial instruction throws a NullPointerException?.
+ *
+ * Otherwise, if the selected method is native and the code that implements the
+ * method cannot be bound, invokespecial throws an UnsatisfiedLinkError?.
+ *
+ * NOTES
+ *
+ * The difference between the invokespecial and the invokevirtual instructions is
+ * that invokevirtual invokes a method based on the class of the object. The
+ * invokespecial instruction is used to invoke instance initialization methods
+ * (3.9) as well as private methods and methods of a superclass of the current
+ * class.
+ *
+ * ACC_SUPER:
+ *
+ * The setting of the ACC_SUPER flag indicates which of two alternative semantics
+ * for its invokespecial instruction the Java virtual machine is to express; the
+ * ACC_SUPER flag exists for backward compatibility for code compiled by Sun's
+ * older compilers for the Java programming language. All new implementations of
+ * the Java virtual machine should implement the semantics for invokespecial
+ * documented in this specification. All new compilers to the instruction set of
+ * the Java virtual machine should set the ACC_SUPER flag. Sun's older compilers
+ * generated ClassFile? flags with ACC_SUPER unset. Sun's older Java virtual
+ * machine implementations ignore the flag if it is set.
+ *
+ * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
+ * invokespecial instruction.
+ *
+ * My Translation:
+ * 1. compile-time resolved class B
+ * 2. A,B,C direct superclass relationships
+ * 3. If B.m is protected
+ * - if the caller is in B
+ * then runtime resolved class must be in B or C
+ * - if the caller is in C
+ * then runtime resolved class must be in C
+ * TODO: otherwise what is thrown? <noWikiWord>AbstractMethodError?
+ * 4. If B.m is an instance initialization method,
+ * invoke B.m
+ * 5. If backward compatible caller does not set ACC_SUPER,
+ * invoke B.m
+ * 6. If B is not a superclass of the caller, e.g. A is caller, or unrelated X
+ * is the caller, invoke B.m
+ * 7. Otherwise:
+ * If superclass of caller contains name/sig match, use it
+ * Else, recursively through that superclass
+ * 8. If none found, throw AbstractMethodError
+ *
+ * Note: there is NO mention of overriding or accessibility in determining
+ * resolved method, except for if the compile-time type is protected.
+ *
+ * Case 1: B.m is protected
+ * Caller in A: if runtime resolved class in A.m, AbstractMethodError
+ * Caller in B: if runtime resolved class in A.m, AbstractMethodError
+ * Case 2: B.m is an instance initialization method
+ * Always invoke B.m
+ * Case 3: older javac, caller does not set ACC_SUPER
+ * Always invoke B.m
+ * Case 4: A or X (not in hierarchy) calls invokespecial on B.m, invoke B.m
+ * Case 5: Caller in B:
+ * if A.m exists, call it, else <noWikiWord>AbstractMethodError
+ * Caller in C:
+ * if B.m exists, call it
+ * if B.m does not exist, and A.m exists, call it
+ */
+
+// TODO: classes without ACC_SUPER attribute
+// TODO: B.m is an instance initialization method
+
+/*
+ * invokespecial <method-spec>
+ *
+ * invokespecial is used in certain special cases to invoke a method
+ * Specifically, invokespecial is used to invoke:
+ * - the instance initialization method, <init>
+ * - a private method of this
+ * - a method in a superclass of this
+ *
+ * The main use of invokespecial is to invoke an object's instance
+ * initialization method, <init>, during the construction phase for a new object.
+ * For example, when you write in Java:
+ *
+ * new StringBuffer()
+ *
+ * code like the following is generated:
+ * new java/lang/StringBuffer ; create a new StringBuffer
+ * dup ; make an extra reference to the new instance
+ * ; now call an instance initialization method
+ * invokespecial java/lang/StringBuffer/<init>()V
+ * ; stack now contains an initialized StringBuffer.
+ *
+ * invokespecial is also used by the Java language by the 'super' keyword to
+ * access a superclass's version of a method. For example, in the class:
+ *
+ * class Example {
+ * // override equals
+ * public boolean equals(Object x) {
+ * // call Object's version of equals
+ * return super.equals(x);
+ * }
+ * }
+ *
+ * the 'super.equals(x)' expression is compiled to:
+ *
+ * aload_0 ; push 'this' onto the stack
+ * aload_1 ; push the first argument (i.e. x) onto the stack
+ * ; now invoke Object's equals() method.
+ * invokespecial java/lang/Object/equals(Ljava/lang/Object;)Z
+ *
+ * Finally, invokespecial is used to invoke a private method. Remember that
+ * private methods are only visible to other methods belonging the same class as
+ * the private method.
+ *
+ * Before performing the method invocation, the class and the method identified
+ * by <method-spec> are resolved. See Chapter 9 for a description of how methods
+ * are resolved.
+ *
+ * invokespecial first looks at the descriptor given in <method-spec>, and
+ * determines how many argument words the method takes (this may be zero). It
+ * pops these arguments off the operand stack. Next it pops objectref (a
+ * reference to an object) off the operand stack. objectref must be an instance
+ * of the class named in <method-spec>, or one of its subclasses. The interpreter
+ * searches the list of methods defined by the class named in <method-spec>,
+ * looking for a method called methodname whose descriptor is descriptor. This
+ * search is not based on the runtime type of objectref, but on the compile time
+ * type given in <method-spec>.
+ *
+ * Once a method has been located, invokespecial calls the method. First, if
+ * the method is marked as synchronized, the monitor associated with objectref is
+ * entered. Next, a new stack frame structure is established on the call stack.
+ * Then the arguments for the method (which were popped off the current method's
+ * operand stack) are placed in local variables of the new stack frame structure.
+ * arg1 is stored in local variable 1, arg2 is stored in local variable 2 and so
+ * on. objectref is stored in local variable 0 (the local variable used for the
+ * special Java variable this). Finally, execution continues at the first
+ *instruction in the bytecode of the new method.
+ *
+ * Methods marked as native are handled slightly differently. For native
+ * methods, the runtime system locates the platform-specific code for the method,
+ * loading it and linking it into the JVM if necessary. Then the native method
+ * code is executed with the arguments popped from the operand stack. The exact
+ * mechanism used to invoke native methods is implementation-specific.
+ *
+ * When the method called by invokespecial returns, any single (or double) word
+ * return result is placed on the operand stack of the current method. If the
+ * invoked method was marked as synchronized, the monitor associated with
+ * objectref is exited. Execution continues at the instruction that follows
+ * invokespecial in the bytecode.
+ *
+ * Notes
+ *
+ * 1. In Java Virtual Machine implementations prior to version JDK 1.02, this
+ * instruction was called invokenonvirtual, and was less restrictive than
+ * invokespecial - it wasn't limited to invoking only superclass, private or
+ * <init> methods. The class access flag ACC_SUPER (see Chapter 4) is used to
+ * indicate which semantics are used by a class. In older class files, the
+ * ACC_SUPER flag is unset. In all new classes, the ACC_SUPER flag should be set,
+ * indicating that the restrictions enforced by invokespecial are obeyed. (In
+ * practice, all the common uses of invokenonvirtual continue to be supported
+ * by invokespecial, so this change should have little impact on JVM users).
+ *
+ */
+
+package invokespecial;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import shared.AbstractGenerator;
+import shared.AccessType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Generator extends AbstractGenerator {
+ public static void main (String[] args) throws Exception {
+ new Generator(args).run();
+ }
+ public Generator(String[] args) {
+ super(args);
+ }
+
+ protected Checker getChecker(Class paramClass, Class targetClass) {
+ return new Checker(paramClass, targetClass);
+ }
+
+ public void run() throws Exception {
+ // Specify package names
+ String pkg1 = "a.";
+ String pkg2 = "b.";
+ String[] packages = new String[] { "", pkg1, pkg2 };
+
+ boolean isPassed = true;
+
+ // HIERARCHIES
+ // The following triples will be used during further
+ // hierarchy construction and will specify packages for A, B and C
+ String[][] packageSets = new String[][] {
+ { "", "", "" }
+ , { "", pkg1, pkg1 }
+ , { "", pkg1, pkg2 }
+ , { pkg1, "", pkg1 }
+ , { pkg1, "", pkg2 }
+ , { pkg1, pkg1, "" }
+ , { pkg1, pkg2, "" }
+ , { pkg1, pkg1, pkg1 }
+ , { pkg1, pkg1, pkg2 }
+ , { pkg1, pkg2, pkg1 }
+ , { pkg1, pkg2, pkg2 }
+ };
+
+ String [] header = new String[] {
+ String.format("%30s %35s", "Method access modifiers", "Call site location")
+ , String.format("%4s %-10s %-10s %-10s %7s %7s %7s %7s %7s %7s %7s"
+ , " # "
+ , "A.m()"
+ , "B.m()"
+ , "C.m()"
+ , " A "
+ , "pkgA"
+ , " B "
+ , " pkgB"
+ , " C "
+ , "pkgC "
+ , " X "
+ )
+ , "-----------------------------------------------------------------------------------------------------------"
+ };
+
+ // Print header
+ for (String str : header) {
+ System.out.println(str);
+ }
+
+ // Iterate over all interesting package combinations
+ for (String[] pkgSet : packageSets) {
+ String packageA = pkgSet[0];
+ String packageB = pkgSet[1];
+ String packageC = pkgSet[2];
+
+ String classNameA = packageA + "A";
+ String classNameB = packageB + "B";
+ String classNameC = packageC + "C";
+
+ // For all possible access modifier combinations
+ for (AccessType accessFlagA : AccessType.values()) {
+ for (AccessType accessFlagB : AccessType.values()) {
+ for (AccessType accessFlagC : AccessType.values()) {
+ Map<String, byte[]> classes = new HashMap<String, byte[]>();
+
+ String calleeClassName = classNameB;
+ int classFlags = ACC_PUBLIC;
+
+ // The following hierarhcy is created:
+ // c.C extends b.B extends a.A extends Object - base hierarchy
+ // X extends Object - external caller
+ // c.Caller, b.Caller, a.Caller extends Object - package callers
+
+ // Generate result storage
+ classes.put(
+ "Result"
+ , new ClassGenerator(
+ "Result"
+ , "java.lang.Object"
+ , ACC_PUBLIC
+ )
+ .addField(
+ ACC_PUBLIC | ACC_STATIC
+ , "value"
+ , "java.lang.String"
+ )
+ .getClassFile()
+ );
+
+ // Generate class A
+ classes.put(
+ classNameA
+ , new ClassGenerator(
+ classNameA
+ , "java.lang.Object"
+ , classFlags
+ )
+ .addTargetConstructor(accessFlagA)
+ .addTargetMethod(accessFlagA)
+ .addCaller(calleeClassName)
+ .getClassFile()
+ );
+
+ // Generate class B
+ classes.put(
+ classNameB
+ , new ClassGenerator(
+ classNameB
+ , classNameA
+ , classFlags
+ )
+ .addTargetConstructor(accessFlagB)
+ .addTargetMethod(accessFlagB)
+ .addCaller(calleeClassName)
+ .getClassFile()
+ );
+
+ // Generate class C
+ classes.put(
+ classNameC
+ , new ClassGenerator(
+ classNameC
+ , classNameB
+ , classFlags
+ )
+ .addTargetConstructor(accessFlagC)
+ .addTargetMethod(accessFlagC)
+ .addCaller(calleeClassName)
+ .getClassFile()
+ );
+
+ // Generate class X
+ String classNameX = "x.X";
+ classes.put(
+ classNameX
+ , new ClassGenerator(
+ classNameX
+ , "java.lang.Object"
+ , classFlags
+ )
+ .addTargetMethod(accessFlagC)
+ .addCaller(calleeClassName)
+ .getClassFile()
+ );
+
+ // Generate package callers
+ for (String pkg : packages) {
+ classes.put(
+ pkg+"Caller"
+ , new ClassGenerator(
+ pkg+"Caller"
+ , "java.lang.Object"
+ , classFlags
+ )
+ .addCaller(calleeClassName)
+ .getClassFile()
+ );
+ }
+
+ String[] callSites = new String[] {
+ classNameA
+ , packageA+"Caller"
+ , classNameB
+ , packageB+"Caller"
+ , classNameC
+ , packageC+"Caller"
+ , classNameX
+ };
+
+ String caseDescription = String.format(
+ "%-10s %-10s %-10s| "
+ , classNameA + " " + accessFlagA
+ , classNameB + " " + accessFlagB
+ , classNameC + " " + accessFlagC
+ );
+
+ boolean result = exec(classes, caseDescription, calleeClassName, 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"));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokespecialTests.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 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.
+ *
+ */
+
+/*
+ * @test
+ * @bug 8224137
+ * @summary Run invokespecial invocation tests
+ * @library /test/lib
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * java.base/jdk.internal.misc
+ * @compile shared/AbstractGenerator.java shared/AccessCheck.java shared/AccessType.java
+ * shared/Caller.java shared/ExecutorGenerator.java shared/Utils.java
+ * shared/ByteArrayClassLoader.java shared/Checker.java shared/GenericClassGenerator.java
+ * @compile invokespecial/Checker.java invokespecial/ClassGenerator.java invokespecial/Generator.java
+ *
+ * @run main/othervm/timeout=1800 invokespecialTests
+ */
+
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.compiler.InMemoryJavaCompiler;
+
+public class invokespecialTests {
+
+ public static void runTest(String classFileVersion, String option) throws Exception {
+ System.out.println("\ninvokespecial invocation tests, option: " + option +
+ ", class file version: " + classFileVersion);
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false, "-Xmx128M", option,
+ "invokespecial.Generator", "--classfile_version=" + classFileVersion);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ try {
+ output.shouldContain("EXECUTION STATUS: PASSED");
+ output.shouldHaveExitValue(0);
+ } catch (Throwable e) {
+ System.out.println(
+ "\nNote that an entry such as 'B.m/C.m' in the failure chart means that" +
+ " the test case failed because method B.m was invoked but the test " +
+ "expected method C.m to be invoked. Similarly, a result such as 'AME/C.m'" +
+ " means that an AbstractMethodError exception was thrown but the test" +
+ " case expected method C.m to be invoked.");
+ System.out.println(
+ "\nAlso note that passing --dump to invokespecial.Generator will" +
+ " dump the generated classes (for debugging purposes).\n");
+ System.exit(1);
+ }
+ }
+
+ public static void main(String args[]) throws Throwable {
+ // Get current major class file version and test with it.
+ byte klassbuf[] = InMemoryJavaCompiler.compile("blah", "public class blah { }");
+ int major_version = klassbuf[6] << 8 | klassbuf[7];
+ runTest(String.valueOf(major_version), "-Xint");
+ runTest(String.valueOf(major_version), "-Xcomp");
+
+ // Test old class file version.
+ runTest("51", "-Xint"); // JDK-7
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokevirtual/Checker.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ *
+ */
+
+package invokevirtual;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+
+public class Checker extends shared.Checker {
+ public Checker(Class staticTargetClass, Class dynamicTargetClass) {
+ super(staticTargetClass, dynamicTargetClass);
+ }
+
+ public String check (Class callerClass) {
+ Method m;
+ try {
+ // May cause java.lang.VerifyError
+ m = getOverriddenMethod();
+ } catch (Throwable e) {
+ return e.getClass().getName();
+ }
+
+ // Check method accessibility (it's a static property, according to JLS #6.6: Access Control)
+ if (m != null) {
+ Method staticTargetMethod = getDeclaredMethod(staticTargetClass);
+
+ if (checkAccess(staticTargetMethod, callerClass)) {
+ // Can't invoke abstract method
+ if ( Modifier.isAbstract(m.getModifiers())) {
+ return "java.lang.AbstractMethodError";
+ }
+
+ return String.format("%s.%s"
+ , m.getDeclaringClass().getSimpleName()
+ , methodName
+ );
+ } else {
+ // if method isn't accessible, IllegalAccessError is thrown
+ return "java.lang.IllegalAccessError";
+ }
+ } else {
+ // if method == null, NoSuchMethodError is thrown
+ return "java.lang.NoSuchMethodError";
+ }
+ }
+
+ public Method getOverriddenMethod() {
+ return getOverriddenMethod(staticTargetClass, dynamicTargetClass);
+ }
+
+ public Method getOverriddenMethod(Class staticTarget, Class dynamicTarget) {
+ // Assertion #1. C is a subclass of A
+ if (!staticTarget.isAssignableFrom(dynamicTarget)) {
+ return null;
+ }
+
+ Method staticTargetMethod = getDeclaredMethod(staticTarget);
+ Method dynamicTargetMethod = getDeclaredMethod(dynamicTarget);
+
+ if (staticTarget.equals(dynamicTarget)) {
+ return staticTargetMethod;
+ }
+
+ // TODO: ? need to find out the right behavior
+ if (staticTargetMethod == null) {
+ return null;
+ }
+
+ // Dynamic target doesn't have desired method, so check it's superclass
+ if (dynamicTargetMethod == null) {
+ return getOverriddenMethod(staticTarget, dynamicTarget.getSuperclass());
+ } else {
+ // Private method can't override anything
+ if (Modifier.isPrivate(dynamicTargetMethod.getModifiers())) {
+ return getOverriddenMethod(staticTarget, dynamicTarget.getSuperclass());
+ }
+ }
+
+ // TODO: abstract methods
+
+ //Assertion #3.a: A.m2 is PUB || PROT || (PP && PKG(A) == PKG(C))
+ int staticTargetModifiers = staticTargetMethod.getModifiers();
+ {
+ boolean isPublic = Modifier.isPublic(staticTargetModifiers);
+ boolean isProtected = Modifier.isProtected(staticTargetModifiers);
+ boolean isPrivate = Modifier.isPrivate(staticTargetModifiers) ;
+ String staticTargetPkg = getClassPackageName(staticTarget);
+ String dynamicTargetPkg = getClassPackageName(dynamicTarget);
+
+ if ( isPublic || isProtected
+ || ( !isPublic && !isProtected && !isPrivate
+ && staticTargetPkg.equals(dynamicTargetPkg)
+ ))
+ {
+ return dynamicTargetMethod;
+ }
+ }
+ // OR
+ //Assertion #3.b: exists m3: C.m1 != B.m3, A.m2 != B.m3, B.m3 overrides A.m2, C.m1 overrides B.m3
+ Class ancestor = dynamicTarget.getSuperclass();
+ while (ancestor != staticTarget) {
+ Method OverriddenM2 = getOverriddenMethod(staticTarget, ancestor);
+ Method m3 = getDeclaredMethod(ancestor);
+ Method m1 = getOverriddenMethod(ancestor, dynamicTarget);
+
+ if (m1 != null && m3 != null) {
+ if (m1.equals(dynamicTargetMethod) && m3.equals(OverriddenM2)) {
+ return dynamicTargetMethod;
+ }
+ } else {
+ if (m1 == null && dynamicTargetMethod == null
+ && m3 == null && OverriddenM2 == null)
+ {
+ return null;
+ }
+ }
+
+ ancestor = ancestor.getSuperclass();
+ }
+
+ return getOverriddenMethod(staticTarget, dynamicTarget.getSuperclass());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokevirtual/ClassGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+package invokevirtual;
+
+import jdk.internal.org.objectweb.asm.Opcodes;
+import shared.GenericClassGenerator;
+
+/*******************************************************************/
+class ClassGenerator extends GenericClassGenerator<ClassGenerator> {
+ public ClassGenerator(String fullClassName) {
+ super(fullClassName);
+ }
+
+ public ClassGenerator(String fullClassName, String parentClassName) {
+ super(fullClassName, parentClassName);
+ }
+
+ public ClassGenerator(String fullClassName, String parentClassName, int flags) {
+ super(fullClassName, parentClassName, flags);
+ }
+
+ // Add target method call site into current class
+ public ClassGenerator addCaller(String targetClass) {
+ return super.addCaller(targetClass, Opcodes.INVOKEVIRTUAL);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokevirtual/Generator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ *
+ */
+
+package invokevirtual;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import shared.AbstractGenerator;
+import shared.AccessType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Generator extends AbstractGenerator {
+ public Generator(String[] args) {
+ super(args);
+ }
+
+ public static void main (String[] args) throws Exception {
+ new Generator(args).run();
+ }
+
+ protected Checker getChecker(Class paramClass, Class targetClass) {
+ return new Checker(paramClass, targetClass);
+ }
+
+ private void run() throws Exception {
+ // Specify package names
+ String pkg1 = "a.";
+ String pkg2 = "b.";
+ String pkg3 = "c.";
+ String[] packages = new String[] { "", pkg1, pkg2, pkg3 };
+
+ 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[][] {
+ { "", "", "" }
+ , { "", pkg1, pkg1 }
+ , { "", pkg1, pkg2 }
+ , { pkg1, pkg1, pkg1 }
+ , { pkg1, pkg1, pkg2 }
+ , { pkg1, pkg2, pkg1 }
+ , { pkg1, pkg2, pkg2 }
+ };
+
+ String [] header = new String[] {
+ String.format("%30s %45s %20s", "Method access modifiers", "Call site location", "Status")
+ , String.format("%4s %-12s %-12s %-12s %7s %7s %7s %7s %7s %7s"
+ , " # "
+ , "A.m()"
+ , "B.m()"
+ , "C.m()"
+ , " A "
+ , "pkgA "
+ , " B "
+ , " pkgB"
+ , " C "
+ , "pkgC "
+ )
+ , "-------------------------------------------------------------------------------------------------"
+ };
+
+ for (String str : header) {
+ System.out.println(str);
+ }
+
+ for (String[] pkgSet : packageSets) {
+ String packageA = pkgSet[0];
+ String packageB = pkgSet[1];
+ String packageC = pkgSet[2];
+
+ String classNameA = packageA + "A";
+ String classNameB = packageB + "B";
+ String classNameC = packageC + "C";
+
+ String staticCallerParam = classNameA;
+
+ // For all possible access modifier combinations
+ for (AccessType accessA : AccessType.values()) {
+ for (AccessType accessB : AccessType.values()) {
+ for (AccessType accessC : AccessType.values()) {
+
+ if (accessA == AccessType.UNDEF) {
+ continue;
+ }
+
+ for (int I = 0; I < 4; I++) {
+ boolean isAbstractA = ((I & 1) != 0);
+ boolean isAbstractB = ((I & 2) != 0);
+
+ Map<String, byte[]> classes = new HashMap<String, byte[]>();
+
+ // Generate class A
+ classes.put(
+ classNameA
+ , new ClassGenerator( classNameA
+ , "java.lang.Object"
+ , ACC_PUBLIC | (isAbstractA ? ACC_ABSTRACT : 0))
+ .addTargetMethod( accessA
+ , (isAbstractA ? ACC_ABSTRACT : 0))
+ .addCaller(staticCallerParam)
+ .getClassFile()
+ );
+
+ // Generate class B
+ classes.put(
+ classNameB
+ , new ClassGenerator( classNameB
+ , classNameA
+ , ACC_PUBLIC | (isAbstractB ? ACC_ABSTRACT : 0))
+ .addTargetMethod( accessB
+ , (isAbstractB ? ACC_ABSTRACT : 0))
+ .addCaller(staticCallerParam)
+ .getClassFile()
+ );
+
+ // Generate class C
+ classes.put(
+ classNameC
+ , new ClassGenerator(classNameC, classNameB)
+ .addTargetMethod(accessC)
+ .addCaller(staticCallerParam)
+ .getClassFile()
+ );
+
+ // Generate package callers
+ for (String pkg : packages) {
+ classes.put(
+ pkg+"Caller"
+ , new ClassGenerator(pkg+"Caller")
+ .addCaller(staticCallerParam)
+ .getClassFile()
+ );
+ }
+
+ String[] callSites = new String[] {
+ classNameA
+ , packageA+"Caller"
+ , classNameB
+ , packageB+"Caller"
+ , classNameC
+ , packageC+"Caller"
+ };
+
+
+ String caseDescription =
+ String.format("%-12s %-12s %-12s| "
+ , (isAbstractA ? "! " : " ") + classNameA + " " + accessA
+ , (isAbstractB ? "! " : " ") + classNameB + " " + accessB
+ , classNameC + " " + accessC
+ );
+
+ boolean result = exec(classes, caseDescription, staticCallerParam, 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"));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/invokevirtualTests.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 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.
+ *
+ */
+
+/*
+ * @test
+ * @bug 8224137
+ * @summary Run invokevirtual invocation tests
+ * @library /test/lib
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * java.base/jdk.internal.misc
+ * @compile shared/AbstractGenerator.java shared/AccessCheck.java shared/AccessType.java
+ * shared/Caller.java shared/ExecutorGenerator.java shared/Utils.java
+ * shared/ByteArrayClassLoader.java shared/Checker.java shared/GenericClassGenerator.java
+ * @compile invokevirtual/Checker.java invokevirtual/ClassGenerator.java invokevirtual/Generator.java
+ *
+ * @run main/othervm/timeout=1800 invokevirtualTests
+ */
+
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.compiler.InMemoryJavaCompiler;
+
+public class invokevirtualTests {
+
+ public static void runTest(String classFileVersion, String option) throws Exception {
+ System.out.println("\ninvokevirtual invocation tests, option: " + option +
+ ", class file version: " + classFileVersion);
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false, "-Xmx128M", option,
+ "invokevirtual.Generator", "--classfile_version=" + classFileVersion);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ try {
+ output.shouldContain("EXECUTION STATUS: PASSED");
+ output.shouldHaveExitValue(0);
+ } catch (Throwable e) {
+ System.out.println(
+ "\nNote that an entry such as 'B.m/C.m' in the failure chart means that" +
+ " the test case failed because method B.m was invoked but the test " +
+ "expected method C.m to be invoked. Similarly, a result such as 'AME/C.m'" +
+ " means that an AbstractMethodError exception was thrown but the test" +
+ " case expected method C.m to be invoked.");
+ System.out.println(
+ "\nAlso note that passing --dump to invokevirtual.Generator will" +
+ " dump the generated classes (for debugging purposes).\n");
+ System.exit(1);
+ }
+ }
+
+ public static void main(String args[]) throws Throwable {
+ // Get current major class file version and test with it.
+ byte klassbuf[] = InMemoryJavaCompiler.compile("blah", "public class blah { }");
+ int major_version = klassbuf[6] << 8 | klassbuf[7];
+ runTest(String.valueOf(major_version), "-Xint");
+// Uncomment the below test once JDK-8226588 is fixed
+// runTest(String.valueOf(major_version), "-Xcomp");
+
+ // Test old class file version.
+ runTest("51", "-Xint"); // JDK-7
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/AbstractGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ *
+ */
+public abstract class AbstractGenerator {
+ protected final boolean dumpClasses;
+ protected final boolean executeTests;
+ private static int testNum = 0;
+
+ protected AbstractGenerator(String[] args) {
+ List<String> params = new ArrayList<String>(Arrays.asList(args));
+
+ if (params.contains("--help")) {
+ Utils.printHelp();
+ System.exit(0);
+ }
+
+ dumpClasses = params.contains("--dump");
+ executeTests = !params.contains("--noexecute");
+
+ params.remove("--dump");
+ params.remove("--noexecute");
+
+ Utils.init(params);
+ }
+
+ /*******************************************************************/
+ public static void writeToFile(File dir, Map<String, byte[]> classes) {
+ for (String name : classes.keySet()) {
+ try {
+ writeToFile(dir, name, classes.get(name));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /*******************************************************************/
+ public static void writeToFile(File dir, String fullName, byte[] classBytecode) {
+ if (!dir.isDirectory()) {
+ throw new RuntimeException("Invalid parameter: dir doesn't point to an existing directory");
+ }
+
+ File classFile =
+ new File(
+ dir.getPath() + File.separator
+ + fullName.replaceAll("\\.", File.separator)
+ + ".class"
+ );
+
+ classFile.getParentFile().mkdirs();
+
+ try {
+ FileOutputStream fos = new FileOutputStream(classFile);
+ try {
+ fos.write(classBytecode);
+ } finally {
+ fos.close();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected boolean exec(Map<String, byte[]> classes, String description, String calleeClassName, String classNameC, String[] callSites) throws ClassNotFoundException {
+ boolean isPassed = true;
+
+ testNum++;
+
+ String caseDescription = String.format("%4d| %s", testNum, description);
+
+ // Create test executor for a single case
+ classes.put(
+ ExecutorGenerator.className
+ , new ExecutorGenerator(
+ caseDescription
+ , calleeClassName
+ , classNameC
+ ).generateExecutor(callSites)
+ );
+
+ // Dump generated set to disk, if needed
+ if (dumpClasses) {
+ File dir = new File("classes" + File.separator + String.format("%04d", testNum));
+ dir.mkdirs();
+ writeToFile(dir, classes);
+ }
+
+ ByteArrayClassLoader loader = new ByteArrayClassLoader(classes);
+
+ Class paramClass;
+ Class targetClass;
+ Checker checker;
+
+ try {
+ paramClass = loader.loadClass(calleeClassName);
+ targetClass = loader.loadClass(classNameC);
+
+ checker = getChecker(paramClass, targetClass);
+ } catch (Throwable e) {
+ String result = Checker.abbreviateResult(e.getClass().getName());
+
+ System.out.printf(caseDescription);
+
+ for (String site : callSites) {
+ System.out.printf(" %7s", result);
+ }
+
+ System.out.println("");
+
+ return true;
+ }
+
+ if (executeTests) {
+ // Check runtime behavior
+ Caller caller = new Caller(loader, checker, paramClass, targetClass);
+ boolean printedCaseDes = false;
+ for (String site : callSites) {
+ String callResult = caller.call(site);
+
+ if (!caller.isPassed()) {
+ isPassed = false;
+ if (!printedCaseDes) {
+ System.out.printf(caseDescription);
+ printedCaseDes = true;
+ }
+ System.out.printf(" %7s", callResult);
+ }
+ }
+ if (!caller.isPassed()) {
+ System.out.println(" | FAILED");
+ }
+ } else {
+ for (String site : callSites) {
+ String result = checker.check(loader.loadClass(site));
+ System.out.printf(" %7s", Checker.abbreviateResult(result));
+ }
+ }
+
+ return isPassed;
+ }
+
+ protected abstract Checker getChecker(Class paramClass, Class targetClass);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/AccessCheck.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+/**
+ *
+ * @author vi158347
+ */
+public class AccessCheck {
+ public static boolean isAbstract(int access) {
+ return (access & ACC_ABSTRACT) != 0;
+ }
+
+ public static boolean isPublic(int access) {
+ return (access & ACC_PUBLIC) != 0;
+ }
+
+ public static boolean isProtected(int access) {
+ return (access & ACC_PROTECTED) != 0;
+ }
+
+ public static boolean isPackagePrivate(int access) {
+ return !isPublic(access) && !isProtected(access) && !isPrivate(access);
+ }
+
+ public static boolean isPrivate(int access) {
+ return (access & ACC_PRIVATE) != 0;
+ }
+
+ public static boolean isInterface(int access) {
+ return (access & ACC_INTERFACE) != 0;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/AccessType.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+public enum AccessType {
+ PUBLIC ("PUB") { public int value() { return ACC_PUBLIC; } }
+ , PROTECTED ("PROT") { public int value() { return ACC_PROTECTED; } }
+ , PACKAGE_PRIVATE ("PP") { public int value() { return 0; } }
+ , PRIVATE ("PRIV") { public int value() { return ACC_PRIVATE; } }
+ , UNDEF ("UNDEF") { public int value() { return -1; } }
+ ;
+
+ private String name;
+
+ AccessType(String name) {
+ this.name = name;
+ }
+
+ public abstract int value();
+
+ public String toString() {
+ return name;
+ }
+};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/ByteArrayClassLoader.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/*******************************************************************/
+// Class loader which has local class file storage in memory
+/*******************************************************************/
+
+public class ByteArrayClassLoader extends ClassLoader {
+ private Map<String, byte[]> classes;
+
+ public ByteArrayClassLoader() {
+ classes = new HashMap<String, byte[]>();
+ }
+
+ public ByteArrayClassLoader(Map<String, byte[]> classes) {
+ this.classes = classes;
+ }
+
+ public void appendClass(String name, byte[] classFile) {
+ classes.put(name, classFile);
+ }
+
+ public Class findClass (String name) throws ClassNotFoundException {
+ if (classes.containsKey(name)) {
+ byte[] classData = classes.get(name);
+ return defineClass(name, classData, 0, classData.length);
+ } else {
+ throw new ClassNotFoundException("Can't find requested class: " + name);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/Caller.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import java.lang.reflect.InvocationTargetException;
+
+/*******************************************************************/
+// Invoke different target method callers
+/*******************************************************************/
+
+public class Caller {
+ private ClassLoader loader;
+ private Class paramClass;
+ private Class targetClass;
+ private boolean passed = true;
+ private Checker checker;
+
+ public Caller(ClassLoader loader, Checker checker,
+ Class paramClass, Class targetClass) {
+ this.loader = loader;
+ this.paramClass = paramClass;
+ this.targetClass = targetClass;
+ this.checker = checker;
+ }
+
+ public boolean isPassed() {
+ return passed;
+ }
+
+ public String call(String invoker) {
+ try {
+ Class clazz = loader.loadClass(invoker);
+
+ String expectedBehavior = checker.check(clazz);
+
+ String result = null;
+ Throwable exc = null;
+ try {
+ java.lang.reflect.Method m = clazz.getDeclaredMethod("call", paramClass);
+ result = (String) m.invoke(null, targetClass.newInstance());
+ } catch (InvocationTargetException e) {
+ exc = e.getCause();
+ } catch (Throwable e) {
+ exc = e;
+ }
+
+ if (result == null) {
+ if (exc != null) {
+ result = exc.getClass().getName();
+ } else {
+ result = "null";
+ }
+ }
+
+ if (!(result.equals(expectedBehavior) || "".equals(expectedBehavior)) ) {
+ passed = false;
+ result = String.format("%s/%s", result, expectedBehavior);
+ }
+
+ return Checker.abbreviateResult(result);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/Checker.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public abstract class Checker {
+ protected Class staticTargetClass;
+ protected Class dynamicTargetClass;
+ protected String methodName;
+
+ public abstract String check (Class callSite);
+
+ public Checker(Class staticTargetClass, Class dynamicTargetClass) {
+ if (!staticTargetClass.isAssignableFrom(dynamicTargetClass)) {
+ throw new RuntimeException("Dynamic target class should be a subclass of the static target class.");
+ }
+
+ // **********************************************
+ // NB!!! All classes are assumed to be PUBLIC !!!
+ // **********************************************
+ Class klass = dynamicTargetClass;
+ while (klass != Object.class) {
+ if (!Modifier.isPublic(klass.getModifiers())) {
+ throw new AssertionError("Class "+klass.getName()+" isn't public.");
+ }
+
+ klass = klass.getSuperclass();
+ }
+
+ this.methodName = Utils.TARGET_METHOD_NAME;
+ this.staticTargetClass = staticTargetClass;
+ this.dynamicTargetClass = dynamicTargetClass;
+ }
+
+ protected Method getMethodInHierarchy (Class klass) {
+ return getMethodInHierarchy(klass, methodName);
+ }
+
+ protected Method getMethodInHierarchy (Class klass, String name) {
+ while (klass != null) {
+ Method method = getDeclaredMethod (klass, name);
+
+ if ( method != null) {
+// TODO: why doesn't this check work in VM?
+// int modifiers = method.getModifiers();
+//
+// if (Modifier.isPrivate(modifiers)) {
+// if (klass == initialClass) {
+// return method;
+// }
+// } else {
+// return method;
+// }
+ return method;
+ }
+ klass = klass.getSuperclass();
+ }
+
+ return null;
+ }
+
+ protected Method getMethod (Class klass) {
+ return getMethod (klass, methodName);
+ }
+
+ protected Method getDeclaredMethod (Class klass) {
+ return getDeclaredMethod (klass, methodName);
+ }
+
+ static protected Method getMethod (Class klass, String name) {
+ return findMethod (klass.getMethods(), name);
+ }
+
+ static protected Method getDeclaredMethod (Class klass, String name) {
+ return findMethod (klass.getDeclaredMethods(), name);
+ }
+
+ static protected Method findMethod (Method[] methods, String name) {
+ for (Method method : methods) {
+ if (name.equals(method.getName())) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ static public String getClassPackageName(Class klass) {
+ String name = klass.getName();
+ return getClassPackageName(name);
+ }
+
+ static public String getClassPackageName(String name) {
+ int lastDotIndex = name.lastIndexOf('.');
+ if (lastDotIndex > -1) {
+ return name.substring(0, lastDotIndex);
+ } else {
+ return "";
+ }
+ }
+
+ public static String abbreviateResult(String result) {
+ // Abbreviate exception names
+ result = result.replaceAll("java.lang.NullPointerException", "NPE");
+ result = result.replaceAll("java.lang.IllegalAccessError", "IAE");
+ result = result.replaceAll("java.lang.IllegalAccessException", "IAExc");
+ result = result.replaceAll("java.lang.NoSuchMethodError", "NSME");
+ result = result.replaceAll("java.lang.AbstractMethodError", "AME");
+ result = result.replaceAll("java.lang.IncompatibleClassChangeError", "ICCE");
+ result = result.replaceAll("java.lang.VerifyError", "VE");
+ result = result.replaceAll("java.lang.ClassFormatError", "CFE");
+
+ return result;
+ }
+
+ // Check access possibility from particular call site
+ protected boolean checkAccess(Class klass, Class callerClass) {
+ int modifiers = klass.getModifiers();
+
+ return checkAccess(modifiers, klass, callerClass);
+ }
+
+ protected boolean checkAccess(Method m, Class callerClass) {
+ int modifiers = m.getModifiers();
+ Class declaringClass = m.getDeclaringClass();
+
+ return checkAccess(modifiers, declaringClass, callerClass);
+ }
+
+ protected boolean checkAccess(int modifiers, Class klass, Class callerClass) {
+ if ( Modifier.isPublic(modifiers) ) {
+ return true;
+ } else if ( Modifier.isProtected(modifiers) ) {
+ if (klass.isAssignableFrom(callerClass)) {
+ return true;
+ } else if (getClassPackageName(klass).equals(getClassPackageName(callerClass))) {
+ return true;
+ }
+ } else if ( Modifier.isPrivate(modifiers)) {
+ if (klass == callerClass) {
+ return true;
+ }
+ } else if (getClassPackageName(klass).equals(getClassPackageName(callerClass))) {
+ return true;
+ } else {
+ // if method isn't accessible, IllegalAccessException is thrown
+ return false;
+ }
+
+ return false;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/ExecutorGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,259 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import static jdk.internal.org.objectweb.asm.ClassWriter.*;
+import jdk.internal.org.objectweb.asm.Label;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+public class ExecutorGenerator {
+ public static final String className = Utils.getInternalName("Test");
+ private String caseDescription;
+ private String staticTargetName;
+ private String dynamicTargetName;
+
+ private String callerSignature;
+
+ public ExecutorGenerator(String caseDescription,
+ String staticTargetName,
+ String dynamicTargetName) {
+ this.caseDescription = caseDescription;
+ this.staticTargetName = Utils.getInternalName(staticTargetName);
+ this.dynamicTargetName = Utils.getInternalName(dynamicTargetName);
+ callerSignature = String.format("(L%s;)Ljava/lang/String;", this.staticTargetName);
+ }
+
+ public byte[] generateExecutor(String[] callSites) {
+ ClassWriter cw = new ClassWriter(COMPUTE_MAXS);
+
+ cw.visit(Utils.version, ACC_PUBLIC | (Utils.isACC_SUPER ? ACC_SUPER : 0), className, null, "java/lang/Object", null);
+
+ // Generate constructor
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
+ mv.visitInsn(RETURN);
+ mv.visitEnd();
+ mv.visitMaxs(0, 0);
+ }
+
+ // public static void main(String[] args) {
+ // new Test().run();
+ // }
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, className);
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V");
+ mv.visitMethodInsn(INVOKEVIRTUAL, className, "run", "()V");
+ mv.visitInsn(RETURN);
+ mv.visitEnd();
+ mv.visitMaxs(0, 0);
+ }
+
+ // private String indent(String result) {
+ // while (result.length() < 8) {
+ // result = " "+result;
+ // }
+ // return result;
+ // }
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "indent", "(Ljava/lang/String;)Ljava/lang/String;", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "length", "()I");
+ mv.visitIntInsn(BIPUSH, 8);
+ Label l1 = new Label();
+ mv.visitJumpInsn(IF_ICMPGE, l1);
+ mv.visitTypeInsn(NEW, "java/lang/StringBuffer");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuffer", "<init>", "()V");
+ mv.visitLdcInsn(" ");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuffer", "toString", "()Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitJumpInsn(GOTO, l0);
+ mv.visitLabel(l1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(ARETURN);
+ mv.visitEnd();
+ mv.visitMaxs(0, 0);
+ }
+
+ //private String abbr(String result) {
+ // result = result.replaceAll("java.lang.NullPointerException", "NPE");
+ // result = result.replaceAll("java.lang.IllegalAccessError", "IAE");
+ // result = result.replaceAll("java.lang.IllegalAccessException", "IAExc");
+ // result = result.replaceAll("java.lang.NoSuchMethodError", "NSME");
+ // result = result.replaceAll("java.lang.AbstractMethodError", "AME");
+ // result = result.replaceAll("java.lang.IncompatibleClassChangeError", "ICCE");
+ //
+ // return result;
+ //}
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "abbr", "(Ljava/lang/String;)Ljava/lang/String;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.NullPointerException");
+ mv.visitLdcInsn("NPE");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.IllegalAccessError");
+ mv.visitLdcInsn("IAE");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.IllegalAccessException");
+ mv.visitLdcInsn("IAExc");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.NoSuchMethodError");
+ mv.visitLdcInsn("NSME");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.AbstractMethodError");
+ mv.visitLdcInsn("AME");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitLdcInsn("java.lang.IncompatibleClassChangeError");
+ mv.visitLdcInsn("ICCE");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(ARETURN);
+ mv.visitEnd();
+ mv.visitMaxs(0, 0);
+ }
+
+ // Generate execution method
+ // public void run() {
+ // System.out.print("2048| ! a.A PUB ! b.B PP a.C PROT |");
+ //
+ // C object = new C();
+ //
+ // try {
+ // System.out.print(indent(A.call(object)));
+ // } catch (Throwable e) {
+ // System.out.print(indent(abbr(e.getClass().getName())));
+ // }
+ //
+ // ...
+ //
+ // System.out.println();
+ // }
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
+ mv.visitCode();
+
+ // Generate try/catch blocks
+ Label[][] tryCatchLabels = new Label[callSites.length][3];
+ for (int I = 0; I < tryCatchLabels.length; I++) {
+ Label[] labels = tryCatchLabels[I];
+ for (int K = 0; K < labels.length; K++) {
+ labels[K] = new Label();
+ }
+
+ mv.visitTryCatchBlock(labels[0], labels[1], labels[2], "java/lang/Throwable");
+ }
+
+ // System.out.print("2048| ! a.A PUB ! b.B PP a.C PROT |");
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn(caseDescription);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V");
+
+ // C object = new C();
+ mv.visitTypeInsn(NEW, dynamicTargetName);
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, dynamicTargetName, "<init>", "()V");
+ mv.visitVarInsn(ASTORE, 1);
+
+// for (String site: callSites) {
+ // System.out.print(indent(A.call(object)));
+// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+// mv.visitVarInsn(ALOAD, 0);
+// mv.visitVarInsn(ALOAD, 1);
+// mv.visitMethodInsn(INVOKESTATIC, AbstractGenerator.getInternalName(site), "call", callerSignature);
+// mv.visitMethodInsn(INVOKESPECIAL, className, "indent", "(Ljava/lang/String;)Ljava/lang/String;");
+// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V");
+// }
+
+ Label returnLabel = new Label();
+ for (int I = 0; I < callSites.length; I++) {
+ String site = callSites[I];
+ Label[] l = tryCatchLabels[I];
+
+ Label nextBlock = (I+1 < callSites.length ? tryCatchLabels[I+1][0] : returnLabel);
+
+ mv.visitLabel(l[0]);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESTATIC, Utils.getInternalName(site), "call", callerSignature);
+ mv.visitMethodInsn(INVOKESPECIAL, className, "indent", "(Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V");
+ mv.visitLabel(l[1]);
+ mv.visitJumpInsn(GOTO, nextBlock);
+ mv.visitLabel(l[2]);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKESPECIAL, className, "abbr", "(Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKESPECIAL, className, "indent", "(Ljava/lang/String;)Ljava/lang/String;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V");
+ }
+ mv.visitLabel(returnLabel);
+
+ // System.out.println();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V");
+ mv.visitInsn(RETURN);
+
+ mv.visitEnd();
+ mv.visitMaxs(0, 0);
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/GenericClassGenerator.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,255 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
+import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import static shared.AccessCheck.*;
+
+public class GenericClassGenerator<T extends GenericClassGenerator> {
+ private static final String targetMethodName = Utils.TARGET_METHOD_NAME;
+
+ private int flags = 0;
+ private ClassWriter writer;
+ private String fullClassName = null;
+ private String parentClassName = null;
+
+ /*******************************************************************/
+ public GenericClassGenerator(String fullClassName) {
+ this(fullClassName, "java/lang/Object");
+ }
+
+ /*******************************************************************/
+ public GenericClassGenerator(String fullClassName, String parentClassName ) {
+ this(fullClassName, parentClassName, ACC_PUBLIC);
+ }
+
+ /*******************************************************************/
+ public GenericClassGenerator(String fullClassName, String parentClassName, int flags) {
+ this(fullClassName, parentClassName, flags, new String[0]);
+ }
+
+ /*******************************************************************/
+ public GenericClassGenerator(String fullClassName, String parentClassName, int flags, String[] implementedInterfaces) {
+ writer = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
+
+ this.fullClassName = fullClassName;
+ this.flags = flags;
+
+ // Construct simple class
+ if (parentClassName != null) {
+ this.parentClassName = getInternalName(parentClassName);
+ } else {
+ this.parentClassName = "java/lang/Object";
+ }
+
+ String parent = this.parentClassName;
+ String name = getInternalName(fullClassName);
+
+ if (Utils.isACC_SUPER) {
+ flags = flags | ACC_SUPER;
+ }
+
+ writer.visit(Utils.version, flags, name, null, parent, implementedInterfaces);
+
+ // Add constructor
+ if ( !isInterface(flags) ) {
+ MethodVisitor m =
+ writer.visitMethod(
+ ACC_PUBLIC
+ , "<init>"
+ , "()V"
+ , null
+ , null
+ );
+
+ m.visitCode();
+ m.visitVarInsn(ALOAD, 0);
+ m.visitMethodInsn(
+ INVOKESPECIAL
+ , getInternalName(parent)
+ , "<init>"
+ , "()V"
+ );
+ m.visitInsn(RETURN);
+ m.visitEnd();
+ m.visitMaxs(0,0);
+ }
+ }
+
+ /*******************************************************************/
+ protected static String getInternalName(String fullClassName) {
+ return fullClassName.replaceAll("\\.", "/");
+ }
+
+ /*******************************************************************/
+ public T addTargetConstructor(AccessType access) {
+ // AccessType.UNDEF means that the target method isn't defined, so do nothing
+ if (access == AccessType.UNDEF || isInterface(flags) ) {
+ return (T)this;
+ }
+
+ // Add target constructor
+ int methodAccessType = access.value();
+
+ MethodVisitor m =
+ writer.visitMethod(
+ methodAccessType
+ , "<init>"
+ , "(I)V"
+ , null
+ , null
+ );
+
+ // Add a call to parent constructor
+ m.visitCode();
+ m.visitVarInsn(ALOAD, 0);
+ m.visitMethodInsn(
+ INVOKESPECIAL
+ , getInternalName(parentClassName)
+ , "<init>"
+ , "()V"
+ );
+
+ // Add result reporting
+ String shortName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
+ m.visitLdcInsn(shortName+".<init>");
+ m.visitFieldInsn(
+ PUTSTATIC
+ , "Result"
+ , "value"
+ , "Ljava/lang/String;"
+ );
+
+ m.visitInsn(RETURN);
+ m.visitEnd();
+ m.visitMaxs(0,0);
+
+ return (T)this;
+
+ }
+
+ /*******************************************************************/
+ public T addTargetMethod(AccessType access) {
+ return addTargetMethod(access, 0);
+ }
+
+ /*******************************************************************/
+ public T addTargetMethod(AccessType access, int additionalFlags) {
+ // AccessType.UNDEF means that the target method isn't defined, so do nothing
+ if (access == AccessType.UNDEF) {
+ return (T)this;
+ }
+
+ // Add target method
+ int methodAccessType = access.value();
+ if ( isInterface(flags) || isAbstract(flags) ) {
+ methodAccessType |= ACC_ABSTRACT;
+ }
+
+ // Skip method declaration for abstract private case, which doesn't pass
+ // classfile verification stage
+ if ( isPrivate(methodAccessType) && isAbstract(methodAccessType) ) {
+ return (T)this;
+ }
+
+ MethodVisitor m =
+ writer.visitMethod(
+ methodAccessType | additionalFlags
+ , targetMethodName
+ , "()Ljava/lang/String;"
+ , null
+ , null
+ );
+
+ // Don't generate body if the method is abstract
+ if ( (methodAccessType & ACC_ABSTRACT) == 0 ) {
+ String shortName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
+
+ // Simply returns info about itself
+ m.visitCode();
+ m.visitLdcInsn(shortName+"."+targetMethodName);
+ m.visitInsn(ARETURN);
+ m.visitEnd();
+ m.visitMaxs(0,0);
+ }
+
+ return (T)this;
+ }
+
+ /*******************************************************************/
+ public T addField(int access, String name, String type) {
+ writer.visitField(
+ access
+ , name
+ , getInternalName(type)
+ , null
+ , null
+ )
+ .visitEnd();
+
+ return (T)this;
+ }
+
+ /*******************************************************************/
+ // Add target method call site into current class
+ public T addCaller(String targetClass, int callType) {
+ MethodVisitor m = writer.visitMethod(
+ ACC_PUBLIC | ACC_STATIC
+ , "call"
+ , String.format( "(L%s;)Ljava/lang/String;" , getInternalName(targetClass))
+ , null
+ , null
+ );
+
+ m.visitCode();
+ m.visitVarInsn(ALOAD, 0);
+ m.visitMethodInsn(
+ callType
+ , getInternalName(targetClass)
+ , targetMethodName
+ , "()Ljava/lang/String;"
+ );
+ m.visitInsn(ARETURN);
+ m.visitEnd();
+ m.visitMaxs(0,0);
+
+ return (T)this;
+ }
+
+ /*******************************************************************/
+ public byte[] getClassFile() {
+ writer.visitEnd();
+ return writer.toByteArray();
+ }
+
+ /*******************************************************************/
+ public String getFullClassName() {
+ return fullClassName;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/InvocationTests/shared/Utils.java Wed Jun 26 09:06:32 2019 -0400
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ *
+ */
+
+package shared;
+
+import java.util.List;
+
+/**
+ * Just a set of constants
+ */
+public class Utils {
+ public static final String TARGET_METHOD_NAME = "m";
+ public static int version = 50;
+
+ public static boolean isACC_SUPER = false;
+
+ public static void init(List<String> args) {
+ for (String param : args) {
+ String name = "classfile_version";
+ String pattern = "--"+name+"=";
+ if (param.startsWith(pattern)) {
+ String value = param.substring(pattern.length());
+ int majorVersion = 50;
+ int minorVersion = 0;
+
+ try {
+ String[] versions = value.split(":");
+ if (versions.length > 2) {
+ throw new RuntimeException(String.format("Unknown %s value: %s", name, value));
+ }
+
+ try {
+ majorVersion = Integer.parseInt(versions[0]);
+ if (versions.length > 1) {
+ minorVersion = Integer.parseInt(versions[1]);
+ }
+ } catch(Exception e) {
+ throw new RuntimeException(String.format("Can't parse %s value: '%s'", name, value));
+ }
+ } catch (Exception e) {
+ System.out.println("ERROR: "+e.getMessage());
+ }
+
+ version = majorVersion + (minorVersion << 16);
+
+ System.out.printf("INFO: Class file version: major: %d; minor: %d\n", majorVersion, minorVersion);
+
+ if (majorVersion < 49 && !args.contains("--no_acc_super")) {
+ isACC_SUPER = true;
+ System.out.printf("INFO: Enabling ACC_SUPER flag for major: %d\nTo disable it, specify --no_acc_super option.\n", majorVersion, minorVersion);
+ }
+ } else if (param.equals("--no_acc_super")){
+ System.out.println("INFO: ACC_SUPER flag is disabled");
+ isACC_SUPER = false;
+ } else if (param.equals("--acc_super")){
+ isACC_SUPER = true;
+ } else {
+ System.out.println("ERROR: Unknown option: "+param);
+ printHelp();
+ System.exit(1);
+ }
+ }
+ }
+
+ public static void printHelp() {
+ System.out.println(
+ "Supported parameters:\n"
+ + "\t--classfile_version=major_version[:minor_version]\n"
+ + "\t\t- specify class file version for generated classes\n"
+ + "\t--no_acc_super\n"
+ + "\t\t- don't add ACC_SUPER flag into generated classes\n"
+ + "\t--acc_super\n"
+ + "\t\t- force ACC_SUPER flag in generated classes\n"
+ + "\t--dump\n"
+ + "\t\t- dump generated classes\n"
+ + "\t--noexecute\n"
+ + "\t\t- print only expected results, don't execute tests\n"
+ );
+ }
+
+ /*******************************************************************/
+ public static String getInternalName(String s) {
+ return s.replaceAll("\\.", "/");
+ }
+
+}