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 
  64 
  65 /**
  66  * A class loader that loads classes and resources from a collection of
  67  * modules, or from a single module where the class loader is a member
  68  * of a pool of class loaders.
  69  *
  70  * <p> The delegation model used by this ClassLoader differs to the regular
  71  * delegation model. When requested to load a class then this ClassLoader first
  72  * maps the class name to its package name. If there a module defined to the
  73  * Loader containing the package then the class loader attempts to load from
  74  * that module. If the package is instead defined to a module in a "remote"
  75  * ClassLoader then this class loader delegates directly to that class loader.
  76  * The map of package name to remote class loader is created based on the
  77  * modules read by modules defined to this class loader. If the package is not
  78  * local or remote then this class loader will delegate to the parent class
  79  * loader. This allows automatic modules (for example) to link to types in the
  80  * unnamed module of the parent class loader.
  81  *
  82  * @see Layer#defineModulesWithOneLoader
  83  * @see Layer#defineModulesWithManyLoaders
  84  */
  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     // maps package name to a remote class loader, populated post initialization
 105     private final Map<String, ClassLoader> remotePackageToLoader
 106         = new ConcurrentHashMap<>();
 107 
 108     // maps a module reference to a module reader, populated lazily
 109     private final Map<ModuleReference, ModuleReader> moduleToReader
 110         = new ConcurrentHashMap<>();
 111 
 112     // ACC used when loading classes and resources */
 113     private final AccessControlContext acc;
 114 
 115     /**
 116      * A module defined/loaded to a {@code Loader}.
 117      */
 118     private static class LoadedModule {
 119         private final ModuleReference mref;
 120         private final URL url;          // may be null
 121         private final CodeSource cs;
 122 
 123         LoadedModule(ModuleReference mref) {
 124             URL url = null;
 125             if (mref.location().isPresent()) {
 126                 try {
 127                     url = mref.location().get().toURL();
 128                 } catch (MalformedURLException | IllegalArgumentException e) { }
 129             }
 130             this.mref = mref;
 131             this.url = url;
 132             this.cs = new CodeSource(url, (CodeSigner[]) null);
 133         }
 134 
 135         ModuleReference mref() { return mref; }
 136         String name() { return mref.descriptor().name(); }
 137         URL location() { return url; }
 138         CodeSource codeSource() { return cs; }
 139     }
 140 
 141 
 142     /**
 143      * Creates a {@code Loader} in a loader pool that loads classes/resources
 144      * from one module.
 145      */
 146     public Loader(ResolvedModule resolvedModule,
 147                   LoaderPool pool,
 148                   ClassLoader parent)
 149     {
 150         super("Loader-" + resolvedModule.name(), parent);
 151 
 152         this.pool = pool;
 153         this.parent = parent;
 154 
 155         ModuleReference mref = resolvedModule.reference();
 156         ModuleDescriptor descriptor = mref.descriptor();
 157         String mn = descriptor.name();
 158         this.nameToModule = Map.of(mn, mref);
 159 
 160         Map<String, LoadedModule> localPackageToModule = new HashMap<>();
 161         LoadedModule lm = new LoadedModule(mref);
 162         descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm));
 163         this.localPackageToModule = localPackageToModule;
 164 
 165         this.acc = AccessController.getContext();
 166     }
 167 
 168     /**
 169      * Creates a {@code Loader} that loads classes/resources from a collection
 170      * of modules.
 171      *
 172      * @throws IllegalArgumentException
 173      *         If two or more modules have the same package
 174      */
 175     public Loader(Collection<ResolvedModule> modules, ClassLoader parent) {
 176         super(parent);
 177 
 178         this.pool = null;
 179         this.parent = parent;
 180 
 181         Map<String, ModuleReference> nameToModule = new HashMap<>();
 182         Map<String, LoadedModule> localPackageToModule = new HashMap<>();
 183         for (ResolvedModule resolvedModule : modules) {
 184             ModuleReference mref = resolvedModule.reference();
 185             ModuleDescriptor descriptor = mref.descriptor();
 186             nameToModule.put(descriptor.name(), mref);
 187             descriptor.packages().forEach(pn -> {
 188                 LoadedModule lm = new LoadedModule(mref);
 189                 if (localPackageToModule.put(pn, lm) != null)
 190                     throw new IllegalArgumentException("Package "
 191                         + pn + " in more than one module");
 192             });
 193         }
 194         this.nameToModule = nameToModule;
 195         this.localPackageToModule = localPackageToModule;
 196 
 197         this.acc = AccessController.getContext();
 198     }
 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 parentLayers the parent Layers
 210      */
 211     public Loader initRemotePackageMap(Configuration cf,
 212                                        List<Layer> parentLayers)
 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                     Layer layer = parentLayers.stream()
 239                         .map(parent -> findLayer(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                 String target = resolvedModule.name();
 256                 ModuleDescriptor descriptor = other.reference().descriptor();
 257                 for (ModuleDescriptor.Exports e : descriptor.exports()) {
 258                     boolean delegate;
 259                     if (e.isQualified()) {
 260                         // qualified export in same configuration
 261                         delegate = (other.configuration() == cf)
 262                                 && e.targets().contains(target);
 263                     } else {
 264                         // unqualified
 265                         delegate = true;
 266                     }
 267 
 268                     if (delegate) {
 269                         String pn = e.source();
 270                         ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader);
 271                         if (l != null && l != loader) {
 272                             throw new IllegalArgumentException("Package "
 273                                 + pn + " cannot be imported from multiple loaders");
 274                         }
 275                     }
 276                 }
 277             }
 278 
 279         }
 280 
 281         return this;
 282     }
 283 
 284     /**
 285      * Find the layer corresponding to the given configuration in the tree
 286      * of layers rooted at the given parent.
 287      */
 288     private Optional<Layer> findLayer(Layer parent, Configuration cf) {
 289         return SharedSecrets.getJavaLangReflectModuleAccess().layers(parent)
 290                 .filter(l -> l.configuration() == cf)
 291                 .findAny();
 292     }
 293 
 294 
 295     /**
 296      * Returns the loader pool that this loader is in or {@code null} if this
 297      * loader is not in a loader pool.
 298      */
 299     public LoaderPool pool() {
 300         return pool;
 301     }
 302 
 303 
 304     // -- resources --
 305 
 306 
 307     /**
 308      * Returns a URL to a resource of the given name in a module defined to
 309      * this class loader.
 310      */
 311     @Override
 312     protected URL findResource(String mn, String name) throws IOException {
 313         ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null;
 314         if (mref == null)
 315             return null;   // not defined to this class loader
 316 
 317         // locate resource
 318         URL url = null;
 319         try {
 320             url = AccessController.doPrivileged(
 321                 new PrivilegedExceptionAction<URL>() {
 322                     @Override
 323                     public URL run() throws IOException {
 324                         Optional<URI> ouri = moduleReaderFor(mref).find(name);
 325                         if (ouri.isPresent()) {
 326                             try {
 327                                 return ouri.get().toURL();
 328                             } catch (MalformedURLException |
 329                                      IllegalArgumentException e) { }
 330                         }
 331                         return null;
 332                     }
 333                 });
 334         } catch (PrivilegedActionException pae) {
 335             throw (IOException) pae.getCause();
 336         }
 337 
 338         // check access with permissions restricted by ACC
 339         if (url != null && System.getSecurityManager() != null) {
 340             try {
 341                 URL urlToCheck = url;
 342                 url = AccessController.doPrivileged(
 343                     new PrivilegedExceptionAction<URL>() {
 344                         @Override
 345                         public URL run() throws IOException {
 346                             return URLClassPath.checkURL(urlToCheck);
 347                         }
 348                     }, acc);
 349             } catch (PrivilegedActionException pae) {
 350                 url = null;
 351             }
 352         }
 353 
 354         return url;
 355     }
 356 
 357     @Override
 358     public URL findResource(String name) {
 359         URL url = null;
 360         String pn = ResourceHelper.getPackageName(name);
 361         LoadedModule module = localPackageToModule.get(pn);
 362         if (module != null) {
 363             if (name.endsWith(".class") || isOpen(module.mref(), pn)) {
 364                 try {
 365                     url = findResource(module.name(), name);
 366                 } catch (IOException ioe) {
 367                     // ignore
 368                 }
 369             }
 370         } else {
 371             for (ModuleReference mref : nameToModule.values()) {
 372                 try {
 373                     url = findResource(mref.descriptor().name(), name);
 374                     if (url != null)
 375                         break;
 376                 } catch (IOException ioe) {
 377                     // ignore
 378                 }
 379             }
 380         }
 381         return url;
 382     }
 383 
 384     @Override
 385     public Enumeration<URL> findResources(String name) throws IOException {
 386         List<URL> urls = new ArrayList<>();
 387         String pn = ResourceHelper.getPackageName(name);
 388         LoadedModule module = localPackageToModule.get(pn);
 389         if (module != null) {
 390             if (name.endsWith(".class") || isOpen(module.mref(), pn)) {
 391                 try {
 392                     URL url = findResource(module.name(), name);
 393                     if (url != null)
 394                         urls.add(url);
 395                 } catch (IOException ioe) {
 396                     // ignore
 397                 }
 398             }
 399         } else {
 400             for (ModuleReference mref : nameToModule.values()) {
 401                 try {
 402                     URL url = findResource(mref.descriptor().name(), name);
 403                     if (url != null)
 404                         urls.add(url);
 405                 } catch (IOException ioe) {
 406                     // ignore
 407                 }
 408             }
 409         }
 410         return Collections.enumeration(urls);
 411     }
 412 
 413 
 414     // -- finding/loading classes
 415 
 416     /**
 417      * Finds the class with the specified binary name.
 418      */
 419     @Override
 420     protected Class<?> findClass(String cn) throws ClassNotFoundException {
 421         Class<?> c = null;
 422         LoadedModule loadedModule = findLoadedModule(cn);
 423         if (loadedModule != null)
 424             c = findClassInModuleOrNull(loadedModule, cn);
 425         if (c == null)
 426             throw new ClassNotFoundException(cn);
 427         return c;
 428     }
 429 
 430     /**
 431      * Finds the class with the specified binary name in a given module.
 432      * This method returns {@code null} if the class cannot be found.
 433      */
 434     @Override
 435     protected Class<?> findClass(String mn, String cn) {
 436         Class<?> c = null;
 437         LoadedModule loadedModule = findLoadedModule(cn);
 438         if (loadedModule != null && loadedModule.name().equals(mn))
 439             c = findClassInModuleOrNull(loadedModule, cn);
 440         return c;
 441     }
 442 
 443     /**
 444      * Loads the class with the specified binary name.
 445      */
 446     @Override
 447     protected Class<?> loadClass(String cn, boolean resolve)
 448         throws ClassNotFoundException
 449     {
 450         SecurityManager sm = System.getSecurityManager();
 451         if (sm != null) {
 452             String pn = packageName(cn);
 453             if (!pn.isEmpty()) {
 454                 sm.checkPackageAccess(pn);
 455             }
 456         }
 457 
 458         synchronized (getClassLoadingLock(cn)) {
 459             // check if already loaded
 460             Class<?> c = findLoadedClass(cn);
 461 
 462             if (c == null) {
 463 
 464                 LoadedModule loadedModule = findLoadedModule(cn);
 465 
 466                 if (loadedModule != null) {
 467 
 468                     // class is in module defined to this class loader
 469                     c = findClassInModuleOrNull(loadedModule, cn);
 470 
 471                 } else {
 472 
 473                     // type in another module or visible via the parent loader
 474                     String pn = packageName(cn);
 475                     ClassLoader loader = remotePackageToLoader.get(pn);
 476                     if (loader == null) {
 477                         // type not in a module read by any of the modules
 478                         // defined to this loader, so delegate to parent
 479                         // class loader
 480                         loader = parent;
 481                     }
 482                     if (loader == null) {
 483                         c = BootLoader.loadClassOrNull(cn);
 484                     } else {
 485                         c = loader.loadClass(cn);
 486                     }
 487 
 488                 }
 489             }
 490 
 491             if (c == null)
 492                 throw new ClassNotFoundException(cn);
 493 
 494             if (resolve)
 495                 resolveClass(c);
 496 
 497             return c;
 498         }
 499     }
 500 
 501 
 502     /**
 503      * Finds the class with the specified binary name if in a module
 504      * defined to this ClassLoader.
 505      *
 506      * @return the resulting Class or {@code null} if not found
 507      */
 508     private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
 509         PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
 510         return AccessController.doPrivileged(pa, acc);
 511     }
 512 
 513     /**
 514      * Defines the given binary class name to the VM, loading the class
 515      * bytes from the given module.
 516      *
 517      * @return the resulting Class or {@code null} if an I/O error occurs
 518      */
 519     private Class<?> defineClass(String cn, LoadedModule loadedModule) {
 520         ModuleReader reader = moduleReaderFor(loadedModule.mref());
 521 
 522         try {
 523             // read class file
 524             String rn = cn.replace('.', '/').concat(".class");
 525             ByteBuffer bb = reader.read(rn).orElse(null);
 526             if (bb == null) {
 527                 // class not found
 528                 return null;
 529             }
 530 
 531             try {
 532                 return defineClass(cn, bb, loadedModule.codeSource());
 533             } finally {
 534                 reader.release(bb);
 535             }
 536 
 537         } catch (IOException ioe) {
 538             // TBD on how I/O errors should be propagated
 539             return null;
 540         }
 541     }
 542 
 543 
 544     // -- permissions
 545 
 546     /**
 547      * Returns the permissions for the given CodeSource.
 548      */
 549     @Override
 550     protected PermissionCollection getPermissions(CodeSource cs) {
 551         PermissionCollection perms = super.getPermissions(cs);
 552 
 553         URL url = cs.getLocation();
 554         if (url == null)
 555             return perms;
 556 
 557         // add the permission to access the resource
 558         try {
 559             Permission p = url.openConnection().getPermission();
 560             if (p != null) {
 561                 // for directories then need recursive access
 562                 if (p instanceof FilePermission) {
 563                     String path = p.getName();
 564                     if (path.endsWith(File.separator)) {
 565                         path += "-";
 566                         p = new FilePermission(path, "read");
 567                     }
 568                 }
 569                 perms.add(p);
 570             }
 571         } catch (IOException ioe) { }
 572 
 573         return perms;
 574     }
 575 
 576 
 577     // -- miscellaneous supporting methods
 578 
 579     /**
 580      * Find the candidate module for the given class name.
 581      * Returns {@code null} if none of the modules defined to this
 582      * class loader contain the API package for the class.
 583      */
 584     private LoadedModule findLoadedModule(String cn) {
 585         String pn = packageName(cn);
 586         return pn.isEmpty() ? null : localPackageToModule.get(pn);
 587     }
 588 
 589     /**
 590      * Returns the package name for the given class name
 591      */
 592     private String packageName(String cn) {
 593         int pos = cn.lastIndexOf('.');
 594         return (pos < 0) ? "" : cn.substring(0, pos);
 595     }
 596 
 597 
 598     /**
 599      * Returns the ModuleReader for the given module.
 600      */
 601     private ModuleReader moduleReaderFor(ModuleReference mref) {
 602         return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));
 603     }
 604 
 605     /**
 606      * Creates a ModuleReader for the given module.
 607      */
 608     private ModuleReader createModuleReader(ModuleReference mref) {
 609         try {
 610             return mref.open();
 611         } catch (IOException e) {
 612             // Return a null module reader to avoid a future class load
 613             // attempting to open the module again.
 614             return new NullModuleReader();
 615         }
 616     }
 617 
 618     /**
 619      * A ModuleReader that doesn't read any resources.
 620      */
 621     private static class NullModuleReader implements ModuleReader {
 622         @Override
 623         public Optional<URI> find(String name) {
 624             return Optional.empty();
 625         }
 626         @Override
 627         public Stream<String> list() {
 628             return Stream.empty();
 629         }
 630         @Override
 631         public void close() {
 632             throw new InternalError("Should not get here");
 633         }
 634     }
 635 
 636     /**
 637      * Returns true if the given module opens the given package
 638      * unconditionally.
 639      *
 640      * @implNote This method currently iterates over each of the open
 641      * packages. This will be replaced once the ModuleDescriptor.Opens
 642      * API is updated.
 643      */
 644     private boolean isOpen(ModuleReference mref, String pn) {
 645         ModuleDescriptor descriptor = mref.descriptor();
 646         if (descriptor.isOpen())
 647             return true;
 648         for (ModuleDescriptor.Opens opens : descriptor.opens()) {
 649             String source = opens.source();
 650             if (!opens.isQualified() && source.equals(pn)) {
 651                 return true;
 652             }
 653         }
 654         return false;
 655     }
 656 }