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 }