1 /* 2 * Copyright (c) 2022, 2024, 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 * @bug 8214781 8293187 28 * @summary Test for the -XX:ArchiveHeapTestClass flag 29 * @requires vm.debug == true & vm.cds.write.archived.java.heap 30 * @modules java.logging 31 * @library /test/jdk/lib/testlibrary /test/lib 32 * /test/hotspot/jtreg/runtime/cds/appcds 33 * /test/hotspot/jtreg/runtime/cds/appcds/test-classes 34 * @build ArchiveHeapTestClass Hello pkg.ClassInPackage 35 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar 36 * CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY 37 * CDSTestClassB CDSTestClassC CDSTestClassD 38 * CDSTestClassE CDSTestClassF CDSTestClassG CDSTestClassG$MyEnum CDSTestClassG$Wrapper 39 * pkg.ClassInPackage 40 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello 41 * @run driver ArchiveHeapTestClass 42 */ 43 44 import jdk.test.lib.Platform; 45 import jdk.test.lib.helpers.ClassFileInstaller; 46 import jdk.test.lib.process.OutputAnalyzer; 47 48 public class ArchiveHeapTestClass { 49 static final String bootJar = ClassFileInstaller.getJarPath("boot.jar"); 50 static final String appJar = ClassFileInstaller.getJarPath("app.jar"); 51 static final String[] appClassList = {"Hello"}; 52 53 static final String CDSTestClassA_name = CDSTestClassA.class.getName(); 54 static final String CDSTestClassB_name = CDSTestClassB.class.getName(); 55 static final String CDSTestClassC_name = CDSTestClassC.class.getName(); 56 static final String CDSTestClassD_name = CDSTestClassD.class.getName(); 57 static final String CDSTestClassE_name = CDSTestClassE.class.getName(); 58 static final String CDSTestClassF_name = CDSTestClassF.class.getName(); 59 static final String CDSTestClassG_name = CDSTestClassG.class.getName(); 60 static final String ClassInPackage_name = pkg.ClassInPackage.class.getName().replace('.', '/'); 61 static final String ARCHIVE_TEST_FIELD_NAME = "archivedObjects"; 62 63 public static void main(String[] args) throws Exception { 64 testDebugBuild(); 65 } 66 67 static OutputAnalyzer dumpHelloOnly(String... extraOpts) throws Exception { 68 return TestCommon.dump(appJar, appClassList, extraOpts); 69 } 70 71 static OutputAnalyzer dumpBootAndHello(String bootClass, String... extraOpts) throws Exception { 72 String classlist[] = TestCommon.concat(appClassList, bootClass); 73 extraOpts = TestCommon.concat(extraOpts, 74 "-Xbootclasspath/a:" + bootJar, 75 "-XX:ArchiveHeapTestClass=" + bootClass, 76 "-Xlog:cds+heap"); 77 return TestCommon.dump(appJar, classlist, extraOpts); 78 } 79 80 static int caseNum = 0; 81 static void testCase(String s) { 82 System.out.println("=================================================="); 83 System.out.println(" Test " + (++caseNum) + ": " + s); 84 } 85 86 static void mustContain(OutputAnalyzer output, String... expectStrs) throws Exception { 87 for (String s : expectStrs) { 88 output.shouldContain(s); 89 } 90 } 91 92 static void mustFail(OutputAnalyzer output, String... expectStrs) throws Exception { 93 mustContain(output, expectStrs); 94 output.shouldNotHaveExitValue(0); 95 } 96 97 static void mustSucceed(OutputAnalyzer output, String... expectStrs) throws Exception { 98 mustContain(output, expectStrs); 99 output.shouldHaveExitValue(0); 100 } 101 102 static void testDebugBuild() throws Exception { 103 OutputAnalyzer output; 104 105 testCase("Simple positive case"); 106 output = dumpBootAndHello(CDSTestClassA_name); 107 mustSucceed(output, CDSTestClassA.getOutput()); // make sure <clinit> is executed 108 output.shouldMatch("warning.*cds.*Loading ArchiveHeapTestClass " + CDSTestClassA_name); 109 output.shouldMatch("warning.*cds.*Initializing ArchiveHeapTestClass " + CDSTestClassA_name); 110 output.shouldContain("Archived field " + CDSTestClassA_name + "::" + ARCHIVE_TEST_FIELD_NAME); 111 output.shouldMatch("Archived object klass CDSTestClassA .*\\[LCDSTestClassA;"); 112 output.shouldMatch("Archived object klass CDSTestClassA .*CDSTestClassA\\$YY"); 113 114 TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:cds+heap", CDSTestClassA_name) 115 .assertNormalExit(CDSTestClassA.getOutput(), 116 "resolve subgraph " + CDSTestClassA_name); 117 118 testCase("Class doesn't exist"); 119 output = dumpHelloOnly("-XX:ArchiveHeapTestClass=NoSuchClass"); 120 mustFail(output, "Fail to initialize archive heap: NoSuchClass cannot be loaded"); 121 122 testCase("Class doesn't exist (objarray)"); 123 output = dumpHelloOnly("-XX:ArchiveHeapTestClass=[LNoSuchClass;"); 124 mustFail(output, "Fail to initialize archive heap: [LNoSuchClass; cannot be loaded"); 125 126 testCase("Not an instance klass"); 127 output = dumpHelloOnly("-XX:ArchiveHeapTestClass=[Ljava/lang/Object;"); 128 mustFail(output, "Fail to initialize archive heap: [Ljava/lang/Object; is not an instance class"); 129 130 testCase("Not in boot loader"); 131 output = dumpHelloOnly("-XX:ArchiveHeapTestClass=Hello"); 132 mustFail(output, "Fail to initialize archive heap: Hello cannot be loaded by the boot loader"); 133 134 testCase("Not from unnamed module"); 135 output = dumpHelloOnly("-XX:ArchiveHeapTestClass=java/lang/Object"); 136 mustFail(output, "ArchiveHeapTestClass java/lang/Object is not in unnamed module"); 137 138 testCase("Not from unnamed package"); 139 output = dumpBootAndHello(ClassInPackage_name); 140 mustFail(output, "ArchiveHeapTestClass pkg/ClassInPackage is not in unnamed package"); 141 142 testCase("Field not found"); 143 output = dumpBootAndHello(CDSTestClassB_name); 144 mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassB::archivedObjects"); 145 146 testCase("Not a static field"); 147 output = dumpBootAndHello(CDSTestClassC_name); 148 mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassC::archivedObjects"); 149 150 testCase("Not a T_OBJECT field"); 151 output = dumpBootAndHello(CDSTestClassD_name); 152 mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassD::archivedObjects"); 153 154 testCase("Use a disallowed class: in unnamed module but not in unname package"); 155 output = dumpBootAndHello(CDSTestClassE_name); 156 mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap"); 157 158 testCase("Use a disallowed class: not in java.base module"); 159 output = dumpBootAndHello(CDSTestClassF_name); 160 mustFail(output, "Class java.util.logging.Level not allowed in archive heap"); 161 162 testCase("Complex enums"); 163 output = dumpBootAndHello(CDSTestClassG_name, "-XX:+AOTClassLinking", "-Xlog:cds+class=debug"); 164 mustSucceed(output); 165 166 TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:cds+heap,cds+init", 167 CDSTestClassG_name) 168 .assertNormalExit("init subgraph " + CDSTestClassG_name, 169 "Initialized from CDS"); 170 } 171 } 172 173 class CDSTestClassA { 174 static final String output = "CDSTestClassA.<clinit> was executed"; 175 static Object[] archivedObjects; 176 static { 177 // The usual convention would be to call this here: 178 // CDS.initializeFromArchive(CDSTestClassA.class); 179 // However, the CDS class is not exported to the unnamed module by default, 180 // and we don't want to use "--add-exports java.base/jdk.internal.misc=ALL-UNNAMED", as 181 // that would disable the archived full module graph, which will disable 182 // CDSConfig::is_using_aot_linked_classes(). 183 // 184 // Instead, HeapShared::initialize_test_class_from_archive() will set up the 185 // "archivedObjects" field first, before calling CDSTestClassA.<clinit>. So 186 // if we see that archivedObjects is magically non-null here, that means 187 // it has been restored from the CDS archive. 188 if (archivedObjects == null) { 189 archivedObjects = new Object[5]; 190 archivedObjects[0] = output; 191 archivedObjects[1] = new CDSTestClassA[0]; 192 archivedObjects[2] = new YY(); 193 archivedObjects[3] = new int[0]; 194 archivedObjects[4] = new int[2][2]; 195 } else { 196 System.out.println("Initialized from CDS"); 197 } 198 System.out.println(output); 199 System.out.println("CDSTestClassA module = " + CDSTestClassA.class.getModule()); 200 System.out.println("CDSTestClassA package = " + CDSTestClassA.class.getPackage()); 201 System.out.println("CDSTestClassA[] module = " + archivedObjects[1].getClass().getModule()); 202 System.out.println("CDSTestClassA[] package = " + archivedObjects[1].getClass().getPackage()); 203 } 204 205 static String getOutput() { 206 return output; 207 } 208 209 public static void main(String args[]) { 210 if (CDSTestClassA.class.getModule().isNamed()) { 211 throw new RuntimeException("CDSTestClassA must be in unnamed module"); 212 } 213 if (CDSTestClassA.class.getPackage() != null) { 214 throw new RuntimeException("CDSTestClassA must be in null package"); 215 } 216 if (archivedObjects[1].getClass().getModule().isNamed()) { 217 throw new RuntimeException("CDSTestClassA[] must be in unnamed module"); 218 } 219 if (archivedObjects[1].getClass().getPackage() != null) { 220 throw new RuntimeException("CDSTestClassA[] must be in null package"); 221 } 222 XX.doit(); 223 YY.doit(); 224 } 225 226 // This is an inner class that has NOT been archived. 227 static class XX { 228 static void doit() { 229 System.out.println("XX module = " + XX.class.getModule()); 230 System.out.println("XX package = " + XX.class.getPackage()); 231 232 if (XX.class.getModule().isNamed()) { 233 throw new RuntimeException("XX must be in unnamed module"); 234 } 235 if (XX.class.getPackage() != null) { 236 throw new RuntimeException("XX must be in null package"); 237 } 238 } 239 } 240 241 // This is an inner class that HAS been archived. 242 static class YY { 243 static void doit() { 244 System.out.println("YY module = " + YY.class.getModule()); 245 System.out.println("YY package = " + YY.class.getPackage()); 246 247 if (YY.class.getModule().isNamed()) { 248 throw new RuntimeException("YY must be in unnamed module"); 249 } 250 if (YY.class.getPackage() != null) { 251 throw new RuntimeException("YY must be in null package"); 252 } 253 } 254 } 255 } 256 257 class CDSTestClassB { 258 // No field named "archivedObjects" 259 } 260 261 class CDSTestClassC { 262 Object[] archivedObjects; // Not a static field 263 } 264 265 class CDSTestClassD { 266 static int archivedObjects; // Not an int field 267 } 268 269 class CDSTestClassE { 270 static Object[] archivedObjects; 271 static { 272 // Not in unnamed package of unnamed module 273 archivedObjects = new Object[1]; 274 archivedObjects[0] = new pkg.ClassInPackage(); 275 } 276 } 277 278 class CDSTestClassF { 279 static Object[] archivedObjects; 280 static { 281 // Not in java.base 282 archivedObjects = new Object[1]; 283 archivedObjects[0] = java.util.logging.Level.OFF; 284 } 285 } 286 287 class CDSTestClassG { 288 static Object[] archivedObjects; 289 static { 290 if (archivedObjects == null) { 291 archivedObjects = new Object[13]; 292 archivedObjects[0] = Wrapper.BOOLEAN; 293 archivedObjects[1] = Wrapper.INT.zero(); 294 archivedObjects[2] = Wrapper.DOUBLE.zero(); 295 archivedObjects[3] = MyEnum.DUMMY1; 296 297 archivedObjects[4] = Boolean.class; 298 archivedObjects[5] = Byte.class; 299 archivedObjects[6] = Character.class; 300 archivedObjects[7] = Short.class; 301 archivedObjects[8] = Integer.class; 302 archivedObjects[9] = Long.class; 303 archivedObjects[10] = Float.class; 304 archivedObjects[11] = Double.class; 305 archivedObjects[12] = Void.class; 306 } else { 307 System.out.println("Initialized from CDS"); 308 } 309 } 310 311 public static void main(String args[]) { 312 if (archivedObjects[0] != Wrapper.BOOLEAN) { 313 throw new RuntimeException("Huh 0"); 314 } 315 316 if (archivedObjects[1] != Wrapper.INT.zero()) { 317 throw new RuntimeException("Huh 1"); 318 } 319 320 if (archivedObjects[2] != Wrapper.DOUBLE.zero()) { 321 throw new RuntimeException("Huh 2"); 322 } 323 324 if (archivedObjects[3] != MyEnum.DUMMY1) { 325 throw new RuntimeException("Huh 3"); 326 } 327 328 if (MyEnum.BOOLEAN != true) { 329 throw new RuntimeException("Huh 10.1"); 330 } 331 if (MyEnum.BYTE != -128) { 332 throw new RuntimeException("Huh 10.2"); 333 } 334 if (MyEnum.CHAR != 'c') { 335 throw new RuntimeException("Huh 10.3"); 336 } 337 if (MyEnum.SHORT != -12345) { 338 throw new RuntimeException("Huh 10.4"); 339 } 340 if (MyEnum.INT != -123456) { 341 throw new RuntimeException("Huh 10.5"); 342 } 343 if (MyEnum.LONG != 0x1234567890L) { 344 throw new RuntimeException("Huh 10.6"); 345 } 346 if (MyEnum.LONG2 != -0x1234567890L) { 347 throw new RuntimeException("Huh 10.7"); 348 } 349 if (MyEnum.FLOAT != 567891.0f) { 350 throw new RuntimeException("Huh 10.8"); 351 } 352 if (MyEnum.DOUBLE != 12345678905678.890) { 353 throw new RuntimeException("Huh 10.9"); 354 } 355 356 checkClass(4, Boolean.class); 357 checkClass(5, Byte.class); 358 checkClass(6, Character.class); 359 checkClass(7, Short.class); 360 checkClass(8, Integer.class); 361 checkClass(9, Long.class); 362 checkClass(10, Float.class); 363 checkClass(11, Double.class); 364 checkClass(12, Void.class); 365 366 System.out.println("Success!"); 367 } 368 369 static void checkClass(int index, Class c) { 370 if (archivedObjects[index] != c) { 371 throw new RuntimeException("archivedObjects[" + index + "] should be " + c); 372 } 373 } 374 375 // Simplified version of sun.invoke.util.Wrapper 376 public enum Wrapper { 377 // wrapperType simple primitiveType simple char emptyArray 378 BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]), 379 INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]), 380 DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0]) 381 ; 382 383 public static final int COUNT = 10; 384 private static final Object DOUBLE_ZERO = (Double)(double)0; 385 386 private final Class<?> wrapperType; 387 private final Class<?> primitiveType; 388 private final char basicTypeChar; 389 private final String basicTypeString; 390 private final Object emptyArray; 391 392 Wrapper(Class<?> wtype, 393 String wtypeName, 394 Class<?> ptype, 395 String ptypeName, 396 char tchar, 397 Object emptyArray) { 398 this.wrapperType = wtype; 399 this.primitiveType = ptype; 400 this.basicTypeChar = tchar; 401 this.basicTypeString = String.valueOf(this.basicTypeChar); 402 this.emptyArray = emptyArray; 403 } 404 405 public Object zero() { 406 return switch (this) { 407 case BOOLEAN -> Boolean.FALSE; 408 case INT -> (Integer)0; 409 case DOUBLE -> DOUBLE_ZERO; 410 default -> null; 411 }; 412 } 413 } 414 415 enum MyEnum { 416 DUMMY1, 417 DUMMY2; 418 419 static final boolean BOOLEAN = true; 420 static final byte BYTE = -128; 421 static final short SHORT = -12345; 422 static final char CHAR = 'c'; 423 static final int INT = -123456; 424 static final long LONG = 0x1234567890L; 425 static final long LONG2 = -0x1234567890L; 426 static final float FLOAT = 567891.0f; 427 static final double DOUBLE = 12345678905678.890; 428 } 429 }