1 /*
  2  * Copyright (c) 2016, 2021, 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.nio.file.Files;
 31 import java.nio.file.Path;
 32 import java.nio.file.Paths;
 33 import java.nio.file.StandardOpenOption;
 34 import java.util.ArrayList;
 35 import java.util.Collections;
 36 import java.util.HashMap;
 37 import java.util.List;
 38 import java.util.Map;
 39 import java.util.Properties;
 40 import java.util.Set;
 41 import java.util.concurrent.Callable;
 42 import java.util.concurrent.TimeUnit;
 43 import java.util.function.Supplier;
 44 import java.util.regex.Matcher;
 45 import java.util.regex.Pattern;
 46 
 47 import jdk.test.whitebox.code.Compiler;
 48 import jdk.test.whitebox.cpuinfo.CPUInfo;
 49 import jdk.test.whitebox.gc.GC;
 50 import jdk.test.whitebox.WhiteBox;
 51 import jdk.test.lib.Platform;
 52 import jdk.test.lib.Container;
 53 
 54 /**
 55  * The Class to be invoked by jtreg prior Test Suite execution to
 56  * collect information about VM.
 57  * Do not use any APIs that may not be available in all target VMs.
 58  * Properties set by this Class will be available in the @requires expressions.
 59  */
 60 public class VMProps implements Callable<Map<String, String>> {
 61     // value known to jtreg as an indicator of error state
 62     private static final String ERROR_STATE = "__ERROR__";
 63 
 64     private static final WhiteBox WB = WhiteBox.getWhiteBox();
 65 
 66     private static class SafeMap {
 67         private final Map<String, String> map = new HashMap<>();
 68 
 69         public void put(String key, Supplier<String> s) {
 70             String value;
 71             try {
 72                 value = s.get();
 73             } catch (Throwable t) {
 74                 System.err.println("failed to get value for " + key);
 75                 t.printStackTrace(System.err);
 76                 value = ERROR_STATE + t;
 77             }
 78             map.put(key, value);
 79         }
 80     }
 81 
 82     /**
 83      * Collects information about VM properties.
 84      * This method will be invoked by jtreg.
 85      *
 86      * @return Map of property-value pairs.
 87      */
 88     @Override
 89     public Map<String, String> call() {
 90         SafeMap map = new SafeMap();
 91         map.put("vm.flavor", this::vmFlavor);
 92         map.put("vm.compMode", this::vmCompMode);
 93         map.put("vm.bits", this::vmBits);
 94         map.put("vm.flightRecorder", this::vmFlightRecorder);
 95         map.put("vm.simpleArch", this::vmArch);
 96         map.put("vm.debug", this::vmDebug);
 97         map.put("vm.jvmci", this::vmJvmci);
 98         map.put("vm.emulatedClient", this::vmEmulatedClient);
 99         // vm.hasSA is "true" if the VM contains the serviceability agent
100         // and jhsdb.
101         map.put("vm.hasSA", this::vmHasSA);
102         // vm.hasJFR is "true" if JFR is included in the build of the VM and
103         // so tests can be executed.
104         map.put("vm.hasJFR", this::vmHasJFR);
105         map.put("vm.jvmti", this::vmHasJVMTI);
106         map.put("vm.cpu.features", this::cpuFeatures);
107         map.put("vm.pageSize", this::vmPageSize);
108         map.put("vm.rtm.cpu", this::vmRTMCPU);
109         map.put("vm.rtm.compiler", this::vmRTMCompiler);
110         // vm.cds is true if the VM is compiled with cds support.
111         map.put("vm.cds", this::vmCDS);
112         map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders);
113         map.put("vm.cds.write.archived.java.heap", this::vmCDSCanWriteArchivedJavaHeap);
114         // vm.graal.enabled is true if Graal is used as JIT
115         map.put("vm.graal.enabled", this::isGraalEnabled);
116         map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
117         map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
118         map.put("docker.support", this::dockerSupport);
119         map.put("vm.musl", this::isMusl);
120         map.put("release.implementor", this::implementor);
121         map.put("jdk.containerized", this::jdkContainerized);
122         map.put("vm.flagless", this::isFlagless);
123         vmGC(map); // vm.gc.X = true/false
124         vmGCforCDS(map); // may set vm.gc
125         vmOptFinalFlags(map);
126 
127         dump(map.map);
128         return map.map;
129     }
130 
131     /**
132      * Print a stack trace before returning error state;
133      * Used by the various helper functions which parse information from
134      * VM properties in the case where they don't find an expected property
135      * or a property doesn't conform to an expected format.
136      *
137      * @return {@link #ERROR_STATE}
138      */
139     private String errorWithMessage(String message) {
140         new Exception(message).printStackTrace();
141         return ERROR_STATE + message;
142     }
143 
144     /**
145      * @return vm.simpleArch value of "os.simpleArch" property of tested JDK.
146      */
147     protected String vmArch() {
148         String arch = System.getProperty("os.arch");
149         if (arch.equals("x86_64") || arch.equals("amd64")) {
150             return "x64";
151         } else if (arch.contains("86")) {
152             return "x86";
153         } else {
154             return arch;
155         }
156     }
157 
158     /**
159      * @return VM type value extracted from the "java.vm.name" property.
160      */
161     protected String vmFlavor() {
162         // E.g. "Java HotSpot(TM) 64-Bit Server VM"
163         String vmName = System.getProperty("java.vm.name");
164         if (vmName == null) {
165             return errorWithMessage("Can't get 'java.vm.name' property");
166         }
167 
168         Pattern startP = Pattern.compile(".* (\\S+) VM");
169         Matcher m = startP.matcher(vmName);
170         if (m.matches()) {
171             return m.group(1).toLowerCase();
172         }
173         return errorWithMessage("Can't get VM flavor from 'java.vm.name'");
174     }
175 
176     /**
177      * @return VM compilation mode extracted from the "java.vm.info" property.
178      */
179     protected String vmCompMode() {
180         // E.g. "mixed mode"
181         String vmInfo = System.getProperty("java.vm.info");
182         if (vmInfo == null) {
183             return errorWithMessage("Can't get 'java.vm.info' property");
184         }
185         vmInfo = vmInfo.toLowerCase();
186         if (vmInfo.contains("mixed mode")) {
187             return "Xmixed";
188         } else if (vmInfo.contains("compiled mode")) {
189             return "Xcomp";
190         } else if (vmInfo.contains("interpreted mode")) {
191             return "Xint";
192         } else {
193             return errorWithMessage("Can't get compilation mode from 'java.vm.info'");
194         }
195     }
196 
197     /**
198      * @return VM bitness, the value of the "sun.arch.data.model" property.
199      */
200     protected String vmBits() {
201         String dataModel = System.getProperty("sun.arch.data.model");
202         if (dataModel != null) {
203             return dataModel;
204         } else {
205             return errorWithMessage("Can't get 'sun.arch.data.model' property");
206         }
207     }
208 
209     /**
210      * @return "true" if Flight Recorder is enabled, "false" if is disabled.
211      */
212     protected String vmFlightRecorder() {
213         Boolean isFlightRecorder = WB.getBooleanVMFlag("FlightRecorder");
214         String startFROptions = WB.getStringVMFlag("StartFlightRecording");
215         if (isFlightRecorder != null && isFlightRecorder) {
216             return "true";
217         }
218         if (startFROptions != null && !startFROptions.isEmpty()) {
219             return "true";
220         }
221         return "false";
222     }
223 
224     /**
225      * @return debug level value extracted from the "jdk.debug" property.
226      */
227     protected String vmDebug() {
228         String debug = System.getProperty("jdk.debug");
229         if (debug != null) {
230             return "" + debug.contains("debug");
231         } else {
232             return errorWithMessage("Can't get 'jdk.debug' property");
233         }
234     }
235 
236     /**
237      * @return true if VM supports JVMCI and false otherwise
238      */
239     protected String vmJvmci() {
240         // builds with jvmci have this flag
241         if (WB.getBooleanVMFlag("EnableJVMCI") == null) {
242             return "false";
243         }
244         
245         if (vmCompMode().equals("Xint")) {
246             return "false";
247         }
248 
249         // Not all GCs have full JVMCI support
250         if (!WB.isJVMCISupportedByGC()) {
251           return "false";
252         }
253 
254         // Interpreted mode cannot enable JVMCI
255         if (vmCompMode().equals("Xint")) {
256           return "false";
257         }
258 
259         return "true";
260     }
261 
262     /**
263      * @return true if VM runs in emulated-client mode and false otherwise.
264      */
265     protected String vmEmulatedClient() {
266         String vmInfo = System.getProperty("java.vm.info");
267         if (vmInfo == null) {
268             return errorWithMessage("Can't get 'java.vm.info' property");
269         }
270         return "" + vmInfo.contains(" emulated-client");
271     }
272 
273     /**
274      * @return supported CPU features
275      */
276     protected String cpuFeatures() {
277         return CPUInfo.getFeatures().toString();
278     }
279 
280     /**
281      * For all existing GC sets vm.gc.X property.
282      * Example vm.gc.G1=true means:
283      *    VM supports G1
284      *    User either set G1 explicitely (-XX:+UseG1GC) or did not set any GC
285      *    G1 can be selected, i.e. it doesn't conflict with other VM flags
286      *
287      * @param map - property-value pairs
288      */
289     protected void vmGC(SafeMap map) {
290         var isJVMCIEnabled = Compiler.isJVMCIEnabled();
291         for (GC gc: GC.values()) {
292             map.put("vm.gc." + gc.name(),
293                     () -> "" + (gc.isSupported()
294                             && (!isJVMCIEnabled || gc.isSupportedByJVMCICompiler())
295                             && (gc.isSelected() || GC.isSelectedErgonomically())));
296         }
297     }
298 
299     /**
300      * "jtreg -vmoptions:-Dtest.cds.runtime.options=..." can be used to specify
301      * the GC type to be used when running with a CDS archive. Set "vm.gc" accordingly,
302      * so that tests that need to explicitly choose the GC type can be excluded
303      * with "@requires vm.gc == null".
304      *
305      * @param map - property-value pairs
306      */
307     protected void vmGCforCDS(SafeMap map) {
308         if (!GC.isSelectedErgonomically()) {
309             // The GC has been explicitly specified on the command line, so
310             // jtreg will set the "vm.gc" property. Let's not interfere with it.
311             return;
312         }
313 
314         String GC_PREFIX  = "-XX:+Use";
315         String GC_SUFFIX  = "GC";
316         String jtropts = System.getProperty("test.cds.runtime.options");
317         if (jtropts != null) {
318             for (String opt : jtropts.split(",")) {
319                 if (opt.startsWith(GC_PREFIX) && opt.endsWith(GC_SUFFIX)) {
320                     String gc = opt.substring(GC_PREFIX.length(), opt.length() - GC_SUFFIX.length());
321                     map.put("vm.gc", () -> gc);
322                 }
323             }
324         }
325     }
326 
327     /**
328      * Selected final flag.
329      *
330      * @param map - property-value pairs
331      * @param flagName - flag name
332      */
333     private void vmOptFinalFlag(SafeMap map, String flagName) {
334         map.put("vm.opt.final." + flagName,
335                 () -> String.valueOf(WB.getBooleanVMFlag(flagName)));
336     }
337 
338     /**
339      * Selected sets of final flags.
340      *
341      * @param map - property-value pairs
342      */
343     protected void vmOptFinalFlags(SafeMap map) {
344         vmOptFinalFlag(map, "ClassUnloading");
345         vmOptFinalFlag(map, "ClassUnloadingWithConcurrentMark");
346         vmOptFinalFlag(map, "UseCompressedOops");
347         vmOptFinalFlag(map, "UseVectorizedMismatchIntrinsic");
348         vmOptFinalFlag(map, "EnableJVMCI");
349         vmOptFinalFlag(map, "EliminateAllocations");
350         vmOptFinalFlag(map, "UseVtableBasedCHA");
351     }
352 
353     /**
354      * @return "true" if VM has a serviceability agent.
355      */
356     protected String vmHasSA() {
357         return "" + Platform.hasSA();
358     }
359 
360     /**
361      * @return "true" if the VM is compiled with Java Flight Recorder (JFR)
362      * support.
363      */
364     protected String vmHasJFR() {
365         return "" + WB.isJFRIncluded();
366     }
367 
368     /**
369      * @return "true" if the VM is compiled with JVMTI
370      */
371     protected String vmHasJVMTI() {
372         return "" + WB.isJVMTIIncluded();
373     }
374 
375     /**
376      * @return true if compiler in use supports RTM and false otherwise.
377      */
378     protected String vmRTMCompiler() {
379         boolean isRTMCompiler = false;
380 
381         if (Compiler.isC2Enabled() &&
382             (Platform.isX86() || Platform.isX64() || Platform.isPPC())) {
383             isRTMCompiler = true;
384         }
385         return "" + isRTMCompiler;
386     }
387 
388     /**
389      * @return true if VM runs RTM supported CPU and false otherwise.
390      */
391     protected String vmRTMCPU() {
392         return "" + CPUInfo.hasFeature("rtm");
393     }
394 
395     /**
396      * Check for CDS support.
397      *
398      * @return true if CDS is supported by the VM to be tested.
399      */
400     protected String vmCDS() {
401         return "" + WB.isCDSIncluded();
402     }
403 
404     /**
405      * Check for CDS support for custom loaders.
406      *
407      * @return true if CDS provides support for customer loader in the VM to be tested.
408      */
409     protected String vmCDSForCustomLoaders() {
410         return "" + ("true".equals(vmCDS()) && Platform.areCustomLoadersSupportedForCDS());
411     }
412 
413     /**
414      * @return true if this VM can write Java heap objects into the CDS archive
415      */
416     protected String vmCDSCanWriteArchivedJavaHeap() {
417         return "" + ("true".equals(vmCDS()) && WB.canWriteJavaHeapArchive());
418     }
419 
420     /**
421      * @return System page size in bytes.
422      */
423     protected String vmPageSize() {
424         return "" + WB.getVMPageSize();
425     }
426 
427     /**
428      * Check if Graal is used as JIT compiler.
429      *
430      * @return true if Graal is used as JIT compiler.
431      */
432     protected String isGraalEnabled() {
433         return "" + Compiler.isGraalEnabled();
434     }
435 
436     /**
437      * Check if Compiler1 is present.
438      *
439      * @return true if Compiler1 is used as JIT compiler, either alone or as part of the tiered system.
440      */
441     protected String isCompiler1Enabled() {
442         return "" + Compiler.isC1Enabled();
443     }
444 
445     /**
446      * Check if Compiler2 is present.
447      *
448      * @return true if Compiler2 is used as JIT compiler, either alone or as part of the tiered system.
449      */
450     protected String isCompiler2Enabled() {
451         return "" + Compiler.isC2Enabled();
452     }
453 
454    /**
455      * A simple check for docker support
456      *
457      * @return true if docker is supported in a given environment
458      */
459     protected String dockerSupport() {
460         boolean isSupported = false;
461         if (Platform.isLinux()) {
462            // currently docker testing is only supported for Linux,
463            // on certain platforms
464 
465            String arch = System.getProperty("os.arch");
466 
467            if (Platform.isX64()) {
468               isSupported = true;
469            } else if (Platform.isAArch64()) {
470               isSupported = true;
471            } else if (Platform.isS390x()) {
472               isSupported = true;
473            } else if (arch.equals("ppc64le")) {
474               isSupported = true;
475            }
476         }
477 
478         if (isSupported) {
479            try {
480               isSupported = checkDockerSupport();
481            } catch (Exception e) {
482               isSupported = false;
483            }
484          }
485 
486         return "" + isSupported;
487     }
488 
489     private boolean checkDockerSupport() throws IOException, InterruptedException {
490         ProcessBuilder pb = new ProcessBuilder(Container.ENGINE_COMMAND, "ps");
491         Process p = pb.start();
492         p.waitFor(10, TimeUnit.SECONDS);
493 
494         return (p.exitValue() == 0);
495     }
496 
497     /**
498      * Checks musl libc.
499      *
500      * @return true if musl libc is used.
501      */
502     protected String isMusl() {
503         return Boolean.toString(WB.getLibcName().contains("musl"));
504     }
505 
506     private String implementor() {
507         try (InputStream in = new BufferedInputStream(new FileInputStream(
508                 System.getProperty("java.home") + "/release"))) {
509             Properties properties = new Properties();
510             properties.load(in);
511             String implementorProperty = properties.getProperty("IMPLEMENTOR");
512             if (implementorProperty != null) {
513                 return implementorProperty.replace("\"", "");
514             }
515             return errorWithMessage("Can't get 'IMPLEMENTOR' property from 'release' file");
516         } catch (IOException e) {
517             e.printStackTrace();
518             return errorWithMessage("Failed to read 'release' file " + e);
519         }
520     }
521 
522     private String jdkContainerized() {
523         String isEnabled = System.getenv("TEST_JDK_CONTAINERIZED");
524         return "" + "true".equalsIgnoreCase(isEnabled);
525     }
526 
527     /**
528      * Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags
529      * which JVM is started with don't affect its behavior "significantly".
530      * {@code TEST_VM_FLAGLESS} enviroment variable can be used to force this
531      * method to return true and allow any flags.
532      *
533      * @return true if there are no JVM flags
534      */
535     private String isFlagless() {
536         boolean result = true;
537         if (System.getenv("TEST_VM_FLAGLESS") != null) {
538             return "" + result;
539         }
540 
541         List<String> allFlags = new ArrayList<String>();
542         Collections.addAll(allFlags, System.getProperty("test.vm.opts", "").trim().split("\\s+"));
543         Collections.addAll(allFlags, System.getProperty("test.java.opts", "").trim().split("\\s+"));
544 
545         // check -XX flags
546         var ignoredXXFlags = Set.of(
547                 // added by run-test framework
548                 "MaxRAMPercentage",
549                 // added by test environment
550                 "CreateCoredumpOnCrash"
551         );
552         result &= allFlags.stream()
553                           .filter(s -> s.startsWith("-XX:"))
554                           // map to names:
555                               // remove -XX:
556                               .map(s -> s.substring(4))
557                               // remove +/- from bool flags
558                               .map(s -> s.charAt(0) == '+' || s.charAt(0) == '-' ? s.substring(1) : s)
559                               // remove =.* from others
560                               .map(s -> s.contains("=") ? s.substring(0, s.indexOf('=')) : s)
561                           // skip known-to-be-there flags
562                           .filter(s -> !ignoredXXFlags.contains(s))
563                           .findAny()
564                           .isEmpty();
565 
566         // check -X flags
567         var ignoredXFlags = Set.of(
568                 // default, yet still seen to be explicitly set
569                 "mixed"
570         );
571         result &= allFlags.stream()
572                           .filter(s -> s.startsWith("-X") && !s.startsWith("-XX:"))
573                           // map to names:
574                               // remove -X
575                               .map(s -> s.substring(2))
576                               // remove :.* from flags with values
577                               .map(s -> s.contains(":") ? s.substring(0, s.indexOf(':')) : s)
578                           // skip known-to-be-there flags
579                           .filter(s -> !ignoredXFlags.contains(s))
580                           .findAny()
581                           .isEmpty();
582 
583         return "" + result;
584     }
585 
586     /**
587      * Dumps the map to the file if the file name is given as the property.
588      * This functionality could be helpful to know context in the real
589      * execution.
590      *
591      * @param map
592      */
593     protected static void dump(Map<String, String> map) {
594         String dumpFileName = System.getProperty("vmprops.dump");
595         if (dumpFileName == null) {
596             return;
597         }
598         List<String> lines = new ArrayList<>();
599         map.forEach((k, v) -> lines.add(k + ":" + v));
600         try {
601             Files.write(Paths.get(dumpFileName), lines,
602                     StandardOpenOption.APPEND, StandardOpenOption.CREATE);
603         } catch (IOException e) {
604             throw new RuntimeException("Failed to dump properties into '"
605                     + dumpFileName + "'", e);
606         }
607     }
608 
609     /**
610      * This method is for the testing purpose only.
611      *
612      * @param args
613      */
614     public static void main(String args[]) {
615         Map<String, String> map = new VMProps().call();
616         map.forEach((k, v) -> System.out.println(k + ": '" + v + "'"));
617     }
618 }