8067747: javac throws exception during compilation when annotation processing is enabled
authorjlahoda
Thu, 26 Jan 2017 14:11:38 +0100
changeset 43368 cabe410a7a5c
parent 43367 7797472a9ed5
child 43369 aafd33c96bac
8067747: javac throws exception during compilation when annotation processing is enabled Summary: Enforcing Filer rules regarding initial inputs, to avoid downstream problems. Reviewed-by: darcy, jjg
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java
langtools/test/tools/javac/processing/OverwriteInitialInput.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java	Wed Jan 25 10:43:41 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java	Thu Jan 26 14:11:38 2017 +0100
@@ -190,7 +190,7 @@
             compiler.genEndPos = true;
             notYetEntered = new HashMap<>();
             if (forParse) {
-                compiler.initProcessAnnotations(processors);
+                compiler.initProcessAnnotations(processors, args.getFileObjects(), args.getClassNames());
                 for (JavaFileObject file: args.getFileObjects())
                     notYetEntered.put(file, null);
                 genList = new ListBuffer<>();
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Wed Jan 25 10:43:41 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Thu Jan 26 14:11:38 2017 +0100
@@ -921,7 +921,7 @@
         start_msec = now();
 
         try {
-            initProcessAnnotations(processors);
+            initProcessAnnotations(processors, sourceFileObjects, classnames);
 
             for (String className : classnames) {
                 int sep = className.indexOf('/');
@@ -1123,7 +1123,9 @@
      * @param processors user provided annotation processors to bypass
      * discovery, {@code null} means that no processors were provided
      */
-    public void initProcessAnnotations(Iterable<? extends Processor> processors) {
+    public void initProcessAnnotations(Iterable<? extends Processor> processors,
+                                       Collection<? extends JavaFileObject> initialFiles,
+                                       Collection<String> initialClassNames) {
         // Process annotations if processing is not disabled and there
         // is at least one Processor available.
         if (options.isSet(PROC, "none")) {
@@ -1141,6 +1143,7 @@
                 if (!taskListener.isEmpty())
                     taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING));
                 deferredDiagnosticHandler = new Log.DeferredDiagnosticHandler(log);
+                procEnvImpl.getFiler().setInitialState(initialFiles, initialClassNames);
             } else { // free resources
                 procEnvImpl.close();
             }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java	Wed Jan 25 10:43:41 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java	Thu Jan 26 14:11:38 2017 +0100
@@ -51,9 +51,11 @@
 import static javax.tools.StandardLocation.CLASS_OUTPUT;
 
 import com.sun.tools.javac.code.Lint;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
 import com.sun.tools.javac.code.Symtab;
 import com.sun.tools.javac.comp.Modules;
+import com.sun.tools.javac.model.JavacElements;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.DefinedBy.Api;
 
@@ -321,6 +323,7 @@
     }
 
     JavaFileManager fileManager;
+    JavacElements elementUtils;
     Log log;
     Modules modules;
     Names names;
@@ -331,6 +334,12 @@
     private final boolean lint;
 
     /**
+     * Initial inputs passed to the tool.  This set must be
+     * synchronized.
+     */
+    private final Set<FileObject> initialInputs;
+
+    /**
      * Logical names of all created files.  This set must be
      * synchronized.
      */
@@ -373,26 +382,30 @@
      */
     private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedClassNames;
 
+    private final Set<String> initialClassNames;
 
     JavacFiler(Context context) {
         this.context = context;
         fileManager = context.get(JavaFileManager.class);
+        elementUtils = JavacElements.instance(context);
 
         log = Log.instance(context);
         modules = Modules.instance(context);
         names = Names.instance(context);
         syms = Symtab.instance(context);
 
-        fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
-        generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
-        generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());
+        initialInputs = synchronizedSet(new LinkedHashSet<>());
+        fileObjectHistory = synchronizedSet(new LinkedHashSet<>());
+        generatedSourceNames = synchronizedSet(new LinkedHashSet<>());
+        generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<>());
 
         generatedClasses = synchronizedMap(new LinkedHashMap<>());
 
-        openTypeNames  = synchronizedSet(new LinkedHashSet<String>());
+        openTypeNames  = synchronizedSet(new LinkedHashSet<>());
 
         aggregateGeneratedSourceNames = new LinkedHashSet<>();
         aggregateGeneratedClassNames  = new LinkedHashSet<>();
+        initialClassNames  = new LinkedHashSet<>();
 
         lint = (Lint.instance(context)).isEnabled(PROCESSING);
     }
