1 /*
2 * Copyright (c) 2025, 2026, 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 AOT cache should preserve heap object identity when required by JLS. For example, Enums and Integers.
27 * @requires vm.cds
28 * @requires vm.cds.supports.aot.class.linking
29 * @requires vm.debug
30 * @library /test/lib
31 * @build HeapObjectIdentity
32 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar dummy.jar
33 * Dummy
34 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar
35 * HeapObjectIdentityApp
36 * MyAOTInitedClass
37 * MyAOTInitedClass$MyEnum
38 * MyAOTInitedClass$Wrapper
39 * @run driver HeapObjectIdentity AOT --two-step-training
40 */
41
42 import jdk.test.lib.cds.CDSAppTester;
43 import jdk.test.lib.process.OutputAnalyzer;
44 import jdk.test.lib.helpers.ClassFileInstaller;
45
46 public class HeapObjectIdentity {
47 static final String appJar = ClassFileInstaller.getJarPath("dummy.jar");
48 static final String bootJar = ClassFileInstaller.getJarPath("boot.jar");
49 static final String mainClass = "HeapObjectIdentityApp"; // Loaded from boot.jar
50
51 public static void main(String[] args) throws Exception {
52 Tester t = new Tester();
53 t.run(args);
54
55 // Integer$IntegerCache should preserve the object identity of cached Integer objects,
56 // even when the cache size is different between assembly and production.
57 t.productionRun(new String[] {
58 "-XX:AutoBoxCacheMax=2048"
59 });
60 }
61
62 static class Tester extends CDSAppTester {
63 public Tester() {
64 super(mainClass);
65 }
66
67 @Override
68 public String classpath(RunMode runMode) {
69 return appJar;
70 }
71
72 @Override
73 public String[] vmArgs(RunMode runMode) {
74 String bootcp = "-Xbootclasspath/a:" + bootJar;
75 if (runMode == RunMode.ASSEMBLY) {
76 return new String[] {
77 "-Xlog:aot+class=debug",
78 "-XX:AOTInitTestClass=MyAOTInitedClass",
79 bootcp
80 };
81 } else {
82 return new String[] {bootcp};
83 }
84 }
85
86 @Override
87 public String[] appCommandLine(RunMode runMode) {
88 return new String[] {
89 mainClass,
90 runMode.toString(),
91 };
92 }
93
94 @Override
95 public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
96 if (runMode == RunMode.ASSEMBLY) {
97 out.shouldContain("MyAOTInitedClass aot-linked early inited");
98 }
99 }
100 }
101 }
102
103 class HeapObjectIdentityApp {
104 public static void main(String... args) {
105 MyAOTInitedClass.test();
106 }
107 }
108
109 // This class is loaded by the boot loader, as -XX:AOTInitTestClass is not too friendly
110 // with classes by other loaders.
111 class MyAOTInitedClass {
112 static Object[] archivedObjects;
113 static {
114 if (archivedObjects == null) {
115 archivedObjects = new Object[14];
116 archivedObjects[0] = Wrapper.BOOLEAN;
117 archivedObjects[1] = Wrapper.INT.zero();
118 archivedObjects[2] = Wrapper.DOUBLE.zero();
119 archivedObjects[3] = MyEnum.DUMMY1;
120
121 archivedObjects[4] = Boolean.class;
122 archivedObjects[5] = Byte.class;
123 archivedObjects[6] = Character.class;
124 archivedObjects[7] = Short.class;
125 archivedObjects[8] = Integer.class;
126 archivedObjects[9] = Long.class;
127 archivedObjects[10] = Float.class;
128 archivedObjects[11] = Double.class;
129 archivedObjects[12] = Void.class;
130
131 archivedObjects[13] = Integer.valueOf(1);
132 } else {
133 System.out.println("Initialized from CDS");
134 }
135 }
136
137 public static void test() {
138 if (archivedObjects[0] != Wrapper.BOOLEAN) {
139 throw new RuntimeException("Huh 0");
140 }
141
142 if (archivedObjects[1] != Wrapper.INT.zero()) {
143 throw new RuntimeException("Huh 1");
144 }
145
146 if (archivedObjects[2] != Wrapper.DOUBLE.zero()) {
147 throw new RuntimeException("Huh 2");
148 }
149
150 if (archivedObjects[3] != MyEnum.DUMMY1) {
151 throw new RuntimeException("Huh 3");
152 }
153
154 if (MyEnum.BOOLEAN != true) {
155 throw new RuntimeException("Huh 10.1");
156 }
157 if (MyEnum.BYTE != -128) {
158 throw new RuntimeException("Huh 10.2");
159 }
160 if (MyEnum.CHAR != 'c') {
161 throw new RuntimeException("Huh 10.3");
162 }
163 if (MyEnum.SHORT != -12345) {
164 throw new RuntimeException("Huh 10.4");
165 }
166 if (MyEnum.INT != -123456) {
167 throw new RuntimeException("Huh 10.5");
168 }
169 if (MyEnum.LONG != 0x1234567890L) {
170 throw new RuntimeException("Huh 10.6");
171 }
172 if (MyEnum.LONG2 != -0x1234567890L) {
173 throw new RuntimeException("Huh 10.7");
174 }
175 if (MyEnum.FLOAT != 567891.0f) {
176 throw new RuntimeException("Huh 10.8");
177 }
178 if (MyEnum.DOUBLE != 12345678905678.890) {
179 throw new RuntimeException("Huh 10.9");
180 }
181
182 checkClass(4, Boolean.class);
183 checkClass(5, Byte.class);
184 checkClass(6, Character.class);
185 checkClass(7, Short.class);
186 checkClass(8, Integer.class);
187 checkClass(9, Long.class);
188 checkClass(10, Float.class);
189 checkClass(11, Double.class);
190 checkClass(12, Void.class);
191
192 if (archivedObjects[13] != Integer.valueOf(1)) {
193 throw new RuntimeException("Integer cache identity test failed");
194 }
195
196 System.out.println("Success!");
197 }
198
199 static void checkClass(int index, Class c) {
200 if (archivedObjects[index] != c) {
201 throw new RuntimeException("archivedObjects[" + index + "] should be " + c);
202 }
203 }
204
205 // Simplified version of sun.invoke.util.Wrapper
206 public enum Wrapper {
207 // wrapperType simple primitiveType simple char emptyArray
208 BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]),
209 INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]),
210 DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0])
211 ;
212
213 public static final int COUNT = 10;
214 private static final Object DOUBLE_ZERO = (Double)(double)0;
215
216 private final Class<?> wrapperType;
217 private final Class<?> primitiveType;
218 private final char basicTypeChar;
219 private final String basicTypeString;
220 private final Object emptyArray;
221
222 Wrapper(Class<?> wtype,
223 String wtypeName,
224 Class<?> ptype,
225 String ptypeName,
226 char tchar,
227 Object emptyArray) {
228 this.wrapperType = wtype;
229 this.primitiveType = ptype;
230 this.basicTypeChar = tchar;
231 this.basicTypeString = String.valueOf(this.basicTypeChar);
232 this.emptyArray = emptyArray;
233 }
234
235 public Object zero() {
236 return switch (this) {
237 case BOOLEAN -> Boolean.FALSE;
238 case INT -> (Integer)0;
239 case DOUBLE -> DOUBLE_ZERO;
240 default -> null;
241 };
242 }
243 }
244
245 enum MyEnum {
246 DUMMY1,
247 DUMMY2;
248
249 static final boolean BOOLEAN = true;
250 static final byte BYTE = -128;
251 static final short SHORT = -12345;
252 static final char CHAR = 'c';
253 static final int INT = -123456;
254 static final long LONG = 0x1234567890L;
255 static final long LONG2 = -0x1234567890L;
256 static final float FLOAT = 567891.0f;
257 static final double DOUBLE = 12345678905678.890;
258 }
259 }
260
261 class Dummy {}