diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 45421457a633a..adb31cccd6150 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -105,7 +105,8 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre ClassLoaderData* loader_data = k->class_loader_data(); if (!SystemDictionaryShared::is_builtin_loader(loader_data)) { - if (cfs == NULL || strncmp(cfs->source(), "file:", 5) != 0) { + if (cfs == NULL || (strncmp(cfs->source(), "file:", 5) != 0 && + strncmp(cfs->source(), "jar:", 4) != 0)) { return; } if (!SystemDictionaryShared::add_unregistered_class(Thread::current(), (InstanceKlass*)k)) { @@ -155,13 +156,17 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre } } + if (strncmp(cfs->source(), "file:", 5) == 0) { #ifdef _WINDOWS - // "file:/C:/dir/foo.jar" -> "C:/dir/foo.jar" - stream->print(" source: %s", cfs->source() + 6); + // "file:/C:/dir/foo.jar" -> "C:/dir/foo.jar" + stream->print(" source: %s", cfs->source() + 6); #else - // "file:/dir/foo.jar" -> "/dir/foo.jar" - stream->print(" source: %s", cfs->source() + 5); + // "file:/dir/foo.jar" -> "/dir/foo.jar" + stream->print(" source: %s", cfs->source() + 5); #endif + } else { + stream->print(" source: %s", cfs->source()); + } } stream->cr(); diff --git a/src/hotspot/share/cds/unregisteredClasses.cpp b/src/hotspot/share/cds/unregisteredClasses.cpp index c17c35431096a..ee08fc62226ca 100644 --- a/src/hotspot/share/cds/unregisteredClasses.cpp +++ b/src/hotspot/share/cds/unregisteredClasses.cpp @@ -24,17 +24,12 @@ #include "precompiled.hpp" #include "cds/unregisteredClasses.hpp" -#include "classfile/classFileStream.hpp" -#include "classfile/classLoader.inline.hpp" -#include "classfile/classLoaderExt.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmSymbols.hpp" #include "memory/oopFactory.hpp" -#include "memory/resourceArea.hpp" #include "oops/instanceKlass.hpp" -#include "runtime/handles.hpp" #include "runtime/javaCalls.hpp" #include "services/threadService.hpp" @@ -51,65 +46,22 @@ InstanceKlass* UnregisteredClasses::load_class(Symbol* name, const char* path, T PerfClassTraceTime::CLASS_LOAD); } - Symbol* path_symbol = SymbolTable::new_symbol(path); - Handle url_classloader = get_url_classloader(path_symbol, CHECK_NULL); + Symbol* jar_util_name = vmSymbols::jdk_internal_misc_UberJarUtils(); + Klass* jar_util_klass = SystemDictionary::resolve_or_null(jar_util_name, THREAD); + guarantee(jar_util_klass != NULL, "jdk/internal/misc/UberJarUtils must exist!"); + + Symbol* method = vmSymbols::loadClass_name(); + Symbol* signature = vmSymbols::string_string_class_signature(); + Handle path_string = java_lang_String::create_from_str(path, CHECK_NULL); Handle ext_class_name = java_lang_String::externalize_classname(name, CHECK_NULL); JavaValue result(T_OBJECT); JavaCallArguments args(2); - args.set_receiver(url_classloader); + args.push_oop(path_string); args.push_oop(ext_class_name); - args.push_int(JNI_FALSE); - JavaCalls::call_virtual(&result, - vmClasses::URLClassLoader_klass(), - vmSymbols::loadClass_name(), - vmSymbols::string_boolean_class_signature(), - &args, - CHECK_NULL); + JavaCalls::call_static(&result, jar_util_klass, method, signature, &args, CHECK_NULL); + assert(result.get_type() == T_OBJECT, "just checking"); oop obj = result.get_oop(); return InstanceKlass::cast(java_lang_Class::as_Klass(obj)); } - -class URLClassLoaderTable : public ResourceHashtable< - Symbol*, Handle, - 137, // prime number - ResourceObj::C_HEAP> {}; - -static URLClassLoaderTable* _url_classloader_table = NULL; - -Handle UnregisteredClasses::create_url_classloader(Symbol* path, TRAPS) { - ResourceMark rm(THREAD); - JavaValue result(T_OBJECT); - Handle path_string = java_lang_String::create_from_str(path->as_C_string(), CHECK_NH); - JavaCalls::call_static(&result, - vmClasses::jdk_internal_loader_ClassLoaders_klass(), - vmSymbols::toFileURL_name(), - vmSymbols::toFileURL_signature(), - path_string, CHECK_NH); - assert(result.get_type() == T_OBJECT, "just checking"); - oop url_h = result.get_oop(); - objArrayHandle urls = oopFactory::new_objArray_handle(vmClasses::URL_klass(), 1, CHECK_NH); - urls->obj_at_put(0, url_h); - - Handle url_classloader = JavaCalls::construct_new_instance( - vmClasses::URLClassLoader_klass(), - vmSymbols::url_array_classloader_void_signature(), - urls, Handle(), CHECK_NH); - return url_classloader; -} - -Handle UnregisteredClasses::get_url_classloader(Symbol* path, TRAPS) { - if (_url_classloader_table == NULL) { - _url_classloader_table = new (ResourceObj::C_HEAP, mtClass)URLClassLoaderTable(); - } - Handle* url_classloader_ptr = _url_classloader_table->get(path); - if (url_classloader_ptr != NULL) { - return *url_classloader_ptr; - } else { - Handle url_classloader = create_url_classloader(path, CHECK_NH); - _url_classloader_table->put(path, url_classloader); - path->increment_refcount(); - return url_classloader; - } -} diff --git a/src/hotspot/share/cds/unregisteredClasses.hpp b/src/hotspot/share/cds/unregisteredClasses.hpp index a9f7dceaead62..e46bb4a9e770b 100644 --- a/src/hotspot/share/cds/unregisteredClasses.hpp +++ b/src/hotspot/share/cds/unregisteredClasses.hpp @@ -25,15 +25,11 @@ #ifndef SHARE_CDS_UNREGISTEREDCLASSES_HPP #define SHARE_CDS_UNREGISTEREDCLASSES_HPP -#include "runtime/handles.hpp" +#include "oops/instanceKlass.hpp" class UnregisteredClasses: AllStatic { public: - static InstanceKlass* load_class(Symbol* h_name, const char* path, TRAPS); - -private: - static Handle create_url_classloader(Symbol* path, TRAPS); - static Handle get_url_classloader(Symbol* path, TRAPS); + static InstanceKlass* load_class(Symbol* name, const char* path, TRAPS); }; #endif // SHARE_CDS_UNREGISTEREDCLASSES_HPP diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 357e5538809fb..a4f55641b51cf 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -135,7 +135,6 @@ /* support for CDS */ \ do_klass(ByteArrayInputStream_klass, java_io_ByteArrayInputStream ) \ do_klass(URL_klass, java_net_URL ) \ - do_klass(URLClassLoader_klass, java_net_URLClassLoader ) \ do_klass(Jar_Manifest_klass, java_util_jar_Manifest ) \ do_klass(jdk_internal_loader_BuiltinClassLoader_klass,jdk_internal_loader_BuiltinClassLoader ) \ do_klass(jdk_internal_loader_ClassLoaders_klass, jdk_internal_loader_ClassLoaders ) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 618b97472dced..42d276f32573a 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -553,6 +553,7 @@ template(threadgroup_string_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V") \ template(string_class_signature, "(Ljava/lang/String;)Ljava/lang/Class;") \ template(string_boolean_class_signature, "(Ljava/lang/String;Z)Ljava/lang/Class;") \ + template(string_string_class_signature, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;") \ template(object_object_object_signature, "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") \ template(string_string_string_signature, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") \ template(string_string_signature, "(Ljava/lang/String;)Ljava/lang/String;") \ @@ -706,12 +707,12 @@ template(java_lang_invoke_DelegatingMethodHandle_Holder, "java/lang/invoke/DelegatingMethodHandle$Holder") \ template(jdk_internal_loader_ClassLoaders, "jdk/internal/loader/ClassLoaders") \ template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \ + template(jdk_internal_misc_UberJarUtils, "jdk/internal/misc/UberJarUtils") \ template(java_util_concurrent_ConcurrentHashMap, "java/util/concurrent/ConcurrentHashMap") \ template(java_util_ArrayList, "java/util/ArrayList") \ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_void_signature, "(Ljava/net/URL;)V") \ - template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \ \ /*end*/ diff --git a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java index 75922c34e652c..cc15cd0220949 100644 --- a/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java +++ b/src/java.base/share/classes/jdk/internal/loader/ClassLoaders.java @@ -224,8 +224,7 @@ private void resetArchivedStates() { * * @apiNote This is called by the VM */ - @Deprecated - private static URL toFileURL(String s) { + public static URL toFileURL(String s) { try { // Use an intermediate File object to construct a URI/URL without // authority component as URLClassPath can't handle URLs with a UNC diff --git a/src/java.base/share/classes/jdk/internal/misc/AbstractJarReader.java b/src/java.base/share/classes/jdk/internal/misc/AbstractJarReader.java new file mode 100644 index 0000000000000..18bc760cd8650 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/AbstractJarReader.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, 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 jdk.internal.misc; + +import java.io.IOException; +import java.security.ProtectionDomain; + +/** + * This abstract class is a super class of EmbeddedJarReader and SimpleJarReader. + */ +abstract class AbstractJarReader extends ClassLoader { + Class loadClass(String name, ProtectionDomain pd) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + if (clazz == null) { + try { + String entryName = name.replace('.', '/') + ".class"; + byte[] bytes = getEntry(entryName); + clazz = super.defineClass(name, bytes, 0, bytes.length, pd); + } catch (Exception e) { + clazz = super.loadClass(name, false); + } + } + return clazz; + } + + abstract byte[] getEntry(String name) throws IOException, ClassNotFoundException; +} diff --git a/src/java.base/share/classes/jdk/internal/misc/EmbeddedJarReader.java b/src/java.base/share/classes/jdk/internal/misc/EmbeddedJarReader.java new file mode 100644 index 0000000000000..816f639b1b480 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/EmbeddedJarReader.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021, 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 jdk.internal.misc; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +/** + * This class is for retrieving the class bytes from the bytes of a jar file. + * It delegates to AbstractJarReader to load the class using the class bytes. + */ +class EmbeddedJarReader extends AbstractJarReader { + final static int BUFFER_SIZE = 4096; + String jarPath; + ZipInputStream zins; + HashMap entryCache; + + protected EmbeddedJarReader(String jarPath, byte[] jarBytes) { + this.jarPath = jarPath; + this.zins = new ZipInputStream(new ByteArrayInputStream(jarBytes)); + this.entryCache = new HashMap(); + } + + public Class loadClass(String name) throws ClassNotFoundException { + ProtectionDomain pd = null; + try { + URL u = new URL("file:" + jarPath); + CodeSource cs = new CodeSource(u, (CodeSigner[])null); + pd = new ProtectionDomain(cs, null); + } catch (MalformedURLException mue) { + // ignore MalformedURLException. The class can be loaded with null pd. + // The pd is for showing the "source:" in the class loading trace. + } + return super.loadClass(name, pd); + } + + @Override + byte[] getEntry(String name) throws IOException, ClassNotFoundException { + boolean found = false; + byte[] bytes = entryCache.get(name); + if (bytes != null) { + return bytes; + } else { + ZipEntry ze = null; + while ((ze = zins.getNextEntry()) != null) { + if (!ze.isDirectory()) { + bytes = readEntry(zins, ze); + String entryName = ze.getName(); + entryCache.put(entryName, bytes); + if (entryName.equals(name)) { + found = true; + break; + } + } + } + } + + if (found) { + return bytes; + } else { + throw new ClassNotFoundException(name); + } + } + + private static byte[] readEntry(InputStream in, ZipEntry entry) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + long size = entry.getSize(); + int nRead; + byte[] data = new byte[BUFFER_SIZE]; + while ((nRead = in.read(data, 0, data.length)) != -1) { + baos.write(data, 0, nRead); + } + baos.close(); + return baos.toByteArray(); + } +} diff --git a/src/java.base/share/classes/jdk/internal/misc/SimpleJarReader.java b/src/java.base/share/classes/jdk/internal/misc/SimpleJarReader.java new file mode 100644 index 0000000000000..7b1e0829a119a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/SimpleJarReader.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, 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 jdk.internal.misc; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +/** + * This class makes use of the URLClassLoader to retrieve the bytes of a + * class or jar file. + * It delegates to AbstractJarReader to load the class using the class bytes. + */ +class SimpleJarReader extends AbstractJarReader { + URLClassLoader loader; + URL url; + + protected SimpleJarReader(URL url) throws MalformedURLException { + loader = new URLClassLoader(new URL[]{url}); + this.url = url; + } + + public Class loadClass(String name) throws ClassNotFoundException { + CodeSource cs = new CodeSource(url, (CodeSigner[])null); + ProtectionDomain pd = new ProtectionDomain(cs, null); + return super.loadClass(name, pd); + } + + @Override + byte[] getEntry(String name) throws IOException { + byte[] bytes = null; + if (loader.findResource(name) != null) { + InputStream is = loader.getResourceAsStream(name); + bytes = new byte[is.available()]; + is.read(bytes); + return bytes; + } else { + return null; + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/misc/UberJarUtils.java b/src/java.base/share/classes/jdk/internal/misc/UberJarUtils.java new file mode 100644 index 0000000000000..07c7598f934f6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/UberJarUtils.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2021, 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 jdk.internal.misc; + +import java.io.File; +import java.io.IOException; +import java.lang.IllegalArgumentException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.WeakHashMap; +import jdk.internal.loader.ClassLoaders; + +/** + * This class is being used by VM during CDS static dump time for loading classes + * by custom loaders. + * It supports loading classes from source in the following format: + * 1. directory path, e.g. /some/dir + * 2. jar file, e.g. /some/file.jar + * 3. jar file with jar protocol, e.g. jar:file:/some/file.jar!/ + * 4. jar protocol with embedded jar, e.g. jar:file:/some/file.jar!/another/file2.jar!/ + * 5. jar protocol with embedded directory, e.g. jar:file:/some/file.jar!/another/dir!/ + */ +public class UberJarUtils { + final static String JAR_NAME_SEPARATOR = "!/"; + final static String JAR_SUFFIX = ".jar"; + final static String CLASS_SUFFIX = ".class"; + final static String JAR_PROTOCOL = "jar:file:"; + + /** + * Load a class based on the source and class name. + * This is being called from VM during CDS static dump time. + */ + public static Class loadClass(String source, String className) + throws IOException, ClassNotFoundException { + return getReader(source).loadClass(className); + } + + /** + * Check if the source contains an embedded jar file name, + * e.g. jar:file:/some/file.jar!/another/file2.jar!/ + */ + static boolean isEmbeddedJar(String source) { + if (source.indexOf(JAR_PROTOCOL) < 0) { + return false; + } + if (source.indexOf(JAR_NAME_SEPARATOR) < 0) { + return false; + } + if (source.endsWith(CLASS_SUFFIX + JAR_NAME_SEPARATOR)) { + return false; + } + if (source.length() - JAR_NAME_SEPARATOR.length() != source.lastIndexOf(JAR_NAME_SEPARATOR)) { + return false; + } + if (isSimpleJarURL(source) || isJarProtocolWithDirectory(source)) { + return false; + } + return true; + } + + static WeakHashMap readerCache = + new WeakHashMap(); + + /** + * Create an appropriate jar reader based on the source. + * The jar reader could be an EmbeddedJarReader or a SimpleJarReader. + */ + static AbstractJarReader getReader(String source) + throws IOException, ClassNotFoundException, IllegalArgumentException, MalformedURLException { + AbstractJarReader r = readerCache.get(source); + if (r != null) { + // this means the source has already been checked, no need to check it again + return r; + } + if (isEmbeddedJar(source)) { + // assume source = "jar:file:/some/path/outer.jar!/dir/inner.jar!/" + // String parentPath = "jar:file:/some/path/outer.jar!/"; + // String subPath = "dir/inner.jar"; + String[] urls = source.split("!"); + int endIndex = source.indexOf(urls[urls.length - 2]); + String parentPath = source.substring(0, endIndex + 1); + String subPath = urls[urls.length - 2].substring(1); + AbstractJarReader parentReader = getReader(parentPath); + byte[] jarBytes = parentReader.getEntry(subPath); + + r = new EmbeddedJarReader(subPath, jarBytes); + } else { + String u = source; + URL url; + if (isJarProtocolWithDirectory(source)) { + // Remove the last '!' and end the url with '/'. + // e.g source = "jar:file:test.jar!/a/dir!/ + // url = "jar:file:test.jar!/a/dir/ + u = source.substring(0, source.lastIndexOf('!')) + "/"; + url = new URL(u); + } else if (isSimpleJarURL(source)) { + url = new URL(u); + } else { + File f = new File(source); + if (f.exists()) { + // The source of CDS unregister classes can be either a dir or a jar file. + url = ClassLoaders.toFileURL(source); + } else { + throw new IllegalArgumentException("unsupported source: " + source); + } + } + r = new SimpleJarReader(url); + } + readerCache.put(source, r); + return r; + } + + /** + * Check if the source is a simple jar URL. + * e.g. jar:file:/some/file.jar!/ + */ + static boolean isSimpleJarURL(final String source) { + if (!source.endsWith(JAR_SUFFIX + JAR_NAME_SEPARATOR)) { + return false; + } + int firstIndex = source.indexOf(JAR_NAME_SEPARATOR); + return firstIndex >= 0 && firstIndex == source.lastIndexOf(JAR_NAME_SEPARATOR); + } + + /** + * Check if the source is a directory within a jar protocol. + * e.g. jar:file:/some/file.jar!/another/dir!/ + */ + static boolean isJarProtocolWithDirectory(final String source) { + return source.startsWith(JAR_PROTOCOL) && + source.endsWith(JAR_NAME_SEPARATOR) && + !source.endsWith(JAR_SUFFIX + JAR_NAME_SEPARATOR) && + !source.endsWith(CLASS_SUFFIX + JAR_NAME_SEPARATOR); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/JarBuilder.java b/test/hotspot/jtreg/runtime/cds/appcds/JarBuilder.java index cd56e43413d6f..a3b3955dad2d7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/JarBuilder.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/JarBuilder.java @@ -99,22 +99,28 @@ public static String build(boolean classesInWorkDir, String jarName, String ...c } } - public static String buildWithManifest(String jarName, String manifest, String jarClassesDir, String ...classNames) throws Exception { + return buildWithManifest(jarName, manifest, null, jarClassesDir, classNames); + } + + public static String buildWithManifest(String jarName, String manifest, + String embeddedJar, String jarClassesDir, String ...classNames) throws Exception { String jarPath = getJarFilePath(jarName); ArrayList args = new ArrayList(); args.add("cvfm"); args.add(jarPath); args.add(System.getProperty("test.src") + File.separator + "test-classes" + File.separator + manifest); + if (embeddedJar != null) { + args.add(embeddedJar); + } addClassArgs(args, jarClassesDir, classNames); createJar(args); return jarPath; } - // Execute: jar uvf $jarFile -C $dir . static void update(String jarFile, String dir) throws Exception { String jarExe = JDKToolFinder.getJDKTool("jar"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/UberJarTest.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/UberJarTest.java new file mode 100644 index 0000000000000..f513dd521540e --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/UberJarTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021, 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 + * @summary Test handling of uber jar. + * @requires vm.cds + * @requires vm.cds.custom.loaders + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile test-classes/C2.java + * @run driver UberJarTest + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.StandardCopyOption; +import java.nio.file.Paths; + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class UberJarTest { + static String subDir = null; // set in buildUberJar + static final String subDirName = "subDir"; + static final String embeddedJarName = "x2.jar"; + + public static void main(String[] args) throws Exception { + JarBuilder.build("x2", "C2"); + String embeddedJar = TestCommon.getTestJar(embeddedJarName); + buildUberJar("uber_jar_test", "MainClass.mf", embeddedJarName, "C1", "SimpleHello"); + String uberJar = ClassFileInstaller.getJarPath("uber_jar_test.jar"); + + // test various source format with jar protocol + String[] classlist = new String[] { + "java/lang/Object id: 1", + "C1 id: 2 super: 1 source: jar:file:" + uberJar + "!/", + "C2 id: 3 super: 1 source: jar:file:" + uberJar + "!/" + embeddedJarName + "!/", + "SimpleHello id: 4 super: 1 source: jar:file:" + uberJar + "!/" + subDirName + "!/" + }; + + testPositive(uberJar, classlist); + + // test directory path in "source:" + classlist[3] = "SimpleHello id: 4 super: 1 source: " + subDir; + + testPositive(uberJar, classlist); + + // test various unsupported source format + String[] badSource = new String[] { + "C1 id: 2 super: 1 source: file:" + uberJar, + "C1 id: 2 super: 1 source: jar:file:" + uberJar, + "C1 id: 2 super: 1 source: jar:file:" + uberJar + "/", + "C1 id: 2 super: 1 source: jar:file:" + uberJar + "!/" + subDirName + "/", + "C1 id: 2 super: 1 source: jar:file:" + uberJar + "!/C1.class/" + }; + + testNegative(uberJar, classlist, badSource); + } + + private static void testPositive(String uberJar, String[] classlist) throws Exception { + + String addExports = "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED"; + + OutputAnalyzer output; + output = TestCommon.testDump(null, classlist, "-Xlog:class+load", "-jar", uberJar); + + output = TestCommon.exec(null, "-Xlog:class+load", addExports, "-jar", uberJar, uberJar); + TestCommon.checkExecReturn(output, 0, true /* should contain */, + "C2 source: shared objects file", + "C2: here I am", + "SimpleHello source: shared objects file"); + } + + private static void testNegative(String uberJar, String[] classlist, String[] badSource) throws Exception { + for (String entry : badSource) { + classlist[1] = entry; + OutputAnalyzer output = TestCommon.testDump(null, classlist, "-Xlog:class+load", "-jar", uberJar); + String source = entry.substring(entry.indexOf("source:") + 8); + TestCommon.checkExecReturn(output, 0, true /* should contain */, + "java.lang.IllegalArgumentException: unsupported source: " + source, + "Preload Warning: Cannot find C1"); + } + } + + private static void buildUberJar(String jarName, String manifest, String embeddedJar, + String mainClassName, String className) throws Exception { + String jarClassesDir = CDSTestUtils.getOutputDir() + File.separator + jarName + "_classes"; + try { Files.createDirectory(Paths.get(jarClassesDir)); } catch (FileAlreadyExistsException e) { } + + JarBuilder.compile(jarClassesDir, System.getProperty("test.src") + File.separator + + "test-classes" + File.separator + mainClassName + ".java"); + + String dirName = subDirName; + subDir = jarClassesDir + File.separator + dirName; + try { Files.createDirectory(Paths.get(subDir)); } catch (FileAlreadyExistsException e) { } + + JarBuilder.compile(subDir, System.getProperty("test.src") + File.separator + + "test-classes" + File.separator + className + ".java"); + + String[] testClassNames = {mainClassName, dirName + File.separator + className}; + + JarBuilder.buildWithManifest(jarName, manifest, embeddedJar, jarClassesDir, testClassNames); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C1.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C1.java new file mode 100644 index 0000000000000..b1d703577f79b --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C1.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, 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. + * + */ +import java.lang.reflect.*; + +/** + * Helper class used by the UberJarTest for loading various classes using + * the UberJarUtils. + */ +public class C1 { + public static void main(String args[]) throws Throwable { + String source = "none"; + if (args.length == 1) { + source = args[0]; + } + + // Find the UberJarUtils class and its loadClass method. + Class utilCls = Class.forName("jdk.internal.misc.UberJarUtils"); + Method utilMth = utilCls.getMethod("loadClass", String.class, String.class); + + // Call UberJarUtils.loadClass reflectively to load C2. + System.out.println("C1: getting C2"); + String[] inputArgs = {"jar:file:" + source + "!/x2.jar!/", "C2"}; + Object o = utilMth.invoke(null, inputArgs); + Class cls = (Class)o; + + // Invoke C2's main reflectively. + System.out.println("C1: invoking C2"); + Method mth = cls.getMethod("main",new Class[]{String[].class}); + mth.invoke(null,new Object[]{args}); + + // Call UberJarUtils.loadClass reflectively to load SimpleHello. + System.out.println("C1: getting SimpleHello"); + String[] inputArgs2 = {"jar:file:" + source + "!/subDir!/", "SimpleHello"}; + o = utilMth.invoke(null, inputArgs2); + cls = (Class)o; + + // Invoke SimpleHello's main reflectively. + System.out.println("C1: invoking SimpleHello"); + mth = cls.getMethod("main",new Class[]{String[].class}); + mth.invoke(null,new Object[]{args}); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C2.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C2.java new file mode 100644 index 0000000000000..a5f15af903e3d --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/C2.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, 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. + * + */ +public class C2 { + public static void main(String args[]) throws Throwable { + System.out.println("C2: here I am"); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/MainClass.mf b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/MainClass.mf new file mode 100644 index 0000000000000..94ce94311c8cb --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/test-classes/MainClass.mf @@ -0,0 +1 @@ +Main-Class: C1