test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java
changeset 52319 625f6c742392
child 52626 991fe09c698c
equal deleted inserted replaced
52318:124af9276e44 52319:625f6c742392
       
     1 /*
       
     2  * Copyright (c) 2018, 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 
       
    25 /*
       
    26  * @test
       
    27  * @summary Tests how CDS works when critical library classes are replaced with JVMTI ClassFileLoadHook
       
    28  * @library /test/lib
       
    29  * @requires vm.cds
       
    30  * @build sun.hotspot.WhiteBox
       
    31  * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox
       
    32  * @run main/othervm/native ReplaceCriticalClasses
       
    33  */
       
    34 
       
    35 import java.util.regex.Matcher;
       
    36 import java.util.regex.Pattern;
       
    37 import jdk.test.lib.cds.CDSTestUtils;
       
    38 import jdk.test.lib.cds.CDSOptions;
       
    39 import jdk.test.lib.process.OutputAnalyzer;
       
    40 import sun.hotspot.WhiteBox;
       
    41 
       
    42 public class ReplaceCriticalClasses {
       
    43     public static void main(String args[]) throws Throwable {
       
    44         if (args.length == 0) {
       
    45             launchChildProcesses();
       
    46         } else if (args.length == 3 && args[0].equals("child")) {
       
    47             Class klass = Class.forName(args[2].replace("/", "."));
       
    48             if (args[1].equals("-shared")) {
       
    49                 testInChild(true, klass);
       
    50             } else if (args[1].equals("-notshared")) {
       
    51                 testInChild(false, klass);
       
    52             } else {
       
    53                 throw new RuntimeException("Unknown child exec option " + args[1]);
       
    54             }
       
    55             return;
       
    56         } else {
       
    57             throw new RuntimeException("Usage: @run main/othervm/native ReplaceCriticalClasses");
       
    58         }
       
    59     }
       
    60 
       
    61     static void launchChildProcesses() throws Throwable {
       
    62         String tests[] = {
       
    63             // CDS should be disabled -- these critical classes will be replaced
       
    64             // because JvmtiExport::early_class_hook_env() is true.
       
    65             "-early -notshared java/lang/Object",
       
    66             "-early -notshared java/lang/String",
       
    67             "-early -notshared java/lang/Cloneable",
       
    68             "-early -notshared java/io/Serializable",
       
    69 
       
    70             // CDS should not be disabled -- these critical classes cannot be replaced because
       
    71             // JvmtiExport::early_class_hook_env() is false.
       
    72             "java/lang/Object",
       
    73             "java/lang/String",
       
    74             "java/lang/Cloneable",
       
    75             "java/io/Serializable",
       
    76 
       
    77             // Try to replace classes that are used by the archived subgraph graphs.
       
    78             "-subgraph java/util/ArrayList",
       
    79             "-subgraph java/lang/module/ResolvedModule",
       
    80 
       
    81             // Replace classes that are loaded after JVMTI_PHASE_PRIMORDIAL. It's OK to replace such
       
    82             // classes even when CDS is enabled. Nothing bad should happen.
       
    83             "-notshared jdk/internal/vm/PostVMInitHook",
       
    84             "-notshared java/util/Locale",
       
    85             "-notshared sun/util/locale/BaseLocale",
       
    86             "-notshared java/lang/Readable",
       
    87         };
       
    88 
       
    89         int n = 0;
       
    90         for (String s : tests) {
       
    91             System.out.println("Test case[" + (n++) + "] = \"" + s + "\"");
       
    92             String args[] = s.split("\\s+"); // split by space character
       
    93             launchChild(args);
       
    94         }
       
    95     }
       
    96 
       
    97     static void launchChild(String args[]) throws Throwable {
       
    98         if (args.length < 1) {
       
    99             throw new RuntimeException("Invalid test case. Should be <-early> <-subgraph> <-notshared> klassName");
       
   100         }
       
   101         String klassName = null;
       
   102         String early = "";
       
   103         boolean subgraph = false;
       
   104         String shared = "-shared";
       
   105 
       
   106         for (int i=0; i<args.length-1; i++) {
       
   107             String opt = args[i];
       
   108             if (opt.equals("-early")) {
       
   109                 early = "-early,";
       
   110             } else if (opt.equals("-subgraph")) {
       
   111                 subgraph = true;
       
   112             } else if (opt.equals("-notshared")) {
       
   113                 shared = opt;
       
   114             } else {
       
   115                 throw new RuntimeException("Unknown option: " + opt);
       
   116             }
       
   117         }
       
   118         klassName = args[args.length-1];
       
   119         Class.forName(klassName.replace("/", ".")); // make sure it's a valid class
       
   120 
       
   121         // We will pass an option like "-agentlib:SimpleClassFileLoadHook=java/util/Locale,XXX,XXX".
       
   122         // The SimpleClassFileLoadHook agent would attempt to hook the java/util/Locale class
       
   123         // but leave the class file bytes unchanged (it replaces all bytes "XXX" with "XXX", i.e.,
       
   124         // a no-op). JVMTI doesn't check the class file bytes returned by the agent, so as long
       
   125         // as the agent returns a buffer, it will not load the class from CDS, and will instead
       
   126         // load the class by parsing the buffer.
       
   127         //
       
   128         // Note that for safety we don't change the contents of the class file bytes. If in the
       
   129         // future JVMTI starts checking the contents of the class file bytes, this test would need
       
   130         // to be updated. (You'd see the test case with java/util/Locale staring to fail).
       
   131         String agent = "-agentlib:SimpleClassFileLoadHook=" + early + klassName + ",XXX,XXX";
       
   132 
       
   133         CDSOptions opts = (new CDSOptions())
       
   134             .setXShareMode("auto")
       
   135             .setUseSystemArchive(true)
       
   136             .setUseVersion(false)
       
   137             .addSuffix("-showversion",
       
   138                        "-Xlog:cds",
       
   139                        "-XX:+UnlockDiagnosticVMOptions",
       
   140                        agent,
       
   141                        "-XX:+WhiteBoxAPI",
       
   142                        "-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"));
       
   143 
       
   144         if (subgraph) {
       
   145             opts.addSuffix("-Xlog:cds+heap",
       
   146                            "-Xlog:class+load");
       
   147         }
       
   148 
       
   149         opts.addSuffix("ReplaceCriticalClasses",
       
   150                        "child",
       
   151                        shared,
       
   152                        klassName);
       
   153 
       
   154         final boolean expectDisable = !early.equals("");
       
   155         final boolean checkSubgraph = subgraph;
       
   156         CDSTestUtils.run(opts).assertNormalExit(out -> {
       
   157                 if (expectDisable) {
       
   158                     out.shouldContain("UseSharedSpaces: CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
       
   159                     System.out.println("CDS disabled as expected");
       
   160                 }
       
   161                 if (checkSubgraph) {
       
   162                     // As of 2018/10/21 the classes in the archived subgraphs won't be
       
   163                     // replaced because all archived subgraphs were loaded in JVMTI_PHASE_PRIMORDIAL.
       
   164                     //
       
   165                     // This is the first class to be loaded after JVMTI has exited JVMTI_PHASE_PRIMORDIAL.
       
   166                     // Make sure no subgraphs are loaded afterwards.
       
   167                     //
       
   168                     // Can't use out.shouldNotMatch() because that doesn't match across multiple lines.
       
   169                     String firstNonPrimordialClass = "jdk.jfr.internal.EventWriter";
       
   170                     String regexp = firstNonPrimordialClass + ".*initialize_from_archived_subgraph";
       
   171                     Pattern regex = Pattern.compile(regexp, Pattern.DOTALL);
       
   172                     Matcher matcher = regex.matcher(out.getStdout());
       
   173                     if (matcher.find()) {
       
   174                         out.reportDiagnosticSummary();
       
   175                         throw new RuntimeException("'" + regexp
       
   176                                                    + "' found in stdout: '" + matcher.group() + "' \n");
       
   177                     }
       
   178                 }
       
   179             });
       
   180     }
       
   181 
       
   182     static void testInChild(boolean shouldBeShared, Class klass) {
       
   183         WhiteBox wb = WhiteBox.getWhiteBox();
       
   184 
       
   185         if (shouldBeShared && !wb.isSharedClass(klass)) {
       
   186             throw new RuntimeException(klass + " should be shared but but actually is not.");
       
   187         }
       
   188         if (!shouldBeShared && wb.isSharedClass(klass)) {
       
   189             throw new RuntimeException(klass + " should not be shared but actually is.");
       
   190         }
       
   191         System.out.println("wb.isSharedClass(klass): " + wb.isSharedClass(klass) + " == " + shouldBeShared);
       
   192 
       
   193         String strings[] = {
       
   194             // interned strings from j.l.Object
       
   195             "@",
       
   196             "nanosecond timeout value out of range",
       
   197             "timeoutMillis value is negative",
       
   198 
       
   199             // interned strings from j.l.Integer
       
   200             "0",
       
   201             "0X",
       
   202             "0x",
       
   203             "int"
       
   204         };
       
   205 
       
   206         // Make sure the interned string table is same
       
   207         for (String s : strings) {
       
   208             String i = s.intern();
       
   209             if (s != i) {
       
   210                 throw new RuntimeException("Interned string mismatch: \"" + s + "\" @ " + System.identityHashCode(s) +
       
   211                                            " vs \"" + i + "\" @ " + System.identityHashCode(i));
       
   212             }
       
   213         }
       
   214         // We have tried to use ClassFileLoadHook to replace critical library classes (which may
       
   215         // may not have succeeded, depending on whether the agent has requested
       
   216         // can_generate_all_class_hook_events/can_generate_early_class_hook_events capabilities).
       
   217         //
       
   218         // In any case, the JVM should have started properly (perhaps with CDS disabled) and
       
   219         // the above operations should succeed.
       
   220         System.out.println("If I can come to here without crashing, things should be OK");
       
   221     }
       
   222 }