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 }