New test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java (original) (raw)

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