1 /*
   2  * Copyright (c) 2015, 2016, 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.lang.reflect.Layer;
  37 import java.net.MalformedURLException;
  38 import java.net.URI;
  39 import java.net.URL;
  40 import java.nio.ByteBuffer;
  41 import java.security.AccessControlContext;
  42 import java.security.AccessController;
  43 import java.security.CodeSigner;
  44 import java.security.CodeSource;
  45 import java.security.Permission;
  46 import java.security.PermissionCollection;
  47 import java.security.PrivilegedAction;
  48 import java.security.PrivilegedActionException;
  49 import java.security.PrivilegedExceptionAction;
  50 import java.security.SecureClassLoader;
  51 import java.util.ArrayList;
  52 import java.util.Collection;
  53 import java.util.Collections;
  54 import java.util.Enumeration;
  55 import java.util.HashMap;
  56 import java.util.List;
  57 import java.util.Map;
  58 import java.util.Optional;
  59 import java.util.concurrent.ConcurrentHashMap;
  60 import java.util.stream.Stream;
  61 
  62 import jdk.internal.misc.SharedSecrets;
  63 import jdk.internal.module.Resources;
  64 
  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 Layer#defineModulesWithOneLoader
  84  * @see Layer#defineModulesWithManyLoaders
  85  */
  86 
  87 public final class Loader extends SecureClassLoader {
  88 
  89     static {
  90         ClassLoader.registerAsParallelCapable();
  91     }
  92 
  93     // the loader pool is in a pool, 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     /**
 203      * Completes initialization of this Loader. This method populates
 204      * remotePackageToLoader with the packages of the remote modules, where
 205      * "remote modules" are the modules read by modules defined to this loader.
 206      *
 207      * @param cf the Configuration containing at least modules to be defined to
 208      *           this class loader
 209      *
 210      * @param parentLayers the parent Layers
 211      */
 212     public Loader initRemotePackageMap(Configuration cf,
 213                                        List<Layer> parentLayers)
 214     {
 215         for (String name : nameToModule.keySet()) {
 216             ResolvedModule resolvedModule = cf.findModule(name).get();
 217             assert resolvedModule.configuration() == cf;
 218 
 219             for (ResolvedModule other : resolvedModule.reads()) {
 220                 String mn = other.name();
 221                 ClassLoader loader;
 222 
 223                 if (other.configuration() == cf) {
 224 
 225                     // The module reads another module in the newly created
 226                     // layer. If all modules are defined to the same class
 227                     // loader then the packages are local.
 228                     if (pool == null) {
 229                         assert nameToModule.containsKey(mn);
 230                         continue;
 231                     }
 232 
 233                     loader = pool.loaderFor(mn);
 234                     assert loader != null;
 235 
 236                 } else {
 237 
 238                     // find the layer for the target module
 239                     Layer layer = parentLayers.stream()
 240                         .map(parent -> findLayer(parent, other.configuration()))
 241                         .flatMap(Optional::stream)
 242                         .findAny()
 243                         .orElseThrow(() ->
 244                             new InternalError("Unable to find parent layer"));
 245 
 246                     // find the class loader for the module
 247                     // For now we use the platform loader for modules defined to the
 248                     // boot loader
 249                     assert layer.findModule(mn).isPresent();
 250                     loader = layer.findLoader(mn);
 251                     if (loader == null)
 252                         loader = ClassLoaders.platformClassLoader();
 253                 }
 254 
 255                 // find the packages that are exported to the target module
 256                 String target = resolvedModule.name();
 257                 ModuleDescriptor descriptor = other.reference().descriptor();
 258                 for (ModuleDescriptor.Exports e : descriptor.exports()) {
 259                     boolean delegate;
 260                     if (e.isQualified()) {
 261                         // qualified export in same configuration
 262                         delegate = (other.configuration() == cf)
 263                                 && e.targets().contains(target);
 264                     } else {
 265                         // unqualified
 266                         delegate = true;
 267                     }
 268 
 269                     if (delegate) {
 270                         String pn = e.source();
 271                         ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader);
 272                         if (l != null && l != loader) {
 273                             throw new IllegalArgumentException("Package "
 274                                 + pn + " cannot be imported from multiple loaders");
 275                         }
 276                     }
 277                 }
 278             }
 279 
 280         }
 281 
 282         return this;
 283     }
 284 
 285     /**
 286      * Find the layer corresponding to the given configuration in the tree
 287      * of layers rooted at the given parent.
 288      */
 289     private Optional<Layer> findLayer(Layer parent, Configuration cf) {
 290         return SharedSecrets.getJavaLangReflectModuleAccess().layers(parent)
 291                 .filter(l -> l.configuration() == cf)
 292                 .findAny();
 293     }
 294 
 295 
 296     /**
 297      * Returns the loader pool that this loader is in or {@code null} if this
 298      * loader is not in a loader pool.
 299      */
 300     public LoaderPool pool() {
 301         return pool;
 302     }
 303 
 304 
 305     // -- resources --
 306 
 307 
 308     /**
 309      * Returns a URL to a resource of the given name in a module defined to
 310      * this class loader.
 311      */
 312     @Override
 313     protected URL findResource(String mn, String name) throws IOException {
 314         ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null;
 315         if (mref == null)
 316             return null;   // not defined to this class loader
 317 
 318         // locate resource
 319         URL url = null;
 320         try {
 321             url = AccessController.doPrivileged(
 322                 new PrivilegedExceptionAction<URL>() {
 323                     @Override
 324                     public URL run() throws IOException {
 325                         Optional<URI> ouri = moduleReaderFor(mref).find(name);
 326                         if (ouri.isPresent()) {
 327                             try {
 328                                 return ouri.get().toURL();
 329                             } catch (MalformedURLException |
 330                                      IllegalArgumentException e) { }
 331                         }
 332                         return null;
 333                     }
 334                 });
 335         } catch (PrivilegedActionException pae) {
 336             throw (IOException) pae.getCause();
 337         }
 338 
 339         // check access with permissions restricted by ACC
 340         if (url != null && System.getSecurityManager() != null) {
 341             try {
 342                 URL urlToCheck = url;
 343                 url = AccessController.doPrivileged(
 344                     new PrivilegedExceptionAction<URL>() {
 345                         @Override
 346                         public URL run() throws IOException {
 347                             return URLClassPath.checkURL(urlToCheck);
 348                         }
 349                     }, acc);
 350             } catch (PrivilegedActionException pae) {
 351                 url = null;
 352             }
 353         }
 354 
 355         return url;
 356     }
 357 
 358     @Override
 359     public URL findResource(String name) {
 360         String pn = Resources.toPackageName(name);
 361         LoadedModule module = localPackageToModule.get(pn);
 362 
 363         if (module != null) {
 364             try {
 365                 URL url = findResource(module.name(), name);
 366                 if (url != null
 367                     && (name.endsWith(".class")
 368                         || url.toString().endsWith("/")
 369                         || isOpen(module.mref(), pn))) {
 370                     return url;
 371                 }
 372             } catch (IOException ioe) {
 373                 // ignore
 374             }
 375 
 376         } else {
 377             for (ModuleReference mref : nameToModule.values()) {
 378                 try {
 379                     URL url = findResource(mref.descriptor().name(), name);
 380                     if (url != null) return url;
 381                 } catch (IOException ioe) {
 382                     // ignore
 383                 }
 384             }
 385         }
 386 
 387         return null;
 388     }
 389 
 390     @Override
 391     public Enumeration<URL> findResources(String name) throws IOException {
 392         List<URL> urls = new ArrayList<>();
 393         String pn = Resources.toPackageName(name);
 394         LoadedModule module = localPackageToModule.get(pn);
 395         if (module != null) {
 396             try {
 397                 URL url = findResource(module.name(), name);
 398                 if (url != null
 399                     && (name.endsWith(".class")
 400                         || url.toString().endsWith("/")
 401                         || isOpen(module.mref(), pn))) {
 402                     urls.add(url);
 403                 }
 404             } catch (IOException ioe) {
 405                 // ignore
 406             }
 407         } else {
 408             for (ModuleReference mref : nameToModule.values()) {
 409                 try {
 410                     URL url = findResource(mref.descriptor().name(), name);
 411                     if (url != null)
 412                         urls.add(url);
 413                 } catch (IOException ioe) {
 414                     // ignore
 415                 }
 416             }
 417         }
 418         return Collections.enumeration(urls);
 419     }
 420 
 421 
 422     // -- finding/loading classes
 423 
 424     /**
 425      * Finds the class with the specified binary name.
 426      */
 427     @Override
 428     protected Class<?> findClass(String cn) throws ClassNotFoundException {
 429         Class<?> c = null;
 430         LoadedModule loadedModule = findLoadedModule(cn);
 431         if (loadedModule != null)
 432             c = findClassInModuleOrNull(loadedModule, cn);
 433         if (c == null)
 434             throw new ClassNotFoundException(cn);
 435         return c;
 436     }
 437 
 438     /**
 439      * Finds the class with the specified binary name in a given module.
 440      * This method returns {@code null} if the class cannot be found.
 441      */
 442     @Override
 443     protected Class<?> findClass(String mn, String cn) {
 444         Class<?> c = null;
 445         LoadedModule loadedModule = findLoadedModule(cn);
 446         if (loadedModule != null && loadedModule.name().equals(mn))
 447             c = findClassInModuleOrNull(loadedModule, cn);
 448         return c;
 449     }
 450 
 451     /**
 452      * Loads the class with the specified binary name.
 453      */
 454     @Override
 455     protected Class<?> loadClass(String cn, boolean resolve)
 456         throws ClassNotFoundException
 457     {
 458         SecurityManager sm = System.getSecurityManager();
 459         if (sm != null) {
 460             String pn = packageName(cn);
 461             if (!pn.isEmpty()) {
 462                 sm.checkPackageAccess(pn);
 463             }
 464         }
 465 
 466         synchronized (getClassLoadingLock(cn)) {
 467             // check if already loaded
 468             Class<?> c = findLoadedClass(cn);
 469 
 470             if (c == null) {
 471 
 472                 LoadedModule loadedModule = findLoadedModule(cn);
 473 
 474                 if (loadedModule != null) {
 475 
 476                     // class is in module defined to this class loader
 477                     c = findClassInModuleOrNull(loadedModule, cn);
 478 
 479                 } else {
 480 
 481                     // type in another module or visible via the parent loader
 482                     String pn = packageName(cn);
 483                     ClassLoader loader = remotePackageToLoader.get(pn);
 484                     if (loader == null) {
 485                         // type not in a module read by any of the modules
 486                         // defined to this loader, so delegate to parent
 487                         // class loader
 488                         loader = parent;
 489                     }
 490                     if (loader == null) {
 491                         c = BootLoader.loadClassOrNull(cn);
 492                     } else {
 493                         c = loader.loadClass(cn);
 494                     }
 495 
 496                 }
 497             }
 498 
 499             if (c == null)
 500                 throw new ClassNotFoundException(cn);
 501 
 502             if (resolve)
 503                 resolveClass(c);
 504 
 505             return c;
 506         }
 507     }
 508 
 509 
 510     /**
 511      * Finds the class with the specified binary name if in a module
 512      * defined to this ClassLoader.
 513      *
 514      * @return the resulting Class or {@code null} if not found
 515      */
 516     private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
 517         PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
 518         return AccessController.doPrivileged(pa, acc);
 519     }
 520 
 521     /**
 522      * Defines the given binary class name to the VM, loading the class
 523      * bytes from the given module.
 524      *
 525      * @return the resulting Class or {@code null} if an I/O error occurs
 526      */
 527     private Class<?> defineClass(String cn, LoadedModule loadedModule) {
 528         ModuleReader reader = moduleReaderFor(loadedModule.mref());
 529 
 530         try {
 531             // read class file
 532             String rn = cn.replace('.', '/').concat(".class");
 533             ByteBuffer bb = reader.read(rn).orElse(null);
 534             if (bb == null) {
 535                 // class not found
 536                 return null;
 537             }
 538 
 539             try {
 540                 return defineClass(cn, bb, loadedModule.codeSource());
 541             } finally {
 542                 reader.release(bb);
 543             }
 544 
 545         } catch (IOException ioe) {
 546             // TBD on how I/O errors should be propagated
 547             return null;
 548         }
 549     }
 550 
 551 
 552     // -- permissions
 553 
 554     /**
 555      * Returns the permissions for the given CodeSource.
 556      */
 557     @Override
 558     protected PermissionCollection getPermissions(CodeSource cs) {
 559         PermissionCollection perms = super.getPermissions(cs);
 560 
 561         URL url = cs.getLocation();
 562         if (url == null)
 563             return perms;
 564 
 565         // add the permission to access the resource
 566         try {
 567             Permission p = url.openConnection().getPermission();
 568             if (p != null) {
 569                 // for directories then need recursive access
 570                 if (p instanceof FilePermission) {
 571                     String path = p.getName();
 572                     if (path.endsWith(File.separator)) {
 573                         path += "-";
 574                         p = new FilePermission(path, "read");
 575                     }
 576                 }
 577                 perms.add(p);
 578             }
 579         } catch (IOException ioe) { }
 580 
 581         return perms;
 582     }
 583 
 584 
 585     // -- miscellaneous supporting methods
 586 
 587     /**
 588      * Find the candidate module for the given class name.
 589      * Returns {@code null} if none of the modules defined to this
 590      * class loader contain the API package for the class.
 591      */
 592     private LoadedModule findLoadedModule(String cn) {
 593         String pn = packageName(cn);
 594         return pn.isEmpty() ? null : localPackageToModule.get(pn);
 595     }
 596 
 597     /**
 598      * Returns the package name for the given class name
 599      */
 600     private String packageName(String cn) {
 601         int pos = cn.lastIndexOf('.');
 602         return (pos < 0) ? "" : cn.substring(0, pos);
 603     }
 604 
 605 
 606     /**
 607      * Returns the ModuleReader for the given module.
 608      */
 609     private ModuleReader moduleReaderFor(ModuleReference mref) {
 610         return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));
 611     }
 612 
 613     /**
 614      * Creates a ModuleReader for the given module.
 615      */
 616     private ModuleReader createModuleReader(ModuleReference mref) {
 617         try {
 618             return mref.open();
 619         } catch (IOException e) {
 620             // Return a null module reader to avoid a future class load
 621             // attempting to open the module again.
 622             return new NullModuleReader();
 623         }
 624     }
 625 
 626     /**
 627      * A ModuleReader that doesn't read any resources.
 628      */
 629     private static class NullModuleReader implements ModuleReader {
 630         @Override
 631         public Optional<URI> find(String name) {
 632             return Optional.empty();
 633         }
 634         @Override
 635         public Stream<String> list() {
 636             return Stream.empty();
 637         }
 638         @Override
 639         public void close() {
 640             throw new InternalError("Should not get here");
 641         }
 642     }
 643 
 644     /**
 645      * Returns true if the given module opens the given package
 646      * unconditionally.
 647      *
 648      * @implNote This method currently iterates over each of the open
 649      * packages. This will be replaced once the ModuleDescriptor.Opens
 650      * API is updated.
 651      */
 652     private boolean isOpen(ModuleReference mref, String pn) {
 653         ModuleDescriptor descriptor = mref.descriptor();
 654         if (descriptor.isOpen() || descriptor.isAutomatic())
 655             return true;
 656         for (ModuleDescriptor.Opens opens : descriptor.opens()) {
 657             String source = opens.source();
 658             if (!opens.isQualified() && source.equals(pn)) {
 659                 return true;
 660             }
 661         }
 662         return false;
 663     }
 664 }