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