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             String resourcePath = "/" + module + "/" + name;
434             if (containsResource(resourcePath)) {
435                 URI u = JNUA.create("jrt", resourcePath);
436                 return Optional.of(u);
437             } else {
438                 return Optional.empty();
439             }
440         }
441 
442         @Override
443         public Optional<InputStream> open(String name) throws IOException {
444             return read(name).map(this::toInputStream);
445         }
446 
447         private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer?
448             try {
449                 int rem = bb.remaining();
450                 byte[] bytes = new byte[rem];
451                 bb.get(bytes);
452                 return new ByteArrayInputStream(bytes);
453             } finally {
454                 release(bb);
455             }
456         }
457 
458         /**
459          * Returns the node for the given resource if found. If the name references
460          * a non-resource node, then {@code null} is returned.
461          */
462         private ImageReader.Node findResource(ImageReader reader, String name) throws IOException {
463             Objects.requireNonNull(name);
464             if (closed) {
465                 throw new IOException("ModuleReader is closed");
466             }
467             String nodeName = "/modules/" + module + "/" + name;
468             ImageReader.Node node = reader.findNode(nodeName);
469             return (node != null && node.isResource()) ? node : null;
470         }
471 
472         @Override
473         public Optional<ByteBuffer> read(String name) throws IOException {
474             ImageReader reader = SystemImage.reader();
475             return Optional.ofNullable(findResource(reader, name))
476                     .map(reader::getResourceBuffer);
477         }
478 
479         @Override
480         public void release(ByteBuffer bb) {
481             Objects.requireNonNull(bb);
482             ImageReader.releaseByteBuffer(bb);
483         }
484 
485         @Override
486         public Stream<String> list() throws IOException {
487             if (closed)
488                 throw new IOException("ModuleReader is closed");
489 
490             Spliterator<String> s = new ModuleContentSpliterator(module);
491             return StreamSupport.stream(s, false);
492         }
493 
494         @Override
495         public void close() {
496             // nothing else to do
497             closed = true;
498         }
499     }
500 
501     /**
502      * A Spliterator for traversing the resources of a module linked into the
503      * run-time image.
504      */
505     private static class ModuleContentSpliterator implements Spliterator<String> {
506         final String moduleRoot;
507         final Deque<ImageReader.Node> stack;
508         Iterator<String> iterator;
509 
510         ModuleContentSpliterator(String module) throws IOException {
511             moduleRoot = "/modules/" + module;
512             stack = new ArrayDeque<>();
513 
514             // push the root node to the stack to get started
515             ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot);
516             if (dir == null || !dir.isDirectory())
517                 throw new IOException(moduleRoot + " not a directory");
518             stack.push(dir);
519             iterator = Collections.emptyIterator();
520         }
521 
522         /**
523          * Returns the name of the next non-directory node or {@code null} if
524          * there are no remaining nodes to visit.
525          */
526         private String next() throws IOException {
527             for (;;) {
528                 while (iterator.hasNext()) {
529                     String name = iterator.next();
530                     ImageReader.Node node = SystemImage.reader().findNode(name);
531                     if (node.isDirectory()) {
532                         stack.push(node);
533                     } else {
534                         // strip /modules/$MODULE/ prefix
535                         return name.substring(moduleRoot.length() + 1);
536                     }
537                 }
538 
539                 if (stack.isEmpty()) {
540                     return null;
541                 } else {
542                     ImageReader.Node dir = stack.poll();
543                     assert dir.isDirectory();
544                     iterator = dir.getChildNames().iterator();
545                 }
546             }
547         }
548 
549         @Override
550         public boolean tryAdvance(Consumer<? super String> action) {
551             String next;
552             try {
553                 next = next();
554             } catch (IOException ioe) {
555                 throw new UncheckedIOException(ioe);
556             }
557             if (next != null) {
558                 action.accept(next);
559                 return true;
560             } else {
561                 return false;
562             }
563         }
564 
565         @Override
566         public Spliterator<String> trySplit() {
567             return null;
568         }
569 
570         @Override
571         public int characteristics() {
572             return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
573         }
574 
575         @Override
576         public long estimateSize() {
577             return Long.MAX_VALUE;
578         }
579     }
580 }