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 * 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 }