1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8187950
  27  * @summary Handing of BadClassFile exceptions and CompletionFailures
  28  * @library /tools/javac/lib /tools/lib
  29  * @modules jdk.compiler/com.sun.tools.javac.api
  30  *          jdk.compiler/com.sun.tools.javac.main
  31  * @build JavacTestingAbstractProcessor MissingClassFile
  32  * @run main MissingClassFile
  33  */
  34 
  35 import java.io.OutputStream;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.nio.file.Paths;
  39 import java.util.*;
  40 import java.util.function.BiConsumer;
  41 import java.util.function.Consumer;
  42 
  43 import javax.annotation.processing.*;
  44 import javax.lang.model.SourceVersion;
  45 import javax.lang.model.element.*;
  46 import javax.lang.model.type.DeclaredType;
  47 import javax.lang.model.type.TypeKind;
  48 import javax.lang.model.type.TypeMirror;
  49 import javax.tools.JavaCompiler;
  50 import javax.tools.StandardJavaFileManager;
  51 import javax.tools.ToolProvider;
  52 
  53 import toolbox.*;
  54 import toolbox.Task.*;
  55 
  56 
  57 import com.sun.source.util.TaskEvent;
  58 import com.sun.source.util.TaskListener;
  59 
  60 @SupportedAnnotationTypes("*")
  61 public class MissingClassFile {
  62     ToolBox tb = new ToolBox();
  63 
  64     void testPackageContent() throws Exception {
  65         Path base = Paths.get(".");
  66         Path libClasses = compileLib(base,
  67                                      "package pkg;" +
  68                                      "public class A {" +
  69                                      "}",
  70                                      "package pkg;" +
  71                                      "public class B {" +
  72                                      "}");
  73 
  74         Files.delete(libClasses.resolve("pkg/B.class"));
  75         try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) {
  76             out.write(0);
  77         }
  78 
  79         doRunTest(base,
  80                   t -> {
  81                       PackageElement pe = t.getElements().getPackageElement("pkg");
  82                       for (Element el : pe.getEnclosedElements()) {
  83                           verifyElement(t, el);
  84                       }
  85                   },
  86                   "",
  87                   "pkg.B b;");
  88     }
  89 
  90     void testPackageDirectAPI() throws Exception {
  91         Path base = Paths.get(".");
  92         Path libClasses = compileLib(base,
  93                                      "package pkg;" +
  94                                      "public class A {" +
  95                                      "}",
  96                                      "package pkg;" +
  97                                      "public class B {" +
  98                                      "}");
  99 
 100         Files.delete(libClasses.resolve("pkg/B.class"));
 101         try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) {
 102             out.write(0);
 103         }
 104 
 105         Path testSrc = base.resolve("test-src");
 106         tb.createDirectories(testSrc);
 107         tb.writeJavaFiles(testSrc,
 108                           "package test;\n" +
 109                           "public class Test {\n" +
 110                           "    void t() {\n" +
 111                           "        pkg.B b;\n" +
 112                           "    }\n" +
 113                           "}\n");
 114         Path testClasses = base.resolve("test-classes");
 115         tb.createDirectories(testClasses);
 116 
 117         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 118         List<String> errors = new ArrayList<>();
 119 
 120         try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
 121             com.sun.source.util.JavacTask task = (com.sun.source.util.JavacTask)
 122                     compiler.getTask(null,
 123                                      null,
 124                                      d -> errors.add(d.getCode()),
 125                                      Arrays.asList("-XDrawDiagnostics",
 126                                                    "-classpath",
 127                                                    libClasses.toString()),
 128                                      null,
 129                                      fm.getJavaFileObjects(tb.findJavaFiles(testSrc)));
 130             task.parse();
 131             PackageElement pe = task.getElements().getPackageElement("pkg");
 132             for (Element el : pe.getEnclosedElements()) {
 133                 verifyElement(task, el);
 134             }
 135             task.analyze();
 136         }
 137 
 138         List<String> expected = Arrays.asList("compiler.err.cant.access");
 139 
 140         if (!expected.equals(errors)) {
 141             throw new IllegalStateException("Expected error not found!");
 142         }
 143     }
 144 
 145     void testSuperClass() throws Exception {
 146         doTestCombo("class Test {" +
 147                     "}",
 148                     "package pkg;" +
 149                     "public class A extends # {" +
 150                     "}",
 151                     "pkg.A x;",
 152                     "# a = null; a.toString();",
 153                     (fqn, t) -> {
 154                         TypeElement a = t.getElements()
 155                                          .getTypeElement(t.getElements()
 156                                                           .getModuleElement(""),
 157                                                          "pkg.A");
 158                         TypeMirror superclass = a.getSuperclass();
 159                         verifyTypeMirror(t, superclass);
 160                         assertEquals(TypeKind.DECLARED, superclass.getKind());
 161                         Element superclassEl = ((DeclaredType) superclass).asElement();
 162                         assertEquals(ElementKind.CLASS, superclassEl.getKind());
 163                         assertEquals(TypeKind.ERROR, superclassEl.asType().getKind());
 164                   });
 165         doTestCombo("interface Test {" +
 166                     "}",
 167                     "package pkg;" +
 168                     "public class A implements # {" +
 169                     "}",
 170                     "pkg.A x;",
 171                     "# a = null; a.toString();",
 172                     (fqn, t) -> {
 173                         TypeElement a = t.getElements().getTypeElement("pkg.A");
 174                         TypeMirror superintf = a.getInterfaces().get(0);
 175                         verifyTypeMirror(t, superintf);
 176                         assertEquals(TypeKind.DECLARED, superintf.getKind());
 177                         Element superintfEl = ((DeclaredType) superintf).asElement();
 178                         //superintfEl.getKind() may be either CLASS or INTERFACE, depending on which class is missing
 179                         assertEquals(TypeKind.ERROR, superintfEl.asType().getKind());
 180                   });
 181         doTestCombo("class Test {" +
 182                     "}",
 183                     "package pkg;" +
 184                     "public class A extends # {" +
 185                     "}",
 186                     "pkg.A x;",
 187                     "# a = null; a.toString();",
 188                     (fqn, t) -> {
 189                         TypeElement a = t.getElements()
 190                                          .getTypeElement(t.getElements()
 191                                                           .getModuleElement(""),
 192                                                          "pkg.A");
 193                         DeclaredType superclass = (DeclaredType) a.getSuperclass();
 194                         superclass.getTypeArguments();
 195                   });
 196         doTestCombo("class Test {" +
 197                     "}",
 198                     "package pkg;" +
 199                     "public class A extends # {" +
 200                     "}",
 201                     "pkg.A x;",
 202                     "# a = null; a.toString();",
 203                     (fqn, t) -> {
 204                         TypeElement a = t.getElements()
 205                                          .getTypeElement(t.getElements()
 206                                                           .getModuleElement(""),
 207                                                          "pkg.A");
 208                         DeclaredType superclass = (DeclaredType) a.getSuperclass();
 209                         superclass.getEnclosingType();
 210                   });
 211     }
 212 
 213     void testAnnotation() throws Exception {
 214         doTestCombo("@interface Test {" +
 215                     "}",
 216                     "package pkg;" +
 217                     "@#\n" +
 218                     "public class A {" +
 219                     "}",
 220                     "",
 221                     "# a = null; a.toString();",
 222                     (fqn, t) -> {
 223                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 224                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 225                           verifyTypeMirror(t, am.getAnnotationType());
 226                       }
 227                   });
 228         doTestCombo("@interface Test {" +
 229                     "    public Class<?> value();" +
 230                     "}",
 231                     "package pkg;" +
 232                     "@#(Object.class)\n" +
 233                     "public class A {" +
 234                     "}",
 235                     "",
 236                     "# a = null; a.toString();",
 237                     (fqn, t) -> {
 238                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 239                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 240                           verifyTypeMirror(t, am.getAnnotationType());
 241                           if (am.getAnnotationType().toString().equals(fqn)) {
 242                               verifyTypeMirror(t, (TypeMirror) am.getElementValues().values()
 243                                                                  .iterator().next().getValue());
 244                           }
 245                       }
 246                   });
 247         doTestCombo("class Test { }",
 248                     "package pkg;" +
 249                     "@Ann(#.class)\n" +
 250                     "public class A {" +
 251                     "}" +
 252                     "@interface Ann {" +
 253                     "    public Class<?> value();" +
 254                     "}",
 255                     "",
 256                     "# a = null; a.toString();",
 257                     (fqn, t) -> {
 258                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 259                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 260                           verifyTypeMirror(t, am.getAnnotationType());
 261                           if (am.getAnnotationType().toString().equals(fqn)) {
 262                               verifyTypeMirror(t, (TypeMirror) am.getElementValues().values()
 263                                                                  .iterator().next().getValue());
 264                           }
 265                       }
 266                   });
 267     }
 268 
 269     void testMethod() throws Exception {
 270         doTestCombo("class Test {" +
 271                     "}",
 272                     "package pkg;" +
 273                     "public class A {" +
 274                     "    public void m1(# t) { }" +
 275                     "    public # m2() { return null; }" +
 276                     "}",
 277                     "",
 278                     "pkg.A a = null; a.m2().toString();",
 279                     (fqn, t) -> {
 280                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 281                       List<? extends Element> members = a.getEnclosedElements();
 282                       if (members.size() != 3)
 283                           throw new AssertionError("Unexpected number of members, " +
 284                                                    "received members: " + members);
 285                       for (Element e : members) {
 286                           verifyElement(t, e);
 287                       }
 288                   });
 289     }
 290 
 291     void testAnnotationProcessing() throws Exception {
 292         boolean[] superClass = new boolean[1];
 293         class TestAP extends AbstractProcessor {
 294 
 295             @Override
 296             public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 297                 com.sun.source.util.JavacTask t = com.sun.source.util.JavacTask.instance(processingEnv);
 298                 TypeElement a = t.getElements().getTypeElement("pkg.A");
 299                 if (superClass[0]) {
 300                     verifyTypeMirror(t, a.getSuperclass());
 301                 } else {
 302                     verifyTypeMirror(t, a.getInterfaces().get(0));
 303                 }
 304                 return false;
 305             }
 306 
 307             @Override
 308             public Set<String> getSupportedAnnotationTypes() {
 309                 return Set.of("*");
 310             }
 311 
 312             @Override
 313             public SourceVersion getSupportedSourceVersion() {
 314                 return SourceVersion.latest();
 315             }
 316         }
 317 
 318         superClass[0] = true;
 319         doTestComboCallBack("class Test {" +
 320                             "}",
 321                             "package pkg;" +
 322                             "public class A extends # {" +
 323                             "}",
 324                             "",
 325                             "# a = null; a.toString();",
 326                             (fqn, t) -> t.setProcessors(List.of(new TestAP())));
 327         superClass[0] = false;
 328         doTestComboCallBack("interface Test {" +
 329                             "}",
 330                             "package pkg;" +
 331                             "public class A implements # {" +
 332                             "}",
 333                             "",
 334                             "# a = null; a.toString();",
 335                             (fqn, t) -> t.setProcessors(List.of(new TestAP())));
 336     }
 337 
 338     void testGetTypeElement() throws Exception {
 339         doTestCombo("class Test { }",
 340                     "package pkg;" +
 341                     "public class A extends # {" +
 342                     "}",
 343                     "",
 344                     "pkg.A a = null; a.toString();", //should be generalized/in variant?
 345                     (fqn, t) -> {
 346                           TypeElement a = t.getElements().getTypeElement(fqn);
 347                           if (a != null) {
 348                               throw new IllegalStateException();
 349                           }
 350                       });
 351     }
 352 
 353     private Path compileLib(Path base, String... sources) throws Exception {
 354         Path libSrc = base.resolve("lib-src");
 355         tb.createDirectories(libSrc);
 356         tb.writeJavaFiles(libSrc, sources);
 357         Path libClasses = base.resolve("lib-classes");
 358         tb.createDirectories(libClasses);
 359         new JavacTask(tb).outdir(libClasses.toString())
 360                          .sourcepath(libSrc.toString())
 361                          .files(tb.findJavaFiles(libSrc))
 362                          .run()
 363                          .writeAll();
 364 
 365         return libClasses;
 366     }
 367 
 368     private void doTestCombo(String decl,
 369                              String use,
 370                              String snippetInClass,
 371                              String snippetInMethod,
 372                              BiConsumer<String, com.sun.source.util.JavacTask> test) throws Exception {
 373         doTestComboCallBack(decl,
 374                             use,
 375                             snippetInClass,
 376                             snippetInMethod,
 377                             (fqn, t) -> {
 378             t.addTaskListener(new TaskListener() {
 379                 @Override
 380                 public void finished(TaskEvent e) {
 381                     if (e.getKind() == TaskEvent.Kind.ENTER) {
 382                         test.accept(fqn, t);
 383                     }
 384                 }
 385             });
 386         });
 387     }
 388 
 389     private void doTestComboCallBack(String decl,
 390                                      String use,
 391                                      String snippetInClass,
 392                                      String snippetInMethod,
 393                                      BiConsumer<String, com.sun.source.util.JavacTask> callback) throws Exception {
 394         List<TestVariant> variants = List.of(
 395                 new TestVariant("package pkg; public #", "pkg.Test", "pkg/Test.class"),
 396                 new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O$Test.class"),
 397                 new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O.class"),
 398                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N$Test.class"),
 399                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N.class"),
 400                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O.class")
 401         );
 402 
 403         Path base = Paths.get(".");
 404 
 405         for (TestVariant v : variants) {
 406             System.err.println("-----------------------------------------------------------------------");
 407             System.err.println("variant: " + v.declarationStub + ", " + v.fqn + ", " + v.path);
 408             Path libClasses = compileLib(base,
 409                                          use.replace("#", v.fqn),
 410                                          v.declarationStub.replace("#", decl));
 411 
 412             Files.delete(libClasses.resolve(v.path));
 413 
 414             doRunTestFullCallback(base,
 415                                   t -> callback.accept(v.fqn, t),
 416                                   snippetInClass.replace("#", v.fqn),
 417                                   snippetInMethod.replace("#", v.fqn));
 418         }
 419     }
 420 
 421     private void doRunTest(Path base,
 422                            Consumer<com.sun.source.util.JavacTask> test,
 423                            String snippetInClass,
 424                            String snippetInMethod) throws Exception {
 425         doRunTestFullCallback(base, t -> {
 426             t.addTaskListener(new TaskListener() {
 427                 @Override
 428                 public void finished(TaskEvent e) {
 429                     if (e.getKind() == TaskEvent.Kind.ENTER) {
 430                         test.accept(t);
 431                     }
 432                 }
 433             });
 434         }, snippetInClass, snippetInMethod);
 435     }
 436 
 437     private void doRunTestFullCallback(Path base,
 438                                        Consumer<com.sun.source.util.JavacTask> callback,
 439                                        String snippetInClass,
 440                                        String snippetInMethod) throws Exception {
 441         Path libClasses = base.resolve("lib-classes");
 442         Path testSrc = base.resolve("test-src");
 443         tb.createDirectories(testSrc);
 444         tb.writeJavaFiles(testSrc,
 445                           "package test;\n" +
 446                           "public class Test {\n" +
 447                           snippetInClass + "\n" +
 448                           "    void t() {\n" +
 449                           snippetInMethod + "\n" +
 450                           "    }\n" +
 451                           "}\n");
 452         System.err.println("content: " + "package test;\n" +
 453                           "public class Test {\n" +
 454                           snippetInClass + "\n" +
 455                           "    void t() {\n" +
 456                           snippetInMethod + "\n" +
 457                           "    }\n" +
 458                           "}\n");
 459         Path testClasses = base.resolve("test-classes");
 460         tb.createDirectories(testClasses);
 461 
 462         var expectedErrors = new JavacTask(tb).outdir(testClasses.toString())
 463                                               .options("-XDrawDiagnostics",
 464                                                        "-classpath",
 465                                                        libClasses.toString())
 466                                               .sourcepath(testSrc.toString())
 467                                               .files(tb.findJavaFiles(testSrc))
 468                                               .run(Expect.FAIL)
 469                                               .writeAll()
 470                                               .getOutputLines(OutputKind.DIRECT,
 471                                                               OutputKind.STDERR,
 472                                                               OutputKind.STDOUT);
 473 
 474         var errors = new JavacTask(tb).outdir(testClasses.toString())
 475                                       .options("-XDrawDiagnostics",
 476                                                "-classpath",
 477                                                libClasses.toString())
 478                                       .sourcepath(testSrc.toString())
 479                                       .files(tb.findJavaFiles(testSrc))
 480                                       .callback(callback)
 481                                       .run(Expect.FAIL)
 482                                       .writeAll()
 483                                       .getOutputLines(OutputKind.DIRECT,
 484                                                       OutputKind.STDERR,
 485                                                       OutputKind.STDOUT);
 486 
 487         if (!expectedErrors.equals(errors)) {
 488             throw new IllegalStateException("Expected error not found!");
 489         }
 490     }
 491 
 492     private void verifyTypeMirror(com.sun.source.util.JavacTask t, TypeMirror type) {
 493         Element el = t.getTypes().asElement(type);
 494 
 495         if (el != null) {
 496             verifyElement(t, el);
 497         }
 498     }
 499 
 500     private void verifyElement(com.sun.source.util.JavacTask t, Element el) {
 501         el.getKind(); //forces completion
 502     }
 503 
 504     private static void assertEquals(Object expected, Object actual) {
 505         if (!Objects.equals(expected, actual)) {
 506             throw new AssertionError("Unexpected value, expected: " + expected + ", actual: " + actual);
 507         }
 508     }
 509 
 510     public static void main(String... args) throws Exception {
 511         MissingClassFile t = new MissingClassFile();
 512         t.testPackageContent();
 513         t.testPackageDirectAPI();
 514         t.testSuperClass();
 515         t.testAnnotation();
 516         t.testAnnotationProcessing();
 517         t.testGetTypeElement();
 518     }
 519 
 520     static class TestVariant {
 521         public final String declarationStub;
 522         public final String fqn;
 523         public final String path;
 524 
 525         public TestVariant(String declarationStub, String fqn, String path) {
 526             this.declarationStub = declarationStub;
 527             this.fqn = fqn;
 528             this.path = path;
 529         }
 530 
 531     }
 532 }