test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java
changeset 57567 b000362a89a0
parent 57566 ad84ae073248
child 57568 460ac76019f4
equal deleted inserted replaced
57566:ad84ae073248 57567:b000362a89a0
     1 /*
       
     2  * Copyright (c) 2018, 2019, 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         ReplaceCriticalClasses rcc = new ReplaceCriticalClasses();
       
    45         rcc.process(args);
       
    46     }
       
    47 
       
    48     public void process(String args[]) throws Throwable {
       
    49         if (args.length == 0) {
       
    50             // Dump the shared archive in case it was not generated during the JDK build.
       
    51             // Put the archive at separate file to avoid clashes with concurrent tests.
       
    52             CDSOptions opts = new CDSOptions()
       
    53                 .setXShareMode("dump")
       
    54                 .setArchiveName(ReplaceCriticalClasses.class.getName() + ".jsa")
       
    55                 .setUseVersion(false)
       
    56                 .addSuffix("-showversion");
       
    57             CDSTestUtils.run(opts).assertNormalExit("");
       
    58 
       
    59             launchChildProcesses(getTests());
       
    60         } else if (args.length == 3 && args[0].equals("child")) {
       
    61             Class klass = Class.forName(args[2].replace("/", "."));
       
    62             if (args[1].equals("-shared")) {
       
    63                 testInChild(true, klass);
       
    64             } else if (args[1].equals("-notshared")) {
       
    65                 testInChild(false, klass);
       
    66             } else {
       
    67                 throw new RuntimeException("Unknown child exec option " + args[1]);
       
    68             }
       
    69             return;
       
    70         } else {
       
    71             throw new RuntimeException("Usage: @run main/othervm/native ReplaceCriticalClasses");
       
    72         }
       
    73     }
       
    74 
       
    75     public String[] getTests() {
       
    76         String tests[] = {
       
    77             // CDS should be disabled -- these critical classes will be replaced
       
    78             // because JvmtiExport::early_class_hook_env() is true.
       
    79             "-early -notshared java/lang/Object",
       
    80             "-early -notshared java/lang/String",
       
    81             "-early -notshared java/lang/Cloneable",
       
    82             "-early -notshared java/io/Serializable",
       
    83 
       
    84             // CDS should not be disabled -- these critical classes cannot be replaced because
       
    85             // JvmtiExport::early_class_hook_env() is false.
       
    86             "java/lang/Object",
       
    87             "java/lang/String",
       
    88             "java/lang/Cloneable",
       
    89             "java/io/Serializable",
       
    90 
       
    91             /* Try to replace classes that are used by the archived subgraph graphs.
       
    92                The following test cases are in ReplaceCriticalClassesForSubgraphs.java.
       
    93             "-early -notshared -subgraph java/lang/module/ResolvedModule jdk.internal.module.ArchivedModuleGraph",
       
    94             "-early -notshared -subgraph java/lang/Long java.lang.Long$LongCache",
       
    95             "-subgraph java/lang/Long java.lang.Long$LongCache",
       
    96             */
       
    97 
       
    98             // Replace classes that are loaded after JVMTI_PHASE_PRIMORDIAL. It's OK to replace
       
    99             // such
       
   100             // classes even when CDS is enabled. Nothing bad should happen.
       
   101             "-notshared java/util/Locale",
       
   102             "-notshared sun/util/locale/BaseLocale",
       
   103             "-notshared java/lang/Readable",
       
   104         };
       
   105         return tests;
       
   106     }
       
   107 
       
   108     static void launchChildProcesses(String tests[]) throws Throwable {
       
   109         int n = 0;
       
   110         for (String s : tests) {
       
   111             System.out.println("Test case[" + (n++) + "] = \"" + s + "\"");
       
   112             String args[] = s.split("\\s+"); // split by space character
       
   113             launchChild(args);
       
   114         }
       
   115     }
       
   116 
       
   117     static void launchChild(String args[]) throws Throwable {
       
   118         if (args.length < 1) {
       
   119             throw new RuntimeException("Invalid test case. Should be <-early> <-subgraph> <-notshared> klassName subgraphKlass");
       
   120         }
       
   121         String klassName = null;
       
   122         String subgraphKlass = null;
       
   123         String early = "";
       
   124         boolean subgraph = false;
       
   125         String shared = "-shared";
       
   126 
       
   127         for (int i=0; i<args.length-1; i++) {
       
   128             String opt = args[i];
       
   129             if (opt.equals("-early")) {
       
   130                 early = "-early,";
       
   131             } else if (opt.equals("-subgraph")) {
       
   132                 subgraph = true;
       
   133             } else if (opt.equals("-notshared")) {
       
   134                 shared = opt;
       
   135             } else {
       
   136               if (!subgraph) {
       
   137                 throw new RuntimeException("Unknown option: " + opt);
       
   138               }
       
   139             }
       
   140         }
       
   141         if (subgraph) {
       
   142           klassName = args[args.length-2];
       
   143           subgraphKlass = args[args.length-1];
       
   144         } else {
       
   145           klassName = args[args.length-1];
       
   146         }
       
   147         Class.forName(klassName.replace("/", ".")); // make sure it's a valid class
       
   148         final String subgraphInit = "initialize_from_archived_subgraph " + subgraphKlass;
       
   149 
       
   150         // We will pass an option like "-agentlib:SimpleClassFileLoadHook=java/util/Locale,XXX,XXX".
       
   151         // The SimpleClassFileLoadHook agent would attempt to hook the java/util/Locale class
       
   152         // but leave the class file bytes unchanged (it replaces all bytes "XXX" with "XXX", i.e.,
       
   153         // a no-op). JVMTI doesn't check the class file bytes returned by the agent, so as long
       
   154         // as the agent returns a buffer, it will not load the class from CDS, and will instead
       
   155         // load the class by parsing the buffer.
       
   156         //
       
   157         // Note that for safety we don't change the contents of the class file bytes. If in the
       
   158         // future JVMTI starts checking the contents of the class file bytes, this test would need
       
   159         // to be updated. (You'd see the test case with java/util/Locale staring to fail).
       
   160         String agent = "-agentlib:SimpleClassFileLoadHook=" + early + klassName + ",XXX,XXX";
       
   161 
       
   162         CDSOptions opts = (new CDSOptions())
       
   163             .setXShareMode("auto")
       
   164             .setArchiveName(ReplaceCriticalClasses.class.getName() + ".jsa")
       
   165             .setUseVersion(false)
       
   166             .addSuffix("-showversion",
       
   167                        "-Xlog:cds",
       
   168                        "-XX:+UnlockDiagnosticVMOptions",
       
   169                        agent,
       
   170                        "-XX:+WhiteBoxAPI",
       
   171                        "-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"));
       
   172 
       
   173         if (subgraph) {
       
   174             opts.addSuffix("-Xlog:cds,cds+heap");
       
   175         }
       
   176 
       
   177         opts.addSuffix("ReplaceCriticalClasses",
       
   178                        "child",
       
   179                        shared,
       
   180                        klassName);
       
   181 
       
   182         final boolean expectDisable = !early.equals("");
       
   183         final boolean checkSubgraph = subgraph;
       
   184         final boolean expectShared = shared.equals("-shared");
       
   185         CDSTestUtils.run(opts).assertNormalExit(out -> {
       
   186                 if (expectDisable) {
       
   187                     out.shouldContain("UseSharedSpaces: CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
       
   188                     System.out.println("CDS disabled as expected");
       
   189                 }
       
   190                 if (checkSubgraph) {
       
   191                     if (expectShared) {
       
   192                         if (!out.getOutput().contains("UseSharedSpaces: Unable to map at required address in java heap")) {
       
   193                             out.shouldContain(subgraphInit);
       
   194                         }
       
   195                     } else {
       
   196                       out.shouldNotContain(subgraphInit);
       
   197                     }
       
   198                 }
       
   199             });
       
   200     }
       
   201 
       
   202     static void testInChild(boolean shouldBeShared, Class klass) {
       
   203         WhiteBox wb = WhiteBox.getWhiteBox();
       
   204 
       
   205         if (shouldBeShared && !wb.isSharedClass(klass)) {
       
   206             throw new RuntimeException(klass + " should be shared but but actually is not.");
       
   207         }
       
   208         if (!shouldBeShared && wb.isSharedClass(klass)) {
       
   209             throw new RuntimeException(klass + " should not be shared but actually is.");
       
   210         }
       
   211         System.out.println("wb.isSharedClass(klass): " + wb.isSharedClass(klass) + " == " + shouldBeShared);
       
   212 
       
   213         String strings[] = {
       
   214             // interned strings from j.l.Object
       
   215             "@",
       
   216             "nanosecond timeout value out of range",
       
   217             "timeoutMillis value is negative",
       
   218 
       
   219             // interned strings from j.l.Integer
       
   220             "0",
       
   221             "0X",
       
   222             "0x",
       
   223             "int"
       
   224         };
       
   225 
       
   226         // Make sure the interned string table is same
       
   227         for (String s : strings) {
       
   228             String i = s.intern();
       
   229             if (s != i) {
       
   230                 throw new RuntimeException("Interned string mismatch: \"" + s + "\" @ " + System.identityHashCode(s) +
       
   231                                            " vs \"" + i + "\" @ " + System.identityHashCode(i));
       
   232             }
       
   233         }
       
   234         // We have tried to use ClassFileLoadHook to replace critical library classes (which may
       
   235         // may not have succeeded, depending on whether the agent has requested
       
   236         // can_generate_all_class_hook_events/can_generate_early_class_hook_events capabilities).
       
   237         //
       
   238         // In any case, the JVM should have started properly (perhaps with CDS disabled) and
       
   239         // the above operations should succeed.
       
   240         System.out.println("If I can come to here without crashing, things should be OK");
       
   241     }
       
   242 }