1 /*
  2  * Copyright (c) 2016, 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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package requires;
 25 
 26 import java.io.BufferedInputStream;
 27 import java.io.FileInputStream;
 28 import java.io.IOException;
 29 import java.io.InputStream;
 30 import java.io.File;
 31 import java.nio.charset.Charset;
 32 import java.nio.file.Files;
 33 import java.nio.file.Path;
 34 import java.nio.file.Paths;
 35 import java.nio.file.StandardOpenOption;
 36 import java.time.Instant;
 37 import java.util.ArrayList;
 38 import java.util.Collections;
 39 import java.util.HashMap;
 40 import java.util.List;
 41 import java.util.Map;
 42 import java.util.Properties;
 43 import java.util.Set;
 44 import java.util.concurrent.Callable;
 45 import java.util.concurrent.TimeUnit;
 46 import java.util.function.Predicate;
 47 import java.util.function.Supplier;
 48 import java.util.regex.Matcher;
 49 import java.util.regex.Pattern;
 50 import java.util.stream.Stream;
 51 
 52 import jdk.internal.foreign.CABI;
 53 import jdk.internal.misc.PreviewFeatures;
 54 import jdk.test.whitebox.code.Compiler;
 55 import jdk.test.whitebox.cpuinfo.CPUInfo;
 56 import jdk.test.whitebox.gc.GC;
 57 import jdk.test.whitebox.WhiteBox;
 58 import jdk.test.lib.Platform;
 59 import jdk.test.lib.Container;
 60 
 61 /**
 62  * The Class to be invoked by jtreg prior Test Suite execution to
 63  * collect information about VM.
 64  * Do not use any APIs that may not be available in all target VMs.
 65  * Properties set by this Class will be available in the @requires expressions.
 66  */
 67 public class VMProps implements Callable<Map<String, String>> {
 68     // value known to jtreg as an indicator of error state
 69     private static final String ERROR_STATE = "__ERROR__";
 70 
 71     private static final String GC_PREFIX = "-XX:+Use";
 72     private static final String GC_SUFFIX = "GC";
 73 
 74     private static final WhiteBox WB = WhiteBox.getWhiteBox();
 75 
 76     private static class SafeMap {
 77         private final Map<String, String> map = new HashMap<>();
 78 
 79         public void put(String key, Supplier<String> s) {
 80             String value;
 81             try {
 82                 value = s.get();
 83             } catch (Throwable t) {
 84                 System.err.println("failed to get value for " + key);
 85                 t.printStackTrace(System.err);
 86                 value = ERROR_STATE + t;
 87             }
 88             map.put(key, value);
 89         }
 90     }
 91 
 92     /**
 93      * Collects information about VM properties.
 94      * This method will be invoked by jtreg.
 95      *
 96      * @return Map of property-value pairs.
 97      */
 98     @Override
 99     public Map<String, String> call() {
100         log("Entering call()");
101         SafeMap map = new SafeMap();
102         map.put("vm.flavor", this::vmFlavor);
103         map.put("vm.compMode", this::vmCompMode);
104         map.put("vm.bits", this::vmBits);
105         map.put("vm.flightRecorder", this::vmFlightRecorder);
106         map.put("vm.simpleArch", this::vmArch);
107         map.put("vm.debug", this::vmDebug);
108         map.put("vm.jvmci", this::vmJvmci);
109         map.put("vm.jvmci.enabled", this::vmJvmciEnabled);
110         map.put("vm.emulatedClient", this::vmEmulatedClient);
111         // vm.hasSA is "true" if the VM contains the serviceability agent
112         // and jhsdb.
113         map.put("vm.hasSA", this::vmHasSA);
114         // vm.hasJFR is "true" if JFR is included in the build of the VM and
115         // so tests can be executed.
116         map.put("vm.hasJFR", this::vmHasJFR);
117         map.put("vm.hasDTrace", this::vmHasDTrace);
118         map.put("vm.jvmti", this::vmHasJVMTI);
119         map.put("vm.cpu.features", this::cpuFeatures);
120         map.put("vm.pageSize", this::vmPageSize);
121         // vm.cds is true if the VM is compiled with cds support.
122         map.put("vm.cds", this::vmCDS);
123         map.put("vm.cds.default.archive.available", this::vmCDSDefaultArchiveAvailable);
124         map.put("vm.cds.nocoops.archive.available", this::vmCDSNocoopsArchiveAvailable);
125         map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders);
126         map.put("vm.cds.supports.aot.class.linking", this::vmCDSSupportsAOTClassLinking);
127         map.put("vm.cds.supports.aot.code.caching", this::vmCDSSupportsAOTCodeCaching);
128         map.put("vm.cds.write.archived.java.heap", this::vmCDSCanWriteArchivedJavaHeap);
129         map.put("vm.cds.write.mapped.java.heap", this::vmCDSCanWriteMappedArchivedJavaHeap);
130         map.put("vm.cds.write.streamed.java.heap", this::vmCDSCanWriteStreamedArchivedJavaHeap);
131         map.put("vm.continuations", this::vmContinuations);
132         // vm.graal.enabled is true if Graal is used as JIT
133         map.put("vm.graal.enabled", this::isGraalEnabled);
134         // jdk.hasLibgraal is true if the libgraal shared library file is present
135         map.put("jdk.hasLibgraal", this::hasLibgraal);
136         map.put("java.enablePreview", this::isPreviewEnabled);
137         map.put("vm.libgraal.jit", this::isLibgraalJIT);
138         map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
139         map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
140         map.put("container.support", this::containerSupport);
141         map.put("systemd.support", this::systemdSupport);
142         map.put("vm.musl", this::isMusl);
143         map.put("vm.asan", this::isAsanEnabled);
144         map.put("vm.ubsan", this::isUbsanEnabled);
145         map.put("release.implementor", this::implementor);
146         map.put("jdk.containerized", this::jdkContainerized);
147         map.put("vm.flagless", this::isFlagless);
148         map.put("jdk.foreign.linker", this::jdkForeignLinker);
149         map.put("jdk.explodedImage", this::explodedImage);
150         map.put("jlink.packagedModules", this::packagedModules);
151         map.put("jdk.static", this::isStatic);
152         vmGC(map); // vm.gc.X = true/false
153         vmGCforCDS(map); // may set vm.gc
154         vmOptFinalFlags(map);
155         vmOptFinalIntxFlags(map);
156 
157         dump(map.map);
158         log("Leaving call()");
159         return map.map;
160     }
161 
162     /**
163      * Print a stack trace before returning error state;
164      * Used by the various helper functions which parse information from
165      * VM properties in the case where they don't find an expected property
166      * or a property doesn't conform to an expected format.
167      *
168      * @return {@link #ERROR_STATE}
169      */
170     private String errorWithMessage(String message) {
171         new Exception(message).printStackTrace();
172         return ERROR_STATE + message;
173     }
174 
175     /**
176      * @return vm.simpleArch value of "os.simpleArch" property of tested JDK.
177      */
178     protected String vmArch() {
179         String arch = System.getProperty("os.arch");
180         if (arch.equals("x86_64") || arch.equals("amd64")) {
181             return "x64";
182         } else if (arch.contains("86")) {
183             return "x86";
184         } else {
185             return arch;
186         }
187     }
188 
189     /**
190      * @return VM type value extracted from the "java.vm.name" property.
191      */
192     protected String vmFlavor() {
193         // E.g. "Java HotSpot(TM) 64-Bit Server VM"
194         String vmName = System.getProperty("java.vm.name");
195         if (vmName == null) {
196             return errorWithMessage("Can't get 'java.vm.name' property");
197         }
198 
199         Pattern startP = Pattern.compile(".* (\\S+) VM");
200         Matcher m = startP.matcher(vmName);
201         if (m.matches()) {
202             return m.group(1).toLowerCase();
203         }
204         return errorWithMessage("Can't get VM flavor from 'java.vm.name'");
205     }
206 
207     /**
208      * @return VM compilation mode extracted from the "java.vm.info" property.
209      */
210     protected String vmCompMode() {
211         // E.g. "mixed mode"
212         String vmInfo = System.getProperty("java.vm.info");
213         if (vmInfo == null) {
214             return errorWithMessage("Can't get 'java.vm.info' property");
215         }
216         vmInfo = vmInfo.toLowerCase();
217         if (vmInfo.contains("mixed mode")) {
218             return "Xmixed";
219         } else if (vmInfo.contains("compiled mode")) {
220             return "Xcomp";
221         } else if (vmInfo.contains("interpreted mode")) {
222             return "Xint";
223         } else {
224             return errorWithMessage("Can't get compilation mode from 'java.vm.info'");
225         }
226     }
227 
228     /**
229      * @return VM bitness, the value of the "sun.arch.data.model" property.
230      */
231     protected String vmBits() {
232         String dataModel = System.getProperty("sun.arch.data.model");
233         if (dataModel != null) {
234             return dataModel;
235         } else {
236             return errorWithMessage("Can't get 'sun.arch.data.model' property");
237         }
238     }
239 
240     /**
241      * @return "true" if Flight Recorder is enabled, "false" if is disabled.
242      */
243     protected String vmFlightRecorder() {
244         Boolean isFlightRecorder = WB.getBooleanVMFlag("FlightRecorder");
245         String startFROptions = WB.getStringVMFlag("StartFlightRecording");
246         if (isFlightRecorder != null && isFlightRecorder) {
247             return "true";
248         }
249         if (startFROptions != null && !startFROptions.isEmpty()) {
250             return "true";
251         }
252         return "false";
253     }
254 
255     /**
256      * @return debug level value extracted from the "jdk.debug" property.
257      */
258     protected String vmDebug() {
259         String debug = System.getProperty("jdk.debug");
260         if (debug != null) {
261             return "" + debug.contains("debug");
262         } else {
263             return errorWithMessage("Can't get 'jdk.debug' property");
264         }
265     }
266 
267     /**
268      * @return true if VM supports JVMCI and false otherwise
269      */
270     protected String vmJvmci() {
271         // builds with jvmci have this flag
272         if (WB.getBooleanVMFlag("EnableJVMCI") == null) {
273             return "false";
274         }
275 
276         // Not all GCs have full JVMCI support
277         if (!WB.isJVMCISupportedByGC()) {
278           return "false";
279         }
280 
281         // Interpreted mode cannot enable JVMCI
282         if (vmCompMode().equals("Xint")) {
283           return "false";
284         }
285 
286         return "true";
287     }
288 
289 
290     /**
291      * @return true if JVMCI is enabled
292      */
293     protected String vmJvmciEnabled() {
294         // builds with jvmci have this flag
295         if ("false".equals(vmJvmci())) {
296             return "false";
297         }
298 
299         return "" + Compiler.isJVMCIEnabled();
300     }
301 
302 
303     /**
304      * @return true if VM runs in emulated-client mode and false otherwise.
305      */
306     protected String vmEmulatedClient() {
307         String vmInfo = System.getProperty("java.vm.info");
308         if (vmInfo == null) {
309             return errorWithMessage("Can't get 'java.vm.info' property");
310         }
311         return "" + vmInfo.contains(" emulated-client");
312     }
313 
314     /**
315      * @return supported CPU features
316      */
317     protected String cpuFeatures() {
318         return CPUInfo.getFeatures().toString();
319     }
320 
321     /**
322      * For all existing GC sets vm.gc.X property.
323      * Example vm.gc.G1=true means:
324      *    VM supports G1
325      *    User either set G1 explicitely (-XX:+UseG1GC) or did not set any GC
326      *    G1 can be selected, i.e. it doesn't conflict with other VM flags
327      *
328      * @param map - property-value pairs
329      */
330     protected void vmGC(SafeMap map) {
331         var isJVMCIEnabled = Compiler.isJVMCIEnabled();
332         Predicate<GC> vmGCProperty = (GC gc) -> (gc.isSupported()
333                                         && (!isJVMCIEnabled || gc.isSupportedByJVMCICompiler())
334                                         && (gc.isSelected() || GC.isSelectedErgonomically()));
335         for (GC gc: GC.values()) {
336             map.put("vm.gc." + gc.name(), () -> "" + vmGCProperty.test(gc));
337         }
338     }
339 
340     /**
341      * "jtreg -vmoptions:-Dtest.cds.runtime.options=..." can be used to specify
342      * the GC type to be used when running with a CDS archive. Set "vm.gc" accordingly,
343      * so that tests that need to explicitly choose the GC type can be excluded
344      * with "@requires vm.gc == null".
345      *
346      * @param map - property-value pairs
347      */
348     protected void vmGCforCDS(SafeMap map) {
349         if (!GC.isSelectedErgonomically()) {
350             // The GC has been explicitly specified on the command line, so
351             // jtreg will set the "vm.gc" property. Let's not interfere with it.
352             return;
353         }
354 
355         String jtropts = System.getProperty("test.cds.runtime.options");
356         if (jtropts != null) {
357             for (String opt : jtropts.split(",")) {
358                 if (opt.startsWith(GC_PREFIX) && opt.endsWith(GC_SUFFIX)) {
359                     String gc = opt.substring(GC_PREFIX.length(), opt.length() - GC_SUFFIX.length());
360                     map.put("vm.gc", () -> gc);
361                 }
362             }
363         }
364     }
365 
366     /**
367      * Selected final flag.
368      *
369      * @param map - property-value pairs
370      * @param flagName - flag name
371      */
372     private void vmOptFinalFlag(SafeMap map, String flagName) {
373         map.put("vm.opt.final." + flagName,
374                 () -> String.valueOf(WB.getBooleanVMFlag(flagName)));
375     }
376 
377     /**
378      * Selected sets of final flags.
379      *
380      * @param map - property-value pairs
381      */
382     protected void vmOptFinalFlags(SafeMap map) {
383         vmOptFinalFlag(map, "ClassUnloading");
384         vmOptFinalFlag(map, "ClassUnloadingWithConcurrentMark");
385         vmOptFinalFlag(map, "CriticalJNINatives");
386         vmOptFinalFlag(map, "EnableJVMCI");
387         vmOptFinalFlag(map, "EliminateAllocations");
388         vmOptFinalFlag(map, "TieredCompilation");
389         vmOptFinalFlag(map, "UnlockExperimentalVMOptions");
390         vmOptFinalFlag(map, "UseAdaptiveSizePolicy");
391         vmOptFinalFlag(map, "UseCompressedOops");
392         vmOptFinalFlag(map, "UseLargePages");
393         vmOptFinalFlag(map, "UseTransparentHugePages");
394         vmOptFinalFlag(map, "UseVectorizedMismatchIntrinsic");
395     }
396 
397     /**
398      * Selected final flag of type intx.
399      *
400      * @param map - property-value pairs
401      * @param flagName - flag name
402      */
403     private void vmOptFinalIntxFlag(SafeMap map, String flagName) {
404         map.put("vm.opt.final." + flagName,
405                 () -> String.valueOf(WB.getIntxVMFlag(flagName)));
406     }
407 
408     /**
409      * Selected sets of final flags of type intx.
410      *
411      * @param map - property-value pairs
412      */
413     protected void vmOptFinalIntxFlags(SafeMap map) {
414         vmOptFinalIntxFlag(map, "MaxVectorSize");
415     }
416 
417     /**
418      * @return "true" if VM has a serviceability agent.
419      */
420     protected String vmHasSA() {
421         return "" + Platform.hasSA();
422     }
423 
424     /**
425      * @return "true" if the VM is compiled with Java Flight Recorder (JFR)
426      * support.
427      */
428     protected String vmHasJFR() {
429         return "" + WB.isJFRIncluded();
430     }
431 
432     /**
433      * @return "true" if the VM is compiled with JVMTI
434      */
435     protected String vmHasJVMTI() {
436         return "" + WB.isJVMTIIncluded();
437     }
438 
439     /**
440      * @return "true" if the VM is compiled with DTrace
441      */
442     protected String vmHasDTrace() {
443         return "" + WB.isDTraceIncluded();
444     }
445 
446     /**
447      * Check for CDS support.
448      *
449      * @return true if CDS is supported by the VM to be tested.
450      */
451     protected String vmCDS() {
452         boolean noJvmtiAdded = allFlags()
453                 .filter(s -> s.startsWith("-agentpath"))
454                 .findAny()
455                 .isEmpty();
456 
457         return "" + (noJvmtiAdded && WB.isCDSIncluded());
458     }
459 
460     /**
461      * Check for CDS default archive existence.
462      *
463      * @return true if CDS default archive classes.jsa exists in the JDK to be tested.
464      */
465     protected String vmCDSDefaultArchiveAvailable() {
466         Path archive = Paths.get(System.getProperty("java.home"), "lib", "server", "classes.jsa");
467         return "" + ("true".equals(vmCDS()) && Files.exists(archive));
468     }
469 
470     /**
471      * Check for CDS no compressed oops archive existence.
472      *
473      * @return true if CDS archive classes_nocoops.jsa exists in the JDK to be tested.
474      */
475     protected String vmCDSNocoopsArchiveAvailable() {
476         Path archive = Paths.get(System.getProperty("java.home"), "lib", "server", "classes_nocoops.jsa");
477         return "" + ("true".equals(vmCDS()) && Files.exists(archive));
478     }
479 
480     /**
481      * Check for CDS support for custom loaders.
482      *
483      * @return true if CDS provides support for customer loader in the VM to be tested.
484      */
485     protected String vmCDSForCustomLoaders() {
486         return "" + ("true".equals(vmCDS()) && Platform.areCustomLoadersSupportedForCDS());
487     }
488 
489     /**
490      * @return true if it's possible for "java -Xshare:dump" to write Java heap objects
491      *         with the current set of jtreg VM options. For example, false will be returned
492      *         if -XX:-UseCompressedClassPointers is specified.
493      */
494     protected String vmCDSCanWriteArchivedJavaHeap() {
495         return "" + ("true".equals(vmCDS()) && WB.canWriteJavaHeapArchive());
496     }
497 
498     /**
499      * @return true if it's possible for "java -Xshare:dump" to write Java heap objects
500      *         with the current set of jtreg VM options. For example, false will be returned
501      *         if -XX:-UseCompressedClassPointers is specified.
502      */
503     protected String vmCDSCanWriteMappedArchivedJavaHeap() {
504         return "" + ("true".equals(vmCDS()) && WB.canWriteMappedJavaHeapArchive());
505     }
506 
507     /**
508      * @return true if it's possible for "java -Xshare:dump" to write Java heap objects
509      *         with the current set of jtreg VM options. For example, false will be returned
510      *         if -XX:-UseCompressedClassPointers is specified.
511      */
512     protected String vmCDSCanWriteStreamedArchivedJavaHeap() {
513         return "" + ("true".equals(vmCDS()) && WB.canWriteStreamedJavaHeapArchive());
514     }
515 
516     /**
517      * @return true if this VM can support the -XX:AOTClassLinking option
518      */
519     protected String vmCDSSupportsAOTClassLinking() {
520       // Currently, the VM supports AOTClassLinking as long as it's able to write archived java heap.
521       return vmCDSCanWriteArchivedJavaHeap();
522     }
523 
524     /**
525      * @return true if this VM can support the AOT Code Caching
526      */
527     protected String vmCDSSupportsAOTCodeCaching() {
528       if ("true".equals(vmCDSSupportsAOTClassLinking()) &&
529           !"zero".equals(vmFlavor()) &&
530           "false".equals(vmJvmciEnabled()) &&
531           (Platform.isX64() || Platform.isAArch64())) {
532         return "true";
533       } else {
534         return "false";
535       }
536     }
537 
538     /**
539      * @return "true" if this VM supports continuations.
540      */
541     protected String vmContinuations() {
542         if (WB.getBooleanVMFlag("VMContinuations")) {
543             return "true";
544         } else {
545             return "false";
546         }
547     }
548 
549     /**
550      * @return System page size in bytes.
551      */
552     protected String vmPageSize() {
553         return "" + WB.getVMPageSize();
554     }
555 
556     /**
557      * Check if Graal is used as JIT compiler.
558      *
559      * @return true if Graal is used as JIT compiler.
560      */
561     protected String isGraalEnabled() {
562         return "" + Compiler.isGraalEnabled();
563     }
564 
565     /**
566      * Check if the libgraal shared library file is present.
567      *
568      * @return true if the libgraal shared library file is present.
569      */
570     protected String hasLibgraal() {
571         return "" + WB.hasLibgraal();
572     }
573 
574     /**
575      * Check if libgraal is used as JIT compiler.
576      *
577      * @return true if libgraal is used as JIT compiler.
578      */
579     protected String isLibgraalJIT() {
580         return "" + Compiler.isLibgraalJIT();
581     }
582 
583     /**
584      * Check if Compiler1 is present.
585      *
586      * @return true if Compiler1 is used as JIT compiler, either alone or as part of the tiered system.
587      */
588     protected String isCompiler1Enabled() {
589         return "" + Compiler.isC1Enabled();
590     }
591 
592     /**
593      * Check if Compiler2 is present.
594      *
595      * @return true if Compiler2 is used as JIT compiler, either alone or as part of the tiered system.
596      */
597     protected String isCompiler2Enabled() {
598         return "" + Compiler.isC2Enabled();
599     }
600 
601     protected String isPreviewEnabled() {
602         return "" + PreviewFeatures.isEnabled();
603     }
604     /**
605      * A simple check for container support
606      *
607      * @return true if container is supported in a given environment
608      */
609     protected String containerSupport() {
610         log("Entering containerSupport()");
611 
612         boolean isSupported = false;
613         if (Platform.isLinux()) {
614            // currently container testing is only supported for Linux,
615            // on certain platforms
616 
617            String arch = System.getProperty("os.arch");
618 
619            if (Platform.isX64()) {
620               isSupported = true;
621            } else if (Platform.isAArch64()) {
622               isSupported = true;
623            } else if (Platform.isS390x()) {
624               isSupported = true;
625            } else if (arch.equals("ppc64le")) {
626               isSupported = true;
627            }
628         }
629 
630         log("containerSupport(): platform check: isSupported = " + isSupported);
631 
632         if (isSupported) {
633            try {
634               isSupported = checkProgramSupport("checkContainerSupport()", Container.ENGINE_COMMAND);
635            } catch (Exception e) {
636               isSupported = false;
637            }
638          }
639 
640         log("containerSupport(): returning isSupported = " + isSupported);
641         return "" + isSupported;
642     }
643 
644     /**
645      * A simple check for systemd support
646      *
647      * @return true if systemd is supported in a given environment
648      */
649     protected String systemdSupport() {
650         log("Entering systemdSupport()");
651 
652         boolean isSupported = Platform.isLinux();
653         if (isSupported) {
654            try {
655               isSupported = checkProgramSupport("checkSystemdSupport()", "systemd-run");
656            } catch (Exception e) {
657               isSupported = false;
658            }
659          }
660 
661         log("systemdSupport(): returning isSupported = " + isSupported);
662         return "" + isSupported;
663     }
664 
665     // Configures process builder to redirect process stdout and stderr to a file.
666     // Returns file names for stdout and stderr.
667     private Map<String, String> redirectOutputToLogFile(String msg, ProcessBuilder pb, String fileNameBase) {
668         Map<String, String> result = new HashMap<>();
669         String timeStamp = Instant.now().toString().replace(":", "-").replace(".", "-");
670 
671         String stdoutFileName = String.format("./%s-stdout--%s.log", fileNameBase, timeStamp);
672         pb.redirectOutput(new File(stdoutFileName));
673         log(msg + ": child process stdout redirected to " + stdoutFileName);
674         result.put("stdout", stdoutFileName);
675 
676         String stderrFileName = String.format("./%s-stderr--%s.log", fileNameBase, timeStamp);
677         pb.redirectError(new File(stderrFileName));
678         log(msg + ": child process stderr redirected to " + stderrFileName);
679         result.put("stderr", stderrFileName);
680 
681         return result;
682     }
683 
684     private void printLogfileContent(Map<String, String> logFileNames) {
685         logFileNames.entrySet().stream()
686             .forEach(entry ->
687                 {
688                     log("------------- " + entry.getKey());
689                     try {
690                         Files.lines(Path.of(entry.getValue()))
691                             .forEach(line -> log(line));
692                     } catch (IOException ie) {
693                         log("Exception while reading file: " + ie);
694                     }
695                     log("-------------");
696                 });
697     }
698 
699     private boolean checkProgramSupport(String logString, String cmd) throws IOException, InterruptedException {
700         log(logString + ": entering");
701         ProcessBuilder pb = new ProcessBuilder("which", cmd);
702         Map<String, String> logFileNames =
703             redirectOutputToLogFile(logString + ": which " + cmd,
704                                                       pb, "which-cmd");
705         Process p = pb.start();
706         p.waitFor(10, TimeUnit.SECONDS);
707         int exitValue = p.exitValue();
708 
709         log(String.format("%s: exitValue = %s, pid = %s", logString, exitValue, p.pid()));
710         if (exitValue != 0) {
711             printLogfileContent(logFileNames);
712         }
713 
714         return (exitValue == 0);
715     }
716 
717     /**
718      * Checks musl libc.
719      *
720      * @return true if musl libc is used.
721      */
722     protected String isMusl() {
723         return Boolean.toString(WB.getLibcName().contains("musl"));
724     }
725 
726     // Sanitizer support
727     protected String isAsanEnabled() {
728         return "" + WB.isAsanEnabled();
729     }
730 
731     protected String isUbsanEnabled() {
732         return "" + WB.isUbsanEnabled();
733     }
734 
735     private String implementor() {
736         try (InputStream in = new BufferedInputStream(new FileInputStream(
737                 System.getProperty("java.home") + "/release"))) {
738             Properties properties = new Properties();
739             properties.load(in);
740             String implementorProperty = properties.getProperty("IMPLEMENTOR");
741             if (implementorProperty != null) {
742                 return implementorProperty.replace("\"", "");
743             }
744             return errorWithMessage("Can't get 'IMPLEMENTOR' property from 'release' file");
745         } catch (IOException e) {
746             e.printStackTrace();
747             return errorWithMessage("Failed to read 'release' file " + e);
748         }
749     }
750 
751     private String jdkContainerized() {
752         String isEnabled = System.getenv("TEST_JDK_CONTAINERIZED");
753         return "" + "true".equalsIgnoreCase(isEnabled);
754     }
755 
756     private String explodedImage() {
757         try {
758             Path jmodFile = Path.of(System.getProperty("java.home"), "jmods", "java.base.jmod");
759             if (Files.exists(jmodFile)) {
760                 return Boolean.FALSE.toString();
761             } else {
762                 return Boolean.TRUE.toString();
763             }
764         } catch (Throwable t) {
765             t.printStackTrace();
766             return errorWithMessage("Error in explodedImage " + t);
767         }
768     }
769 
770     private String packagedModules() {
771         // Some jlink tests require packaged modules being present (jmods).
772         // For a runtime linkable image build packaged modules aren't present
773         try {
774             Path jmodsDir = Path.of(System.getProperty("java.home"), "jmods");
775             if (jmodsDir.toFile().exists()) {
776                 return Boolean.TRUE.toString();
777             } else {
778                 return Boolean.FALSE.toString();
779             }
780         } catch (Throwable t) {
781             return Boolean.FALSE.toString();
782         }
783     }
784 
785     /**
786      * Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags
787      * which JVM is started with don't affect its behavior "significantly".
788      * {@code TEST_VM_FLAGLESS} enviroment variable can be used to force this
789      * method to return true or false and allow or reject any flags.
790      *
791      * @return true if there are no JVM flags
792      */
793     private String isFlagless() {
794         boolean result = true;
795         String flagless = System.getenv("TEST_VM_FLAGLESS");
796         if (flagless != null) {
797             return "" + "true".equalsIgnoreCase(flagless);
798         }
799 
800         List<String> allFlags = allFlags().toList();
801 
802         // check -XX flags
803         var ignoredXXFlags = Set.of(
804                 // added by run-test framework
805                 "MaxRAMPercentage",
806                 // added by test environment
807                 "CreateCoredumpOnCrash"
808         );
809         result &= allFlags.stream()
810                           .filter(s -> s.startsWith("-XX:"))
811                           // map to names:
812                               // remove -XX:
813                               .map(s -> s.substring(4))
814                               // remove +/- from bool flags
815                               .map(s -> s.charAt(0) == '+' || s.charAt(0) == '-' ? s.substring(1) : s)
816                               // remove =.* from others
817                               .map(s -> s.contains("=") ? s.substring(0, s.indexOf('=')) : s)
818                           // skip known-to-be-there flags
819                           .filter(s -> !ignoredXXFlags.contains(s))
820                           .findAny()
821                           .isEmpty();
822 
823         // check -X flags
824         var ignoredXFlags = Set.of(
825                 // default, yet still seen to be explicitly set
826                 "mixed",
827                 // -XmxmNNNm added by run-test framework for non-hotspot tests
828                 "mx"
829         );
830         result &= allFlags.stream()
831                           .filter(s -> s.startsWith("-X") && !s.startsWith("-XX:"))
832                           // map to names:
833                           // remove -X
834                           .map(s -> s.substring(2))
835                           // remove :.* from flags with values
836                           .map(s -> s.contains(":") ? s.substring(0, s.indexOf(':')) : s)
837                           // remove size like 4G, 768m which might be set for non-hotspot tests
838                           .map(s -> s.replaceAll("(\\d+)[mMgGkK]", ""))
839                           // skip known-to-be-there flags
840                           .filter(s -> !ignoredXFlags.contains(s))
841                           .findAny()
842                           .isEmpty();
843 
844         return "" + result;
845     }
846 
847     private Stream<String> allFlags() {
848         return Stream.of((System.getProperty("test.vm.opts", "") + " " + System.getProperty("test.java.opts", "")).trim().split("\\s+"));
849     }
850 
851     /*
852      * A string indicating the foreign linker that is currently being used. See jdk.internal.foreign.CABI
853      * for valid values.
854      *
855      * "FALLBACK" and "UNSUPPORTED" are special values. The former indicates the fallback linker is
856      * being used. The latter indicates an unsupported platform.
857      */
858     private String jdkForeignLinker() {
859         return String.valueOf(CABI.current());
860     }
861 
862     private String isStatic() {
863         return Boolean.toString(WB.isStatic());
864     }
865 
866     /**
867      * Dumps the map to the file if the file name is given as the property.
868      * This functionality could be helpful to know context in the real
869      * execution.
870      *
871      * @param map
872      */
873     protected static void dump(Map<String, String> map) {
874         String dumpFileName = System.getProperty("vmprops.dump");
875         if (dumpFileName == null) {
876             return;
877         }
878         List<String> lines = new ArrayList<>();
879         map.forEach((k, v) -> lines.add(k + ":" + v));
880         Collections.sort(lines);
881         try {
882             Files.write(Paths.get(dumpFileName), lines,
883                     StandardOpenOption.APPEND, StandardOpenOption.CREATE);
884         } catch (IOException e) {
885             throw new RuntimeException("Failed to dump properties into '"
886                     + dumpFileName + "'", e);
887         }
888     }
889 
890     /**
891      * Log diagnostic message.
892      *
893      * @param msg
894      */
895     protected static void log(String msg) {
896         // Always log to a file.
897         logToFile(msg);
898 
899         // Also log to stderr; guarded by property to avoid excessive verbosity.
900         // By jtreg design stderr produced here will be visible
901         // in the output of a parent process. Note: stdout should not be used
902         // for logging as jtreg parses that output directly and only echoes it
903         // in the event of a failure.
904         if (Boolean.getBoolean("jtreg.log.vmprops")) {
905             System.err.println("VMProps: " + msg);
906         }
907     }
908 
909     /**
910      * Log diagnostic message to a file.
911      *
912      * @param msg
913      */
914     protected static void logToFile(String msg) {
915         String fileName = "./vmprops.log";
916         try {
917             Files.writeString(Paths.get(fileName), msg + "\n", Charset.forName("ISO-8859-1"),
918                     StandardOpenOption.APPEND, StandardOpenOption.CREATE);
919         } catch (IOException e) {
920             throw new RuntimeException("Failed to log into '" + fileName + "'", e);
921         }
922     }
923 
924     /**
925      * This method is for the testing purpose only.
926      *
927      * @param args
928      */
929     public static void main(String args[]) {
930         Map<String, String> map = new VMProps().call();
931         map.forEach((k, v) -> System.out.println(k + ": '" + v + "'"));
932     }
933 }