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.access.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     /**
 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 parentModuleLayers the parent ModuleLayers
 211      */
 212     public Loader initRemotePackageMap(Configuration cf,
 213                                        List<ModuleLayer> parentModuleLayers)
 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                     ModuleLayer layer = parentModuleLayers.stream()
 240                         .map(parent -> findModuleLayer(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<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) {
 290         return SharedSecrets.getJavaLangAccess().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      * 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         String pn = Resources.toPackageName(name);
 360         LoadedModule module = localPackageToModule.get(pn);
 361 
 362         if (module != null) {
 363             try {
 364                 URL url = findResource(module.name(), name);
 365                 if (url != null
 366                     && (name.endsWith(".class")
 367                         || url.toString().endsWith("/")
 368                         || isOpen(module.mref(), pn))) {
 369                     return url;
 370                 }
 371             } catch (IOException ioe) {
 372                 // ignore
 373             }
 374 
 375         } else {
 376             for (ModuleReference mref : nameToModule.values()) {
 377                 try {
 378                     URL url = findResource(mref.descriptor().name(), name);
 379                     if (url != null) return url;
 380                 } catch (IOException ioe) {
 381                     // ignore
 382                 }
 383             }
 384         }
 385 
 386         return null;
 387     }
 388 
 389     @Override
 390     public Enumeration<URL> findResources(String name) throws IOException {
 391         return Collections.enumeration(findResourcesAsList(name));
 392     }
 393 
 394     @Override
 395     public URL getResource(String name) {
 396         Objects.requireNonNull(name);
 397 
 398         // this loader
 399         URL url = findResource(name);
 400         if (url == null) {
 401             // parent loader
 402             if (parent != null) {
 403                 url = parent.getResource(name);
 404             } else {
 405                 url = BootLoader.findResource(name);
 406             }
 407         }
 408         return url;
 409     }
 410 
 411     @Override
 412     public Enumeration<URL> getResources(String name) throws IOException {
 413         Objects.requireNonNull(name);
 414 
 415         // this loader
 416         List<URL> urls = findResourcesAsList(name);
 417 
 418         // parent loader
 419         Enumeration<URL> e;
 420         if (parent != null) {
 421             e = parent.getResources(name);
 422         } else {
 423             e = BootLoader.findResources(name);
 424         }
 425 
 426         // concat the URLs with the URLs returned by the parent
 427         return new Enumeration<>() {
 428             final Iterator<URL> iterator = urls.iterator();
 429             @Override
 430             public boolean hasMoreElements() {
 431                 return (iterator.hasNext() || e.hasMoreElements());
 432             }
 433             @Override
 434             public URL nextElement() {
 435                 if (iterator.hasNext()) {
 436                     return iterator.next();
 437                 } else {
 438                     return e.nextElement();
 439                 }
 440             }
 441         };
 442     }
 443 
 444     /**
 445      * Finds the resources with the given name in this class loader.
 446      */
 447     private List<URL> findResourcesAsList(String name) throws IOException {
 448         String pn = Resources.toPackageName(name);
 449         LoadedModule module = localPackageToModule.get(pn);
 450         if (module != null) {
 451             URL url = findResource(module.name(), name);
 452             if (url != null
 453                     && (name.endsWith(".class")
 454                     || url.toString().endsWith("/")
 455                     || isOpen(module.mref(), pn))) {
 456                 return List.of(url);
 457             } else {
 458                 return Collections.emptyList();
 459             }
 460         } else {
 461             List<URL> urls = new ArrayList<>();
 462             for (ModuleReference mref : nameToModule.values()) {
 463                 URL url = findResource(mref.descriptor().name(), name);
 464                 if (url != null) {
 465                     urls.add(url);
 466                 }
 467             }
 468             return urls;
 469         }
 470     }
 471 
 472 
 473     // -- finding/loading classes
 474 
 475     /**
 476      * Finds the class with the specified binary name.
 477      */
 478     @Override
 479     protected Class<?> findClass(String cn) throws ClassNotFoundException {
 480         Class<?> c = null;
 481         LoadedModule loadedModule = findLoadedModule(cn);
 482         if (loadedModule != null)
 483             c = findClassInModuleOrNull(loadedModule, cn);
 484         if (c == null)
 485             throw new ClassNotFoundException(cn);
 486         return c;
 487     }
 488 
 489     /**
 490      * Finds the class with the specified binary name in the given module.
 491      * This method returns {@code null} if the class cannot be found.
 492      */
 493     @Override
 494     protected Class<?> findClass(String mn, String cn) {
 495         Class<?> c = null;
 496         LoadedModule loadedModule = findLoadedModule(cn);
 497         if (loadedModule != null && loadedModule.name().equals(mn))
 498             c = findClassInModuleOrNull(loadedModule, cn);
 499         return c;
 500     }
 501 
 502     /**
 503      * Loads the class with the specified binary name.
 504      */
 505     @Override
 506     protected Class<?> loadClass(String cn, boolean resolve)
 507         throws ClassNotFoundException
 508     {
 509         SecurityManager sm = System.getSecurityManager();
 510         if (sm != null) {
 511             String pn = packageName(cn);
 512             if (!pn.isEmpty()) {
 513                 sm.checkPackageAccess(pn);
 514             }
 515         }
 516 
 517         synchronized (getClassLoadingLock(cn)) {
 518             // check if already loaded
 519             Class<?> c = findLoadedClass(cn);
 520 
 521             if (c == null) {
 522 
 523                 LoadedModule loadedModule = findLoadedModule(cn);
 524 
 525                 if (loadedModule != null) {
 526 
 527                     // class is in module defined to this class loader
 528                     c = findClassInModuleOrNull(loadedModule, cn);
 529 
 530                 } else {
 531 
 532                     // type in another module or visible via the parent loader
 533                     String pn = packageName(cn);
 534                     ClassLoader loader = remotePackageToLoader.get(pn);
 535                     if (loader == null) {
 536                         // type not in a module read by any of the modules
 537                         // defined to this loader, so delegate to parent
 538                         // class loader
 539                         loader = parent;
 540                     }
 541                     if (loader == null) {
 542                         c = BootLoader.loadClassOrNull(cn);
 543                     } else {
 544                         c = loader.loadClass(cn);
 545                     }
 546 
 547                 }
 548             }
 549 
 550             if (c == null)
 551                 throw new ClassNotFoundException(cn);
 552 
 553             if (resolve)
 554                 resolveClass(c);
 555 
 556             return c;
 557         }
 558     }
 559 
 560 
 561     /**
 562      * Finds the class with the specified binary name if in a module
 563      * defined to this ClassLoader.
 564      *
 565      * @return the resulting Class or {@code null} if not found
 566      */
 567     private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) {
 568         PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
 569         return AccessController.doPrivileged(pa, acc);
 570     }
 571 
 572     /**
 573      * Defines the given binary class name to the VM, loading the class
 574      * bytes from the given module.
 575      *
 576      * @return the resulting Class or {@code null} if an I/O error occurs
 577      */
 578     private Class<?> defineClass(String cn, LoadedModule loadedModule) {
 579         ModuleReader reader = moduleReaderFor(loadedModule.mref());
 580 
 581         try {
 582             // read class file
 583             String rn = cn.replace('.', '/').concat(".class");
 584             ByteBuffer bb = reader.read(rn).orElse(null);
 585             if (bb == null) {
 586                 // class not found
 587                 return null;
 588             }
 589 
 590             try {
 591                 return defineClass(cn, bb, loadedModule.codeSource());
 592             } finally {
 593                 reader.release(bb);
 594             }
 595 
 596         } catch (IOException ioe) {
 597             // TBD on how I/O errors should be propagated
 598             return null;
 599         }
 600     }
 601 
 602 
 603     // -- permissions
 604 
 605     /**
 606      * Returns the permissions for the given CodeSource.
 607      */
 608     @Override
 609     protected PermissionCollection getPermissions(CodeSource cs) {
 610         PermissionCollection perms = super.getPermissions(cs);
 611 
 612         URL url = cs.getLocation();
 613         if (url == null)
 614             return perms;
 615 
 616         // add the permission to access the resource
 617         try {
 618             Permission p = url.openConnection().getPermission();
 619             if (p != null) {
 620                 // for directories then need recursive access
 621                 if (p instanceof FilePermission) {
 622                     String path = p.getName();
 623                     if (path.endsWith(File.separator)) {
 624                         path += "-";
 625                         p = new FilePermission(path, "read");
 626                     }
 627                 }
 628                 perms.add(p);
 629             }
 630         } catch (IOException ioe) { }
 631 
 632         return perms;
 633     }
 634 
 635 
 636     // -- miscellaneous supporting methods
 637 
 638     /**
 639      * Find the candidate module for the given class name.
 640      * Returns {@code null} if none of the modules defined to this
 641      * class loader contain the API package for the class.
 642      */
 643     private LoadedModule findLoadedModule(String cn) {
 644         String pn = packageName(cn);
 645         return pn.isEmpty() ? null : localPackageToModule.get(pn);
 646     }
 647 
 648     /**
 649      * Returns the package name for the given class name
 650      */
 651     private String packageName(String cn) {
 652         int pos = cn.lastIndexOf('.');
 653         return (pos < 0) ? "" : cn.substring(0, pos);
 654     }
 655 
 656 
 657     /**
 658      * Returns the ModuleReader for the given module.
 659      */
 660     private ModuleReader moduleReaderFor(ModuleReference mref) {
 661         return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref));
 662     }
 663 
 664     /**
 665      * Creates a ModuleReader for the given module.
 666      */
 667     private ModuleReader createModuleReader(ModuleReference mref) {
 668         try {
 669             return mref.open();
 670         } catch (IOException e) {
 671             // Return a null module reader to avoid a future class load
 672             // attempting to open the module again.
 673             return new NullModuleReader();
 674         }
 675     }
 676 
 677     /**
 678      * A ModuleReader that doesn't read any resources.
 679      */
 680     private static class NullModuleReader implements ModuleReader {
 681         @Override
 682         public Optional<URI> find(String name) {
 683             return Optional.empty();
 684         }
 685         @Override
 686         public Stream<String> list() {
 687             return Stream.empty();
 688         }
 689         @Override
 690         public void close() {
 691             throw new InternalError("Should not get here");
 692         }
 693     }
 694 
 695     /**
 696      * Returns true if the given module opens the given package
 697      * unconditionally.
 698      *
 699      * @implNote This method currently iterates over each of the open
 700      * packages. This will be replaced once the ModuleDescriptor.Opens
 701      * API is updated.
 702      */
 703     private boolean isOpen(ModuleReference mref, String pn) {
 704         ModuleDescriptor descriptor = mref.descriptor();
 705         if (descriptor.isOpen() || descriptor.isAutomatic())
 706             return true;
 707         for (ModuleDescriptor.Opens opens : descriptor.opens()) {
 708             String source = opens.source();
 709             if (!opens.isQualified() && source.equals(pn)) {
 710                 return true;
 711             }
 712         }
 713         return false;
 714     }
 715 }