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 = \"&amp;&&lt;<&gt;>&apos;'\";" +
 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 = \"&amp;&&lt;<&gt;>&apos;\\'\";\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 }