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