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