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