1 /*
2 * Copyright (c) 2024, 2025, 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 id=static
27 * @summary Dump time resolution of constant pool entries (Static CDS archive).
28 * @requires vm.cds
29 * @requires vm.cds.supports.aot.class.linking
30 * @requires vm.compMode != "Xcomp"
31 * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/
32 * @build OldProvider OldClass OldConsumer StringConcatTestOld
33 * @build ResolvedConstants
34 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
35 * ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar
36 * MyInterface InterfaceWithClinit NormalClass
37 * OldProvider OldClass OldConsumer SubOfOldClass
38 * StringConcatTest StringConcatTestOld
39 * @run driver ResolvedConstants STATIC
40 */
41
42 /*
43 * @test id=dynamic
44 * @summary Dump time resolution of constant pool entries (Dynamic CDS archive)
45 * @requires vm.cds
46 * @requires vm.cds.supports.aot.class.linking
47 * @requires vm.compMode != "Xcomp"
48 * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/
49 * @build OldProvider OldClass OldConsumer StringConcatTestOld
50 * @build ResolvedConstants
51 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
52 * ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar
53 * MyInterface InterfaceWithClinit NormalClass
54 * OldProvider OldClass OldConsumer SubOfOldClass
55 * StringConcatTest StringConcatTestOld
56 * @build jdk.test.whitebox.WhiteBox
57 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
58 * @run main/othervm -Dcds.app.tester.workflow=DYNAMIC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. ResolvedConstants DYNAMIC
59 */
60
61 import java.util.function.Consumer;
62 import jdk.test.lib.cds.CDSOptions;
63 import jdk.test.lib.cds.CDSTestUtils;
64 import jdk.test.lib.cds.SimpleCDSAppTester;
65 import jdk.test.lib.helpers.ClassFileInstaller;
66 import jdk.test.lib.process.OutputAnalyzer;
67
68 public class ResolvedConstants {
69 static final String classList = "ResolvedConstants.classlist";
70 static final String appJar = ClassFileInstaller.getJarPath("app.jar");
71 static final String mainClass = ResolvedConstantsApp.class.getName();
72
73 static boolean aotClassLinking;
74 public static void main(String[] args) throws Exception {
75 test(args, false);
76 if (!args[0].equals("DYNAMIC")) {
77 test(args, true);
78 }
79 }
80
81 static void test(String[] args, boolean testMode) throws Exception {
82 aotClassLinking = testMode;
83
84 SimpleCDSAppTester.of("ResolvedConstantsApp" + (aotClassLinking ? "1" : "0"))
85 .addVmArgs(aotClassLinking ? "-XX:+AOTClassLinking" : "-XX:-AOTClassLinking",
86 "-Xlog:aot+resolve=trace",
87 "-Xlog:aot+class=debug",
88 "-Xlog:cds+class=debug")
89 .classpath(appJar)
90 .appCommandLine(mainClass)
91 .setAssemblyChecker((OutputAnalyzer out) -> {
92 checkAssemblyOutput(args, out);
93 })
94 .setProductionChecker((OutputAnalyzer out) -> {
95 out.shouldContain("Hello ResolvedConstantsApp");
96 })
97 .run(args);
98 }
99
100 static void checkAssemblyOutput(String args[], OutputAnalyzer out) {
101 testGroup("Class References", out)
102 // Always resolve reference when a class references itself
103 .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => ResolvedConstantsApp app"))
104
105 // Always resolve reference when a class references a super class
106 .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => java/lang/Object boot"))
107 .shouldMatch(ALWAYS("klass.* ResolvedConstantsBar app => ResolvedConstantsFoo app"))
108
109 // Always resolve reference when a class references a super interface
110 .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => java/lang/Runnable boot"))
111
112 // Without -XX:+AOTClassLinking:
113 // java/lang/System is in the boot loader but ResolvedConstantsApp is loaded by the app loader.
114 // Even though System is in the vmClasses list, when ResolvedConstantsApp looks up
115 // "java/lang/System" in its ConstantPool, the app loader may not have resolved the System
116 // class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary)
117 .shouldMatch(AOTLINK_ONLY("klass.* ResolvedConstantsApp .*java/lang/System"));
118
119 testGroup("Field References", out)
120 // Always resolve references to fields in the current class or super class(es)
121 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I"))
122 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I"))
123 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I"))
124 .shouldMatch(ALWAYS("field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I"))
125
126 // Resolve field references to child classes ONLY when using -XX:+AOTClassLinking
127 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.a:I"))
128 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.b:I"))
129
130 // Resolve field references to unrelated classes ONLY when using -XX:+AOTClassLinking
131 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.a:I"))
132 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.b:I"));
133
134 if (args[0].equals("DYNAMIC")) {
135 // AOT resolution of CP methods/indy references is not implemeted
136 return;
137 }
138
139 testGroup("Method References", out)
140 // Should resolve references to own constructor
141 .shouldMatch(ALWAYS("method.* ResolvedConstantsApp ResolvedConstantsApp.<init>:"))
142 // Should resolve references to super constructor
143 .shouldMatch(ALWAYS("method.* ResolvedConstantsApp java/lang/Object.<init>:"))
144
145 // Should resolve interface methods in VM classes
146 .shouldMatch(ALWAYS("interface method .* ResolvedConstantsApp java/lang/Runnable.run:"))
147
148 // Should resolve references to own non-static method (private or public)
149 .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:"))
150 .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:"))
151 .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:"))
152
153 // Should not resolve references to static method
154 .shouldNotMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:"))
155
156 // Should resolve references to method in super type
157 .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:"))
158
159 // Without -XX:+AOTClassLinking App class cannot resolve references to methods in boot classes:
160 // When the app class loader tries to resolve a class X that's normally loaded by
161 // the boot loader, it's possible for the app class loader to get a different copy of
162 // X (by using MethodHandles.Lookup.defineClass(), etc). Therefore, let's be on
163 // the side of safety and revert all such references.
164 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp java/io/PrintStream.println:"))
165 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsBar java/lang/Class.getName:"))
166
167 // Resole resolve methods in unrelated classes ONLY when using -XX:+AOTClassLinking
168 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:"))
169
170 // End ---
171 ;
172
173
174 // Indy References ---
175 if (aotClassLinking) {
176 testGroup("Indy References", out)
177 .shouldContain("Cannot aot-resolve Lambda proxy because OldConsumer is excluded")
178 .shouldContain("Cannot aot-resolve Lambda proxy because OldProvider is excluded")
179 .shouldContain("Cannot aot-resolve Lambda proxy because OldClass is excluded")
180 .shouldContain("Cannot aot-resolve Lambda proxy of interface type InterfaceWithClinit")
181 .shouldMatch("klasses.* app *NormalClass[$][$]Lambda/.* hidden aot-linked inited")
182 .shouldNotMatch("klasses.* app *SubOfOldClass[$][$]Lambda/")
183 .shouldMatch("archived indy *CP entry.*StringConcatTest .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants")
184 .shouldNotMatch("archived indy *CP entry.*StringConcatTestOld .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants");
185 }
186 }
187
188 static String ALWAYS(String s) {
189 return ",resolve.*archived " + s;
190 }
191
192 static String AOTLINK_ONLY(String s) {
193 if (aotClassLinking) {
194 return ALWAYS(s);
195 } else {
196 return ",resolve.*reverted " + s;
197 }
198 }
199
200 static OutputAnalyzer testGroup(String name, OutputAnalyzer out) {
201 System.out.println("Checking for: " + name);
202 return out;
203 }
204 }
205
206 class ResolvedConstantsApp implements Runnable {
207 public static void main(String args[]) {
208 System.out.println("Hello ResolvedConstantsApp");
209 ResolvedConstantsApp app = new ResolvedConstantsApp();
210 ResolvedConstantsApp.staticCall();
211 app.privateInstanceCall();
212 app.publicInstanceCall();
213 Object a = app;
214 ((Runnable)a).run();
215
216 ResolvedConstantsFoo foo = new ResolvedConstantsFoo();
217 ResolvedConstantsBar bar = new ResolvedConstantsBar();
218 bar.a ++;
219 bar.b ++;
220 bar.doit();
221
222 testLambda();
223 StringConcatTest.test();
224 StringConcatTestOld.main(null);
225 }
226 private static void staticCall() {}
227 private void privateInstanceCall() {}
228 public void publicInstanceCall() {}
229
230 public void run() {}
231
232 static void testLambda() {
233 // The functional type used in the Lambda is an excluded class
234 OldProvider op = () -> {
235 return null;
236 };
237
238 // A captured value is an instance of an excluded Class
239 OldClass c = new OldClass();
240 Runnable r = () -> {
241 System.out.println("Test 1 " + c);
242 };
243 r.run();
244
245 // The functional interface accepts an argument that's an excluded class
246 MyInterface i = (o) -> {
247 System.out.println("Test 2 " + o);
248 };
249 i.dispatch(c);
250
251 // Method reference to old class
252 OldConsumer oldConsumer = new OldConsumer();
253 Consumer<String> wrapper = oldConsumer::consumeString;
254 wrapper.accept("Hello");
255
256 // Lambda of interfaces that have <clinit> are not archived.
257 InterfaceWithClinit i2 = () -> {
258 System.out.println("Test 3");
259 };
260 i2.dispatch();
261
262 // These two classes have almost identical source code, but
263 // only NormalClass should have its lambdas pre-resolved.
264 // SubOfOldClass is "old" -- it should be excluded from the AOT cache,
265 // so none of its lambda proxies should be cached
266 NormalClass.testLambda(); // Lambda proxy should be cached
267 SubOfOldClass.testLambda(); // Lambda proxy shouldn't be cached
268 }
269 }
270
271 class StringConcatTest {
272 static void test() {
273 System.out.println("StringConcatTest <concat> " + new StringConcatTest()); // concat should be aot-resolved
274 }
275 }
276
277 /* see StringConcatTestOld.jasm
278
279 class StringConcatTestOld {
280 public static void main(String args[]) {
281 // concat should be aot-resolved => the MethodType refers to an old class
282 System.out.println("StringConcatTestOld <concat> " + new OldConsumer());
283 }
284 }
285 */
286
287 class NormalClass {
288 static void testLambda() {
289 Runnable r = () -> {
290 System.out.println("NormalClass testLambda");
291 };
292 r.run();
293 }
294 }
295
296 class SubOfOldClass extends OldClass {
297 static void testLambda() {
298 Runnable r = () -> {
299 System.out.println("SubOfOldClass testLambda");
300 };
301 r.run();
302 }
303 }
304
305 interface MyInterface {
306 void dispatch(OldClass c);
307 }
308
309 interface InterfaceWithClinit {
310 static final long X = System.currentTimeMillis();
311 void dispatch();
312 default long dummy() { return X; }
313 }
314
315 class ResolvedConstantsFoo {
316 int a = 1;
317 void doit() {
318 }
319
320 void doBar(ResolvedConstantsBar bar) {
321 bar.a ++;
322 bar.b ++;
323 }
324 }
325
326 class ResolvedConstantsBar extends ResolvedConstantsFoo {
327 int b = 2;
328 void doit() {
329 System.out.println("Hello ResolvedConstantsBar and " + ResolvedConstantsFoo.class.getName());
330 System.out.println("a = " + a);
331 System.out.println("a = " + ((ResolvedConstantsFoo)this).a);
332 System.out.println("b = " + b);
333
334 doBar(this);
335
336 ((ResolvedConstantsFoo)this).doBar(this);
337 }
338 }