@@ -596,8 +609,13 @@
         // TODO: Check if type already exists on source or class path?
         // If so, use warning message key proc.type.already.exists
         checkName(typename, allowUnnamedPackageInfo);
-        if (aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) ||
-            aggregateGeneratedClassNames.contains(Pair.of(mod, typename))) {
+        ClassSymbol existing;
+        boolean alreadySeen = aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) ||
+                              aggregateGeneratedClassNames.contains(Pair.of(mod, typename)) ||
+                              initialClassNames.contains(typename) ||
+                              ((existing = elementUtils.getTypeElement(typename)) != null &&
+                               initialInputs.contains(existing.sourcefile));
+        if (alreadySeen) {
             if (lint)
                 log.warning("proc.type.recreate", typename);
             throw new FilerException("Attempt to recreate a file for type " + typename);
@@ -611,16 +629,48 @@
      * Check to see if the file has already been opened; if so, throw
      * an exception, otherwise add it to the set of files.
      */
-    private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException {
+    private void checkFileReopening(FileObject fileObject, boolean forWriting) throws FilerException {
+        if (isInFileObjectHistory(fileObject, forWriting)) {
+            if (lint)
+                log.warning("proc.file.reopening", fileObject.getName());
+            throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
+        }
+        if (forWriting)
+            fileObjectHistory.add(fileObject);
+    }
+
+    private boolean isInFileObjectHistory(FileObject fileObject, boolean forWriting) {
+        if (forWriting) {
+            for(FileObject veteran : initialInputs) {
+                try {
+                    if (fileManager.isSameFile(veteran, fileObject)) {
+                        return true;
+                    }
+                } catch (IllegalArgumentException e) {
+                    //ignore...
+                }
+            }
+            for (String className : initialClassNames) {
+                try {
+                    ClassSymbol existing = elementUtils.getTypeElement(className);
+                    if (   existing != null
+                        && (   (existing.sourcefile != null && fileManager.isSameFile(existing.sourcefile, fileObject))
+                            || (existing.classfile != null && fileManager.isSameFile(existing.classfile, fileObject)))) {
+                        return true;
+                    }
+                } catch (IllegalArgumentException e) {
+                    //ignore...
+                }
+            }
+        }
+
         for(FileObject veteran : fileObjectHistory) {
             if (fileManager.isSameFile(veteran, fileObject)) {
-                if (lint)
-                    log.warning("proc.file.reopening", fileObject.getName());
-                throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
+                return true;
             }
         }
-        if (addToHistory)
-            fileObjectHistory.add(fileObject);
+
+        return false;
     }
 
     public boolean newFiles() {
@@ -656,9 +706,17 @@
         this.lastRound = lastRound;
     }
 
+    public void setInitialState(Collection<? extends JavaFileObject> initialInputs,
+                                Collection<String> initialClassNames) {
+        this.initialInputs.addAll(initialInputs);
+        this.initialClassNames.addAll(initialClassNames);
+    }
+
     public void close() {
         clearRoundState();
         // Cross-round state
+        initialClassNames.clear();
+        initialInputs.clear();
         fileObjectHistory.clear();
         openTypeNames.clear();
         aggregateGeneratedSourceNames.clear();
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Wed Jan 25 10:43:41 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Thu Jan 26 14:11:38 2017 +0100
@@ -1628,7 +1628,7 @@
     }
 
     @DefinedBy(Api.ANNOTATION_PROCESSING)
-    public Filer getFiler() {
+    public JavacFiler getFiler() {
         return filer;
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/processing/OverwriteInitialInput.java	Thu Jan 26 14:11:38 2017 +0100
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2015, 2016, 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 8067747
+ * @summary Verify the correct Filer behavior w.r.t. initial inputs
+ *          (should throw FilerException when overwriting initial inputs).
+ * @library /tools/lib /tools/javac/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.ToolBox toolbox.JavacTask toolbox.Task
+ * @build OverwriteInitialInput JavacTestingAbstractProcessor
+ * @run main OverwriteInitialInput
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+
+import javax.annotation.processing.FilerException;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.TypeElement;
+import javax.tools.StandardLocation;
+
+import toolbox.JavacTask;
+import toolbox.Task;
+import toolbox.ToolBox;
+
+@SupportedOptions({OverwriteInitialInput.EXPECT_EXCEPTION_OPTION,
+                   OverwriteInitialInput.TEST_SOURCE
+                  })
+public class OverwriteInitialInput extends JavacTestingAbstractProcessor {
+
+    public static final String EXPECT_EXCEPTION_OPTION = "exception";
+    public static final String TEST_SOURCE = "testSource";
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        if (roundEnv.processingOver()) {
+            if (processingEnv.getOptions().containsKey(EXPECT_EXCEPTION_OPTION)) {
+                try (Writer w = processingEnv.getFiler().createSourceFile("Test").openWriter()) {
+                    throw new AssertionError("Expected IOException not seen.");
+                } catch (FilerException ex) {
+                    //expected
+                } catch (IOException ex) {
+                    throw new IllegalStateException(ex);
+                }
+                try (OutputStream out = processingEnv.getFiler().createClassFile("Test").openOutputStream()) {
+                    throw new AssertionError("Expected IOException not seen.");
+                } catch (FilerException ex) {
+                    //expected
+                } catch (IOException ex) {
+                    throw new IllegalStateException(ex);
+                }
+                if (processingEnv.getOptions().containsKey(TEST_SOURCE)) {
+                    try (OutputStream out = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", "Test.java").openOutputStream()) {
+                        throw new AssertionError("Expected IOException not seen.");
+                    } catch (FilerException ex) {
+                        //expected
+                    } catch (IOException ex) {
+                        throw new IllegalStateException(ex);
+                    }
+                } else {
+                    try (OutputStream out = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "Test2.class").openOutputStream()) {
+                        throw new AssertionError("Expected IOException not seen.");
+                    } catch (FilerException ex) {
+                        //expected
+                    } catch (IOException ex) {
+                        throw new IllegalStateException(ex);
+                    }
+                }
+            } else {
+                try (Writer w = processingEnv.getFiler().createSourceFile("Test").openWriter()) {
+                    w.append("public class Test {}");
+                } catch (IOException ex) {
+                    throw new IllegalStateException(ex);
+                }
+                try (OutputStream out = processingEnv.getFiler().createClassFile("Test2").openOutputStream()) {
+                    try (ToolBox.MemoryFileManager mfm = new ToolBox.MemoryFileManager()) {
+                        ToolBox tb = new ToolBox();
+                        new JavacTask(tb)
+                          .sources("public class Test2 {}")
+                          .fileManager(mfm)
+                          .run()
+                          .writeAll();
+
+                        out.write(mfm.getFileBytes(StandardLocation.CLASS_OUTPUT, "Test2"));
+                    }
+                } catch (IOException ex) {
+                    throw new IllegalStateException(ex);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public static void main(String... args) throws IOException {
+        new OverwriteInitialInput().run();
+    }
+
+    void run() throws IOException {
+        run(Task.Mode.API);
+        run(Task.Mode.CMDLINE);
+    }
+
+    void run(Task.Mode mode) throws IOException {
+        ToolBox tb = new ToolBox();
+        Path path = Paths.get("output");
+        if (Files.isDirectory(path))
+            tb.cleanDirectory(path);
+        Files.deleteIfExists(path);
+        tb.createDirectories(path);
+        Path thisSource = Paths.get(System.getProperty("test.src"), "OverwriteInitialInput.java");
+        new JavacTask(tb, mode).options("-processor", "OverwriteInitialInput",
+                                        "-d", path.toString(),
+                                        "-XDaccessInternalAPI=true")
+                               .files(thisSource)
+                               .run()
+                               .writeAll();
+        new JavacTask(tb, mode).options("-processor", "OverwriteInitialInput",
+                                        "-d", path.toString(),
+                                        "-A" + EXPECT_EXCEPTION_OPTION,
+                                        "-A" + TEST_SOURCE,
+                                        "-XDaccessInternalAPI=true")
+                               .files(thisSource, path.resolve("Test.java"))
+                               .run()
+                               .writeAll();
+        new JavacTask(tb, mode).options("-processor", "OverwriteInitialInput",
+                                        "-classpath", path.toString(),
+                                        "-processorpath", System.getProperty("test.class.path"),
+                                        "-d", path.toString(),
+                                        "-A" + EXPECT_EXCEPTION_OPTION,
+                                        "-XDaccessInternalAPI=true")
+                               .classes("Test", "Test2")
+                               .run()
+                               .writeAll();
+    }
+
+}