| /* |
| * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 8187950 |
| * @summary Handing of BadClassFile exceptions and CompletionFailures |
| * @library /tools/javac/lib /tools/lib |
| * @modules jdk.compiler/com.sun.tools.javac.api |
| * jdk.compiler/com.sun.tools.javac.main |
| * @build JavacTestingAbstractProcessor MissingClassFile |
| * @run main MissingClassFile |
| */ |
| |
| import java.io.OutputStream; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.*; |
| import java.util.function.BiConsumer; |
| import java.util.function.Consumer; |
| |
| import javax.annotation.processing.*; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.*; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ErrorType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.tools.JavaCompiler; |
| import javax.tools.StandardJavaFileManager; |
| import javax.tools.ToolProvider; |
| |
| import toolbox.*; |
| import toolbox.Task.*; |
| |
| import com.sun.source.tree.Scope; |
| import com.sun.source.util.TaskEvent; |
| import com.sun.source.util.TaskListener; |
| import com.sun.source.util.TreePath; |
| import com.sun.source.util.Trees; |
| |
| @SupportedAnnotationTypes("*") |
| public class MissingClassFile { |
| ToolBox tb = new ToolBox(); |
| |
| void testPackageContent() throws Exception { |
| Path base = Paths.get("."); |
| Path libClasses = compileLib(base, |
| "package pkg;" + |
| "public class A {" + |
| "}", |
| "package pkg;" + |
| "public class B {" + |
| "}"); |
| |
| Files.delete(libClasses.resolve("pkg/B.class")); |
| try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) { |
| out.write(0); |
| } |
| |
| doRunTest(base, |
| t -> { |
| PackageElement pe = t.getElements().getPackageElement("pkg"); |
| for (Element el : pe.getEnclosedElements()) { |
| verifyElement(t, el); |
| } |
| }, |
| "", |
| "pkg.B b;"); |
| } |
| |
| void testPackageDirectAPI() throws Exception { |
| Path base = Paths.get("."); |
| Path libClasses = compileLib(base, |
| "package pkg;" + |
| "public class A {" + |
| "}", |
| "package pkg;" + |
| "public class B {" + |
| "}"); |
| |
| Files.delete(libClasses.resolve("pkg/B.class")); |
| try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) { |
| out.write(0); |
| } |
| |
| Path testSrc = base.resolve("test-src"); |
| tb.createDirectories(testSrc); |
| tb.writeJavaFiles(testSrc, |
| "package test;\n" + |
| "public class Test {\n" + |
| " void t() {\n" + |
| " pkg.B b;\n" + |
| " }\n" + |
| "}\n"); |
| Path testClasses = base.resolve("test-classes"); |
| tb.createDirectories(testClasses); |
| |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
| List<String> errors = new ArrayList<>(); |
| |
| try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { |
| com.sun.source.util.JavacTask task = (com.sun.source.util.JavacTask) |
| compiler.getTask(null, |
| null, |
| d -> errors.add(d.getCode()), |
| Arrays.asList("-XDrawDiagnostics", |
| "-classpath", |
| libClasses.toString()), |
| null, |
| fm.getJavaFileObjects(tb.findJavaFiles(testSrc))); |
| task.parse(); |
| PackageElement pe = task.getElements().getPackageElement("pkg"); |
| for (Element el : pe.getEnclosedElements()) { |
| verifyElement(task, el); |
| } |
| task.analyze(); |
| } |
| |
| List<String> expected = Arrays.asList("compiler.err.cant.access"); |
| |
| if (!expected.equals(errors)) { |
| throw new IllegalStateException("Expected error not found!"); |
| } |
| } |
| |
| void testSuperClass() throws Exception { |
| doTestCombo("class Test {" + |
| "}", |
| "package pkg;" + |
| "public class A extends # {" + |
| "}", |
| "pkg.A x;", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements() |
| .getTypeElement(t.getElements() |
| .getModuleElement(""), |
| "pkg.A"); |
| TypeMirror superclass = a.getSuperclass(); |
| verifyTypeMirror(t, superclass); |
| assertEquals(TypeKind.ERROR, superclass.getKind()); |
| Element superclassEl = ((DeclaredType) superclass).asElement(); |
| assertEquals(ElementKind.CLASS, superclassEl.getKind()); |
| assertEquals(TypeKind.ERROR, superclassEl.asType().getKind()); |
| TypeMirror originalType = Trees.instance(t).getOriginalType((ErrorType) superclass); |
| assertEquals(TypeKind.DECLARED, originalType.getKind()); |
| assertEquals(superclassEl, ((DeclaredType) originalType).asElement()); |
| }); |
| doTestCombo("interface Test {" + |
| "}", |
| "package pkg;" + |
| "public class A implements # {" + |
| "}", |
| "pkg.A x;", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| TypeMirror superintf = a.getInterfaces().get(0); |
| verifyTypeMirror(t, superintf); |
| assertEquals(TypeKind.ERROR, superintf.getKind()); |
| Element superintfEl = ((DeclaredType) superintf).asElement(); |
| //superintfEl.getKind() may be either CLASS or INTERFACE, depending on which class is missing |
| assertEquals(TypeKind.ERROR, superintfEl.asType().getKind()); |
| TypeMirror originalType = Trees.instance(t).getOriginalType((ErrorType) superintf); |
| assertEquals(TypeKind.DECLARED, originalType.getKind()); |
| assertEquals(superintfEl, ((DeclaredType) originalType).asElement()); |
| }); |
| doTestCombo("class Test {" + |
| "}", |
| "package pkg;" + |
| "public class A extends # {" + |
| "}", |
| "pkg.A x;", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements() |
| .getTypeElement(t.getElements() |
| .getModuleElement(""), |
| "pkg.A"); |
| DeclaredType superclass = (DeclaredType) a.getSuperclass(); |
| superclass.getTypeArguments(); |
| }); |
| doTestCombo("class Test {" + |
| "}", |
| "package pkg;" + |
| "public class A extends # {" + |
| "}", |
| "pkg.A x;", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements() |
| .getTypeElement(t.getElements() |
| .getModuleElement(""), |
| "pkg.A"); |
| DeclaredType superclass = (DeclaredType) a.getSuperclass(); |
| superclass.getEnclosingType(); |
| }); |
| } |
| |
| void testAnnotation() throws Exception { |
| doTestCombo("@interface Test {" + |
| "}", |
| "package pkg;" + |
| "@#\n" + |
| "public class A {" + |
| "}", |
| "", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| for (AnnotationMirror am : a.getAnnotationMirrors()) { |
| verifyTypeMirror(t, am.getAnnotationType()); |
| } |
| }); |
| doTestCombo("@interface Test {" + |
| " public Class<?> value();" + |
| "}", |
| "package pkg;" + |
| "@#(Object.class)\n" + |
| "public class A {" + |
| "}", |
| "", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| for (AnnotationMirror am : a.getAnnotationMirrors()) { |
| verifyTypeMirror(t, am.getAnnotationType()); |
| if (am.getAnnotationType().toString().equals(fqn)) { |
| verifyTypeMirror(t, (TypeMirror) am.getElementValues().values() |
| .iterator().next().getValue()); |
| } |
| } |
| }); |
| doTestCombo("class Test { }", |
| "package pkg;" + |
| "@Ann(#.class)\n" + |
| "public class A {" + |
| "}" + |
| "@interface Ann {" + |
| " public Class<?> value();" + |
| "}", |
| "", |
| "# a = null; a.toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| for (AnnotationMirror am : a.getAnnotationMirrors()) { |
| verifyTypeMirror(t, am.getAnnotationType()); |
| if (am.getAnnotationType().toString().equals(fqn)) { |
| verifyTypeMirror(t, (TypeMirror) am.getElementValues().values() |
| .iterator().next().getValue()); |
| } |
| } |
| }); |
| } |
| |
| void testMethod() throws Exception { |
| doTestCombo("class Test {" + |
| "}", |
| "package pkg;" + |
| "public class A {" + |
| " public void m1(# t) { }" + |
| " public # m2() { return null; }" + |
| "}", |
| "", |
| "pkg.A a = null; a.m2().toString();", |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| List<? extends Element> members = a.getEnclosedElements(); |
| if (members.size() != 3) |
| throw new AssertionError("Unexpected number of members, " + |
| "received members: " + members); |
| for (Element e : members) { |
| verifyElement(t, e); |
| } |
| }); |
| } |
| |
| void testAnnotationProcessing() throws Exception { |
| boolean[] superClass = new boolean[1]; |
| boolean[] inInit = new boolean[1]; |
| class TestAP extends AbstractProcessor { |
| |
| @Override |
| public void init(ProcessingEnvironment processingEnv) { |
| super.init(processingEnv); |
| if (inInit[0]) |
| doCheck(); |
| } |
| |
| @Override |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| if (!inInit[0]) |
| doCheck(); |
| return false; |
| } |
| |
| private void doCheck() { |
| com.sun.source.util.JavacTask t = com.sun.source.util.JavacTask.instance(processingEnv); |
| TypeElement a = t.getElements().getTypeElement("pkg.A"); |
| if (superClass[0]) { |
| verifyTypeMirror(t, a.getSuperclass()); |
| } else { |
| verifyTypeMirror(t, a.getInterfaces().get(0)); |
| } |
| } |
| |
| @Override |
| public Set<String> getSupportedAnnotationTypes() { |
| return Set.of("*"); |
| } |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latest(); |
| } |
| } |
| |
| for (boolean supClass : new boolean[] {false, true}) { |
| for (boolean init : new boolean[] {false, true}) { |
| String decl = supClass ? "class Test { }" : "interface Test { }"; |
| String snip = supClass ? "extends #" : "implements #"; |
| |
| superClass[0] = supClass; |
| inInit[0] = init; |
| |
| doTestComboCallBack(decl, |
| "package pkg;" + |
| "public class A " + snip + " {" + |
| "}", |
| "", |
| "# a = null; a.toString();", |
| (fqn, t) -> t.setProcessors(List.of(new TestAP()))); |
| } |
| } |
| } |
| |
| void testGetTypeElement() throws Exception { |
| doTestCombo("class Test { }", |
| "package pkg;" + |
| "public class A extends # {" + |
| "}", |
| "", |
| "pkg.A a = null; a.toString();", //should be generalized/in variant? |
| (fqn, t) -> { |
| TypeElement a = t.getElements().getTypeElement(fqn); |
| if (a != null) { |
| throw new IllegalStateException(); |
| } |
| }); |
| } |
| |
| void testScope() throws Exception { |
| class Variant { |
| private final String code; |
| private final String fqn; |
| public Variant(String code, String fqn) { |
| this.code = code; |
| this.fqn = fqn; |
| } |
| } |
| Path base = Paths.get("."); |
| Path libClasses = compileLib(base, |
| "package pkg;" + |
| "public class A {" + |
| " public static class I {}" + |
| "}", |
| "package pkg;" + |
| "public class B {" + |
| "}"); |
| try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/B.class"))) { |
| out.write(0); |
| } |
| try (OutputStream out = Files.newOutputStream(libClasses.resolve("pkg/A$I.class"))) { |
| out.write(0); |
| } |
| |
| Path testSrc = base.resolve("test-src"); |
| tb.createDirectories(testSrc); |
| Path testClasses = base.resolve("test-classes"); |
| tb.createDirectories(testClasses); |
| |
| Variant[] variants = new Variant[] { |
| //JDK-8198378: |
| // new Variant("package test;\n" + |
| // "import pkg.B;\n" + |
| // "public class Test {}\n", |
| // "test.Test"), |
| new Variant("package test;\n" + |
| "import pkg.*;\n" + |
| "public class Test {}\n", |
| "test.Test"), |
| new Variant("package test;\n" + |
| "import pkg.A.*;\n" + |
| "public class Test extends I {}\n", |
| "test.Test"), |
| new Variant("package test;\n" + |
| "import static pkg.A.*;\n" + |
| "public class Test extends I {}\n", |
| "test.Test"), |
| new Variant("package pkg;\n" + |
| "public class Test {}\n", |
| "pkg.Test") |
| }; |
| for (Variant variant : variants) { |
| System.err.println("variant: " + variant.code); |
| tb.writeJavaFiles(testSrc, variant.code); |
| tb.cleanDirectory(testClasses); |
| |
| JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
| List<String> errors = new ArrayList<>(); |
| |
| try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { |
| com.sun.source.util.JavacTask task = (com.sun.source.util.JavacTask) |
| compiler.getTask(null, |
| null, |
| d -> errors.add(d.getCode()), |
| Arrays.asList("-XDrawDiagnostics", |
| "-classpath", |
| libClasses.toString()), |
| null, |
| fm.getJavaFileObjects(tb.findJavaFiles(testSrc))); |
| task.analyze(); |
| TypeElement a = task.getElements() |
| .getTypeElement(task.getElements() |
| .getModuleElement(""), |
| variant.fqn); |
| Trees trees = Trees.instance(task); |
| TreePath tpA = trees.getPath(a); |
| Scope scope = trees.getScope(tpA); |
| while (scope != null) { |
| for (Element el : scope.getLocalElements()) { |
| verifyElement(task, el); |
| } |
| scope = scope.getEnclosingScope(); |
| } |
| } |
| } |
| } |
| |
| private Path compileLib(Path base, String... sources) throws Exception { |
| Path libSrc = base.resolve("lib-src"); |
| tb.createDirectories(libSrc); |
| tb.writeJavaFiles(libSrc, sources); |
| Path libClasses = base.resolve("lib-classes"); |
| tb.createDirectories(libClasses); |
| new JavacTask(tb).outdir(libClasses.toString()) |
| .sourcepath(libSrc.toString()) |
| .files(tb.findJavaFiles(libSrc)) |
| .run() |
| .writeAll(); |
| |
| return libClasses; |
| } |
| |
| private void doTestCombo(String decl, |
| String use, |
| String snippetInClass, |
| String snippetInMethod, |
| BiConsumer<String, com.sun.source.util.JavacTask> test) throws Exception { |
| doTestComboCallBack(decl, |
| use, |
| snippetInClass, |
| snippetInMethod, |
| (fqn, t) -> { |
| t.addTaskListener(new TaskListener() { |
| @Override |
| public void finished(TaskEvent e) { |
| if (e.getKind() == TaskEvent.Kind.ENTER) { |
| test.accept(fqn, t); |
| } |
| } |
| }); |
| }); |
| } |
| |
| private void doTestComboCallBack(String decl, |
| String use, |
| String snippetInClass, |
| String snippetInMethod, |
| BiConsumer<String, com.sun.source.util.JavacTask> callback) throws Exception { |
| List<TestVariant> variants = List.of( |
| new TestVariant("package pkg; public #", "pkg.Test", "pkg/Test.class"), |
| new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O$Test.class"), |
| new TestVariant("package pkg; public class O { public static # }", "pkg.O.Test", "pkg/O.class"), |
| new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N$Test.class"), |
| new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O$N.class"), |
| new TestVariant("package pkg; public class O { public static class N { public static # } }", "pkg.O.N.Test", "pkg/O.class") |
| ); |
| |
| Path base = Paths.get("."); |
| |
| for (TestVariant v : variants) { |
| System.err.println("-----------------------------------------------------------------------"); |
| System.err.println("variant: " + v.declarationStub + ", " + v.fqn + ", " + v.path); |
| Path libClasses = compileLib(base, |
| use.replace("#", v.fqn), |
| v.declarationStub.replace("#", decl)); |
| |
| Files.delete(libClasses.resolve(v.path)); |
| |
| doRunTestFullCallback(base, |
| t -> callback.accept(v.fqn, t), |
| snippetInClass.replace("#", v.fqn), |
| snippetInMethod.replace("#", v.fqn)); |
| } |
| } |
| |
| private void doRunTest(Path base, |
| Consumer<com.sun.source.util.JavacTask> test, |
| String snippetInClass, |
| String snippetInMethod) throws Exception { |
| doRunTestFullCallback(base, t -> { |
| t.addTaskListener(new TaskListener() { |
| @Override |
| public void finished(TaskEvent e) { |
| if (e.getKind() == TaskEvent.Kind.ENTER) { |
| test.accept(t); |
| } |
| } |
| }); |
| }, snippetInClass, snippetInMethod); |
| } |
| |
| private void doRunTestFullCallback(Path base, |
| Consumer<com.sun.source.util.JavacTask> callback, |
| String snippetInClass, |
| String snippetInMethod) throws Exception { |
| Path libClasses = base.resolve("lib-classes"); |
| Path testSrc = base.resolve("test-src"); |
| tb.createDirectories(testSrc); |
| tb.writeJavaFiles(testSrc, |
| "package test;\n" + |
| "public class Test {\n" + |
| snippetInClass + "\n" + |
| " void t() {\n" + |
| snippetInMethod + "\n" + |
| " }\n" + |
| "}\n"); |
| System.err.println("content: " + "package test;\n" + |
| "public class Test {\n" + |
| snippetInClass + "\n" + |
| " void t() {\n" + |
| snippetInMethod + "\n" + |
| " }\n" + |
| "}\n"); |
| Path testClasses = base.resolve("test-classes"); |
| tb.createDirectories(testClasses); |
| |
| var expectedErrors = new JavacTask(tb).outdir(testClasses.toString()) |
| .options("-XDrawDiagnostics", |
| "-classpath", |
| libClasses.toString()) |
| .sourcepath(testSrc.toString()) |
| .files(tb.findJavaFiles(testSrc)) |
| .run(Expect.FAIL) |
| .writeAll() |
| .getOutputLines(OutputKind.DIRECT, |
| OutputKind.STDERR, |
| OutputKind.STDOUT); |
| |
| var errors = new JavacTask(tb).outdir(testClasses.toString()) |
| .options("-XDrawDiagnostics", |
| "-classpath", |
| libClasses.toString()) |
| .sourcepath(testSrc.toString()) |
| .files(tb.findJavaFiles(testSrc)) |
| .callback(callback) |
| .run(Expect.FAIL) |
| .writeAll() |
| .getOutputLines(OutputKind.DIRECT, |
| OutputKind.STDERR, |
| OutputKind.STDOUT); |
| |
| if (!expectedErrors.equals(errors)) { |
| throw new IllegalStateException("Expected error not found!"); |
| } |
| } |
| |
| private void verifyTypeMirror(com.sun.source.util.JavacTask t, TypeMirror type) { |
| Element el = t.getTypes().asElement(type); |
| |
| if (el != null) { |
| verifyElement(t, el); |
| } |
| } |
| |
| private void verifyElement(com.sun.source.util.JavacTask t, Element el) { |
| el.getKind(); //forces completion |
| } |
| |
| private static void assertEquals(Object expected, Object actual) { |
| if (!Objects.equals(expected, actual)) { |
| throw new AssertionError("Unexpected value, expected: " + expected + ", actual: " + actual); |
| } |
| } |
| |
| public static void main(String... args) throws Exception { |
| MissingClassFile t = new MissingClassFile(); |
| t.testPackageContent(); |
| t.testPackageDirectAPI(); |
| t.testSuperClass(); |
| t.testAnnotation(); |
| t.testAnnotationProcessing(); |
| t.testGetTypeElement(); |
| t.testScope(); |
| } |
| |
| static class TestVariant { |
| public final String declarationStub; |
| public final String fqn; |
| public final String path; |
| |
| public TestVariant(String declarationStub, String fqn, String path) { |
| this.declarationStub = declarationStub; |
| this.fqn = fqn; |
| this.path = path; |
| } |
| |
| } |
| } |