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