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.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 
  87 public final class Loader extends SecureClassLoader {
  88 
  89     static {
  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 
 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 
 136         ModuleReference mref() { return mref; }
 137         String name() { return mref.descriptor().name(); }
 138         URL location() { return url; }
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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
 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
 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
 405     public Enumeration<URL> findResources(String name) throws IOException {
 406         return Collections.enumeration(findResourcesAsList(name));
 407     }
 408 
 409     @Override
 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
 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      */
 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
 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
 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
 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      */
 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      */
 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
 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      */
 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      */
 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      */
 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      */
 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
 697         public Optional<URI> find(String name) {
 698             return Optional.empty();
 699         }
 700         @Override
 701         public Stream<String> list() {
 702             return Stream.empty();
 703         }
 704         @Override
 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      */
 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 }