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