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
--- 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();
+ }
+
+}