1 /* 2 * Copyright (c) 2015, 2021, 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.reflect.Method; 30 import java.util.Arrays; 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 import java.io.IOException; 36 import java.io.OutputStream; 37 import java.nio.charset.StandardCharsets; 38 import java.nio.file.DirectoryStream; 39 import java.nio.file.FileVisitResult; 40 import java.nio.file.FileVisitor; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.attribute.BasicFileAttributes; 45 import java.util.Enumeration; 46 import java.util.HashSet; 47 import java.util.Set; 48 import java.util.jar.JarEntry; 49 import java.util.jar.JarFile; 50 import java.util.stream.Collectors; 51 import java.util.stream.Stream; 52 import toolbox.JavacTask; 53 import toolbox.Task; 54 import toolbox.Task.Expect; 55 import toolbox.ToolBox; 56 import build.tools.symbolgenerator.CreateSymbols; 57 import build.tools.symbolgenerator.CreateSymbols.ClassDescription; 58 import build.tools.symbolgenerator.CreateSymbols.ClassList; 59 import build.tools.symbolgenerator.CreateSymbols.ExcludeIncludeList; 60 import build.tools.symbolgenerator.CreateSymbols.VersionDescription; 61 import com.sun.tools.classfile.Attribute; 62 import com.sun.tools.classfile.Attributes; 63 import com.sun.tools.classfile.ClassFile; 64 import com.sun.tools.classfile.ClassWriter; 65 import com.sun.tools.classfile.ConstantPool; 66 import com.sun.tools.classfile.ConstantPool.CPInfo; 67 import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info; 68 import com.sun.tools.classfile.ModulePackages_attribute; 69 70 public class CreateSymbolsTestImpl { 71 72 static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols"; 73 74 public static void main(String... args) throws Exception { 75 new CreateSymbolsTestImpl().doTest(); 76 } 77 78 void doTest() throws Exception { 79 boolean testRun = false; 80 for (Method m : CreateSymbolsTestImpl.class.getDeclaredMethods()) { 81 if (m.isAnnotationPresent(Test.class)) { 82 m.invoke(this); 83 testRun = true; 84 } 85 } 86 if (!testRun) { 87 throw new IllegalStateException("No tests found."); 88 } 89 } 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 systemModules = ctSym.resolve("systemModules"); 755 756 Files.newBufferedWriter(systemModules).close(); 757 758 Path classesZip = output.resolve("classes.zip"); 759 Path classesDir = output.resolve("classes"); 760 761 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classesZip.toAbsolutePath().toString(), 0, "9", systemModules.toString()); 762 763 try (JarFile jf = new JarFile(classesZip.toFile())) { 764 Enumeration<JarEntry> en = jf.entries(); 765 766 while (en.hasMoreElements()) { 767 JarEntry je = en.nextElement(); 768 if (je.isDirectory()) continue; 769 Path target = classesDir.resolve(je.getName()); 770 Files.createDirectories(target.getParent()); 771 Files.copy(jf.getInputStream(je), target); 772 } 773 } 774 775 Path classes = classesDir; 776 Path scratch = output.resolve("scratch"); 777 778 Files.createDirectories(scratch); 779 780 String modulePath; 781 782 try (Stream<Path> elements = Files.list(classes)) { 783 modulePath = elements.filter(el -> el.getFileName().toString().contains("9")) 784 .map(el -> el.resolve("m")) 785 .map(el -> el.toAbsolutePath().toString()) 786 .collect(Collectors.joining(File.pathSeparator)); 787 } 788 789 { 790 String out = new JavacTask(tb, Task.Mode.CMDLINE) 791 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 792 "--add-modules", "m", "-Xprint", "api.Api") 793 .run(Expect.SUCCESS) 794 .getOutput(Task.OutputKind.STDOUT) 795 .replaceAll("\\R", "\n"); 796 797 if (!out.equals(expected)) { 798 throw new AssertionError("out=" + out + "; expected=" + expected); 799 } 800 } 801 802 { 803 new JavacTask(tb) 804 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 805 "--add-modules", "m") 806 .sources(depSuccess) 807 .run(Expect.SUCCESS) 808 .writeAll(); 809 } 810 811 { 812 String expectedFailure = new JavacTask(tb) 813 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", output.resolve("temp").toString(), 814 "--add-modules", "m", "-XDrawDiagnostics") 815 .sources(depFailure) 816 .run(Expect.FAIL) 817 .getOutput(Task.OutputKind.DIRECT) 818 .replaceAll("\\R", "\n"); 819 820 String out = new JavacTask(tb) 821 .options("-d", scratch.toAbsolutePath().toString(), "--module-path", modulePath, 822 "--add-modules", "m", "-XDrawDiagnostics") 823 .sources(depFailure) 824 .run(Expect.FAIL) 825 .getOutput(Task.OutputKind.DIRECT) 826 .replaceAll("\\R", "\n"); 827 828 if (!out.equals(expectedFailure)) { 829 throw new AssertionError("out=" + out + "; expected=" + expectedFailure); 830 } 831 } 832 } 833 834 @Test 835 void testExtendsInternalData1() throws Exception { 836 doTestData(""" 837 module name m 838 header exports api,nonapi[java.base] requires name\\u0020;java.base\\u0020;flags\\u0020;8000\\u0020;version\\u0020;0 flags 8000 839 840 class name api/Ann 841 header extends java/lang/Object implements java/lang/annotation/Annotation flags 2601 842 843 class name api/Api 844 header extends nonapi/Impl$Nested$Exp flags 21 845 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 846 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 847 method name <init> descriptor ()V flags 1 848 849 class name nonapi/Impl 850 header extends java/lang/Object nestMembers nonapi/Impl$Nested,nonapi/Impl$Nested$Exp flags 21 851 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 852 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 853 field name C descriptor Ljava/lang/String; constantValue flags 19 854 method name <init> descriptor ()V flags 1 855 method name test descriptor ()V flags 1 856 857 class name nonapi/Impl$Nested 858 header extends java/lang/Object nestHost nonapi/Impl flags 21 classAnnotations @Lapi/Ann; 859 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 860 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 861 method name <init> descriptor ()V flags 1 862 863 class name nonapi/Impl$Nested$Exp 864 header extends nonapi/Impl$Nested implements java/lang/Runnable nestHost nonapi/Impl flags 21 865 innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 866 innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 867 method name <init> descriptor ()V flags 1 868 method name run descriptor ()V flags 1 869 method name get descriptor ()Lnonapi/Impl$OtherNested; flags 1 870 871 """, 872 """ 873 module m { 874 exports api; 875 exports nonapi to java.base; 876 } 877 """, 878 """ 879 package api; 880 import nonapi.Impl; 881 public class Api extends Impl.Nested.Exp { 882 } 883 """, 884 """ 885 package api; 886 public @interface Ann { 887 } 888 """, 889 """ 890 package nonapi; 891 import api.Ann; 892 public class Impl { 893 public static final String C = ""; 894 public void test() {} 895 @Ann 896 public static class Nested { 897 public static class Exp extends Nested implements Runnable { 898 public void run() {} 899 public OtherNested get() { return null; } 900 } 901 } 902 public static class OtherNested {} 903 } 904 """); 905 } 906 907 void doTestData(String data, 908 String... code) throws Exception { 909 String testClasses = System.getProperty("test.classes"); 910 Path output = Paths.get(testClasses, "test-data" + i++); 911 deleteRecursively(output); 912 Files.createDirectories(output); 913 Path ver9Jar = output.resolve("9.jar"); 914 compileAndPack(output, 915 ver9Jar, 916 code); 917 918 Path ctSym = output.resolve("ct.sym"); 919 920 deleteRecursively(ctSym); 921 922 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 923 CreateSymbols.DO_NOT_MODIFY = ""; 924 CreateSymbols.EXTENSION = ".class"; 925 CreateSymbols.INJECTED_VERSION = "0"; 926 927 deleteRecursively(ctSym); 928 929 List<VersionDescription> versions = 930 Arrays.asList(new VersionDescription(ver9Jar.toAbsolutePath().toString(), "9", null)); 931 932 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 933 @Override public boolean accepts(String className, boolean includePrivateClasses) { 934 return true; 935 } 936 }; 937 new CreateSymbols().createBaseLine(versions, acceptAll, ctSym, new String[0]); 938 939 Path symFile = null; 940 941 try (DirectoryStream<Path> ds = Files.newDirectoryStream(ctSym)) { 942 for (Path p : ds) { 943 if (p.toString().endsWith(".sym.txt")) { 944 if (symFile != null) { 945 throw new IllegalStateException("Multiple sym files!"); 946 } else { 947 symFile = p; 948 } 949 } 950 } 951 } 952 String acutalContent = new String(Files.readAllBytes(symFile), StandardCharsets.UTF_8); 953 if (!acutalContent.equals(data)) { 954 throw new AssertionError("out=" + acutalContent + "; expected=" + data); 955 } 956 } 957 958 void doTestIncluded(String code, String... includedClasses) throws Exception { 959 boolean oldIncludeAll = includeAll; 960 try { 961 includeAll = false; 962 Path classes = prepareVersionedCTSym(new String[] {code}, new String[] {"package other; public class Other {}"}); 963 Path root = classes.resolve("7").resolve("java.base"); 964 try (Stream<Path> classFiles = Files.walk(root)) { 965 Set<String> names = classFiles.map(p -> root.relativize(p)) 966 .map(p -> p.toString()) 967 .map(n -> {System.err.println("n= " + n); return n;}) 968 .filter(n -> n.endsWith(".class")) 969 .map(n -> n.substring(0, n.lastIndexOf('.'))) 970 .map(n -> n.replace(File.separator, ".")) 971 .collect(Collectors.toSet()); 972 973 if (!names.equals(new HashSet<>(Arrays.asList(includedClasses)))) 974 throw new AssertionError("Expected classes not included: " + names); 975 } 976 } finally { 977 includeAll = oldIncludeAll; 978 } 979 } 980 981 Path prepareVersionedCTSym(String[] code7, String[] code8) throws Exception { 982 String testClasses = System.getProperty("test.classes"); 983 Path output = Paths.get(testClasses, "test-data" + i++); 984 deleteRecursively(output); 985 Files.createDirectories(output); 986 Path ver7Jar = output.resolve("7.jar"); 987 compileAndPack(output, ver7Jar, code7); 988 Path ver8Jar = output.resolve("8.jar"); 989 compileAndPack(output, ver8Jar, code8); 990 991 Path classes = output.resolve("classes.zip"); 992 993 Path ctSym = output.resolve("ct.sym"); 994 995 deleteRecursively(ctSym); 996 997 CreateSymbols.ALLOW_NON_EXISTING_CLASSES = true; 998 CreateSymbols.EXTENSION = ".class"; 999 1000 testGenerate(ver7Jar, ver8Jar, ctSym, "8", classes.toAbsolutePath().toString()); 1001 1002 Path classesDir = output.resolve("classes"); 1003 1004 try (JarFile jf = new JarFile(classes.toFile())) { 1005 Enumeration<JarEntry> en = jf.entries(); 1006 1007 while (en.hasMoreElements()) { 1008 JarEntry je = en.nextElement(); 1009 if (je.isDirectory()) continue; 1010 Path target = classesDir.resolve(je.getName()); 1011 Files.createDirectories(target.getParent()); 1012 Files.copy(jf.getInputStream(je), target); 1013 } 1014 } 1015 1016 return classesDir; 1017 } 1018 1019 boolean includeAll = true; 1020 1021 void testGenerate(Path jar7, Path jar8, Path descDest, String version, String classDest) throws IOException { 1022 deleteRecursively(descDest); 1023 1024 List<VersionDescription> versions = 1025 Arrays.asList(new VersionDescription(jar7.toAbsolutePath().toString(), "7", null), 1026 new VersionDescription(jar8.toAbsolutePath().toString(), "8", "7")); 1027 1028 ExcludeIncludeList acceptAll = new ExcludeIncludeList(null, null) { 1029 @Override public boolean accepts(String className, boolean includePrivateClasses) { 1030 return true; 1031 } 1032 }; 1033 new CreateSymbols() { 1034 @Override 1035 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { 1036 return includeAll ? true : super.includeEffectiveAccess(classes, clazz); 1037 } 1038 }.createBaseLine(versions, acceptAll, descDest, new String[0]); 1039 Path symbolsDesc = descDest.resolve("symbols"); 1040 Path systemModules = descDest.resolve("systemModules"); 1041 1042 Files.newBufferedWriter(systemModules).close(); 1043 1044 try { 1045 new CreateSymbols().createSymbols(null, symbolsDesc.toAbsolutePath().toString(), classDest, 0, "8", systemModules.toString()); 1046 } catch (Throwable t) { 1047 t.printStackTrace(); 1048 throw t; 1049 } 1050 } 1051 1052 void compileAndPack(Path output, Path outputFile, String... code) throws Exception { 1053 ToolBox tb = new ToolBox(); 1054 Path scratch = output.resolve("temp"); 1055 deleteRecursively(scratch); 1056 Files.createDirectories(scratch); 1057 System.err.println(Arrays.asList(code)); 1058 new JavacTask(tb).sources(code).options("-d", scratch.toAbsolutePath().toString()).run(Expect.SUCCESS); 1059 List<String> classFiles = collectClassFile(scratch); 1060 Path moduleInfo = scratch.resolve("module-info.class"); 1061 if (Files.exists(moduleInfo)) { 1062 Set<String> packages = new HashSet<>(); 1063 for (String cf : classFiles) { 1064 int sep = cf.lastIndexOf(scratch.getFileSystem().getSeparator()); 1065 if (sep != (-1)) { 1066 packages.add(cf.substring(0, sep)); 1067 } 1068 } 1069 ClassFile cf = ClassFile.read(moduleInfo); 1070 List<CPInfo> cp = new ArrayList<>(); 1071 cp.add(null); 1072 cf.constant_pool.entries().forEach(cp::add); 1073 Map<String, Attribute> attrs = new HashMap<>(cf.attributes.map); 1074 int[] encodedPackages = new int[packages.size()]; 1075 int i = 0; 1076 for (String p : packages) { 1077 int nameIndex = cp.size(); 1078 cp.add(new CONSTANT_Utf8_info(p)); 1079 encodedPackages[i++] = cp.size(); 1080 cp.add(new ConstantPool.CONSTANT_Package_info(null, nameIndex)); 1081 } 1082 int attrName = cp.size(); 1083 cp.add(new CONSTANT_Utf8_info(Attribute.ModulePackages)); 1084 attrs.put(Attribute.ModulePackages, new ModulePackages_attribute(attrName, encodedPackages)); 1085 ClassFile newFile = new ClassFile(cf.magic, cf.minor_version, cf.major_version, new ConstantPool(cp.toArray(new CPInfo[0])), cf.access_flags, cf.this_class, cf.super_class, cf.interfaces, cf.fields, cf.methods, new Attributes(attrs)); 1086 try (OutputStream out = Files.newOutputStream(moduleInfo)) { 1087 new ClassWriter().write(newFile, out); 1088 } 1089 } 1090 try (Writer out = Files.newBufferedWriter(outputFile)) { 1091 for (String classFile : classFiles) { 1092 try (InputStream in = Files.newInputStream(scratch.resolve(classFile))) { 1093 int read; 1094 1095 while ((read = in.read()) != (-1)) { 1096 out.write(String.format("%02x", read)); 1097 } 1098 1099 out.write("\n"); 1100 } 1101 } 1102 } 1103 } 1104 1105 List<String> collectClassFile(Path root) throws IOException { 1106 try (Stream<Path> files = Files.walk(root)) { 1107 return files.filter(p -> Files.isRegularFile(p)) 1108 .filter(p -> p.getFileName().toString().endsWith(".class")) 1109 .map(p -> root.relativize(p).toString()) 1110 .filter(p -> !p.contains("impl")) 1111 .collect(Collectors.toList()); 1112 } 1113 } 1114 1115 void deleteRecursively(Path dir) throws IOException { 1116 Files.walkFileTree(dir, new FileVisitor<Path>() { 1117 @Override 1118 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 1119 return FileVisitResult.CONTINUE; 1120 } 1121 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 1122 Files.delete(file); 1123 return FileVisitResult.CONTINUE; 1124 } 1125 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 1126 return FileVisitResult.CONTINUE; 1127 } 1128 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 1129 Files.delete(dir); 1130 return FileVisitResult.CONTINUE; 1131 } 1132 }); 1133 } 1134 1135 @Retention(RetentionPolicy.RUNTIME) 1136 @interface Test { 1137 } 1138 }