diff --git a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java index 207d1e345ae..4e3ebb3834d 100644 --- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -632,7 +632,7 @@ class InvokerBytecodeGenerator { else if (c == Object[].class) return OBJARY; else if (c == Class.class) return CLS; else if (c == MethodHandle.class) return MH; - assert(VerifyAccess.isTypeVisible(c, Object.class)) : c.getName(); + assert(VerifyAccess.ensureTypeVisible(c, Object.class)) : c.getName(); if (c == lastClass) { return lastInternalName; diff --git a/src/java.base/share/classes/java/lang/invoke/MemberName.java b/src/java.base/share/classes/java/lang/invoke/MemberName.java index c8b38b1d16d..9b8c215e4c3 100644 --- a/src/java.base/share/classes/java/lang/invoke/MemberName.java +++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java @@ -800,7 +800,7 @@ final class MemberName implements Member, Cloneable { assert(isResolved() == isResolved); } - void checkForTypeAlias(Class refc) { + void ensureTypeVisible(Class refc) { if (isInvocable()) { MethodType type; if (this.type instanceof MethodType mt) @@ -808,7 +808,7 @@ final class MemberName implements Member, Cloneable { else this.type = type = getMethodType(); if (type.erase() == type) return; - if (VerifyAccess.isTypeVisible(type, refc)) return; + if (VerifyAccess.ensureTypeVisible(type, refc)) return; throw new LinkageError("bad method type alias: "+type+" not visible from "+refc); } else { Class type; @@ -816,7 +816,7 @@ final class MemberName implements Member, Cloneable { type = cl; else this.type = type = getFieldType(); - if (VerifyAccess.isTypeVisible(type, refc)) return; + if (VerifyAccess.ensureTypeVisible(type, refc)) return; throw new LinkageError("bad field type alias: "+type+" not visible from "+refc); } } @@ -958,7 +958,7 @@ final class MemberName implements Member, Cloneable { if (m == null && speculativeResolve) { return null; } - m.checkForTypeAlias(m.getDeclaringClass()); + m.ensureTypeVisible(m.getDeclaringClass()); m.resolution = null; } catch (ClassNotFoundException | LinkageError ex) { // JVM reports that the "bytecode behavior" would get an error diff --git a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java index 28373ad6bda..076d04632c9 100644 --- a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java +++ b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java @@ -268,7 +268,7 @@ public class VerifyAccess { * @param type the supposed type of a member or symbolic reference of refc * @param refc the class attempting to make the reference */ - public static boolean isTypeVisible(Class type, Class refc) { + public static boolean ensureTypeVisible(Class type, Class refc) { if (type == refc) { return true; // easy check } @@ -284,12 +284,14 @@ public class VerifyAccess { if (refcLoader == null && typeLoader != null) { return false; } - if (typeLoader == null && type.getName().startsWith("java.")) { - // Note: The API for actually loading classes, ClassLoader.defineClass, - // guarantees that classes with names beginning "java." cannot be aliased, - // because class loaders cannot load them directly. - return true; - } + + // The API for actually loading classes, ClassLoader.defineClass, + // guarantees that classes with names beginning "java." cannot be aliased, + // because class loaders cannot load them directly. However, it is beneficial + // for JIT-compilers to ensure all signature classes are loaded. + // JVM doesn't install any loader contraints when performing MemberName resolution, + // so eagerly resolving signature classes is a way to match what JVM achieves + // with loader constraints during method resolution for invoke bytecodes. // Do it the hard way: Look up the type name from the refc loader. // @@ -338,12 +340,12 @@ public class VerifyAccess { * @param type the supposed type of a member or symbolic reference of refc * @param refc the class attempting to make the reference */ - public static boolean isTypeVisible(java.lang.invoke.MethodType type, Class refc) { - if (!isTypeVisible(type.returnType(), refc)) { + public static boolean ensureTypeVisible(java.lang.invoke.MethodType type, Class refc) { + if (!ensureTypeVisible(type.returnType(), refc)) { return false; } for (int n = 0, max = type.parameterCount(); n < max; n++) { - if (!isTypeVisible(type.parameterType(n), refc)) { + if (!ensureTypeVisible(type.parameterType(n), refc)) { return false; } } diff --git a/test/hotspot/jtreg/compiler/runtime/unloaded/TestUnloadedSignatureClass.java b/test/hotspot/jtreg/compiler/runtime/unloaded/TestUnloadedSignatureClass.java new file mode 100644 index 00000000000..01982e4bda4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/runtime/unloaded/TestUnloadedSignatureClass.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, 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 + * @library /test/lib + * @run driver compiler.runtime.unloaded.TestUnloadedSignatureClass + */ + +package compiler.runtime.unloaded; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestUnloadedSignatureClass { + static class Test { + static int test(Integer i) { + // Bound to a wrapper around a method with (Ljava/lang/Object;ILjava/util/function/BiPredicate;Ljava/util/List;)I signature. + // Neither BiPredicate nor List are guaranteed to be resolved by the context class loader. + return switch (i) { + case null -> -1; + case 0 -> 0; + default -> 1; + }; + } + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test(i); + } + } + } + + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-Xbatch", "-XX:-TieredCompilation", + "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,*::test", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintCompilation", "-XX:+PrintInlining", + Test.class.getName() + ); + + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + out.shouldNotContain("unloaded signature classes"); + } +}