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 }