1 /* 2 * Copyright (c) 2015, 2024, 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 package jdk.internal.module; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.UncheckedIOException; 31 import java.lang.module.ModuleDescriptor; 32 import java.lang.module.ModuleFinder; 33 import java.lang.module.ModuleReader; 34 import java.lang.module.ModuleReference; 35 import java.lang.reflect.Constructor; 36 import java.net.URI; 37 import java.net.URLConnection; 38 import java.nio.ByteBuffer; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.util.ArrayDeque; 42 import java.util.Collections; 43 import java.util.Deque; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.Iterator; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.Optional; 50 import java.util.Set; 51 import java.util.Spliterator; 52 import java.util.function.Consumer; 53 import java.util.function.Supplier; 54 import java.util.stream.Stream; 55 import java.util.stream.StreamSupport; 56 57 import jdk.internal.jimage.ImageLocation; 58 import jdk.internal.jimage.ImageReader; 59 import jdk.internal.jimage.ImageReaderFactory; 60 import jdk.internal.access.JavaNetUriAccess; 61 import jdk.internal.access.SharedSecrets; 62 import jdk.internal.util.StaticProperty; 63 import jdk.internal.module.ModuleHashes.HashSupplier; 64 65 /** 66 * The factory for SystemModules objects and for creating ModuleFinder objects 67 * that find modules in the runtime image. 68 * 69 * This class supports initializing the module system when the runtime is an 70 * images build, an exploded build, or an images build with java.base patched 71 * by an exploded java.base. It also supports a testing mode that re-parses 72 * the module-info.class resources in the run-time image. 73 */ 74 75 public final class SystemModuleFinders { 76 private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess(); 77 78 private static final boolean USE_FAST_PATH; 79 static { 80 String value = System.getProperty("jdk.system.module.finder.disableFastPath"); 81 if (value == null) { 82 USE_FAST_PATH = true; 83 } else { 84 USE_FAST_PATH = !value.isEmpty() && !Boolean.parseBoolean(value); 85 } 86 } 87 88 // cached ModuleFinder returned from ofSystem 89 private static volatile ModuleFinder cachedSystemModuleFinder; 90 91 private SystemModuleFinders() { } 92 93 /** 94 * Returns the SystemModules object to reconstitute all modules. Returns 95 * null if this is an exploded build or java.base is patched by an exploded 96 * build. 97 */ 98 static SystemModules allSystemModules() { 99 if (USE_FAST_PATH) { 100 return SystemModulesMap.allSystemModules(); 101 } else { 102 return null; 103 } 104 } 105 106 /** 107 * Returns a SystemModules object to reconstitute the modules for the 108 * given initial module. If the initial module is null then return the 109 * SystemModules object to reconstitute the default modules. 110 * 111 * Return null if there is no SystemModules class for the initial module, 112 * this is an exploded build, or java.base is patched by an exploded build. 113 */ 114 static SystemModules systemModules(String initialModule) { 115 if (USE_FAST_PATH) { 116 if (initialModule == null) { 117 return SystemModulesMap.defaultSystemModules(); 118 } 119 120 String[] initialModules = SystemModulesMap.moduleNames(); 121 for (int i = 0; i < initialModules.length; i++) { 122 String moduleName = initialModules[i]; 123 if (initialModule.equals(moduleName)) { 124 String cn = SystemModulesMap.classNames()[i]; 125 try { 126 // one-arg Class.forName as java.base may not be defined 127 Constructor<?> ctor = Class.forName(cn).getConstructor(); 128 return (SystemModules) ctor.newInstance(); 129 } catch (Exception e) { 130 throw new InternalError(e); 131 } 132 } 133 } 134 } 135 return null; 136 } 137 138 /** 139 * Returns a ModuleFinder that is backed by the given SystemModules object. 140 * 141 * @apiNote The returned ModuleFinder is thread safe. 142 */ 143 static ModuleFinder of(SystemModules systemModules) { 144 ModuleDescriptor[] descriptors = systemModules.moduleDescriptors(); 145 ModuleTarget[] targets = systemModules.moduleTargets(); 146 ModuleHashes[] recordedHashes = systemModules.moduleHashes(); 147 ModuleResolution[] moduleResolutions = systemModules.moduleResolutions(); 148 149 int moduleCount = descriptors.length; 150 ModuleReference[] mrefs = new ModuleReference[moduleCount]; 151 @SuppressWarnings(value = {"rawtypes", "unchecked"}) 152 Map.Entry<String, ModuleReference>[] map 153 = (Map.Entry<String, ModuleReference>[])new Map.Entry[moduleCount]; 154 155 Map<String, byte[]> nameToHash = generateNameToHash(recordedHashes); 156 157 for (int i = 0; i < moduleCount; i++) { 158 String name = descriptors[i].name(); 159 HashSupplier hashSupplier = hashSupplier(nameToHash, name); 160 ModuleReference mref = toModuleReference(descriptors[i], 161 targets[i], 162 recordedHashes[i], 163 hashSupplier, 164 moduleResolutions[i]); 165 mrefs[i] = mref; 166 map[i] = Map.entry(name, mref); 167 } 168 169 return new SystemModuleFinder(mrefs, map); 170 } 171 172 /** 173 * Returns the ModuleFinder to find all system modules. Supports both 174 * images and exploded builds. 175 * 176 * @apiNote Used by ModuleFinder.ofSystem() 177 */ 178 public static ModuleFinder ofSystem() { 179 ModuleFinder finder = cachedSystemModuleFinder; 180 if (finder != null) { 181 return finder; 182 } 183 184 // probe to see if this is an images build 185 String home = StaticProperty.javaHome(); 186 Path modules = Path.of(home, "lib", "modules"); 187 if (Files.isRegularFile(modules)) { 188 if (USE_FAST_PATH) { 189 SystemModules systemModules = allSystemModules(); 190 if (systemModules != null) { 191 finder = of(systemModules); 192 } 193 } 194 195 // fall back to parsing the module-info.class files in image 196 if (finder == null) { 197 finder = ofModuleInfos(); 198 } 199 200 cachedSystemModuleFinder = finder; 201 return finder; 202 203 } 204 205 // exploded build (do not cache module finder) 206 Path dir = Path.of(home, "modules"); 207 if (!Files.isDirectory(dir)) 208 throw new InternalError("Unable to detect the run-time image"); 209 return ModulePath.of(ModuleBootstrap.patcher(), dir); 210 } 211 212 /** 213 * Parses the module-info.class of all module in the runtime image and 214 * returns a ModuleFinder to find the modules. 215 * 216 * @apiNote The returned ModuleFinder is thread safe. 217 */ 218 private static ModuleFinder ofModuleInfos() { 219 // parse the module-info.class in every module 220 Map<String, ModuleInfo.Attributes> nameToAttributes = new HashMap<>(); 221 Map<String, byte[]> nameToHash = new HashMap<>(); 222 ImageReader reader = SystemImage.reader(); 223 for (String mn : reader.getModuleNames()) { 224 ImageLocation loc = reader.findLocation(mn, "module-info.class"); 225 ModuleInfo.Attributes attrs 226 = ModuleInfo.read(reader.getResourceBuffer(loc), null); 227 228 nameToAttributes.put(mn, attrs); 229 ModuleHashes hashes = attrs.recordedHashes(); 230 if (hashes != null) { 231 for (String name : hashes.names()) { 232 nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name)); 233 } 234 } 235 } 236 237 // create a ModuleReference for each module 238 Set<ModuleReference> mrefs = new HashSet<>(); 239 Map<String, ModuleReference> nameToModule = new HashMap<>(); 240 for (Map.Entry<String, ModuleInfo.Attributes> e : nameToAttributes.entrySet()) { 241 String mn = e.getKey(); 242 ModuleInfo.Attributes attrs = e.getValue(); 243 HashSupplier hashSupplier = hashSupplier(nameToHash, mn); 244 ModuleReference mref = toModuleReference(attrs.descriptor(), 245 attrs.target(), 246 attrs.recordedHashes(), 247 hashSupplier, 248 attrs.moduleResolution()); 249 mrefs.add(mref); 250 nameToModule.put(mn, mref); 251 } 252 253 return new SystemModuleFinder(mrefs, nameToModule); 254 } 255 256 /** 257 * A ModuleFinder that finds module in an array or set of modules. 258 */ 259 private static class SystemModuleFinder implements ModuleFinder { 260 final Set<ModuleReference> mrefs; 261 final Map<String, ModuleReference> nameToModule; 262 263 SystemModuleFinder(ModuleReference[] array, 264 Map.Entry<String, ModuleReference>[] map) { 265 this.mrefs = Set.of(array); 266 this.nameToModule = Map.ofEntries(map); 267 } 268 269 SystemModuleFinder(Set<ModuleReference> mrefs, 270 Map<String, ModuleReference> nameToModule) { 271 this.mrefs = Set.copyOf(mrefs); 272 this.nameToModule = Map.copyOf(nameToModule); 273 } 274 275 @Override 276 public Optional<ModuleReference> find(String name) { 277 Objects.requireNonNull(name); 278 return Optional.ofNullable(nameToModule.get(name)); 279 } 280 281 @Override 282 public Set<ModuleReference> findAll() { 283 return mrefs; 284 } 285 } 286 287 /** 288 * Creates a ModuleReference to the system module. 289 */ 290 static ModuleReference toModuleReference(ModuleDescriptor descriptor, 291 ModuleTarget target, 292 ModuleHashes recordedHashes, 293 HashSupplier hasher, 294 ModuleResolution mres) { 295 String mn = descriptor.name(); 296 URI uri = JNUA.create("jrt", "/".concat(mn)); 297 298 Supplier<ModuleReader> readerSupplier = new Supplier<>() { 299 @Override 300 public ModuleReader get() { 301 return new SystemModuleReader(mn); 302 } 303 }; 304 305 ModuleReference mref = new ModuleReferenceImpl(descriptor, 306 uri, 307 readerSupplier, 308 null, 309 target, 310 recordedHashes, 311 hasher, 312 mres); 313 314 // may need a reference to a patched module if --patch-module specified 315 mref = ModuleBootstrap.patcher().patchIfNeeded(mref); 316 317 return mref; 318 } 319 320 /** 321 * Generates a map of module name to hash value. 322 */ 323 static Map<String, byte[]> generateNameToHash(ModuleHashes[] recordedHashes) { 324 Map<String, byte[]> nameToHash = null; 325 326 boolean secondSeen = false; 327 // record the hashes to build HashSupplier 328 for (ModuleHashes mh : recordedHashes) { 329 if (mh != null) { 330 // if only one module contain ModuleHashes, use it 331 if (nameToHash == null) { 332 nameToHash = mh.hashes(); 333 } else { 334 if (!secondSeen) { 335 nameToHash = new HashMap<>(nameToHash); 336 secondSeen = true; 337 } 338 nameToHash.putAll(mh.hashes()); 339 } 340 } 341 } 342 return (nameToHash != null) ? nameToHash : Map.of(); 343 } 344 345 /** 346 * Returns a HashSupplier that returns the hash of the given module. 347 */ 348 static HashSupplier hashSupplier(Map<String, byte[]> nameToHash, String name) { 349 byte[] hash = nameToHash.get(name); 350 if (hash != null) { 351 // avoid lambda here 352 return new HashSupplier() { 353 @Override 354 public byte[] generate(String algorithm) { 355 return hash; 356 } 357 }; 358 } else { 359 return null; 360 } 361 } 362 363 /** 364 * Holder class for the ImageReader. 365 */ 366 private static class SystemImage { 367 static final ImageReader READER = ImageReaderFactory.getImageReader(); 368 static ImageReader reader() { 369 return READER; 370 } 371 } 372 373 /** 374 * A ModuleReader for reading resources from a module linked into the 375 * run-time image. 376 */ 377 private static class SystemModuleReader implements ModuleReader { 378 private final String module; 379 private volatile boolean closed; 380 381 SystemModuleReader(String module) { 382 this.module = module; 383 } 384 385 /** 386 * Returns the ImageLocation for the given resource, {@code null} 387 * if not found. 388 */ 389 private ImageLocation findImageLocation(String name) throws IOException { 390 Objects.requireNonNull(name); 391 if (closed) 392 throw new IOException("ModuleReader is closed"); 393 ImageReader imageReader = SystemImage.reader(); 394 if (imageReader != null) { 395 return imageReader.findLocation(module, name); 396 } else { 397 // not an images build 398 return null; 399 } 400 } 401 402 /** 403 * Returns {@code true} if the given resource exists, {@code false} 404 * if not found. 405 */ 406 private boolean containsImageLocation(String name) throws IOException { 407 Objects.requireNonNull(name); 408 if (closed) 409 throw new IOException("ModuleReader is closed"); 410 ImageReader imageReader = SystemImage.reader(); 411 if (imageReader != null) { 412 return imageReader.verifyLocation(module, name); 413 } else { 414 // not an images build 415 return false; 416 } 417 } 418 419 @Override 420 public Optional<URI> find(String name) throws IOException { 421 if (containsImageLocation(name)) { 422 URI u = JNUA.create("jrt", "/" + module + "/" + name); 423 return Optional.of(u); 424 } else { 425 return Optional.empty(); 426 } 427 } 428 429 @Override 430 public Optional<InputStream> open(String name) throws IOException { 431 return read(name).map(this::toInputStream); 432 } 433 434 private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer? 435 try { 436 int rem = bb.remaining(); 437 byte[] bytes = new byte[rem]; 438 bb.get(bytes); 439 return new ByteArrayInputStream(bytes); 440 } finally { 441 release(bb); 442 } 443 } 444 445 @Override 446 public Optional<ByteBuffer> read(String name) throws IOException { 447 ImageLocation location = findImageLocation(name); 448 if (location != null) { 449 return Optional.of(SystemImage.reader().getResourceBuffer(location)); 450 } else { 451 return Optional.empty(); 452 } 453 } 454 455 @Override 456 public void release(ByteBuffer bb) { 457 Objects.requireNonNull(bb); 458 ImageReader.releaseByteBuffer(bb); 459 } 460 461 @Override 462 public Stream<String> list() throws IOException { 463 if (closed) 464 throw new IOException("ModuleReader is closed"); 465 466 Spliterator<String> s = new ModuleContentSpliterator(module); 467 return StreamSupport.stream(s, false); 468 } 469 470 @Override 471 public void close() { 472 // nothing else to do 473 closed = true; 474 } 475 } 476 477 /** 478 * A Spliterator for traversing the resources of a module linked into the 479 * run-time image. 480 */ 481 private static class ModuleContentSpliterator implements Spliterator<String> { 482 final String moduleRoot; 483 final Deque<ImageReader.Node> stack; 484 Iterator<ImageReader.Node> iterator; 485 486 ModuleContentSpliterator(String module) throws IOException { 487 moduleRoot = "/modules/" + module; 488 stack = new ArrayDeque<>(); 489 490 // push the root node to the stack to get started 491 ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot); 492 if (dir == null || !dir.isDirectory()) 493 throw new IOException(moduleRoot + " not a directory"); 494 stack.push(dir); 495 iterator = Collections.emptyIterator(); 496 } 497 498 /** 499 * Returns the name of the next non-directory node or {@code null} if 500 * there are no remaining nodes to visit. 501 */ 502 private String next() throws IOException { 503 for (;;) { 504 while (iterator.hasNext()) { 505 ImageReader.Node node = iterator.next(); 506 String name = node.getName(); 507 if (node.isDirectory()) { 508 // build node 509 ImageReader.Node dir = SystemImage.reader().findNode(name); 510 assert dir.isDirectory(); 511 stack.push(dir); 512 } else { 513 // strip /modules/$MODULE/ prefix 514 return name.substring(moduleRoot.length() + 1); 515 } 516 } 517 518 if (stack.isEmpty()) { 519 return null; 520 } else { 521 ImageReader.Node dir = stack.poll(); 522 assert dir.isDirectory(); 523 iterator = dir.getChildren().iterator(); 524 } 525 } 526 } 527 528 @Override 529 public boolean tryAdvance(Consumer<? super String> action) { 530 String next; 531 try { 532 next = next(); 533 } catch (IOException ioe) { 534 throw new UncheckedIOException(ioe); 535 } 536 if (next != null) { 537 action.accept(next); 538 return true; 539 } else { 540 return false; 541 } 542 } 543 544 @Override 545 public Spliterator<String> trySplit() { 546 return null; 547 } 548 549 @Override 550 public int characteristics() { 551 return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE; 552 } 553 554 @Override 555 public long estimateSize() { 556 return Long.MAX_VALUE; 557 } 558 } 559 }