1 /*
2 * Copyright (c) 2015, 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 * @test
26 * @bug 8072480 8277106 8331027
27 * @summary Unit test for CreateSymbols
28 * @modules java.compiler
29 * jdk.compiler/com.sun.tools.javac.api
30 * jdk.compiler/com.sun.tools.javac.jvm
31 * jdk.compiler/com.sun.tools.javac.main
32 * jdk.compiler/com.sun.tools.javac.util
33 * @clean *
34 * @run junit/othervm CreateSymbolsTest
35 */
36
37 import java.io.File;
38 import java.io.InputStream;
39 import java.io.Writer;
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.lang.classfile.ClassFile;
43 import java.lang.classfile.ClassModel;
44 import java.lang.classfile.attribute.ModuleAttribute;
45 import java.lang.classfile.attribute.ModulePackagesAttribute;
46 import java.lang.constant.PackageDesc;
47 import java.lang.reflect.Method;
48 import java.util.Arrays;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.io.IOException;
54 import java.io.OutputStream;
55 import java.nio.charset.StandardCharsets;
56 import java.nio.file.DirectoryStream;
57 import java.nio.file.FileVisitResult;
58 import java.nio.file.FileVisitor;
59 import java.nio.file.Files;
60 import java.nio.file.Path;
61 import java.nio.file.Paths;
62 import java.nio.file.attribute.BasicFileAttributes;
63 import java.util.Enumeration;
64 import java.util.HashSet;
65 import java.util.Set;
66 import java.util.jar.JarEntry;
67 import java.util.jar.JarFile;
68 import java.util.stream.Collectors;
69 import java.util.stream.Stream;
70
71 import org.junit.jupiter.api.Test;
72 import toolbox.JavacTask;
73 import toolbox.Task;
74 import toolbox.Task.Expect;
75 import toolbox.ToolBox;
76 import build.tools.symbolgenerator.CreateSymbols;
77 import build.tools.symbolgenerator.CreateSymbols.ClassDescription;
78 import build.tools.symbolgenerator.CreateSymbols.ClassList;
79 import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList;
80 import build.tools.symbolgenerator.CreateSymbols.VersionDescription;
81 import java.io.UncheckedIOException;
82 import java.lang.classfile.attribute.ModuleMainClassAttribute;
83 import java.lang.constant.ClassDesc;
84 import java.util.Objects;
85 import java.util.function.Consumer;
86
87 public class CreateSymbolsTestImpl {
88
89 static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols";
90
91 @Test
92 void testMethodRemoved() throws Exception {
93 doTest("package t; public class T { public void m() { } }",
94 "package t; public class T { }",
95 "package t; public class Test { { T t = null; t.m(); } }",
96 Expect.SUCCESS,
97 Expect.FAIL);
98 doTest("package t; public class T { public void b() { } public void m() { } public void a() { } }",
99 "package t; public class T { public void b() { } public void a() { } }",
100 "package t; public class Test { { T t = null; t.b(); t.a(); } }",
101 Expect.SUCCESS,
102 Expect.SUCCESS);
103 //with additional attribute (need to properly skip the member):
104 doTest("package t; public class T { public void m() throws IllegalStateException { } public void a() { } }",
105 "package t; public class T { public void a() { } }",
106 "package t; public class Test { { T t = null; t.a(); } }",
107 Expect.SUCCESS,
108 Expect.SUCCESS);
109 }
110
111 @Test
112 void testMethodAdded() throws Exception {
113 doTest("package t; public class T { }",
114 "package t; public class T { public void m() { } }",
115 "package t; public class Test { { T t = null; t.m(); } }",
116 Expect.FAIL,
117 Expect.SUCCESS);
118 doTest("package t; public class T { public void b() { } public void a() { } }",
119 "package t; public class T { public void b() { } public void m() { } public void a() { } }",
120 "package t; public class Test { { T t = null; t.b(); t.a(); } }",
121 Expect.SUCCESS,
122 Expect.SUCCESS);
123 }
124
125 //verify fields added/modified/removed
126
127 @Test
128 void testClassAdded() throws Exception {
129 doTest("class Dummy {}",
130 "package t; public class T { }",
131 "package t; public class Test { { T t = new T(); } }",
132 Expect.FAIL,
133 Expect.SUCCESS);
134 }
135
136 @Test
137 void testClassModified() throws Exception {
138 doTest("package t; public class T { public void m() { } }",
139 "package t; public class T implements java.io.Serializable { public void m() { } }",
140 "package t; public class Test { { java.io.Serializable t = new T(); } }",
141 Expect.FAIL,
142 Expect.SUCCESS);
143 }
144
145 @Test
146 void testClassRemoved() throws Exception {
147 doTest("package t; public class T { }",
148 "class Dummy {}",
149 "package t; public class Test { { T t = new T(); } }",
150 Expect.SUCCESS,
151 Expect.FAIL);
152 }
153
154 @Test
155 void testInnerClassAttributes() throws Exception {
156 doTest("package t; public class T { public static class Inner { } }",
157 "package t; public class T { public static class Inner { } public void extra() {} }",
158 "package t; import t.T.Inner; public class Test { Inner i; }",
159 Expect.SUCCESS,
160 Expect.SUCCESS);
161 }
162
163 @Test
164 void testConstantAdded() throws Exception {
165 doTest("package t; public class T { }",
166 "package t; public class T { public static final int A = 0; }",
167 "package t; public class Test { void t(int i) { switch (i) { case T.A: break;} } }",
168 Expect.FAIL,
169 Expect.SUCCESS);
170 }
171
172 @Test
173 void testAnnotationAttributeDefaultvalue() throws Exception {
174 //TODO: this only verifies that there is *some* value, but we should also verify there is a specific value:
175 doTest("package t; public @interface T { }",
176 "package t;\n" +
177 "public @interface T {\n" +
178 " public boolean booleanValue() default true;\n" +
179 " public byte byteValue() default 1;\n" +
180 " public char charValue() default 2;\n" +
181 " public short shortValue() default 3;\n" +
182 " public int intValue() default 4;\n" +
183 " public long longValue() default 5;\n" +
184 " public float floatValue() default 6;\n" +
185 " public double doubleValue() default 7;\n" +
186 " public String stringValue() default \"8\";\n" +
187 " public java.lang.annotation.RetentionPolicy enumValue() default java.lang.annotation.RetentionPolicy.RUNTIME;\n" +
188 " public Class classValue() default Number.class;\n" +
189 " public int[] arrayValue() default {1, 2};\n" +
190 " public SuppressWarnings annotationValue() default @SuppressWarnings(\"cast\");\n" +
191 "}\n",
192 "package t; public @T class Test { }",
193 Expect.SUCCESS,
194 Expect.SUCCESS);
195 }
196
197 @Test
198 void testConstantTest() throws Exception {
199 //XXX: other constant types (String in particular) - see testStringConstant
200 doPrintElementTest("package t; public class T { public static final int A = 1; }",
201 "package t; public class T { public static final int A = 2; }",
202 "t.T",
203 "package t;\n\n" +
204 "public class T {\n" +
205 " public static final int A = 1;\n\n" +
206 " public T();\n" +
207 "}\n",
208 "t.T",
209 "package t;\n\n" +
210 "public class T {\n" +
211 " public static final int A = 2;\n\n" +
212 " public T();\n" +
213 "}\n");
214 doPrintElementTest("package t; public class T { public static final boolean A = false; }",
215 "package t; public class T { public static final boolean A = true; }",
216 "t.T",
217 "package t;\n\n" +
218 "public class T {\n" +
219 " public static final boolean A = false;\n\n" +
220 " public T();\n" +
221 "}\n",
222 "t.T",
223 "package t;\n\n" +
224 "public class T {\n" +
225 " public static final boolean A = true;\n\n" +
226 " public T();\n" +
227 "}\n");
228 }
229
230 @Test
231 void testAnnotations() throws Exception {
232 Set<String> extraAnnotations = Set.of("Ljava/lang/annotation/Retention;");
233 CreateSymbols.HARDCODED_ANNOTATIONS.addAll(extraAnnotations);
234 try {
235 doPrintElementTest("package t;" +
236 "import java.lang.annotation.*;" +
237 "public @Visible @Invisible class T { public void extra() { } }" +
238 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
239 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
240 "package t;" +
241 "import java.lang.annotation.*;" +
242 "public @Visible @Invisible class T { }" +
243 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
244 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
245 "t.T",
246 "package t;\n\n" +
247 "@t.Invisible\n" +
248 "@t.Visible\n" +
249 "public class T {\n\n" +
250 " public T();\n\n" +
251 " public void extra();\n" +
252 "}\n",
253 "t.Visible",
254 "package t;\n\n" +
255 "@java.lang.annotation.Retention(RUNTIME)\n" +
256 "@interface Visible {\n" +
257 "}\n");
258 doPrintElementTest("package t;" +
259 "import java.lang.annotation.*;" +
260 "import java.util.*;" +
261 "public class T {" +
262 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" +
263 "}" +
264 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
265 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
266 "package t;" +
267 "import java.lang.annotation.*;" +
268 "import java.util.*;" +
269 "public class T {" +
270 " public void test(int h, @Invisible int i, @Visible List<String> j, int k) { }" +
271 " public void extra() { }" +
272 "}" +
273 "@Retention(RetentionPolicy.RUNTIME) @interface Visible { }" +
274 "@Retention(RetentionPolicy.CLASS) @interface Invisible { }",
275 "t.T",
276 "package t;\n\n" +
277 "public class T {\n\n" +
278 " public T();\n\n" +
279 " public void test(int arg0,\n" +
280 " @t.Invisible int arg1,\n" +
281 " @t.Visible java.util.List<java.lang.String> arg2,\n" +
282 " int arg3);\n" +
283 "}\n",
284 "t.Visible",
285 "package t;\n\n" +
286 "@java.lang.annotation.Retention(RUNTIME)\n" +
287 "@interface Visible {\n" +
288 "}\n");
289 doPrintElementTest("package t;" +
290 "import java.lang.annotation.*;" +
291 "public class T {" +
292 " public void test(@Ann(v=\"url\", dv=\"\\\"\\\"\") String str) { }" +
293 "}" +
294 "@Retention(RetentionPolicy.RUNTIME) @interface Ann {" +
295 " public String v();" +
296 " public String dv();" +
297 "}",
298 "package t;" +
299 "public class T { }",
300 "t.T",
301 "package t;\n\n" +
302 "public class T {\n\n" +
303 " public T();\n\n" +
304 " public void test(@t.Ann(dv=\"\\\"\\\"\", v=\"url\") java.lang.String arg0);\n" +
305 "}\n",
306 "t.T",
307 "package t;\n\n" +
308 "public class T {\n\n" +
309 " public T();\n" +
310 "}\n");
311 } finally {
312 CreateSymbols.HARDCODED_ANNOTATIONS.removeAll(extraAnnotations);
313 }
314 }
315
316 @Test
317 void testStringConstant() throws Exception {
318 doTest("package t; public class T { public static final String C = \"\"; }",
319 "package t; public class T { public static final String C = \"\"; public void extra() { } }",
320 "package t; public class Test { { System.err.println(T.C); } }",
321 Expect.SUCCESS,
322 Expect.SUCCESS);
323 }
324
325 @Test
326 void testCopyProfileAnnotation() throws Exception {
327 String oldProfileAnnotation = CreateSymbols.PROFILE_ANNOTATION;
328 try {
329 CreateSymbols.PROFILE_ANNOTATION = "Lt/Ann;";
330 doTestEquivalence("package t; public @Ann class T { public void t() {} } @interface Ann { }",
331 "package t; public class T { public void t() {} }",
332 "t.T");
333 } finally {
334 CreateSymbols.PROFILE_ANNOTATION = oldProfileAnnotation;
335 }
336 }
337
338 @Test
339 void testParseAnnotation() throws Exception {
340 CreateSymbols.parseAnnotations("@Lsun/Proprietary+Annotation;@Ljdk/Profile+Annotation;(value=I1)", new int[1]);
341 CreateSymbols.parseAnnotations("@Ltest;(value={\"\"})", new int[1]);
342 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value={\"path\"})", new int[1]);
343 CreateSymbols.parseAnnotations("@Ljava/beans/ConstructorProperties;(value=I-2)", new int[1]);
344 }
345
346 @Test
347 void testStringCharLiterals() throws Exception {
348 doPrintElementTest("package t;" +
349 "public class T {" +
350 " public static final String STR = \"\\u0000\\u0001\\uffff\";" +
351 " public static final String EMPTY = \"\";" +
352 " public static final String AMP = \"&&<<>>''\";" +
353 "}",
354 "package t;" +
355 " public class T {" +
356 " public static final char c = '\\uffff';" +
357 "}",
358 "t.T",
359 "package t;\n\n" +
360 "public class T {\n" +
361 " public static final java.lang.String STR = \"\\u0000\\u0001\\uffff\";\n" +
362 " public static final java.lang.String EMPTY = \"\";\n" +
363 " public static final java.lang.String AMP = \"&&<<>>''\";\n\n" +
364 " public T();\n" +
365 "}\n",
366 "t.T",
367 "package t;\n\n" +
368 "public class T {\n" +
369 " public static final char c = '\\uffff';\n\n" +
370 " public T();\n" +
371 "}\n");
372 }
373
374 @Test
375 void testGenerification() throws Exception {
376 doTest("package t; public class T { public class TT { public Object t() { return null; } } }",
377 "package t; public class T<E> { public class TT { public E t() { return null; } } }",
378 "package t; public class Test { { T.TT tt = null; tt.t(); } }",
379 Expect.SUCCESS,
380 Expect.SUCCESS);
381 }
382
383 @Test
384 void testClearMissingAnnotations() throws Exception {
385 doPrintElementTest(new String[] {
386 """
387 package t;
388 import t.impl.HC;
389 import t.impl.HR;
390 @HC @HR public class T {
391 @HC @HR public static final int i = 0;
392 @HC @HR public void t() {}
393 }
394 """,
395 """
396 package t.impl;
397 import java.lang.annotation.*;
398 @Retention(RetentionPolicy.CLASS)
399 public @interface HC {
400 }
401 """,
402 """
403 package t.impl;
404 import java.lang.annotation.*;
405 @Retention(RetentionPolicy.RUNTIME)
406 public @interface HR {
407 }
408 """
409 },
410 new String[] {
411 """
412 package t;
413 public class T {
414 public static final int i = 0;
415 }
416 """
417 },
418 "t.T",
419 """
420 package t;
421
422 public class T {
423 public static final int i = 0;
424
425 public T();
426
427 public void t();
428 }
429 """,
430 "t.T",
431 """
432 package t;
433
434 public class T {
435 public static final int i = 0;
436
437 public T();
438 }
439 """);
440 }
441
442 int i = 0;
443
444 void doTest(String code7, String code8, String testCode, Expect result7, Expect result8) throws Exception {
445 ToolBox tb = new ToolBox();
446 Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8});
447 Path output = classes.getParent();
448 Path scratch = output.resolve("scratch");
449
450 Files.createDirectories(scratch);
451
452 new JavacTask(tb)
453 .sources(testCode)
454 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"))
455 .run(result7)
456 .writeAll();
457 new JavacTask(tb)
458 .sources(testCode)
459 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"))
460 .run(result8)
461 .writeAll();
462 }
463
464 private static String computeClassPath(Path classes, String version) throws IOException {
465 try (Stream<Path> elements = Files.list(classes)) {
466 return elements.filter(el -> el.getFileName().toString().contains(version))
467 .map(el -> el.resolve("java.base"))
468 .map(el -> el.toAbsolutePath().toString())
469 .collect(Collectors.joining(File.pathSeparator));
470 }
471 }
472
473 void doPrintElementTest(String code7, String code8, String className7, String printed7, String className8, String printed8) throws Exception {
474 doPrintElementTest(new String[] {code7}, new String[] {code8}, className7, printed7, className8, printed8);
475 }
476
477 void doPrintElementTest(String[] code7, String[] code8, String className7, String printed7, String className8, String printed8) throws Exception {
478 ToolBox tb = new ToolBox();
479 Path classes = prepareVersionedCTSym(code7, code8);
480 Path output = classes.getParent();
481 Path scratch = output.resolve("scratch");
482
483 Files.createDirectories(scratch);
484
485 String out;
486 out = new JavacTask(tb, Task.Mode.CMDLINE)
487 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "7"), "-Xprint", className7)
488 .run(Expect.SUCCESS)
489 .getOutput(Task.OutputKind.STDOUT)
490 .replaceAll("\\R", "\n");
491 if (!out.equals(printed7)) {
492 throw new AssertionError("out=" + out + "; printed7=" + printed7);
493 }
494 out = new JavacTask(tb, Task.Mode.CMDLINE)
495 .options("-d", scratch.toAbsolutePath().toString(), "-classpath", computeClassPath(classes, "8"), "-Xprint", className8)
496 .run(Expect.SUCCESS)
497 .getOutput(Task.OutputKind.STDOUT)
498 .replaceAll("\\R", "\n");
499 if (!out.equals(printed8)) {
500 throw new AssertionError("out=" + out + "; printed8=" + printed8);
501 }
502 }
503
504 void doTestEquivalence(String code7, String code8, String testClass) throws Exception {
505 Path classes = prepareVersionedCTSym(new String[] {code7}, new String[] {code8});
506 Path classfile = classes.resolve("78").resolve("java.base").resolve(testClass.replace('.', '/') + ".class");
507
508 if (!Files.isReadable(classfile)) {
509 throw new AssertionError("Cannot find expected class.");
510 }
511 }
512
513 @Test
514 void testIncluded() throws Exception {
515 doTestIncluded("package t;\n" +
516 "public class Test extends PP1<PP2> implements PP3<PP4>, PP5<PP6> {\n" +
517 " public PP7 m1(PP8 p) { return null;}\n" +
518 " public PP9<PPA> m2(PPB<PPC> p) { return null;}\n" +
519 " public PPD f1;\n" +
520 " public PPE<PPF> f2;\n" +
521 " public Test2 aux;\n" +
522 "}\n" +
523 "class Test2 extends PPG implements PPH, PPI {\n" +
524 "}\n" +
525 "class PP1<T> {}\n" +
526 "class PP2 {}\n" +
527 "interface PP3<T> {}\n" +
528 "class PP4 {}\n" +
529 "interface PP5<T> {}\n" +
530 "class PP6 {}\n" +
531 "class PP7 {}\n" +
532 "class PP8 {}\n" +
533 "class PP9<T> {}\n" +
534 "class PPA {}\n" +
535 "class PPB<T> {}\n" +
536 "class PPC {}\n" +
537 "class PPD {}\n" +
538 "class PPE<T> {}\n" +
539 "class PPF {}\n" +
540 "class PPG {}\n" +
541 "interface PPH {}\n" +
542 "interface PPI {}\n",
543 "t.Test",
544 "t.Test2",
545 "t.PP1",
546 "t.PP2",
547 "t.PP3",
548 "t.PP4",
549 "t.PP5",
550 "t.PP6",
551 "t.PP7",
552 "t.PP8",
553 "t.PP9",
554 "t.PPA",
555 "t.PPB",
556 "t.PPC",
557 "t.PPD",
558 "t.PPE",
559 "t.PPF",
560 "t.PPG",
561 "t.PPH",
562 "t.PPI");
563 }
564
565 @Test
566 void testRecords() throws Exception {
567 doPrintElementTest("package t;" +
568 "public class T {" +
569 " public record R(int i, java.util.List<String> l) { }" +
570 "}",
571 "package t;" +
572 "public class T {" +
573 " public record R(@Ann int i, long j, java.util.List<String> l) { }" +
574 " public @interface Ann {} " +
575 "}",
576 "t.T$R",
577 """
578
579 public static record R(int i, java.util.List<java.lang.String> l) {
580
581 public R(int i,
582 java.util.List<java.lang.String> l);
583
584 public final java.lang.String toString();
585
586 public final int hashCode();
587
588 public final boolean equals(java.lang.Object arg0);
589
590 public int i();
591
592 public java.util.List<java.lang.String> l();
593 }
594 """,
595 "t.T$R",
596 """
597
598 public static record R(@t.T.Ann int i, long j, java.util.List<java.lang.String> l) {
599
600 public final java.lang.String toString();
601
602 public final int hashCode();
603
604 public final boolean equals(java.lang.Object arg0);
605
606 public java.util.List<java.lang.String> l();
607
608 public R(@t.T.Ann int i,
609 long j,
610 java.util.List<java.lang.String> l);
611
612 @t.T.Ann
613 public int i();
614
615 public long j();
616 }
617 """);
618 doPrintElementTest("package t;" +
619 "public record R() {" +
620 "}",
621 "package t;" +
622 "public record R(int i) {" +
623 "}",
624 "t.R",
625 """
626 package t;
627 \n\
628 public record R() {
629 \n\
630 public R();
631 \n\
632 public final java.lang.String toString();
633 \n\
634 public final int hashCode();
635 \n\
636 public final boolean equals(java.lang.Object arg0);
637 }
638 """,
639 "t.R",
640 """
641 package t;
642 \n\
643 public record R(int i) {
644 \n\
645 public final java.lang.String toString();
646 \n\
647 public final int hashCode();
648 \n\
649 public final boolean equals(java.lang.Object arg0);
650 \n\
651 public R(int i);
652 \n\
653 public int i();
654 }
655 """);
656 }
657
658 @Test
659 void testNonExportedSuperclass() throws Exception {
660 doTestComplex("api.Api",
661 """
662 package api;
663
664 public class Api extends nonapi.Impl.Nested.Exp {
665
666 public Api();
667 }
668 """,
669 """
670 import api.Api;
671 public class Test {
672 private void t(Api api) {
673 api.run();
674 }
675 }
676 """,
677 """
678 import api.Api;
679 public class Test {
680 private void t(Api api) {
681 fail
682 }
683 }
684 """,
685 """
686 module m {
687 exports api;
688 }
689 """,
690 """
691 package api;
692 import nonapi.Impl;
693 public class Api extends Impl.Nested.Exp {
694 }
695 """,
696 """
697 package api;
698 public @interface Ann {
699 }
700 """,
701 """
702 package nonapi;
703 import api.Ann;
704 public class Impl {
705 public static final String C = "";
706 public void test() {}
707 @Ann
708 public static class Nested {
709 public static class Exp extends Nested implements Runnable {
710 public void run() {}
711 public OtherNested get() { return null; }
712 }
713 }
714 public static class OtherNested {}
715 }
716 """);
717 }
718
719 void doTestComplex(String printClass,
720 String expected,
721 String depSuccess,
722 String depFailure,
723 String... code) throws Exception {
724 ToolBox tb = new ToolBox();
725 String testClasses = System.getProperty("test.classes");
726 Path output = Paths.get(testClasses, "test-data" + i++);
727 deleteRecursively(output);
728 Files.createDirectories(output);
729 Path ver9Jar = output.resolve("9.jar");
730 compileAndPack(output,
731 ver9Jar,
732 code);
733
734
735 Path ctSym = output.resolve("ct.sym");
736
737 deleteRecursively(ctSym);
738
739 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
740 CreateSymbols.EXTENSION = ".class";
741
742 deleteRecursively(ctSym);
743
744 List<VersionDescription> versions =
745 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
746
747 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
748 @Override public boolean accepts(String className, boolean includePrivateClasses) {
749 return true;
750 }
751 };
752 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
753 Path symbolsDesc = ctSym.resolve("symbols");
754 Path modules = ctSym.resolve("modules");
755 Path modulesList = ctSym.resolve("modules-list");
756
757 Files.createDirectories(modules);
758 try (Writer w = Files.newBufferedWriter(modulesList)) {}
759
760 Path classesZip = output.resolve("classes.zip");
761 Path classesDir = output.resolve("classes");
762
763 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString());
764
765 try (JarFile jf = new JarFile(classesZip.toFile())) {
766 Enumeration<JarEntry> en = jf.entries();
767
768 while (en.hasMoreElements()) {
769 JarEntry je = en.nextElement();
770 if (je.isDirectory()) continue;
771 Path target = classesDir.resolve(je.getName());
772 Files.createDirectories(target.getParent());
773 Files.copy(jf.getInputStream(je), target);
774 }
775 }
776
777 Path classes = classesDir;
778 Path scratch = output.resolve("scratch");
779
780 Files.createDirectories(scratch);
781
782 String modulePath;
783
784 try (Stream<Path> elements = Files.list(classes)) {
785 modulePath = elements.filter(el -> el.getFileName().toString().contains("9"))
786 .map(el -> el.resolve("m"))
787 .map(el -> el.toAbsolutePath().toString())
788 .collect(Collectors.joining(File.pathSeparator));
789 }
790
791 {
792 String out = new JavacTask(tb, Task.Mode.CMDLINE)
793 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
794 "--add-modules", "m", "-Xprint", "api.Api")
795 .run(Expect.SUCCESS)
796 .getOutput(Task.OutputKind.STDOUT)
797 .replaceAll("\\R", "\n");
798
799 if (!out.equals(expected)) {
800 throw new AssertionError("out=" + out + "; expected=" + expected);
801 }
802 }
803
804 {
805 new JavacTask(tb)
806 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
807 "--add-modules", "m")
808 .sources(depSuccess)
809 .run(Expect.SUCCESS)
810 .writeAll();
811 }
812
813 {
814 String expectedFailure = new JavacTask(tb)
815 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", output.resolve("temp").toString(),
816 "--add-modules", "m", "-XDrawDiagnostics")
817 .sources(depFailure)
818 .run(Expect.FAIL)
819 .getOutput(Task.OutputKind.DIRECT)
820 .replaceAll("\\R", "\n");
821
822 String out = new JavacTask(tb)
823 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath,
824 "--add-modules", "m", "-XDrawDiagnostics")
825 .sources(depFailure)
826 .run(Expect.FAIL)
827 .getOutput(Task.OutputKind.DIRECT)
828 .replaceAll("\\R", "\n");
829
830 if (!out.equals(expectedFailure)) {
831 throw new AssertionError("out=" + out + "; expected=" + expectedFailure);
832 }
833 }
834 }
835
836 @Test
837 void testExtendsInternalData1() throws Exception {
838 doTestData("""
839 module name m
840 header exports api extraModulePackages nonapi requires name\\u0020;java.base\\u0020;flags\\u0020;8000\\u0020;version\\u0020;0 flags 8000
841
842 class name api/Ann
843 header extends java/lang/Object implements java/lang/annotation/Annotation flags 2601
844
845 class name api/Api
846 header extends nonapi/Impl$Nested$Exp flags 21
847 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
848 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
849 method name <init> descriptor ()V flags 1
850
851 class name nonapi/Impl
852 header extends java/lang/Object nestMembers nonapi/Impl$Nested,nonapi/Impl$Nested$Exp flags 21
853 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
854 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
855 field name C descriptor Ljava/lang/String; constantValue flags 19
856 method name <init> descriptor ()V flags 1
857 method name test descriptor ()V flags 1
858
859 class name nonapi/Impl$Nested
860 header extends java/lang/Object nestHost nonapi/Impl flags 21 classAnnotations @Lapi/Ann;
861 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
862 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
863 method name <init> descriptor ()V flags 1
864
865 class name nonapi/Impl$Nested$Exp
866 header extends nonapi/Impl$Nested implements java/lang/Runnable nestHost nonapi/Impl flags 21
867 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29
868 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29
869 method name <init> descriptor ()V flags 1
870 method name run descriptor ()V flags 1
871 method name get descriptor ()Lnonapi/Impl$OtherNested; flags 1
872
873 """,
874 """
875 module m {
876 exports api;
877 exports nonapi to java.base;
878 }
879 """,
880 """
881 package api;
882 import nonapi.Impl;
883 public class Api extends Impl.Nested.Exp {
884 }
885 """,
886 """
887 package api;
888 public @interface Ann {
889 }
890 """,
891 """
892 package nonapi;
893 import api.Ann;
894 public class Impl {
895 public static final String C = "";
896 public void test() {}
897 @Ann
898 public static class Nested {
899 public static class Exp extends Nested implements Runnable {
900 public void run() {}
901 public OtherNested get() { return null; }
902 }
903 }
904 public static class OtherNested {}
905 }
906 """);
907 }
908
909 @Test
910 void testTypeAnnotations() throws Exception {
911 doPrintElementTest("""
912 package t;
913 public class T {
914 }
915 """,
916 """
917 package t;
918 import java.lang.annotation.*;
919 import java.util.*;
920 public class T<@AnnInvisible @AnnVisible E extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> extends @AnnInvisible @AnnVisible ArrayList {
921 public @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> field;
922 public <@AnnInvisible @AnnVisible M extends @AnnInvisible @AnnVisible ArrayList<@AnnInvisible @AnnVisible ArrayList>> @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible M> convert(@AnnInvisible @AnnVisible T<E> this, @AnnInvisible @AnnVisible M e1, @AnnInvisible @AnnVisible List<@AnnInvisible @AnnVisible E> e2) throws @AnnInvisible @AnnVisible IllegalStateException, @AnnInvisible @AnnVisible IllegalArgumentException {
923 return null;
924 }
925 }
926 @Retention(RetentionPolicy.RUNTIME)
927 @Target(ElementType.TYPE_USE)
928 @interface AnnVisible {
929 }
930 @Retention(RetentionPolicy.CLASS)
931 @Target(ElementType.TYPE_USE)
932 @interface AnnInvisible {
933 }
934 """,
935 "t.T",
936 """
937 package t;
938
939 public class T {
940
941 public T();
942 }
943 """,
944 "t.T",
945 """
946 package t;
947
948 public class T<@t.AnnInvisible @t.AnnVisible E extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList<java.util.@t.AnnInvisible @t.AnnVisible ArrayList>> extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList {
949 public java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> field;
950
951 public T();
952
953 public <@t.AnnInvisible @t.AnnVisible M extends java.util.@t.AnnInvisible @t.AnnVisible ArrayList<java.util.@t.AnnInvisible @t.AnnVisible ArrayList>> java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible M> convert(@t.AnnInvisible @t.AnnVisible M arg0,
954 java.util.@t.AnnInvisible @t.AnnVisible List<@t.AnnInvisible @t.AnnVisible E> arg1) throws java.lang.@t.AnnInvisible @t.AnnVisible IllegalStateException,\s
955 java.lang.@t.AnnInvisible @t.AnnVisible IllegalArgumentException;
956 }
957 """);
958 }
959
960 @Test
961 void testParameterAnnotations() throws Exception {
962 doPrintElementTest("""
963 package t;
964 public class T {
965 public void test(int p1, int p2) {
966 }
967 }
968 """,
969 """
970 package t;
971 import java.lang.annotation.*;
972 import java.util.*;
973 public class T {
974 public void test(@AnnVisible int p1, @AnnInvisible int p2) {
975 }
976 }
977 @Retention(RetentionPolicy.RUNTIME)
978 @Target(ElementType.PARAMETER)
979 @interface AnnVisible {
980 }
981 @Retention(RetentionPolicy.CLASS)
982 @Target(ElementType.PARAMETER)
983 @interface AnnInvisible {
984 }
985 """,
986 "t.T",
987 """
988 package t;
989
990 public class T {
991
992 public T();
993
994 public void test(int arg0,
995 int arg1);
996 }
997 """,
998 "t.T",
999 """
1000 package t;
1001
1002 public class T {
1003
1004 public T();
1005
1006 public void test(@t.AnnVisible int arg0,
1007 @t.AnnInvisible int arg1);
1008 }
1009 """);
1010 }
1011
1012 void doTestData(String data,
1013 String... code) throws Exception {
1014 String testClasses = System.getProperty("test.classes");
1015 Path output = Paths.get(testClasses, "test-data" + i++);
1016 deleteRecursively(output);
1017 Files.createDirectories(output);
1018 Path ver9Jar = output.resolve("9.jar");
1019 compileAndPack(output,
1020 ver9Jar,
1021 code);
1022
1023 Path ctSym = output.resolve("ct.sym");
1024
1025 deleteRecursively(ctSym);
1026
1027 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1028 CreateSymbols.DO_NOT_MODIFY = "";
1029 CreateSymbols.EXTENSION = ".class";
1030 CreateSymbols.INJECTED_VERSION = "0";
1031
1032 deleteRecursively(ctSym);
1033
1034 List<VersionDescription> versions =
1035 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
1036
1037 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1038 @Override public boolean accepts(String className, boolean includePrivateClasses) {
1039 return true;
1040 }
1041 };
1042 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
1043
1044 Path symFile = null;
1045
1046 try (DirectoryStream<Path> ds = Files.newDirectoryStream(ctSym)) {
1047 for (Path p : ds) {
1048 if (p.toString().endsWith(".sym.txt")) {
1049 if (symFile != null) {
1050 throw new IllegalStateException("Multiple sym files!");
1051 } else {
1052 symFile = p;
1053 }
1054 }
1055 }
1056 }
1057 String acutalContent = new String(Files.readAllBytes(symFile), StandardCharsets.UTF_8);
1058 if (!acutalContent.equals(data)) {
1059 throw new AssertionError("out=" + acutalContent + "; expected=" + data);
1060 }
1061 }
1062
1063 void doTestIncluded(String code, String... includedClasses) throws Exception {
1064 boolean oldIncludeAll = includeAll;
1065 try {
1066 includeAll = false;
1067 Path classes = prepareVersionedCTSym(new String[] {code}, new String[] {"package other; public class Other {}"});
1068 Path root = classes.resolve("7").resolve("java.base");
1069 try (Stream<Path> classFiles = Files.walk(root)) {
1070 Set<String> names = classFiles.map(p -> root.relativize(p))
1071 .map(p -> p.toString())
1072 .map(n -> {System.err.println("n= " + n); return n;})
1073 .filter(n -> n.endsWith(".class"))
1074 .map(n -> n.substring(0, n.lastIndexOf('.')))
1075 .map(n -> n.replace(File.separator, "."))
1076 .collect(Collectors.toSet());
1077
1078 if (!names.equals(new HashSet<>(Arrays.asList(includedClasses))))
1079 throw new AssertionError("Expected classes not included: " + names);
1080 }
1081 } finally {
1082 includeAll = oldIncludeAll;
1083 }
1084 }
1085
1086 Path prepareVersionedCTSym(String[] code7, String[] code8) throws Exception {
1087 return prepareVersionedCTSym(code7, code8, _ -> {});
1088 }
1089
1090 Path prepareVersionedCTSym(String[] code7, String[] code8,
1091 Consumer<Path> adjustClassFiles) throws Exception {
1092 String testClasses = System.getProperty("test.classes");
1093 Path output = Paths.get(testClasses, "test-data" + i++);
1094 deleteRecursively(output);
1095 Files.createDirectories(output);
1096 Path ver7Jar = output.resolve("7.jar");
1097 compileAndPack(output, ver7Jar, adjustClassFiles, code7);
1098 Path ver8Jar = output.resolve("8.jar");
1099 compileAndPack(output, ver8Jar, adjustClassFiles, code8);
1100
1101 Path classes = output.resolve("classes.zip");
1102
1103 Path ctSym = output.resolve("ct.sym");
1104
1105 deleteRecursively(ctSym);
1106
1107 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1108 CreateSymbols.EXTENSION = ".class";
1109
1110 testGenerate(ver7Jar, ver8Jar, ctSym, "8", classes.toAbsolutePath().toString());
1111
1112 Path classesDir = output.resolve("classes");
1113
1114 try (JarFile jf = new JarFile(classes.toFile())) {
1115 Enumeration<JarEntry> en = jf.entries();
1116
1117 while (en.hasMoreElements()) {
1118 JarEntry je = en.nextElement();
1119 if (je.isDirectory()) continue;
1120 Path target = classesDir.resolve(je.getName());
1121 Files.createDirectories(target.getParent());
1122 Files.copy(jf.getInputStream(je), target);
1123 }
1124 }
1125
1126 return classesDir;
1127 }
1128
1129 boolean includeAll = true;
1130
1131 void testGenerate(Path jar7, Path jar8, Path descDest, String version, String classDest) throws IOException {
1132 deleteRecursively(descDest);
1133
1134 List<VersionDescription> versions =
1135 Arrays.asList(new VersionDescription(jar7.toAbsolutePath().toString(), "7", null),
1136 new VersionDescription(jar8.toAbsolutePath().toString(), "8", "7"));
1137
1138 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1139 @Override public boolean accepts(String className, boolean includePrivateClasses) {
1140 return true;
1141 }
1142 };
1143 new CreateSymbols() {
1144 @Override
1145 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
1146 return includeAll ? true : super.includeEffectiveAccess(classes, clazz);
1147 }
1148 }.createBaseLine(versions, acceptAll, descDest, new String[0]);
1149 Path symbolsDesc = descDest.resolve("symbols");
1150 Path modules = descDest.resolve("modules");
1151 Path modulesList = descDest.resolve("modules-list");
1152
1153 Files.createDirectories(modules);
1154 try (Writer w = Files.newBufferedWriter(modulesList)) {}
1155
1156 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classDest, 0, "8", "", modules.toString(), modulesList.toString());
1157 }
1158
1159 @Test
1160 void testModuleMainClass() throws Exception {
1161 ClassFile cf = ClassFile.of();
1162 ToolBox tb = new ToolBox();
1163 String testClasses = System.getProperty("test.classes");
1164 Path output = Paths.get(testClasses, "test-data" + i++);
1165 deleteRecursively(output);
1166 Files.createDirectories(output);
1167 Path ver9Jar = output.resolve("9.jar");
1168 compileAndPack(output,
1169 ver9Jar,
1170 classesDir -> {
1171 try {
1172 Path moduleInfo = classesDir.resolve("module-info.class");
1173 byte[] newClassData =
1174 cf.transformClass(cf.parse(moduleInfo),
1175 (builder, element) -> {
1176 builder.with(element);
1177 if (element instanceof ModuleAttribute) {
1178 builder.with(ModuleMainClassAttribute.of(ClassDesc.of("main.Main")));
1179 }
1180 });
1181 try (OutputStream out = Files.newOutputStream(moduleInfo)) {
1182 out.write(newClassData);
1183 }
1184 } catch (IOException ex) {
1185 throw new UncheckedIOException(ex);
1186 }
1187 },
1188 """
1189 module m {
1190 }
1191 """,
1192 """
1193 package main;
1194 public class Main {}
1195 """);
1196
1197
1198 Path ctSym = output.resolve("ct.sym");
1199
1200 deleteRecursively(ctSym);
1201
1202 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true;
1203 CreateSymbols.EXTENSION = ".class";
1204
1205 List<VersionDescription> versions =
1206 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null));
1207
1208 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) {
1209 @Override public boolean accepts(String className, boolean includePrivateClasses) {
1210 return true;
1211 }
1212 };
1213 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]);
1214 Path symbolsDesc = ctSym.resolve("symbols");
1215 Path modules = ctSym.resolve("modules");
1216 Path modulesList = ctSym.resolve("modules-list");
1217
1218 Files.createDirectories(modules);
1219 try (Writer w = Files.newBufferedWriter(modulesList)) {}
1220
1221 Path classesZip = output.resolve("classes.zip");
1222 Path classesDir = output.resolve("classes");
1223
1224 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", "", modules.toString(), modulesList.toString());
1225
1226 try (JarFile jf = new JarFile(classesZip.toFile())) {
1227 Enumeration<JarEntry> en = jf.entries();
1228
1229 while (en.hasMoreElements()) {
1230 JarEntry je = en.nextElement();
1231 if (je.isDirectory()) continue;
1232 Path target = classesDir.resolve(je.getName());
1233 Files.createDirectories(target.getParent());
1234 Files.copy(jf.getInputStream(je), target);
1235 }
1236 }
1237
1238 Path moduleInfo = classesDir.resolve("9")
1239 .resolve("m")
1240 .resolve("module-info.class");
1241
1242 cf.parse(moduleInfo)
1243 .attributes()
1244 .stream()
1245 .filter(attr -> attr instanceof ModuleMainClassAttribute)
1246 .forEach(attr -> {
1247 String expectedMain = "Lmain/Main;";
1248 String mainClass =
1249 ((ModuleMainClassAttribute) attr).mainClass()
1250 .asSymbol()
1251 .descriptorString();
1252 if (!Objects.equals(expectedMain, mainClass)) {
1253 throw new AssertionError("Expected " + expectedMain + " as a main class, " +
1254 "but got: " + mainClass);
1255 }
1256 });
1257 }
1258
1259 void compileAndPack(Path output, Path outputFile, String... code) throws Exception {
1260 compileAndPack(output, outputFile, _ -> {}, code);
1261 }
1262
1263 void compileAndPack(Path output, Path outputFile,
1264 Consumer<Path> adjustClassFiles, String... code) throws Exception {
1265 ToolBox tb = new ToolBox();
1266 Path scratch = output.resolve("temp");
1267 deleteRecursively(scratch);
1268 Files.createDirectories(scratch);
1269 System.err.println(Arrays.asList(code));
1270 new JavacTask(tb).sources(code).options("-d", scratch.toAbsolutePath().toString()).run(Expect.SUCCESS);
1271 List<String> classFiles = collectClassFile(scratch);
1272 Path moduleInfo = scratch.resolve("module-info.class");
1273 if (Files.exists(moduleInfo)) {
1274 Set<String> packages = new HashSet<>();
1275 for (String cf : classFiles) {
1276 int sep = cf.lastIndexOf(scratch.getFileSystem().getSeparator());
1277 if (sep != (-1)) {
1278 packages.add(cf.substring(0, sep));
1279 }
1280 }
1281 ClassFile cf = ClassFile.of();
1282 ClassModel cm = cf.parse(moduleInfo);
1283 byte[] newData = cf.transformClass(cm, (builder, element) -> {
1284 builder.with(element);
1285 if (element instanceof ModuleAttribute) {
1286 builder.with(ModulePackagesAttribute.ofNames(packages.stream()
1287 .map(pack -> PackageDesc.of(pack))
1288 .toList()));
1289 }
1290 });
1291 try (OutputStream out = Files.newOutputStream(moduleInfo)) {
1292 out.write(newData);
1293 }
1294 }
1295 adjustClassFiles.accept(scratch);
1296 try (Writer out = Files.newBufferedWriter(outputFile)) {
1297 for (String classFile : classFiles) {
1298 try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) {
1299 int read;
1300
1301 while ((read = in.read()) != (-1)) {
1302 out.write(String.format("%02x", read));
1303 }
1304
1305 out.write("\n");
1306 }
1307 }
1308 }
1309 }
1310
1311 List<String> collectClassFile(Path root) throws IOException {
1312 try (Stream<Path> files = Files.walk(root)) {
1313 return files.filter(p -> Files.isRegularFile(p))
1314 .filter(p -> p.getFileName().toString().endsWith(".class"))
1315 .map(p -> root.relativize(p).toString())
1316 .filter(p -> !p.contains("impl"))
1317 .collect(Collectors.toList());
1318 }
1319 }
1320
1321 void deleteRecursively(Path dir) throws IOException {
1322 Files.walkFileTree(dir, new FileVisitor<Path>() {
1323 @Override
1324 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
1325 return FileVisitResult.CONTINUE;
1326 }
1327 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
1328 Files.delete(file);
1329 return FileVisitResult.CONTINUE;
1330 }
1331 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
1332 return FileVisitResult.CONTINUE;
1333 }
1334 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
1335 Files.delete(dir);
1336 return FileVisitResult.CONTINUE;
1337 }
1338 });
1339 }
1340 }