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.hasDTrace", this::vmHasDTrace);
106         map.put("vm.jvmti", this::vmHasJVMTI);
107         map.put("vm.cpu.features", this::cpuFeatures);
108         map.put("vm.pageSize", this::vmPageSize);
109         map.put("vm.rtm.cpu", this::vmRTMCPU);
110         map.put("vm.rtm.compiler", this::vmRTMCompiler);
111         // vm.cds is true if the VM is compiled with cds support.
112         map.put("vm.cds", this::vmCDS);
113         map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders);
114         map.put("vm.cds.archived.java.heap", this::vmCDSForArchivedJavaHeap);
115         // vm.graal.enabled is true if Graal is used as JIT
116         map.put("vm.graal.enabled", this::isGraalEnabled);
117         map.put("vm.compiler1.enabled", this::isCompiler1Enabled);
118         map.put("vm.compiler2.enabled", this::isCompiler2Enabled);
119         map.put("docker.support", this::dockerSupport);
120         map.put("vm.musl", this::isMusl);
121         map.put("release.implementor", this::implementor);
122         map.put("jdk.containerized", this::jdkContainerized);
123         map.put("vm.flagless", this::isFlagless);
124         vmGC(map); // vm.gc.X = true/false
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         // Not all GCs have full JVMCI support
246         if (!WB.isJVMCISupportedByGC()) {
247           return "false";
248         }
249 
250         // Interpreted mode cannot enable JVMCI
251         if (vmCompMode().equals("Xint")) {
252           return "false";
253         }
254 
255         return "true";
256     }
257 
258     /**
259      * @return true if VM runs in emulated-client mode and false otherwise.
260      */
261     protected String vmEmulatedClient() {
262         String vmInfo = System.getProperty("java.vm.info");
263         if (vmInfo == null) {
264             return errorWithMessage("Can't get 'java.vm.info' property");
265         }
266         return "" + vmInfo.contains(" emulated-client");
267     }
268 
269     /**
270      * @return supported CPU features
271      */
272     protected String cpuFeatures() {
273         return CPUInfo.getFeatures().toString();
274     }
275 
276     /**
277      * For all existing GC sets vm.gc.X property.
278      * Example vm.gc.G1=true means:
279      *    VM supports G1
280      *    User either set G1 explicitely (-XX:+UseG1GC) or did not set any GC
281      *    G1 can be selected, i.e. it doesn't conflict with other VM flags
282      *
283      * @param map - property-value pairs
284      */
285     protected void vmGC(SafeMap map) {
286         var isJVMCIEnabled = Compiler.isJVMCIEnabled();
287         for (GC gc: GC.values()) {
288             map.put("vm.gc." + gc.name(),
289                     () -> "" + (gc.isSupported()
290                             && (!isJVMCIEnabled || gc.isSupportedByJVMCICompiler())
291                             && (gc.isSelected() || GC.isSelectedErgonomically())));
292         }
293     }
294 
295     /**
296      * Selected final flag.
297      *
298      * @param map - property-value pairs
299      * @param flagName - flag name
300      */
301     private void vmOptFinalFlag(SafeMap map, String flagName) {
302         map.put("vm.opt.final." + flagName,
303                 () -> String.valueOf(WB.getBooleanVMFlag(flagName)));
304     }
305 
306     /**
307      * Selected sets of final flags.
308      *
309      * @param map - property-value pairs
310      */
311     protected void vmOptFinalFlags(SafeMap map) {
312         vmOptFinalFlag(map, "ClassUnloading");
313         vmOptFinalFlag(map, "ClassUnloadingWithConcurrentMark");
314         vmOptFinalFlag(map, "UseCompressedOops");
315         vmOptFinalFlag(map, "UseVectorizedMismatchIntrinsic");
316         vmOptFinalFlag(map, "EnableJVMCI");
317         vmOptFinalFlag(map, "EliminateAllocations");
318         vmOptFinalFlag(map, "UseVtableBasedCHA");
319     }
320 
321     /**
322      * @return "true" if VM has a serviceability agent.
323      */
324     protected String vmHasSA() {
325         return "" + Platform.hasSA();
326     }
327 
328     /**
329      * @return "true" if the VM is compiled with Java Flight Recorder (JFR)
330      * support.
331      */
332     protected String vmHasJFR() {
333         return "" + WB.isJFRIncluded();
334     }
335 
336     /**
337      * @return "true" if the VM is compiled with JVMTI
338      */
339     protected String vmHasJVMTI() {
340         return "" + WB.isJVMTIIncluded();
341     }
342 
343     /**
344      * @return "true" if the VM is compiled with DTrace
345      */
346     protected String vmHasDTrace() {
347         return "" + WB.isDTraceIncluded();
348     }
349 
350     /**
351      * @return true if compiler in use supports RTM and false otherwise.
352      */
353     protected String vmRTMCompiler() {
354         boolean isRTMCompiler = false;
355 
356         if (Compiler.isC2Enabled() &&
357             (Platform.isX86() || Platform.isX64() || Platform.isPPC())) {
358             isRTMCompiler = true;
359         }
360         return "" + isRTMCompiler;
361     }
362 
363     /**
364      * @return true if VM runs RTM supported CPU and false otherwise.
365      */
366     protected String vmRTMCPU() {
367         return "" + CPUInfo.hasFeature("rtm");
368     }
369 
370     /**
371      * Check for CDS support.
372      *
373      * @return true if CDS is supported by the VM to be tested.
374      */
375     protected String vmCDS() {
376         return "" + WB.isCDSIncluded();
377     }
378 
379     /**
380      * Check for CDS support for custom loaders.
381      *
382      * @return true if CDS provides support for customer loader in the VM to be tested.
383      */
384     protected String vmCDSForCustomLoaders() {
385         return "" + ("true".equals(vmCDS()) && Platform.areCustomLoadersSupportedForCDS());
386     }
387 
388     /**
389      * Check for CDS support for archived Java heap regions.
390      *
391      * @return true if CDS provides support for archive Java heap regions in the VM to be tested.
392      */
393     protected String vmCDSForArchivedJavaHeap() {
394         return "" + ("true".equals(vmCDS()) && WB.isJavaHeapArchiveSupported());
395     }
396 
397     /**
398      * @return System page size in bytes.
399      */
400     protected String vmPageSize() {
401         return "" + WB.getVMPageSize();
402     }
403 
404     /**
405      * Check if Graal is used as JIT compiler.
406      *
407      * @return true if Graal is used as JIT compiler.
408      */
409     protected String isGraalEnabled() {
410         return "" + Compiler.isGraalEnabled();
411     }
412 
413     /**
414      * Check if Compiler1 is present.
415      *
416      * @return true if Compiler1 is used as JIT compiler, either alone or as part of the tiered system.
417      */
418     protected String isCompiler1Enabled() {
419         return "" + Compiler.isC1Enabled();
420     }
421 
422     /**
423      * Check if Compiler2 is present.
424      *
425      * @return true if Compiler2 is used as JIT compiler, either alone or as part of the tiered system.
426      */
427     protected String isCompiler2Enabled() {
428         return "" + Compiler.isC2Enabled();
429     }
430 
431    /**
432      * A simple check for docker support
433      *
434      * @return true if docker is supported in a given environment
435      */
436     protected String dockerSupport() {
437         boolean isSupported = false;
438         if (Platform.isLinux()) {
439            // currently docker testing is only supported for Linux,
440            // on certain platforms
441 
442            String arch = System.getProperty("os.arch");
443 
444            if (Platform.isX64()) {
445               isSupported = true;
446            } else if (Platform.isAArch64()) {
447               isSupported = true;
448            } else if (Platform.isS390x()) {
449               isSupported = true;
450            } else if (arch.equals("ppc64le")) {
451               isSupported = true;
452            }
453         }
454 
455         if (isSupported) {
456            try {
457               isSupported = checkDockerSupport();
458            } catch (Exception e) {
459               isSupported = false;
460            }
461          }
462 
463         return "" + isSupported;
464     }
465 
466     private boolean checkDockerSupport() throws IOException, InterruptedException {
467         ProcessBuilder pb = new ProcessBuilder(Container.ENGINE_COMMAND, "ps");
468         Process p = pb.start();
469         p.waitFor(10, TimeUnit.SECONDS);
470 
471         return (p.exitValue() == 0);
472     }
473 
474     /**
475      * Checks musl libc.
476      *
477      * @return true if musl libc is used.
478      */
479     protected String isMusl() {
480         return Boolean.toString(WB.getLibcName().contains("musl"));
481     }
482 
483     private String implementor() {
484         try (InputStream in = new BufferedInputStream(new FileInputStream(
485                 System.getProperty("java.home") + "/release"))) {
486             Properties properties = new Properties();
487             properties.load(in);
488             String implementorProperty = properties.getProperty("IMPLEMENTOR");
489             if (implementorProperty != null) {
490                 return implementorProperty.replace("\"", "");
491             }
492             return errorWithMessage("Can't get 'IMPLEMENTOR' property from 'release' file");
493         } catch (IOException e) {
494             e.printStackTrace();
495             return errorWithMessage("Failed to read 'release' file " + e);
496         }
497     }
498 
499     private String jdkContainerized() {
500         String isEnabled = System.getenv("TEST_JDK_CONTAINERIZED");
501         return "" + "true".equalsIgnoreCase(isEnabled);
502     }
503 
504     /**
505      * Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags
506      * which JVM is started with don't affect its behavior "significantly".
507      * {@code TEST_VM_FLAGLESS} enviroment variable can be used to force this
508      * method to return true and allow any flags.
509      *
510      * @return true if there are no JVM flags
511      */
512     private String isFlagless() {
513         boolean result = true;
514         if (System.getenv("TEST_VM_FLAGLESS") != null) {
515             return "" + result;
516         }
517 
518         List<String> allFlags = new ArrayList<String>();
519         Collections.addAll(allFlags, System.getProperty("test.vm.opts", "").trim().split("\\s+"));
520         Collections.addAll(allFlags, System.getProperty("test.java.opts", "").trim().split("\\s+"));
521 
522         // check -XX flags
523         var ignoredXXFlags = Set.of(
524                 // added by run-test framework
525                 "MaxRAMPercentage",
526                 // added by test environment
527                 "CreateCoredumpOnCrash",
528                 // experimental features unlocking flag does not affect behavior
529                 "UnlockExperimentalVMOptions",
530                 // all compact headers settings should run flagless tests
531                 "UseCompactObjectHeaders"
532         );
533         result &= allFlags.stream()
534                           .filter(s -> s.startsWith("-XX:"))
535                           // map to names:
536                               // remove -XX:
537                               .map(s -> s.substring(4))
538                               // remove +/- from bool flags
539                               .map(s -> s.charAt(0) == '+' || s.charAt(0) == '-' ? s.substring(1) : s)
540                               // remove =.* from others
541                               .map(s -> s.contains("=") ? s.substring(0, s.indexOf('=')) : s)
542                           // skip known-to-be-there flags
543                           .filter(s -> !ignoredXXFlags.contains(s))
544                           .findAny()
545                           .isEmpty();
546 
547         // check -X flags
548         var ignoredXFlags = Set.of(
549                 // default, yet still seen to be explicitly set
550                 "mixed"
551         );
552         result &= allFlags.stream()
553                           .filter(s -> s.startsWith("-X") && !s.startsWith("-XX:"))
554                           // map to names:
555                               // remove -X
556                               .map(s -> s.substring(2))
557                               // remove :.* from flags with values
558                               .map(s -> s.contains(":") ? s.substring(0, s.indexOf(':')) : s)
559                           // skip known-to-be-there flags
560                           .filter(s -> !ignoredXFlags.contains(s))
561                           .findAny()
562                           .isEmpty();
563 
564         return "" + result;
565     }
566 
567     /**
568      * Dumps the map to the file if the file name is given as the property.
569      * This functionality could be helpful to know context in the real
570      * execution.
571      *
572      * @param map
573      */
574     protected static void dump(Map<String, String> map) {
575         String dumpFileName = System.getProperty("vmprops.dump");
576         if (dumpFileName == null) {
577             return;
578         }
579         List<String> lines = new ArrayList<>();
580         map.forEach((k, v) -> lines.add(k + ":" + v));
581         try {
582             Files.write(Paths.get(dumpFileName), lines,
583                     StandardOpenOption.APPEND, StandardOpenOption.CREATE);
584         } catch (IOException e) {
585             throw new RuntimeException("Failed to dump properties into '"
586                     + dumpFileName + "'", e);
587         }
588     }
589 
590     /**
591      * This method is for the testing purpose only.
592      *
593      * @param args
594      */
595     public static void main(String args[]) {
596         Map<String, String> map = new VMProps().call();
597         map.forEach((k, v) -> System.out.println(k + ": '" + v + "'"));
598     }
599 }