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.ErrorType;
  48 import javax.lang.model.type.TypeKind;
  49 import javax.lang.model.type.TypeMirror;
  50 import javax.tools.JavaCompiler;
  51 import javax.tools.StandardJavaFileManager;
  52 import javax.tools.ToolProvider;
  53 
  54 import toolbox.*;
  55 import toolbox.Task.*;
  56 
  57 
  58 import com.sun.source.util.TaskEvent;
  59 import com.sun.source.util.TaskListener;
  60 import com.sun.tools.javac.api.JavacTrees;
  61 
  62 @SupportedAnnotationTypes("*")
  63 public class MissingClassFile {
  64     ToolBox tb = new ToolBox();
  65 
  66     void testPackageContent() throws Exception {
  67         Path base = Paths.get(".");
  68         Path libClasses = compileLib(base,
  69                                      "package pkg;" +
  70                                      "public class A {" +
  71                                      "}",
  72                                      "package pkg;" +
  73                                      "public class B {" +
  74                                      "}");
  75 
  76         Files.delete(libClasses.resolve("pkg/B.class"));
  77         try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) {
  78             out.write(0);
  79         }
  80 
  81         doRunTest(base,
  82                   t -> {
  83                       PackageElement pe = t.getElements().getPackageElement("pkg");
  84                       for (Element el : pe.getEnclosedElements()) {
  85                           verifyElement(t, el);
  86                       }
  87                   },
  88                   "",
  89                   "pkg.B b;");
  90     }
  91 
  92     void testPackageDirectAPI() throws Exception {
  93         Path base = Paths.get(".");
  94         Path libClasses = compileLib(base,
  95                                      "package pkg;" +
  96                                      "public class A {" +
  97                                      "}",
  98                                      "package pkg;" +
  99                                      "public class B {" +
 100                                      "}");
 101 
 102         Files.delete(libClasses.resolve("pkg/B.class"));
 103         try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) {
 104             out.write(0);
 105         }
 106 
 107         Path testSrc = base.resolve("test-src");
 108         tb.createDirectories(testSrc);
 109         tb.writeJavaFiles(testSrc,
 110                           "package test;\n" +
 111                           "public class Test {\n" +
 112                           "    void t() {\n" +
 113                           "        pkg.B b;\n" +
 114                           "    }\n" +
 115                           "}\n");
 116         Path testClasses = base.resolve("test-classes");
 117         tb.createDirectories(testClasses);
 118 
 119         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 120         List<String> errors = new ArrayList<>();
 121 
 122         try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
 123             com.sun.source.util.JavacTask task = (com.sun.source.util.JavacTask)
 124                     compiler.getTask(null,
 125                                      null,
 126                                      d -> errors.add(d.getCode()),
 127                                      Arrays.asList("-XDrawDiagnostics",
 128                                                    "-classpath",
 129                                                    libClasses.toString()),
 130                                      null,
 131                                      fm.getJavaFileObjects(tb.findJavaFiles(testSrc)));
 132             task.parse();
 133             PackageElement pe = task.getElements().getPackageElement("pkg");
 134             for (Element el : pe.getEnclosedElements()) {
 135                 verifyElement(task, el);
 136             }
 137             task.analyze();
 138         }
 139 
 140         List<String> expected = Arrays.asList("compiler.err.cant.access");
 141 
 142         if (!expected.equals(errors)) {
 143             throw new IllegalStateException("Expected error not found!");
 144         }
 145     }
 146 
 147     void testSuperClass() throws Exception {
 148         doTestCombo("class Test {" +
 149                     "}",
 150                     "package pkg;" +
 151                     "public class A extends # {" +
 152                     "}",
 153                     "pkg.A x;",
 154                     "# a = null; a.toString();",
 155                     (fqn, t) -> {
 156                         TypeElement a = t.getElements()
 157                                          .getTypeElement(t.getElements()
 158                                                           .getModuleElement(""),
 159                                                          "pkg.A");
 160                         TypeMirror superclass = a.getSuperclass();
 161                         verifyTypeMirror(t, superclass);
 162                         assertEquals(TypeKind.ERROR, superclass.getKind());
 163                         Element superclassEl = ((DeclaredType) superclass).asElement();
 164                         assertEquals(ElementKind.CLASS, superclassEl.getKind());
 165                         assertEquals(TypeKind.ERROR, superclassEl.asType().getKind());
 166                         TypeMirror originalType = JavacTrees.instance(t).getOriginalType((ErrorType) superclass);
 167                         assertEquals(TypeKind.DECLARED, originalType.getKind());
 168                         assertEquals(superclassEl, ((DeclaredType) originalType).asElement());
 169                   });
 170         doTestCombo("interface Test {" +
 171                     "}",
 172                     "package pkg;" +
 173                     "public class A implements # {" +
 174                     "}",
 175                     "pkg.A x;",
 176                     "# a = null; a.toString();",
 177                     (fqn, t) -> {
 178                         TypeElement a = t.getElements().getTypeElement("pkg.A");
 179                         TypeMirror superintf = a.getInterfaces().get(0);
 180                         verifyTypeMirror(t, superintf);
 181                         assertEquals(TypeKind.ERROR, superintf.getKind());
 182                         Element superintfEl = ((DeclaredType) superintf).asElement();
 183                         //superintfEl.getKind() may be either CLASS or INTERFACE, depending on which class is missing
 184                         assertEquals(TypeKind.ERROR, superintfEl.asType().getKind());
 185                         TypeMirror originalType = JavacTrees.instance(t).getOriginalType((ErrorType) superintf);
 186                         assertEquals(TypeKind.DECLARED, originalType.getKind());
 187                         assertEquals(superintfEl, ((DeclaredType) originalType).asElement());
 188                   });
 189         doTestCombo("class Test {" +
 190                     "}",
 191                     "package pkg;" +
 192                     "public class A extends # {" +
 193                     "}",
 194                     "pkg.A x;",
 195                     "# a = null; a.toString();",
 196                     (fqn, t) -> {
 197                         TypeElement a = t.getElements()
 198                                          .getTypeElement(t.getElements()
 199                                                           .getModuleElement(""),
 200                                                          "pkg.A");
 201                         DeclaredType superclass = (DeclaredType) a.getSuperclass();
 202                         superclass.getTypeArguments();
 203                   });
 204         doTestCombo("class Test {" +
 205                     "}",
 206                     "package pkg;" +
 207                     "public class A extends # {" +
 208                     "}",
 209                     "pkg.A x;",
 210                     "# a = null; a.toString();",
 211                     (fqn, t) -> {
 212                         TypeElement a = t.getElements()
 213                                          .getTypeElement(t.getElements()
 214                                                           .getModuleElement(""),
 215                                                          "pkg.A");
 216                         DeclaredType superclass = (DeclaredType) a.getSuperclass();
 217                         superclass.getEnclosingType();
 218                   });
 219     }
 220 
 221     void testAnnotation() throws Exception {
 222         doTestCombo("@interface Test {" +
 223                     "}",
 224                     "package pkg;" +
 225                     "@#\n" +
 226                     "public class A {" +
 227                     "}",
 228                     "",
 229                     "# a = null; a.toString();",
 230                     (fqn, t) -> {
 231                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 232                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 233                           verifyTypeMirror(t, am.getAnnotationType());
 234                       }
 235                   });
 236         doTestCombo("@interface Test {" +
 237                     "    public Class<?> value();" +
 238                     "}",
 239                     "package pkg;" +
 240                     "@#(Object.class)\n" +
 241                     "public class A {" +
 242                     "}",
 243                     "",
 244                     "# a = null; a.toString();",
 245                     (fqn, t) -> {
 246                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 247                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 248                           verifyTypeMirror(t, am.getAnnotationType());
 249                           if (am.getAnnotationType().toString().equals(fqn)) {
 250                               verifyTypeMirror(t, (TypeMirror) am.getElementValues().values()
 251                                                                  .iterator().next().getValue());
 252                           }
 253                       }
 254                   });
 255         doTestCombo("class Test { }",
 256                     "package pkg;" +
 257                     "@Ann(#.class)\n" +
 258                     "public class A {" +
 259                     "}" +
 260                     "@interface Ann {" +
 261                     "    public Class<?> value();" +
 262                     "}",
 263                     "",
 264                     "# a = null; a.toString();",
 265                     (fqn, t) -> {
 266                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 267                       for (AnnotationMirror am : a.getAnnotationMirrors()) {
 268                           verifyTypeMirror(t, am.getAnnotationType());
 269                           if (am.getAnnotationType().toString().equals(fqn)) {
 270                               verifyTypeMirror(t, (TypeMirror) am.getElementValues().values()
 271                                                                  .iterator().next().getValue());
 272                           }
 273                       }
 274                   });
 275     }
 276 
 277     void testMethod() throws Exception {
 278         doTestCombo("class Test {" +
 279                     "}",
 280                     "package pkg;" +
 281                     "public class A {" +
 282                     "    public void m1(# t) { }" +
 283                     "    public # m2() { return null; }" +
 284                     "}",
 285                     "",
 286                     "pkg.A a = null; a.m2().toString();",
 287                     (fqn, t) -> {
 288                       TypeElement a = t.getElements().getTypeElement("pkg.A");
 289                       List<? extends Element> members = a.getEnclosedElements();
 290                       if (members.size() != 3)
 291                           throw new AssertionError("Unexpected number of members, " +
 292                                                    "received members: " + members);
 293                       for (Element e : members) {
 294                           verifyElement(t, e);
 295                       }
 296                   });
 297     }
 298 
 299     void testAnnotationProcessing() throws Exception {
 300         boolean[] superClass = new boolean[1];
 301         boolean[] inInit = new boolean[1];
 302         class TestAP extends AbstractProcessor {
 303 
 304             @Override
 305             public void init(ProcessingEnvironment processingEnv) {
 306                 super.init(processingEnv);
 307                 if (inInit[0])
 308                     doCheck();
 309             }
 310 
 311             @Override
 312             public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 313                 if (!inInit[0])
 314                     doCheck();
 315                 return false;
 316             }
 317 
 318             private void doCheck() {
 319                 com.sun.source.util.JavacTask t = com.sun.source.util.JavacTask.instance(processingEnv);
 320                 TypeElement a = t.getElements().getTypeElement("pkg.A");
 321                 if (superClass[0]) {
 322                     verifyTypeMirror(t, a.getSuperclass());
 323                 } else {
 324                     verifyTypeMirror(t, a.getInterfaces().get(0));
 325                 }
 326             }
 327 
 328             @Override
 329             public Set<String> getSupportedAnnotationTypes() {
 330                 return Set.of("*");
 331             }
 332 
 333             @Override
 334             public SourceVersion getSupportedSourceVersion() {
 335                 return SourceVersion.latest();
 336             }
 337         }
 338 
 339         for (boolean supClass : new boolean[] {false, true}) {
 340             for (boolean init : new boolean[] {false, true}) {
 341                 String decl = supClass ? "class Test { }" : "interface Test { }";
 342                 String snip = supClass ? "extends #" : "implements #";
 343 
 344                 superClass[0] = supClass;
 345                 inInit[0] = init;
 346 
 347                 doTestComboCallBack(decl,
 348                                     "package pkg;" +
 349                                     "public class A " + snip + " {" +
 350                                     "}",
 351                                     "",
 352                                     "# a = null; a.toString();",
 353                                     (fqn, t) -> t.setProcessors(List.of(new TestAP())));
 354             }
 355         }
 356     }
 357 
 358     void testGetTypeElement() throws Exception {
 359         doTestCombo("class Test { }",
 360                     "package pkg;" +
 361                     "public class A extends # {" +
 362                     "}",
 363                     "",
 364                     "pkg.A a = null; a.toString();", //should be generalized/in variant?
 365                     (fqn, t) -> {
 366                           TypeElement a = t.getElements().getTypeElement(fqn);
 367                           if (a != null) {
 368                               throw new IllegalStateException();
 369                           }
 370                       });
 371     }
 372 
 373     private Path compileLib(Path base, String... sources) throws Exception {
 374         Path libSrc = base.resolve("lib-src");
 375         tb.createDirectories(libSrc);
 376         tb.writeJavaFiles(libSrc, sources);
 377         Path libClasses = base.resolve("lib-classes");
 378         tb.createDirectories(libClasses);
 379         new JavacTask(tb).outdir(libClasses.toString())
 380                          .sourcepath(libSrc.toString())
 381                          .files(tb.findJavaFiles(libSrc))
 382                          .run()
 383                          .writeAll();
 384 
 385         return libClasses;
 386     }
 387 
 388     private void doTestCombo(String decl,
 389                              String use,
 390                              String snippetInClass,
 391                              String snippetInMethod,
 392                              BiConsumer<String, com.sun.source.util.JavacTask> test) throws Exception {
 393         doTestComboCallBack(decl,
 394                             use,
 395                             snippetInClass,
 396                             snippetInMethod,
 397                             (fqn, t) -> {
 398             t.addTaskListener(new TaskListener() {
 399                 @Override
 400                 public void finished(TaskEvent e) {
 401                     if (e.getKind() == TaskEvent.Kind.ENTER) {
 402                         test.accept(fqn, t);
 403                     }
 404                 }
 405             });
 406         });
 407     }
 408 
 409     private void doTestComboCallBack(String decl,
 410                                      String use,
 411                                      String snippetInClass,
 412                                      String snippetInMethod,
 413                                      BiConsumer<String, com.sun.source.util.JavacTask> callback) throws Exception {
 414         List<TestVariant> variants = List.of(
 415                 new TestVariant("package pkg; public #", "pkg.Test", "pkg/Test.class"),
 416                 new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O$Test.class"),
 417                 new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O.class"),
 418                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N$Test.class"),
 419                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N.class"),
 420                 new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O.class")
 421         );
 422 
 423         Path base = Paths.get(".");
 424 
 425         for (TestVariant v : variants) {
 426             System.err.println("-----------------------------------------------------------------------");
 427             System.err.println("variant: " + v.declarationStub + ", " + v.fqn + ", " + v.path);
 428             Path libClasses = compileLib(base,
 429                                          use.replace("#", v.fqn),
 430                                          v.declarationStub.replace("#", decl));
 431 
 432             Files.delete(libClasses.resolve(v.path));
 433 
 434             doRunTestFullCallback(base,
 435                                   t -> callback.accept(v.fqn, t),
 436                                   snippetInClass.replace("#", v.fqn),
 437                                   snippetInMethod.replace("#", v.fqn));
 438         }
 439     }
 440 
 441     private void doRunTest(Path base,
 442                            Consumer<com.sun.source.util.JavacTask> test,
 443                            String snippetInClass,
 444                            String snippetInMethod) throws Exception {
 445         doRunTestFullCallback(base, t -> {
 446             t.addTaskListener(new TaskListener() {
 447                 @Override
 448                 public void finished(TaskEvent e) {
 449                     if (e.getKind() == TaskEvent.Kind.ENTER) {
 450                         test.accept(t);
 451                     }
 452                 }
 453             });
 454         }, snippetInClass, snippetInMethod);
 455     }
 456 
 457     private void doRunTestFullCallback(Path base,
 458                                        Consumer<com.sun.source.util.JavacTask> callback,
 459                                        String snippetInClass,
 460                                        String snippetInMethod) throws Exception {
 461         Path libClasses = base.resolve("lib-classes");
 462         Path testSrc = base.resolve("test-src");
 463         tb.createDirectories(testSrc);
 464         tb.writeJavaFiles(testSrc,
 465                           "package test;\n" +
 466                           "public class Test {\n" +
 467                           snippetInClass + "\n" +
 468                           "    void t() {\n" +
 469                           snippetInMethod + "\n" +
 470                           "    }\n" +
 471                           "}\n");
 472         System.err.println("content: " + "package test;\n" +
 473                           "public class Test {\n" +
 474                           snippetInClass + "\n" +
 475                           "    void t() {\n" +
 476                           snippetInMethod + "\n" +
 477                           "    }\n" +
 478                           "}\n");
 479         Path testClasses = base.resolve("test-classes");
 480         tb.createDirectories(testClasses);
 481 
 482         var expectedErrors = new JavacTask(tb).outdir(testClasses.toString())
 483                                               .options("-XDrawDiagnostics",
 484                                                        "-classpath",
 485                                                        libClasses.toString())
 486                                               .sourcepath(testSrc.toString())
 487                                               .files(tb.findJavaFiles(testSrc))
 488                                               .run(Expect.FAIL)
 489                                               .writeAll()
 490                                               .getOutputLines(OutputKind.DIRECT,
 491                                                               OutputKind.STDERR,
 492                                                               OutputKind.STDOUT);
 493 
 494         var errors = new JavacTask(tb).outdir(testClasses.toString())
 495                                       .options("-XDrawDiagnostics",
 496                                                "-classpath",
 497                                                libClasses.toString())
 498                                       .sourcepath(testSrc.toString())
 499                                       .files(tb.findJavaFiles(testSrc))
 500                                       .callback(callback)
 501                                       .run(Expect.FAIL)
 502                                       .writeAll()
 503                                       .getOutputLines(OutputKind.DIRECT,
 504                                                       OutputKind.STDERR,
 505                                                       OutputKind.STDOUT);
 506 
 507         if (!expectedErrors.equals(errors)) {
 508             throw new IllegalStateException("Expected error not found!");
 509         }
 510     }
 511 
 512     private void verifyTypeMirror(com.sun.source.util.JavacTask t, TypeMirror type) {
 513         Element el = t.getTypes().asElement(type);
 514 
 515         if (el != null) {
 516             verifyElement(t, el);
 517         }
 518     }
 519 
 520     private void verifyElement(com.sun.source.util.JavacTask t, Element el) {
 521         el.getKind(); //forces completion
 522     }
 523 
 524     private static void assertEquals(Object expected, Object actual) {
 525         if (!Objects.equals(expected, actual)) {
 526             throw new AssertionError("Unexpected value, expected: " + expected + ", actual: " + actual);
 527         }
 528     }
 529 
 530     public static void main(String... args) throws Exception {
 531         MissingClassFile t = new MissingClassFile();
 532         t.testPackageContent();
 533         t.testPackageDirectAPI();
 534         t.testSuperClass();
 535         t.testAnnotation();
 536         t.testAnnotationProcessing();
 537         t.testGetTypeElement();
 538     }
 539 
 540     static class TestVariant {
 541         public final String declarationStub;
 542         public final String fqn;
 543         public final String path;
 544 
 545         public TestVariant(String declarationStub, String fqn, String path) {
 546             this.declarationStub = declarationStub;
 547             this.fqn = fqn;
 548             this.path = path;
 549         }
 550 
 551     }
 552 }