| /* |
| * Copyright (c) 2015, 2016, 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * 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.loader; |
| |
| import java.io.File; |
| import java.io.FilePermission; |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.lang.module.Configuration; |
| import java.lang.module.ModuleDescriptor; |
| import java.lang.module.ModuleReader; |
| import java.lang.module.ModuleReference; |
| import java.lang.module.ResolvedModule; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.nio.ByteBuffer; |
| import java.security.AccessControlContext; |
| import java.security.AccessController; |
| import java.security.CodeSigner; |
| import java.security.CodeSource; |
| import java.security.Permission; |
| import java.security.PermissionCollection; |
| import java.security.PrivilegedAction; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.security.SecureClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Spliterator; |
| import java.util.Spliterators; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.function.Supplier; |
| import java.util.stream.Stream; |
| import java.util.stream.StreamSupport; |
| |
| import jdk.internal.misc.SharedSecrets; |
| import jdk.internal.module.Resources; |
| |
| |
| /** |
| * A class loader that loads classes and resources from a collection of |
| * modules, or from a single module where the class loader is a member |
| * of a pool of class loaders. |
| * |
| * <p> The delegation model used by this ClassLoader differs to the regular |
| * delegation model. When requested to load a class then this ClassLoader first |
| * maps the class name to its package name. If there a module defined to the |
| * Loader containing the package then the class loader attempts to load from |
| * that module. If the package is instead defined to a module in a "remote" |
| * ClassLoader then this class loader delegates directly to that class loader. |
| * The map of package name to remote class loader is created based on the |
| * modules read by modules defined to this class loader. If the package is not |
| * local or remote then this class loader will delegate to the parent class |
| * loader. This allows automatic modules (for example) to link to types in the |
| * unnamed module of the parent class loader. |
| * |
| * @see ModuleLayer#defineModulesWithOneLoader |
| * @see ModuleLayer#defineModulesWithManyLoaders |
| */ |
| |
| public final class Loader extends SecureClassLoader { |
| |
| static { |
| ClassLoader.registerAsParallelCapable(); |
| } |
| |
| // the loader pool is in a pool, can be null |
| private final LoaderPool pool; |
| |
| // parent ClassLoader, can be null |
| private final ClassLoader parent; |
| |
| // maps a module name to a module reference |
| private final Map<String, ModuleReference> nameToModule; |
| |
| // maps package name to a module loaded by this class loader |
| private final Map<String, LoadedModule> localPackageToModule; |
| |
| // maps package name to a remote class loader, populated post initialization |
| private final Map<String, ClassLoader> remotePackageToLoader |
| = new ConcurrentHashMap<>(); |
| |
| // maps a module reference to a module reader, populated lazily |
| private final Map<ModuleReference, ModuleReader> moduleToReader |
| = new ConcurrentHashMap<>(); |
| |
| // ACC used when loading classes and resources */ |
| private final AccessControlContext acc; |
| |
| /** |
| * A module defined/loaded to a {@code Loader}. |
| */ |
| private static class LoadedModule { |
| private final ModuleReference mref; |
| private final URL url; // may be null |
| private final CodeSource cs; |
| |
| LoadedModule(ModuleReference mref) { |
| URL url = null; |
| if (mref.location().isPresent()) { |
| try { |
| url = mref.location().get().toURL(); |
| } catch (MalformedURLException | IllegalArgumentException e) { } |
| } |
| this.mref = mref; |
| this.url = url; |
| this.cs = new CodeSource(url, (CodeSigner[]) null); |
| } |
| |
| ModuleReference mref() { return mref; } |
| String name() { return mref.descriptor().name(); } |
| URL location() { return url; } |
| CodeSource codeSource() { return cs; } |
| } |
| |
| |
| /** |
| * Creates a {@code Loader} in a loader pool that loads classes/resources |
| * from one module. |
| */ |
| public Loader(ResolvedModule resolvedModule, |
| LoaderPool pool, |
| ClassLoader parent) |
| { |
| super("Loader-" + resolvedModule.name(), parent); |
| |
| this.pool = pool; |
| this.parent = parent; |
| |
| ModuleReference mref = resolvedModule.reference(); |
| ModuleDescriptor descriptor = mref.descriptor(); |
| String mn = descriptor.name(); |
| this.nameToModule = Map.of(mn, mref); |
| |
| Map<String, LoadedModule> localPackageToModule = new HashMap<>(); |
| LoadedModule lm = new LoadedModule(mref); |
| descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm)); |
| this.localPackageToModule = localPackageToModule; |
| |
| this.acc = AccessController.getContext(); |
| } |
| |
| /** |
| * Creates a {@code Loader} that loads classes/resources from a collection |
| * of modules. |
| * |
| * @throws IllegalArgumentException |
| * If two or more modules have the same package |
| */ |
| public Loader(Collection<ResolvedModule> modules, ClassLoader parent) { |
| super(parent); |
| |
| this.pool = null; |
| this.parent = parent; |
| |
| Map<String, ModuleReference> nameToModule = new HashMap<>(); |
| Map<String, LoadedModule> localPackageToModule = new HashMap<>(); |
| for (ResolvedModule resolvedModule : modules) { |
| ModuleReference mref = resolvedModule.reference(); |
| ModuleDescriptor descriptor = mref.descriptor(); |
| nameToModule.put(descriptor.name(), mref); |
| descriptor.packages().forEach(pn -> { |
| LoadedModule lm = new LoadedModule(mref); |
| if (localPackageToModule.put(pn, lm) != null) |
| throw new IllegalArgumentException("Package " |
| + pn + " in more than one module"); |
| }); |
| } |
| this.nameToModule = nameToModule; |
| this.localPackageToModule = localPackageToModule; |
| |
| this.acc = AccessController.getContext(); |
| } |
| |
| |
| /** |
| * Completes initialization of this Loader. This method populates |
| * remotePackageToLoader with the packages of the remote modules, where |
| * "remote modules" are the modules read by modules defined to this loader. |
| * |
| * @param cf the Configuration containing at least modules to be defined to |
| * this class loader |
| * |
| * @param parentModuleLayers the parent ModuleLayers |
| */ |
| public Loader initRemotePackageMap(Configuration cf, |
| List<ModuleLayer> parentModuleLayers) |
| { |
| for (String name : nameToModule.keySet()) { |
| ResolvedModule resolvedModule = cf.findModule(name).get(); |
| assert resolvedModule.configuration() == cf; |
| |
| for (ResolvedModule other : resolvedModule.reads()) { |
| String mn = other.name(); |
| ClassLoader loader; |
| |
| if (other.configuration() == cf) { |
| |
| // The module reads another module in the newly created |
| // layer. If all modules are defined to the same class |
| // loader then the packages are local. |
| if (pool == null) { |
| assert nameToModule.containsKey(mn); |
| continue; |
| } |
| |
| loader = pool.loaderFor(mn); |
| assert loader != null; |
| |
| } else { |
| |
| // find the layer for the target module |
| ModuleLayer layer = parentModuleLayers.stream() |
| .map(parent -> findModuleLayer(parent, other.configuration())) |
| .flatMap(Optional::stream) |
| .findAny() |
| .orElseThrow(() -> |
| new InternalError("Unable to find parent layer")); |
| |
| // find the class loader for the module |
| // For now we use the platform loader for modules defined to the |
| // boot loader |
| assert layer.findModule(mn).isPresent(); |
| loader = layer.findLoader(mn); |
| if (loader == null) |
| loader = ClassLoaders.platformClassLoader(); |
| } |
| |
| // find the packages that are exported to the target module |
| String target = resolvedModule.name(); |
| ModuleDescriptor descriptor = other.reference().descriptor(); |
| for (ModuleDescriptor.Exports e : descriptor.exports()) { |
| boolean delegate; |
| if (e.isQualified()) { |
| // qualified export in same configuration |
| delegate = (other.configuration() == cf) |
| && e.targets().contains(target); |
| } else { |
| // unqualified |
| delegate = true; |
| } |
| |
| if (delegate) { |
| String pn = e.source(); |
| ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader); |
| if (l != null && l != loader) { |
| throw new IllegalArgumentException("Package " |
| + pn + " cannot be imported from multiple loaders"); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Find the layer corresponding to the given configuration in the tree |
| * of layers rooted at the given parent. |
| */ |
| private Optional<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) { |
| return SharedSecrets.getJavaLangAccess().layers(parent) |
| .filter(l -> l.configuration() == cf) |
| .findAny(); |
| } |
| |
| |
| /** |
| * Returns the loader pool that this loader is in or {@code null} if this |
| * loader is not in a loader pool. |
| */ |
| public LoaderPool pool() { |
| return pool; |
| } |
| |
| |
| // -- resources -- |
| |
| /** |
| * Returns a URL to a resource of the given name in a module defined to |
| * this class loader. |
| */ |
| @Override |
| protected URL findResource(String mn, String name) throws IOException { |
| ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null; |
| if (mref == null) |
| return null; // not defined to this class loader |
| |
| // locate resource |
| URL url = null; |
| try { |
| url = AccessController.doPrivileged( |
| new PrivilegedExceptionAction<URL>() { |
| @Override |
| public URL run() throws IOException { |
| Optional<URI> ouri = moduleReaderFor(mref).find(name); |
| if (ouri.isPresent()) { |
| try { |
| return ouri.get().toURL(); |
| } catch (MalformedURLException | |
| IllegalArgumentException e) { } |
| } |
| return null; |
| } |
| }); |
| } catch (PrivilegedActionException pae) { |
| throw (IOException) pae.getCause(); |
| } |
| |
| // check access with permissions restricted by ACC |
| if (url != null && System.getSecurityManager() != null) { |
| try { |
| URL urlToCheck = url; |
| url = AccessController.doPrivileged( |
| new PrivilegedExceptionAction<URL>() { |
| @Override |
| public URL run() throws IOException { |
| return URLClassPath.checkURL(urlToCheck); |
| } |
| }, acc); |
| } catch (PrivilegedActionException pae) { |
| url = null; |
| } |
| } |
| |
| return url; |
| } |
| |
| @Override |
| public URL findResource(String name) { |
| String pn = Resources.toPackageName(name); |
| LoadedModule module = localPackageToModule.get(pn); |
| |
| if (module != null) { |
| try { |
| URL url = findResource(module.name(), name); |
| if (url != null |
| && (name.endsWith(".class") |
| || url.toString().endsWith("/") |
| || isOpen(module.mref(), pn))) { |
| return url; |
| } |
| } catch (IOException ioe) { |
| // ignore |
| } |
| |
| } else { |
| for (ModuleReference mref : nameToModule.values()) { |
| try { |
| URL url = findResource(mref.descriptor().name(), name); |
| if (url != null) return url; |
| } catch (IOException ioe) { |
| // ignore |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Enumeration<URL> findResources(String name) throws IOException { |
| return Collections.enumeration(findResourcesAsList(name)); |
| } |
| |
| @Override |
| public URL getResource(String name) { |
| Objects.requireNonNull(name); |
| |
| // this loader |
| URL url = findResource(name); |
| if (url != null) { |
| return url; |
| } else { |
| // parent loader |
| return parent.getResource(name); |
| } |
| } |
| |
| @Override |
| public Enumeration<URL> getResources(String name) throws IOException { |
| Objects.requireNonNull(name); |
| |
| // this loader |
| List<URL> urls = findResourcesAsList(name); |
| |
| // parent loader |
| Enumeration<URL> e = parent.getResources(name); |
| |
| // concat the URLs with the URLs returned by the parent |
| return new Enumeration<>() { |
| final Iterator<URL> iterator = urls.iterator(); |
| @Override |
| public boolean hasMoreElements() { |
| return (iterator.hasNext() || e.hasMoreElements()); |
| } |
| @Override |
| public URL nextElement() { |
| if (iterator.hasNext()) { |
| return iterator.next(); |
| } else { |
| return e.nextElement(); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public Stream<URL> resources(String name) { |
| Objects.requireNonNull(name); |
| // ordering not specified |
| int characteristics = (Spliterator.NONNULL | Spliterator.IMMUTABLE | |
| Spliterator.SIZED | Spliterator.SUBSIZED); |
| Supplier<Spliterator<URL>> supplier = () -> { |
| try { |
| List<URL> urls = findResourcesAsList(name); |
| return Spliterators.spliterator(urls, characteristics); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| }; |
| Stream<URL> s1 = StreamSupport.stream(supplier, characteristics, false); |
| Stream<URL> s2 = parent.resources(name); |
| return Stream.concat(s1, s2); |
| } |
| |
| /** |
| * Finds the resources with the given name in this class loader. |
| */ |
| private List<URL> findResourcesAsList(String name) throws IOException { |
| String pn = Resources.toPackageName(name); |
| LoadedModule module = localPackageToModule.get(pn); |
| if (module != null) { |
| URL url = findResource(module.name(), name); |
| if (url != null |
| && (name.endsWith(".class") |
| || url.toString().endsWith("/") |
| || isOpen(module.mref(), pn))) { |
| return List.of(url); |
| } else { |
| return Collections.emptyList(); |
| } |
| } else { |
| List<URL> urls = new ArrayList<>(); |
| for (ModuleReference mref : nameToModule.values()) { |
| URL url = findResource(mref.descriptor().name(), name); |
| if (url != null) { |
| urls.add(url); |
| } |
| } |
| return urls; |
| } |
| } |
| |
| |
| // -- finding/loading classes |
| |
| /** |
| * Finds the class with the specified binary name. |
| */ |
| @Override |
| protected Class<?> findClass(String cn) throws ClassNotFoundException { |
| Class<?> c = null; |
| LoadedModule loadedModule = findLoadedModule(cn); |
| if (loadedModule != null) |
| c = findClassInModuleOrNull(loadedModule, cn); |
| if (c == null) |
| throw new ClassNotFoundException(cn); |
| return c; |
| } |
| |
| /** |
| * Finds the class with the specified binary name in a given module. |
| * This method returns {@code null} if the class cannot be found. |
| */ |
| @Override |
| protected Class<?> findClass(String mn, String cn) { |
| Class<?> c = null; |
| LoadedModule loadedModule = findLoadedModule(cn); |
| if (loadedModule != null && loadedModule.name().equals(mn)) |
| c = findClassInModuleOrNull(loadedModule, cn); |
| return c; |
| } |
| |
| /** |
| * Loads the class with the specified binary name. |
| */ |
| @Override |
| protected Class<?> loadClass(String cn, boolean resolve) |
| throws ClassNotFoundException |
| { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| String pn = packageName(cn); |
| if (!pn.isEmpty()) { |
| sm.checkPackageAccess(pn); |
| } |
| } |
| |
| synchronized (getClassLoadingLock(cn)) { |
| // check if already loaded |
| Class<?> c = findLoadedClass(cn); |
| |
| if (c == null) { |
| |
| LoadedModule loadedModule = findLoadedModule(cn); |
| |
| if (loadedModule != null) { |
| |
| // class is in module defined to this class loader |
| c = findClassInModuleOrNull(loadedModule, cn); |
| |
| } else { |
| |
| // type in another module or visible via the parent loader |
| String pn = packageName(cn); |
| ClassLoader loader = remotePackageToLoader.get(pn); |
| if (loader == null) { |
| // type not in a module read by any of the modules |
| // defined to this loader, so delegate to parent |
| // class loader |
| loader = parent; |
| } |
| if (loader == null) { |
| c = BootLoader.loadClassOrNull(cn); |
| } else { |
| c = loader.loadClass(cn); |
| } |
| |
| } |
| } |
| |
| if (c == null) |
| throw new ClassNotFoundException(cn); |
| |
| if (resolve) |
| resolveClass(c); |
| |
| return c; |
| } |
| } |
| |
| |
| /** |
| * Finds the class with the specified binary name if in a module |
| * defined to this ClassLoader. |
| * |
| * @return the resulting Class or {@code null} if not found |
| */ |
| private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) { |
| PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule); |
| return AccessController.doPrivileged(pa, acc); |
| } |
| |
| /** |
| * Defines the given binary class name to the VM, loading the class |
| * bytes from the given module. |
| * |
| * @return the resulting Class or {@code null} if an I/O error occurs |
| */ |
| private Class<?> defineClass(String cn, LoadedModule loadedModule) { |
| ModuleReader reader = moduleReaderFor(loadedModule.mref()); |
| |
| try { |
| // read class file |
| String rn = cn.replace('.', '/').concat(".class"); |
| ByteBuffer bb = reader.read(rn).orElse(null); |
| if (bb == null) { |
| // class not found |
| return null; |
| } |
| |
| try { |
| return defineClass(cn, bb, loadedModule.codeSource()); |
| } finally { |
| reader.release(bb); |
| } |
| |
| } catch (IOException ioe) { |
| // TBD on how I/O errors should be propagated |
| return null; |
| } |
| } |
| |
| |
| // -- permissions |
| |
| /** |
| * Returns the permissions for the given CodeSource. |
| */ |
| @Override |
| protected PermissionCollection getPermissions(CodeSource cs) { |
| PermissionCollection perms = super.getPermissions(cs); |
| |
| URL url = cs.getLocation(); |
| if (url == null) |
| return perms; |
| |
| // add the permission to access the resource |
| try { |
| Permission p = url.openConnection().getPermission(); |
| if (p != null) { |
| // for directories then need recursive access |
| if (p instanceof FilePermission) { |
| String path = p.getName(); |
| if (path.endsWith(File.separator)) { |
| path += "-"; |
| p = new FilePermission(path, "read"); |
| } |
| } |
| perms.add(p); |
| } |
| } catch (IOException ioe) { } |
| |
| return perms; |
| } |
| |
| |
| // -- miscellaneous supporting methods |
| |
| /** |
| * Find the candidate module for the given class name. |
| * Returns {@code null} if none of the modules defined to this |
| * class loader contain the API package for the class. |
| */ |
| private LoadedModule findLoadedModule(String cn) { |
| String pn = packageName(cn); |
| return pn.isEmpty() ? null : localPackageToModule.get(pn); |
| } |
| |
| /** |
| * Returns the package name for the given class name |
| */ |
| private String packageName(String cn) { |
| int pos = cn.lastIndexOf('.'); |
| return (pos < 0) ? "" : cn.substring(0, pos); |
| } |
| |
| |
| /** |
| * Returns the ModuleReader for the given module. |
| */ |
| private ModuleReader moduleReaderFor(ModuleReference mref) { |
| return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref)); |
| } |
| |
| /** |
| * Creates a ModuleReader for the given module. |
| */ |
| private ModuleReader createModuleReader(ModuleReference mref) { |
| try { |
| return mref.open(); |
| } catch (IOException e) { |
| // Return a null module reader to avoid a future class load |
| // attempting to open the module again. |
| return new NullModuleReader(); |
| } |
| } |
| |
| /** |
| * A ModuleReader that doesn't read any resources. |
| */ |
| private static class NullModuleReader implements ModuleReader { |
| @Override |
| public Optional<URI> find(String name) { |
| return Optional.empty(); |
| } |
| @Override |
| public Stream<String> list() { |
| return Stream.empty(); |
| } |
| @Override |
| public void close() { |
| throw new InternalError("Should not get here"); |
| } |
| } |
| |
| /** |
| * Returns true if the given module opens the given package |
| * unconditionally. |
| * |
| * @implNote This method currently iterates over each of the open |
| * packages. This will be replaced once the ModuleDescriptor.Opens |
| * API is updated. |
| */ |
| private boolean isOpen(ModuleReference mref, String pn) { |
| ModuleDescriptor descriptor = mref.descriptor(); |
| if (descriptor.isOpen() || descriptor.isAutomatic()) |
| return true; |
| for (ModuleDescriptor.Opens opens : descriptor.opens()) { |
| String source = opens.source(); |
| if (!opens.isQualified() && source.equals(pn)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |