1 /*
2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.internal.loader;
27 
28 import java.io.File;
29 import java.io.FilePermission;
30 import java.io.IOException;
31 import java.lang.module.Configuration;
32 import java.lang.module.ModuleDescriptor;
33 import java.lang.module.ModuleReader;
34 import java.lang.module.ModuleReference;
35 import java.lang.module.ResolvedModule;
36 import java.net.MalformedURLException;
37 import java.net.URI;
38 import java.net.URL;
39 import java.nio.ByteBuffer;
40 import java.security.AccessControlContext;
41 import java.security.AccessController;
42 import java.security.CodeSigner;
43 import java.security.CodeSource;
44 import java.security.Permission;
45 import java.security.PermissionCollection;
46 import java.security.PrivilegedAction;
47 import java.security.PrivilegedActionException;
48 import java.security.PrivilegedExceptionAction;
49 import java.security.SecureClassLoader;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.Enumeration;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Objects;
59 import java.util.Optional;
60 import java.util.concurrent.ConcurrentHashMap;
61 import java.util.stream.Stream;
62 
63 import jdk.internal.access.SharedSecrets;
64 import jdk.internal.module.Resources;
65 
66 /**
67  * A class loader that loads classes and resources from a collection of
68  * modules, or from a single module where the class loader is a member
69  * of a pool of class loaders.
70  *
71  * <p> The delegation model used by this ClassLoader differs to the regular
72  * delegation model. When requested to load a class then this ClassLoader first
73  * maps the class name to its package name. If there a module defined to the
74  * Loader containing the package then the class loader attempts to load from
75  * that module. If the package is instead defined to a module in a "remote"
76  * ClassLoader then this class loader delegates directly to that class loader.
77  * The map of package name to remote class loader is created based on the
78  * modules read by modules defined to this class loader. If the package is not
79  * local or remote then this class loader will delegate to the parent class
80  * loader. This allows automatic modules (for example) to link to types in the
81  * unnamed module of the parent class loader.
82  *
83  * @see ModuleLayer#defineModulesWithOneLoader
84  * @see ModuleLayer#defineModulesWithManyLoaders
85  */
86 
87 public final class Loader extends SecureClassLoader {
88 
89     static {
ClassLoader.registerAsParallelCapable()90         ClassLoader.registerAsParallelCapable();
91     }
92 
93     // the pool this loader is a member of; can be null
94     private final LoaderPool pool;
95 
96     // parent ClassLoader, can be null
97     private final ClassLoader parent;
98 
99     // maps a module name to a module reference
100     private final Map<String, ModuleReference> nameToModule;
101 
102     // maps package name to a module loaded by this class loader
103     private final Map<String, LoadedModule> localPackageToModule;
104 
105     // maps package name to a remote class loader, populated post initialization
106     private final Map<String, ClassLoader> remotePackageToLoader
107         = new ConcurrentHashMap<>();
108 
109     // maps a module reference to a module reader, populated lazily
110     private final Map<ModuleReference, ModuleReader> moduleToReader
111         = new ConcurrentHashMap<>();
112 
113     // ACC used when loading classes and resources
114     private final AccessControlContext acc;
115 
116     /**
117      * A module defined/loaded to a {@code Loader}.
118      */
119     private static class LoadedModule {
120         private final ModuleReference mref;
121         private final URL url;          // may be null
122         private final CodeSource cs;
123 
LoadedModule(ModuleReference mref)124         LoadedModule(ModuleReference mref) {
125             URL url = null;
126             if (mref.location().isPresent()) {
127                 try {
128                     url = mref.location().get().toURL();
129                 } catch (MalformedURLException | IllegalArgumentException e) { }
130             }
131             this.mref = mref;
132             this.url = url;
133             this.cs = new CodeSource(url, (CodeSigner[]) null);
134         }
135 
mref()136         ModuleReference mref() { return mref; }
name()137         String name() { return mref.descriptor().name(); }
location()138         URL location() { return url; }
codeSource()139         CodeSource codeSource() { return cs; }
140     }
141 
142 
143     /**
144      * Creates a {@code Loader} in a loader pool that loads classes/resources
145      * from one module.
146      */
Loader(ResolvedModule resolvedModule, LoaderPool pool, ClassLoader parent)147     public Loader(ResolvedModule resolvedModule,
148                   LoaderPool pool,
149                   ClassLoader parent)
150     {
151         super("Loader-" + resolvedModule.name(), parent);
152 
153         this.pool = pool;
154         this.parent = parent;
155 
156         ModuleReference mref = resolvedModule.reference();
157         ModuleDescriptor descriptor = mref.descriptor();
158         String mn = descriptor.name();
159         this.nameToModule = Map.of(mn, mref);
160 
161         Map<String, LoadedModule> localPackageToModule = new HashMap<>();
162         LoadedModule lm = new LoadedModule(mref);
163         descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm));
164         this.localPackageToModule = localPackageToModule;
165 
166         this.acc = AccessController.getContext();
167     }
168 
169     /**
170      * Creates a {@code Loader} that loads classes/resources from a collection
171      * of modules.
172      *
173      * @throws IllegalArgumentException
174      *         If two or more modules have the same package
175      */
Loader(Collection<ResolvedModule> modules, ClassLoader parent)176     public Loader(Collection<ResolvedModule> modules, ClassLoader parent) {
177         super(parent);
178 
179         this.pool = null;
180         this.parent = parent;
181 
182         Map<String, ModuleReference> nameToModule = new HashMap<>();
183         Map<String, LoadedModule> localPackageToModule = new HashMap<>();
184         for (ResolvedModule resolvedModule : modules) {
185             ModuleReference mref = resolvedModule.reference();
186             ModuleDescriptor descriptor = mref.descriptor();
187             nameToModule.put(descriptor.name(), mref);
188             descriptor.packages().forEach(pn -> {
189                 LoadedModule lm = new LoadedModule(mref);
190                 if (localPackageToModule.put(pn, lm) != null)
191                     throw new IllegalArgumentException("Package "
192                         + pn + " in more than one module");
193             });
194         }
195         this.nameToModule = nameToModule;
196         this.localPackageToModule = localPackageToModule;
197 
198         this.acc = AccessController.getContext();
199     }
200 
201     /**
202      * Completes initialization of this Loader. This method populates
203      * remotePackageToLoader with the packages of the remote modules, where
204      * "remote modules" are the modules read by modules defined to this loader.
205      *
206      * @param cf the Configuration containing at least modules to be defined to
207      *           this class loader
208      *
209      * @param parentModuleLayers the parent ModuleLayers
210      */
initRemotePackageMap(Configuration cf, List<ModuleLayer> parentModuleLayers)211     public Loader initRemotePackageMap(Configuration cf,
212                                        List<ModuleLayer> parentModuleLayers)
213     {
214         for (String name : nameToModule.keySet()) {
215             ResolvedModule resolvedModule = cf.findModule(name).get();
216             assert resolvedModule.configuration() == cf;
217 
218             for (ResolvedModule other : resolvedModule.reads()) {
219                 String mn = other.name();
220                 ClassLoader loader;
221 
222                 if (other.configuration() == cf) {
223 
224                     // The module reads another module in the newly created
225                     // layer. If all modules are defined to the same class
226                     // loader then the packages are local.
227                     if (pool == null) {
228                         assert nameToModule.containsKey(mn);
229                         continue;
230                     }
231 
232                     loader = pool.loaderFor(mn);
233                     assert loader != null;
234 
235                 } else {
236 
237                     // find the layer for the target module
238                     ModuleLayer layer = parentModuleLayers.stream()
239                         .map(parent -> findModuleLayer(parent, other.configuration()))
240                         .flatMap(Optional::stream)
241                         .findAny()
242                         .orElseThrow(() ->
243                             new InternalError("Unable to find parent layer"));
244 
245                     // find the class loader for the module
246                     // For now we use the platform loader for modules defined to the
247                     // boot loader
248                     assert layer.findModule(mn).isPresent();
249                     loader = layer.findLoader(mn);
250                     if (loader == null)
251                         loader = ClassLoaders.platformClassLoader();
252                 }
253 
254                 // find the packages that are exported to the target module
255                 ModuleDescriptor descriptor = other.reference().descriptor();
256                 if (descriptor.isAutomatic()) {
257                     ClassLoader l = loader;
258                     descriptor.packages().forEach(pn -> remotePackage(pn, l));
259                 } else {
260                     String target = resolvedModule.name();
261                     for (ModuleDescriptor.Exports e : descriptor.exports()) {
262                         boolean delegate;
263                         if (e.isQualified()) {
264                             // qualified export in same configuration
265                             delegate = (other.configuration() == cf)
266                                     && e.targets().contains(target);
267                         } else {
268                             // unqualified
269                             delegate = true;
270                         }
271 
272                         if (delegate) {
273                             remotePackage(e.source(), loader);
274                         }
275                     }
276                 }
277             }
278 
279         }
280 
281         return this;
282     }
283 
284     /**
285      * Adds to remotePackageToLoader so that an attempt to load a class in
286      * the package delegates to the given class loader.
287      *
288      * @throws IllegalStateException
289      *         if the package is already mapped to a different class loader
290      */
remotePackage(String pn, ClassLoader loader)291     private void remotePackage(String pn, ClassLoader loader) {
292         ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader);
293         if (l != null && l != loader) {
294             throw new IllegalStateException("Package "
295                 + pn + " cannot be imported from multiple loaders");
296         }
297     }
298 
299 
300     /**
301      * Find the layer corresponding to the given configuration in the tree
302      * of layers rooted at the given parent.
303      */
findModuleLayer(ModuleLayer parent, Configuration cf)304     private Optional<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) {
305         return SharedSecrets.getJavaLangAccess().layers(parent)
306                 .filter(l -> l.configuration() == cf)
307                 .findAny();
308     }
309 
310 
311     /**
312      * Returns the loader pool that this loader is in or {@code null} if this
313      * loader is not in a loader pool.
314      */
pool()315     public LoaderPool pool() {
316         return pool;
317     }
318 
319 
320     // -- resources --
321 
322     /**
323      * Returns a URL to a resource of the given name in a module defined to
324      * this class loader.
325      */
326     @Override
findResource(String mn, String name)327     protected URL findResource(String mn, String name) throws IOException {
328         ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null;
329         if (mref == null)
330             return null;   // not defined to this class loader
331 
332         // locate resource
333         URL url = null;
334         try {
335             url = AccessController.doPrivileged(
336                 new PrivilegedExceptionAction<URL>() {
337                     @Override
338                     public URL run() throws IOException {
339                         Optional<URI> ouri = moduleReaderFor(mref).find(name);
340                         if (ouri.isPresent()) {
341                             try {
342                                 return ouri.get().toURL();
343                             } catch (MalformedURLException |
344                                      IllegalArgumentException e) { }
345                         }
346                         return null;
347                     }
348                 });
349         } catch (PrivilegedActionException pae) {
350             throw (IOException) pae.getCause();
351         }
352 
353         // check access with permissions restricted by ACC
354         if (url != null && System.getSecurityManager() != null) {
355             try {
356                 URL urlToCheck = url;
357                 url = AccessController.doPrivileged(
358                     new PrivilegedExceptionAction<URL>() {
359                         @Override
360                         public URL run() throws IOException {
361                             return URLClassPath.checkURL(urlToCheck);
362                         }
363                     }, acc);
364             } catch (PrivilegedActionException pae) {
365                 url = null;
366             }
367         }
368 
369         return url;
370     }
371 
372     @Override
findResource(String name)373     public URL findResource(String name) {
374         String pn = Resources.toPackageName(name);
375         LoadedModule module = localPackageToModule.get(pn);
376 
377         if (module != null) {
378             try {
379                 URL url = findResource(module.name(), name);
380                 if (url != null
381                     && (name.endsWith(".class")
382                         || url.toString().endsWith("/")
383                         || isOpen(module.mref(), pn))) {
384                     return url;
385                 }
386             } catch (IOException ioe) {
387                 // ignore
388             }
389 
390         } else {
391             for (ModuleReference mref : nameToModule.values()) {
392                 try {
393                     URL url = findResource(mref.descriptor().name(), name);
394                     if (url != null) return url;
395                 } catch (IOException ioe) {
396                     // ignore
397                 }
398             }
399         }
400 
401         return null;
402     }
403 
404     @Override
findResources(String name)405     public Enumeration<URL> findResources(String name) throws IOException {
406         return Collections.enumeration(findResourcesAsList(name));
407     }
408 
409     @Override
getResource(String name)410     public URL getResource(String name) {
411         Objects.requireNonNull(name);
412 
413         // this loader
414         URL url = findResource(name);
415         if (url == null) {
416             // parent loader
417             if (parent != null) {
418                 url = parent.getResource(name);
419             } else {
420                 url = BootLoader.findResource(name);
421             }
422         }
423         return url;
424     }
425 
426     @Override
getResources(String name)427     public Enumeration<URL> getResources(String name) throws IOException {
428         Objects.requireNonNull(name);
429 
430         // this loader
431         List<URL> urls = findResourcesAsList(name);
432 
433         // parent loader
434         Enumeration<URL> e;
435         if (parent != null) {
436             e = parent.getResources(name);
437         } else {
438             e = BootLoader.findResources(name);
439         }
440 
441         // concat the URLs with the URLs returned by the parent
442         return new Enumeration<>() {
443             final Iterator<URL> iterator = urls.iterator();
444             @Override
445             public boolean hasMoreElements() {
446                 return (iterator.hasNext() || e.hasMoreElements());
447             }
448             @Override
449             public URL nextElement() {
450                 if (iterator.hasNext()) {
451                     return iterator.next();
452                 } else {
453                     return e.nextElement();
454                 }
455             }
456         };
457     }
458 
459     /**
460      * Finds the resources with the given name in this class loader.
461      */
findResourcesAsList(String name)462     private List<URL> findResourcesAsList(String name) throws IOException {
463         String pn = Resources.toPackageName(name);
464         LoadedModule module = localPackageToModule.get(pn);
465         if (module != null) {
466             URL url = findResource(module.name(), name);
467             if (url != null
468                     && (name.endsWith(".class")
469                     || url.toString().endsWith("/")
470                     || isOpen(module.mref(), pn))) {
471                 return List.of(url);
472             } else {
473                 return Collections.emptyList();
474             }
475         } else {
476             List<URL> urls = new ArrayList<>();
477             for (ModuleReference mref : nameToModule.values()) {
478                 URL url = findResource(mref.descriptor().name(), name);
479                 if (url != null) {
480                     urls.add(url);
481                 }
482             }
483             return urls;
484         }
485     }
486 
487 
488     // -- finding/loading classes
489 
490     /**
491      * Finds the class with the specified binary name.
492      */
493     @Override
findClass(String cn)494     protected Class<?> findClass(String cn) throws ClassNotFoundException {
495         Class<?> c = null;
496         LoadedModule loadedModule = findLoadedModule(cn);
497         if (loadedModule != null)
498             c = findClassInModuleOrNull(loadedModule, cn);
499         if (c == null)
500             throw new ClassNotFoundException(cn);
501         return c;
502     }
503 
504     /**
505      * Finds the class with the specified binary name in the given module.
506      * This method returns {@code null} if the class cannot be found.
507      */
508     @Override
findClass(String mn, String cn)509     protected Class<?> findClass(String mn, String cn) {
510         Class<?> c = null;
511         LoadedModule loadedModule = findLoadedModule(cn);
512         if (loadedModule != null && loadedModule.name().equals(mn))
513             c = findClassInModuleOrNull(loadedModule, cn);
514         return c;
515     }
516 
517     /**
518      * Loads the class with the specified binary name.
519      */
520     @Override
loadClass(String cn, boolean resolve)521     protected Class<?> loadClass(String cn, boolean resolve)
522         throws ClassNotFoundException
523     {
524         SecurityManager sm = System.getSecurityManager();
525         if (sm != null) {
526             String pn = packageName(cn);
527             if (!pn.isEmpty()) {
528                 sm.checkPackageAccess(pn);
529             }
530         }
531 
532         synchronized (getClassLoadingLock(cn)) {
533             // check if already loaded
534             Class<?> c = findLoadedClass(cn);
535 
536             if (c == null) {
537 
538                 LoadedModule loadedModule = findLoadedModule(cn);
539 
540                 if (loadedModule != null) {
541 
542                     // class is in module defined to this class loader
543                     c = findClassInModuleOrNull(loadedModule, cn);
544 
545                 } else {
546 
547                     // type in another module or visible via the parent loader
548                     String pn = packageName(cn);
549                     ClassLoader loader = remotePackageToLoader.get(pn);
550                     if (loader == null) {
551                         // type not in a module read by any of the modules
552                         // defined to this loader, so delegate to parent
553                         // class loader
554                         loader = parent;
555                     }
556                     if (loader == null) {
557                         c = BootLoader.loadClassOrNull(cn);
558                     } else {
559                         c = loader.loadClass(cn);
560                     }
561 
562                 }
563             }
564 
565             if (c == null)
566                 throw new ClassNotFoundException(cn);
567 
568             if (resolve)
569                 resolveClass(c);
570 
571             return c;
572         }
573     }
574 
575 
576     /**
577      * Finds the class with the specified binary name if in a module
578      * defined to this ClassLoader.
579      *
580      * @return the resulting Class or {@code null} if not found
581      */
findClassInModuleOrNull(LoadedModule loadedModule, String cn)582     private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
583         PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
584         return AccessController.doPrivileged(pa, acc);
585     }
586 
587     /**
588      * Defines the given binary class name to the VM, loading the class
589      * bytes from the given module.
590      *
591      * @return the resulting Class or {@code null} if an I/O error occurs
592      */
defineClass(String cn, LoadedModule loadedModule)593     private Class<?> defineClass(String cn, LoadedModule loadedModule) {
594         ModuleReader reader = moduleReaderFor(loadedModule.mref());
595 
596         try {
597             // read class file
598             String rn = cn.replace('.', '/').concat(".class");
599             ByteBuffer bb = reader.read(rn).orElse(null);
600             if (bb == null) {
601                 // class not found
602                 return null;
603             }
604 
605             try {
606                 return defineClass(cn, bb, loadedModule.codeSource());
607             } finally {
608                 reader.release(bb);
609             }
610 
611         } catch (IOException ioe) {
612             // TBD on how I/O errors should be propagated
613             return null;
614         }
615     }
616 
617 
618     // -- permissions
619 
620     /**
621      * Returns the permissions for the given CodeSource.
622      */
623     @Override
getPermissions(CodeSource cs)624     protected PermissionCollection getPermissions(CodeSource cs) {
625         PermissionCollection perms = super.getPermissions(cs);
626 
627         URL url = cs.getLocation();
628         if (url == null)
629             return perms;
630 
631         // add the permission to access the resource
632         try {
633             Permission p = url.openConnection().getPermission();
634             if (p != null) {
635                 // for directories then need recursive access
636                 if (p instanceof FilePermission) {
637                     String path = p.getName();
638                     if (path.endsWith(File.separator)) {
639                         path += "-";
640                         p = new FilePermission(path, "read");
641                     }
642                 }
643                 perms.add(p);
644             }
645         } catch (IOException ioe) { }
646 
647         return perms;
648     }
649 
650 
651     // -- miscellaneous supporting methods
652 
653     /**
654      * Find the candidate module for the given class name.
655      * Returns {@code null} if none of the modules defined to this
656      * class loader contain the API package for the class.
657      */
findLoadedModule(String cn)658     private LoadedModule findLoadedModule(String cn) {
659         String pn = packageName(cn);
660         return pn.isEmpty() ? null : localPackageToModule.get(pn);
661     }
662 
663     /**
664      * Returns the package name for the given class name
665      */
packageName(String cn)666     private String packageName(String cn) {
667         int pos = cn.lastIndexOf('.');
668         return (pos < 0) ? "" : cn.substring(0, pos);
669     }
670 
671 
672     /**
673      * Returns the ModuleReader for the given module.
674      */
moduleReaderFor(ModuleReference mref)675     private ModuleReader moduleReaderFor(ModuleReference mref) {
676         return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));
677     }
678 
679     /**
680      * Creates a ModuleReader for the given module.
681      */
createModuleReader(ModuleReference mref)682     private ModuleReader createModuleReader(ModuleReference mref) {
683         try {
684             return mref.open();
685         } catch (IOException e) {
686             // Return a null module reader to avoid a future class load
687             // attempting to open the module again.
688             return new NullModuleReader();
689         }
690     }
691 
692     /**
693      * A ModuleReader that doesn't read any resources.
694      */
695     private static class NullModuleReader implements ModuleReader {
696         @Override
find(String name)697         public Optional<URI> find(String name) {
698             return Optional.empty();
699         }
700         @Override
list()701         public Stream<String> list() {
702             return Stream.empty();
703         }
704         @Override
close()705         public void close() {
706             throw new InternalError("Should not get here");
707         }
708     }
709 
710     /**
711      * Returns true if the given module opens the given package
712      * unconditionally.
713      *
714      * @implNote This method currently iterates over each of the open
715      * packages. This will be replaced once the ModuleDescriptor.Opens
716      * API is updated.
717      */
isOpen(ModuleReference mref, String pn)718     private boolean isOpen(ModuleReference mref, String pn) {
719         ModuleDescriptor descriptor = mref.descriptor();
720         if (descriptor.isOpen() || descriptor.isAutomatic())
721             return true;
722         for (ModuleDescriptor.Opens opens : descriptor.opens()) {
723             String source = opens.source();
724             if (!opens.isQualified() && source.equals(pn)) {
725                 return true;
726             }
727         }
728         return false;
729     }
730 }
731