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 /** premain allows static method pre-resolution
113 // Without -XX:+AOTClassLinking:
114 // java/lang/System is in the boot loader but ResolvedConstantsApp is loaded by the app loader.
115 // Even though System is in the vmClasses list, when ResolvedConstantsApp looks up
116 // "java/lang/System" in its ConstantPool, the app loader may not have resolved the System
117 // class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary)
118 .shouldMatch(AOTLINK_ONLY("klass.* ResolvedConstantsApp .*java/lang/System"))
119 **/
120 ;
121
122 testGroup("Field References", out)
123 // Always resolve references to fields in the current class or super class(es)
124 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I"))
125 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I"))
126 .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I"))
127 .shouldMatch(ALWAYS("field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I"))
128
129 // Resolve field references to child classes ONLY when using -XX:+AOTClassLinking
130 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.a:I"))
131 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.b:I"))
132
133 // Resolve field references to unrelated classes ONLY when using -XX:+AOTClassLinking
134 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.a:I"))
135 .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.b:I"));
136
137 if (args[0].equals("DYNAMIC")) {
138 // AOT resolution of CP methods/indy references is not implemeted
139 return;
140 }
141
142 testGroup("Method References", out)
143 // Should resolve references to own constructor
144 .shouldMatch(ALWAYS("method.* ResolvedConstantsApp ResolvedConstantsApp.<init>:"))
145 // Should resolve references to super constructor
146 .shouldMatch(ALWAYS("method.* ResolvedConstantsApp java/lang/Object.<init>:"))
147
148 // Should resolve interface methods in VM classes
149 .shouldMatch(ALWAYS("interface method .* ResolvedConstantsApp java/lang/Runnable.run:"))
150
151 // Should resolve references to own non-static method (private or public)
152 .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:"))
153 .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:"))
154 .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:"))
155 /** premain allows static method pre-resolution
156 // Should not resolve references to static method
157 .shouldNotMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:"))
158 **/
159
160 // Should resolve references to method in super type
161 .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:"))
162
163 // Without -XX:+AOTClassLinking App class cannot resolve references to methods in boot classes:
164 // When the app class loader tries to resolve a class X that's normally loaded by
165 // the boot loader, it's possible for the app class loader to get a different copy of
166 // X (by using MethodHandles.Lookup.defineClass(), etc). Therefore, let's be on
167 // the side of safety and revert all such references.
168 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp java/io/PrintStream.println:"))
169 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsBar java/lang/Class.getName:"))
170
171 // Resole resolve methods in unrelated classes ONLY when using -XX:+AOTClassLinking
172 .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:"))
173
174 // End ---
175 ;
176
177
178 // Indy References ---
179 if (aotClassLinking) {
180 /** premain allows Old classes to be linked
181 testGroup("Indy References", out)
182 .shouldContain("Cannot aot-resolve Lambda proxy because OldConsumer is excluded")
183 .shouldContain("Cannot aot-resolve Lambda proxy because OldProvider is excluded")
184 .shouldContain("Cannot aot-resolve Lambda proxy because OldClass is excluded")
185 .shouldContain("Cannot aot-resolve Lambda proxy of interface type InterfaceWithClinit")
186 .shouldMatch("klasses.* app *NormalClass[$][$]Lambda/.* hidden aot-linked inited")
187 .shouldNotMatch("klasses.* app *SubOfOldClass[$][$]Lambda/")
188 .shouldMatch("archived indy *CP entry.*StringConcatTest .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants")
189 .shouldNotMatch("archived indy *CP entry.*StringConcatTestOld .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants");
190 **/
191 }
192 }
193
194 static String ALWAYS(String s) {
195 return ",resolve.*archived " + s;
196 }
197
198 static String AOTLINK_ONLY(String s) {
199 if (aotClassLinking) {
200 return ALWAYS(s);
201 } else {
202 return ",resolve.*reverted " + s;
203 }
204 }
205
206 static OutputAnalyzer testGroup(String name, OutputAnalyzer out) {
207 System.out.println("Checking for: " + name);
208 return out;
209 }
210 }
211
212 class ResolvedConstantsApp implements Runnable {
213 public static void main(String args[]) {
214 System.out.println("Hello ResolvedConstantsApp");
215 ResolvedConstantsApp app = new ResolvedConstantsApp();
216 ResolvedConstantsApp.staticCall();
217 app.privateInstanceCall();
218 app.publicInstanceCall();
219 Object a = app;
220 ((Runnable)a).run();
221
222 ResolvedConstantsFoo foo = new ResolvedConstantsFoo();
223 ResolvedConstantsBar bar = new ResolvedConstantsBar();
224 bar.a ++;
225 bar.b ++;
226 bar.doit();
227
228 testLambda();
229 StringConcatTest.test();
230 StringConcatTestOld.main(null);
231 }
232 private static void staticCall() {}
233 private void privateInstanceCall() {}
234 public void publicInstanceCall() {}
235
236 public void run() {}
237
238 static void testLambda() {
239 // The functional type used in the Lambda is an excluded class
240 OldProvider op = () -> {
241 return null;
242 };
243
244 // A captured value is an instance of an excluded Class
245 OldClass c = new OldClass();
246 Runnable r = () -> {
247 System.out.println("Test 1 " + c);
248 };
249 r.run();
250
251 // The functional interface accepts an argument that's an excluded class
252 MyInterface i = (o) -> {
253 System.out.println("Test 2 " + o);
254 };
255 i.dispatch(c);
256
257 // Method reference to old class
258 OldConsumer oldConsumer = new OldConsumer();
259 Consumer<String> wrapper = oldConsumer::consumeString;
260 wrapper.accept("Hello");
261
262 // Lambda of interfaces that have <clinit> are not archived.
263 InterfaceWithClinit i2 = () -> {
264 System.out.println("Test 3");
265 };
266 i2.dispatch();
267
268 // These two classes have almost identical source code, but
269 // only NormalClass should have its lambdas pre-resolved.
270 // SubOfOldClass is "old" -- it should be excluded from the AOT cache,
271 // so none of its lambda proxies should be cached
272 NormalClass.testLambda(); // Lambda proxy should be cached
273 SubOfOldClass.testLambda(); // Lambda proxy shouldn't be cached
274 }
275 }
276
277 class StringConcatTest {
278 static void test() {
279 System.out.println("StringConcatTest <concat> " + new StringConcatTest()); // concat should be aot-resolved
280 }
281 }
282
283 /* see StringConcatTestOld.jasm
284
285 class StringConcatTestOld {
286 public static void main(String args[]) {
287 // concat should be aot-resolved => the MethodType refers to an old class
288 System.out.println("StringConcatTestOld <concat> " + new OldConsumer());
289 }
290 }
291 */
292
293 class NormalClass {
294 static void testLambda() {
295 Runnable r = () -> {
296 System.out.println("NormalClass testLambda");
297 };
298 r.run();
299 }
300 }
301
302 class SubOfOldClass extends OldClass {
303 static void testLambda() {
304 Runnable r = () -> {
305 System.out.println("SubOfOldClass testLambda");
306 };
307 r.run();
308 }
309 }
310
311 interface MyInterface {
312 void dispatch(OldClass c);
313 }
314
315 interface InterfaceWithClinit {
316 static final long X = System.currentTimeMillis();
317 void dispatch();
318 default long dummy() { return X; }
319 }
320
321 class ResolvedConstantsFoo {
322 int a = 1;
323 void doit() {
324 }
325
326 void doBar(ResolvedConstantsBar bar) {
327 bar.a ++;
328 bar.b ++;
329 }
330 }
331
332 class ResolvedConstantsBar extends ResolvedConstantsFoo {
333 int b = 2;
334 void doit() {
335 System.out.println("Hello ResolvedConstantsBar and " + ResolvedConstantsFoo.class.getName());
336 System.out.println("a = " + a);
337 System.out.println("a = " + ((ResolvedConstantsFoo)this).a);
338 System.out.println("b = " + b);
339
340 doBar(this);
341
342 ((ResolvedConstantsFoo)this).doBar(this);
343 }
344 }