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