diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk index 974ab77cb7b..6797952bc05 100644 --- a/make/test/JtregNativeJdk.gmk +++ b/make/test/JtregNativeJdk.gmk @@ -62,6 +62,7 @@ ifeq ($(call isTargetOs, windows), true) WIN_LIB_JLI := $(SUPPORT_OUTPUTDIR)/native/java.base/libjli/jli.lib BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeJliLaunchTest := $(WIN_LIB_JLI) BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeCallerAccessTest := jvm.lib + BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerClassLoaderTest := jvm.lib BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerLookupTest := jvm.lib BUILD_JDK_JTREG_EXECUTABLES_LIBS_exerevokeall := advapi32.lib BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libAsyncStackWalk := /EHsc @@ -81,6 +82,7 @@ else endif BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeJliLaunchTest := -ljli BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeCallerAccessTest := -ljvm + BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerClassLoaderTest := -ljvm BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerLookupTest := -ljvm endif diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index ed5ad09da44..0aeb7620189 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -1606,9 +1606,16 @@ public abstract class ClassLoader { * *

Note that once a class loader is registered as parallel capable, there * is no way to change it back.

+ *

+ * In cases where this method is called from a context where the caller is + * not a subclass of {@code ClassLoader} or there is no caller frame on the + * stack (e.g. when called directly from a JNI attached thread), + * {@code IllegalCallerException} is thrown. + *

* * @return {@code true} if the caller is successfully registered as * parallel capable and {@code false} if otherwise. + * @throws IllegalCallerException if the caller is not a subclass of {@code ClassLoader} * * @see #isRegisteredAsParallelCapable() * @@ -1622,6 +1629,9 @@ public abstract class ClassLoader { // Caller-sensitive adapter method for reflective invocation @CallerSensitiveAdapter private static boolean registerAsParallelCapable(Class caller) { + if ((caller == null) || !ClassLoader.class.isAssignableFrom(caller)) { + throw new IllegalCallerException(caller + " not a subclass of ClassLoader"); + } return ParallelLoaders.register(caller.asSubclass(ClassLoader.class)); } diff --git a/test/jdk/java/lang/ClassLoader/BadRegisterAsParallelCapableCaller.java b/test/jdk/java/lang/ClassLoader/BadRegisterAsParallelCapableCaller.java new file mode 100644 index 00000000000..8b1c8d24d5e --- /dev/null +++ b/test/jdk/java/lang/ClassLoader/BadRegisterAsParallelCapableCaller.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, 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 8281000 + * @summary Test ClassLoader.isRegisteredAsParallelCapable() method with an + * invalid caller (non-JNI cases). This test uses reflection and opens + * the java.base module so it runs separate from behavior tests of + * isRegisteredParallelCapable to avoid side effects. + * @modules java.base/java.lang:open + * @run main/othervm BadRegisterAsParallelCapableCaller + */ + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class BadRegisterAsParallelCapableCaller { + + public static void main(String[] args) throws Exception { + + Throwable thrown = null; + final Method m = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable"); + m.setAccessible(true); + try { + m.invoke(null); + } catch (InvocationTargetException ite) { + thrown = ite.getCause(); + } + if (! (thrown instanceof IllegalCallerException)) { + throw new RuntimeException("Didn't get expected IllegalCallerException, got "+ thrown); + } else { + System.out.println("Invalid caller threw IllegalCallerException as expected"); + } + } + +} diff --git a/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/NullCallerClassLoaderTest.java b/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/NullCallerClassLoaderTest.java new file mode 100644 index 00000000000..52427f01e6e --- /dev/null +++ b/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/NullCallerClassLoaderTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, 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 8281000 + * @summary Test uses custom launcher that starts VM using JNI that verifies + * ClassLoader.registerAsParallelCapable with null caller class + * throws an IllegalCallerException. + * @library /test/lib + * @requires os.family != "aix" + * @run main/native NullCallerClassLoaderTest + */ + +// Test disabled on AIX since we cannot invoke the JVM on the primordial thread. + +import java.io.File; +import java.util.Map; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class NullCallerClassLoaderTest { + public static void main(String[] args) throws IOException { + Path launcher = Path.of(System.getProperty("test.nativepath"), "NullCallerClassLoaderTest"); + ProcessBuilder pb = new ProcessBuilder(launcher.toString()); + Map env = pb.environment(); + + String libDir = Platform.libDir().toString(); + String vmDir = Platform.jvmLibDir().toString(); + + // set up shared library path + String sharedLibraryPathEnvName = Platform.sharedLibraryPathVariableName(); + env.compute(sharedLibraryPathEnvName, + (k, v) -> (v == null) ? libDir : v + File.pathSeparator + libDir); + env.compute(sharedLibraryPathEnvName, + (k, v) -> (v == null) ? vmDir : v + File.pathSeparator + vmDir); + + System.out.println("Launching: " + launcher + " shared library path: " + + env.get(sharedLibraryPathEnvName)); + new OutputAnalyzer(pb.start()) + .outputTo(System.out) + .errorTo(System.err) + .shouldHaveExitValue(0); + } +} + diff --git a/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/exeNullCallerClassLoaderTest.c b/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/exeNullCallerClassLoaderTest.c new file mode 100644 index 00000000000..d488c74bf6c --- /dev/null +++ b/test/jdk/java/lang/ClassLoader/exeNullCallerClassLoaderTest/exeNullCallerClassLoaderTest.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022, 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. + */ + +#include +#include + +#include "jni.h" +#include "assert.h" + +static jclass class_IllegalCallerException; + +int checkAndClearIllegalCallerExceptionThrown(JNIEnv *env) { + jthrowable t = (*env)->ExceptionOccurred(env); + if ((*env)->IsInstanceOf(env, t, class_IllegalCallerException) == JNI_TRUE) { + (*env)->ExceptionClear(env); + return JNI_TRUE; + } + return JNI_FALSE; +} + +int main(int argc, char** args) { + JavaVM *jvm; + JNIEnv *env; + JavaVMInitArgs vm_args; + JavaVMOption options[1]; + jint rc; + + + vm_args.version = JNI_VERSION_1_2; + vm_args.nOptions = 0; + vm_args.options = options; + + if ((rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)) != JNI_OK) { + printf("ERROR: cannot create VM.\n"); + exit(-1); + } + class_IllegalCallerException = (*env)->FindClass(env, "java/lang/IllegalCallerException"); + assert (class_IllegalCallerException != NULL); + + // call ClassLoader.registerAsParallelCapable() + jclass class_ClassLoader = (*env)->FindClass(env, "java/lang/ClassLoader"); + assert(class_ClassLoader != NULL); + jmethodID mid_ClassLoader_registerAsParallelCapable = (*env)->GetStaticMethodID(env, class_ClassLoader, "registerAsParallelCapable", "()Z" ); + assert(mid_ClassLoader_registerAsParallelCapable != NULL); + jboolean b = (*env)->CallStaticBooleanMethod(env, class_ClassLoader, mid_ClassLoader_registerAsParallelCapable ); + if ((rc = checkAndClearIllegalCallerExceptionThrown(env)) != JNI_TRUE) { + printf("ERROR: Didn't get the expected IllegalCallerException.\n"); + exit(-1); + } + + printf("Expected IllegalCallerException was thrown\n"); + + (*jvm)->DestroyJavaVM(jvm); + return 0; +} +