Path: blob/master/src/java.base/share/classes/jdk/internal/loader/Loader.java
41159 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.loader;2627import java.io.File;28import java.io.FilePermission;29import java.io.IOException;30import java.lang.module.Configuration;31import java.lang.module.ModuleDescriptor;32import java.lang.module.ModuleReader;33import java.lang.module.ModuleReference;34import java.lang.module.ResolvedModule;35import java.net.MalformedURLException;36import java.net.URI;37import java.net.URL;38import java.nio.ByteBuffer;39import java.security.AccessControlContext;40import java.security.AccessController;41import java.security.CodeSigner;42import java.security.CodeSource;43import java.security.Permission;44import java.security.PermissionCollection;45import java.security.PrivilegedAction;46import java.security.PrivilegedActionException;47import java.security.PrivilegedExceptionAction;48import java.security.SecureClassLoader;49import java.util.ArrayList;50import java.util.Collection;51import java.util.Collections;52import java.util.Enumeration;53import java.util.HashMap;54import java.util.Iterator;55import java.util.List;56import java.util.Map;57import java.util.Objects;58import java.util.Optional;59import java.util.concurrent.ConcurrentHashMap;60import java.util.stream.Stream;6162import jdk.internal.access.SharedSecrets;63import jdk.internal.module.Resources;6465/**66* A class loader that loads classes and resources from a collection of67* modules, or from a single module where the class loader is a member68* of a pool of class loaders.69*70* <p> The delegation model used by this ClassLoader differs to the regular71* delegation model. When requested to load a class then this ClassLoader first72* maps the class name to its package name. If there a module defined to the73* Loader containing the package then the class loader attempts to load from74* that module. If the package is instead defined to a module in a "remote"75* ClassLoader then this class loader delegates directly to that class loader.76* The map of package name to remote class loader is created based on the77* modules read by modules defined to this class loader. If the package is not78* local or remote then this class loader will delegate to the parent class79* loader. This allows automatic modules (for example) to link to types in the80* unnamed module of the parent class loader.81*82* @see ModuleLayer#defineModulesWithOneLoader83* @see ModuleLayer#defineModulesWithManyLoaders84*/8586public final class Loader extends SecureClassLoader {8788static {89ClassLoader.registerAsParallelCapable();90}9192// the pool this loader is a member of; can be null93private final LoaderPool pool;9495// parent ClassLoader, can be null96private final ClassLoader parent;9798// maps a module name to a module reference99private final Map<String, ModuleReference> nameToModule;100101// maps package name to a module loaded by this class loader102private final Map<String, LoadedModule> localPackageToModule;103104// maps package name to a remote class loader, populated post initialization105private final Map<String, ClassLoader> remotePackageToLoader106= new ConcurrentHashMap<>();107108// maps a module reference to a module reader, populated lazily109private final Map<ModuleReference, ModuleReader> moduleToReader110= new ConcurrentHashMap<>();111112// ACC used when loading classes and resources113@SuppressWarnings("removal")114private final AccessControlContext acc;115116/**117* A module defined/loaded to a {@code Loader}.118*/119private static class LoadedModule {120private final ModuleReference mref;121private final URL url; // may be null122private final CodeSource cs;123124LoadedModule(ModuleReference mref) {125URL url = null;126if (mref.location().isPresent()) {127try {128url = mref.location().get().toURL();129} catch (MalformedURLException | IllegalArgumentException e) { }130}131this.mref = mref;132this.url = url;133this.cs = new CodeSource(url, (CodeSigner[]) null);134}135136ModuleReference mref() { return mref; }137String name() { return mref.descriptor().name(); }138URL location() { return url; }139CodeSource codeSource() { return cs; }140}141142143/**144* Creates a {@code Loader} in a loader pool that loads classes/resources145* from one module.146*/147@SuppressWarnings("removal")148public Loader(ResolvedModule resolvedModule,149LoaderPool pool,150ClassLoader parent)151{152super("Loader-" + resolvedModule.name(), parent);153154this.pool = pool;155this.parent = parent;156157ModuleReference mref = resolvedModule.reference();158ModuleDescriptor descriptor = mref.descriptor();159String mn = descriptor.name();160this.nameToModule = Map.of(mn, mref);161162Map<String, LoadedModule> localPackageToModule = new HashMap<>();163LoadedModule lm = new LoadedModule(mref);164descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm));165this.localPackageToModule = localPackageToModule;166167this.acc = AccessController.getContext();168}169170/**171* Creates a {@code Loader} that loads classes/resources from a collection172* of modules.173*174* @throws IllegalArgumentException175* If two or more modules have the same package176*/177@SuppressWarnings("removal")178public Loader(Collection<ResolvedModule> modules, ClassLoader parent) {179super(parent);180181this.pool = null;182this.parent = parent;183184Map<String, ModuleReference> nameToModule = new HashMap<>();185Map<String, LoadedModule> localPackageToModule = new HashMap<>();186for (ResolvedModule resolvedModule : modules) {187ModuleReference mref = resolvedModule.reference();188ModuleDescriptor descriptor = mref.descriptor();189nameToModule.put(descriptor.name(), mref);190descriptor.packages().forEach(pn -> {191LoadedModule lm = new LoadedModule(mref);192if (localPackageToModule.put(pn, lm) != null)193throw new IllegalArgumentException("Package "194+ pn + " in more than one module");195});196}197this.nameToModule = nameToModule;198this.localPackageToModule = localPackageToModule;199200this.acc = AccessController.getContext();201}202203/**204* Completes initialization of this Loader. This method populates205* remotePackageToLoader with the packages of the remote modules, where206* "remote modules" are the modules read by modules defined to this loader.207*208* @param cf the Configuration containing at least modules to be defined to209* this class loader210*211* @param parentModuleLayers the parent ModuleLayers212*/213public Loader initRemotePackageMap(Configuration cf,214List<ModuleLayer> parentModuleLayers)215{216for (String name : nameToModule.keySet()) {217ResolvedModule resolvedModule = cf.findModule(name).get();218assert resolvedModule.configuration() == cf;219220for (ResolvedModule other : resolvedModule.reads()) {221String mn = other.name();222ClassLoader loader;223224if (other.configuration() == cf) {225226// The module reads another module in the newly created227// layer. If all modules are defined to the same class228// loader then the packages are local.229if (pool == null) {230assert nameToModule.containsKey(mn);231continue;232}233234loader = pool.loaderFor(mn);235assert loader != null;236237} else {238239// find the layer for the target module240ModuleLayer layer = parentModuleLayers.stream()241.map(parent -> findModuleLayer(parent, other.configuration()))242.flatMap(Optional::stream)243.findAny()244.orElseThrow(() ->245new InternalError("Unable to find parent layer"));246247// find the class loader for the module248// For now we use the platform loader for modules defined to the249// boot loader250assert layer.findModule(mn).isPresent();251loader = layer.findLoader(mn);252if (loader == null)253loader = ClassLoaders.platformClassLoader();254}255256// find the packages that are exported to the target module257ModuleDescriptor descriptor = other.reference().descriptor();258if (descriptor.isAutomatic()) {259ClassLoader l = loader;260descriptor.packages().forEach(pn -> remotePackage(pn, l));261} else {262String target = resolvedModule.name();263for (ModuleDescriptor.Exports e : descriptor.exports()) {264boolean delegate;265if (e.isQualified()) {266// qualified export in same configuration267delegate = (other.configuration() == cf)268&& e.targets().contains(target);269} else {270// unqualified271delegate = true;272}273274if (delegate) {275remotePackage(e.source(), loader);276}277}278}279}280281}282283return this;284}285286/**287* Adds to remotePackageToLoader so that an attempt to load a class in288* the package delegates to the given class loader.289*290* @throws IllegalStateException291* if the package is already mapped to a different class loader292*/293private void remotePackage(String pn, ClassLoader loader) {294ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader);295if (l != null && l != loader) {296throw new IllegalStateException("Package "297+ pn + " cannot be imported from multiple loaders");298}299}300301302/**303* Find the layer corresponding to the given configuration in the tree304* of layers rooted at the given parent.305*/306private Optional<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) {307return SharedSecrets.getJavaLangAccess().layers(parent)308.filter(l -> l.configuration() == cf)309.findAny();310}311312313/**314* Returns the loader pool that this loader is in or {@code null} if this315* loader is not in a loader pool.316*/317public LoaderPool pool() {318return pool;319}320321322// -- resources --323324/**325* Returns a URL to a resource of the given name in a module defined to326* this class loader.327*/328@SuppressWarnings("removal")329@Override330protected URL findResource(String mn, String name) throws IOException {331ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null;332if (mref == null)333return null; // not defined to this class loader334335// locate resource336URL url = null;337try {338url = AccessController.doPrivileged(339new PrivilegedExceptionAction<URL>() {340@Override341public URL run() throws IOException {342Optional<URI> ouri = moduleReaderFor(mref).find(name);343if (ouri.isPresent()) {344try {345return ouri.get().toURL();346} catch (MalformedURLException |347IllegalArgumentException e) { }348}349return null;350}351});352} catch (PrivilegedActionException pae) {353throw (IOException) pae.getCause();354}355356// check access with permissions restricted by ACC357if (url != null && System.getSecurityManager() != null) {358try {359URL urlToCheck = url;360url = AccessController.doPrivileged(361new PrivilegedExceptionAction<URL>() {362@Override363public URL run() throws IOException {364return URLClassPath.checkURL(urlToCheck);365}366}, acc);367} catch (PrivilegedActionException pae) {368url = null;369}370}371372return url;373}374375@Override376public URL findResource(String name) {377String pn = Resources.toPackageName(name);378LoadedModule module = localPackageToModule.get(pn);379380if (module != null) {381try {382URL url = findResource(module.name(), name);383if (url != null384&& (name.endsWith(".class")385|| url.toString().endsWith("/")386|| isOpen(module.mref(), pn))) {387return url;388}389} catch (IOException ioe) {390// ignore391}392393} else {394for (ModuleReference mref : nameToModule.values()) {395try {396URL url = findResource(mref.descriptor().name(), name);397if (url != null) return url;398} catch (IOException ioe) {399// ignore400}401}402}403404return null;405}406407@Override408public Enumeration<URL> findResources(String name) throws IOException {409return Collections.enumeration(findResourcesAsList(name));410}411412@Override413public URL getResource(String name) {414Objects.requireNonNull(name);415416// this loader417URL url = findResource(name);418if (url == null) {419// parent loader420if (parent != null) {421url = parent.getResource(name);422} else {423url = BootLoader.findResource(name);424}425}426return url;427}428429@Override430public Enumeration<URL> getResources(String name) throws IOException {431Objects.requireNonNull(name);432433// this loader434List<URL> urls = findResourcesAsList(name);435436// parent loader437Enumeration<URL> e;438if (parent != null) {439e = parent.getResources(name);440} else {441e = BootLoader.findResources(name);442}443444// concat the URLs with the URLs returned by the parent445return new Enumeration<>() {446final Iterator<URL> iterator = urls.iterator();447@Override448public boolean hasMoreElements() {449return (iterator.hasNext() || e.hasMoreElements());450}451@Override452public URL nextElement() {453if (iterator.hasNext()) {454return iterator.next();455} else {456return e.nextElement();457}458}459};460}461462/**463* Finds the resources with the given name in this class loader.464*/465private List<URL> findResourcesAsList(String name) throws IOException {466String pn = Resources.toPackageName(name);467LoadedModule module = localPackageToModule.get(pn);468if (module != null) {469URL url = findResource(module.name(), name);470if (url != null471&& (name.endsWith(".class")472|| url.toString().endsWith("/")473|| isOpen(module.mref(), pn))) {474return List.of(url);475} else {476return Collections.emptyList();477}478} else {479List<URL> urls = new ArrayList<>();480for (ModuleReference mref : nameToModule.values()) {481URL url = findResource(mref.descriptor().name(), name);482if (url != null) {483urls.add(url);484}485}486return urls;487}488}489490491// -- finding/loading classes492493/**494* Finds the class with the specified binary name.495*/496@Override497protected Class<?> findClass(String cn) throws ClassNotFoundException {498Class<?> c = null;499LoadedModule loadedModule = findLoadedModule(cn);500if (loadedModule != null)501c = findClassInModuleOrNull(loadedModule, cn);502if (c == null)503throw new ClassNotFoundException(cn);504return c;505}506507/**508* Finds the class with the specified binary name in the given module.509* This method returns {@code null} if the class cannot be found.510*/511@Override512protected Class<?> findClass(String mn, String cn) {513Class<?> c = null;514LoadedModule loadedModule = findLoadedModule(cn);515if (loadedModule != null && loadedModule.name().equals(mn))516c = findClassInModuleOrNull(loadedModule, cn);517return c;518}519520/**521* Loads the class with the specified binary name.522*/523@Override524protected Class<?> loadClass(String cn, boolean resolve)525throws ClassNotFoundException526{527@SuppressWarnings("removal")528SecurityManager sm = System.getSecurityManager();529if (sm != null) {530String pn = packageName(cn);531if (!pn.isEmpty()) {532sm.checkPackageAccess(pn);533}534}535536synchronized (getClassLoadingLock(cn)) {537// check if already loaded538Class<?> c = findLoadedClass(cn);539540if (c == null) {541542LoadedModule loadedModule = findLoadedModule(cn);543544if (loadedModule != null) {545546// class is in module defined to this class loader547c = findClassInModuleOrNull(loadedModule, cn);548549} else {550551// type in another module or visible via the parent loader552String pn = packageName(cn);553ClassLoader loader = remotePackageToLoader.get(pn);554if (loader == null) {555// type not in a module read by any of the modules556// defined to this loader, so delegate to parent557// class loader558loader = parent;559}560if (loader == null) {561c = BootLoader.loadClassOrNull(cn);562} else {563c = loader.loadClass(cn);564}565566}567}568569if (c == null)570throw new ClassNotFoundException(cn);571572if (resolve)573resolveClass(c);574575return c;576}577}578579580/**581* Finds the class with the specified binary name if in a module582* defined to this ClassLoader.583*584* @return the resulting Class or {@code null} if not found585*/586@SuppressWarnings("removal")587private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {588PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);589return AccessController.doPrivileged(pa, acc);590}591592/**593* Defines the given binary class name to the VM, loading the class594* bytes from the given module.595*596* @return the resulting Class or {@code null} if an I/O error occurs597*/598private Class<?> defineClass(String cn, LoadedModule loadedModule) {599ModuleReader reader = moduleReaderFor(loadedModule.mref());600601try {602// read class file603String rn = cn.replace('.', '/').concat(".class");604ByteBuffer bb = reader.read(rn).orElse(null);605if (bb == null) {606// class not found607return null;608}609610try {611return defineClass(cn, bb, loadedModule.codeSource());612} finally {613reader.release(bb);614}615616} catch (IOException ioe) {617// TBD on how I/O errors should be propagated618return null;619}620}621622623// -- permissions624625/**626* Returns the permissions for the given CodeSource.627*/628@Override629protected PermissionCollection getPermissions(CodeSource cs) {630PermissionCollection perms = super.getPermissions(cs);631632URL url = cs.getLocation();633if (url == null)634return perms;635636// add the permission to access the resource637try {638Permission p = url.openConnection().getPermission();639if (p != null) {640// for directories then need recursive access641if (p instanceof FilePermission) {642String path = p.getName();643if (path.endsWith(File.separator)) {644path += "-";645p = new FilePermission(path, "read");646}647}648perms.add(p);649}650} catch (IOException ioe) { }651652return perms;653}654655656// -- miscellaneous supporting methods657658/**659* Find the candidate module for the given class name.660* Returns {@code null} if none of the modules defined to this661* class loader contain the API package for the class.662*/663private LoadedModule findLoadedModule(String cn) {664String pn = packageName(cn);665return pn.isEmpty() ? null : localPackageToModule.get(pn);666}667668/**669* Returns the package name for the given class name670*/671private String packageName(String cn) {672int pos = cn.lastIndexOf('.');673return (pos < 0) ? "" : cn.substring(0, pos);674}675676677/**678* Returns the ModuleReader for the given module.679*/680private ModuleReader moduleReaderFor(ModuleReference mref) {681return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));682}683684/**685* Creates a ModuleReader for the given module.686*/687private ModuleReader createModuleReader(ModuleReference mref) {688try {689return mref.open();690} catch (IOException e) {691// Return a null module reader to avoid a future class load692// attempting to open the module again.693return new NullModuleReader();694}695}696697/**698* A ModuleReader that doesn't read any resources.699*/700private static class NullModuleReader implements ModuleReader {701@Override702public Optional<URI> find(String name) {703return Optional.empty();704}705@Override706public Stream<String> list() {707return Stream.empty();708}709@Override710public void close() {711throw new InternalError("Should not get here");712}713}714715/**716* Returns true if the given module opens the given package717* unconditionally.718*719* @implNote This method currently iterates over each of the open720* packages. This will be replaced once the ModuleDescriptor.Opens721* API is updated.722*/723private boolean isOpen(ModuleReference mref, String pn) {724ModuleDescriptor descriptor = mref.descriptor();725if (descriptor.isOpen() || descriptor.isAutomatic())726return true;727for (ModuleDescriptor.Opens opens : descriptor.opens()) {728String source = opens.source();729if (!opens.isQualified() && source.equals(pn)) {730return true;731}732}733return false;734}735}736737738