# HG changeset patch # User jlahoda # Date 1485436298 -3600 # Node ID cabe410a7a5cfce41fe0732a7b41dabfcd1d5902 # Parent 7797472a9ed5b5d45edcce3f73d1df021e4a18d4 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 diff -r 7797472a9ed5 -r cabe410a7a5c langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskImpl.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<>(); diff -r 7797472a9ed5 -r cabe410a7a5c langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java --- 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 processors) { + public void initProcessAnnotations(Iterable processors, + Collection initialFiles, + Collection 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(); } diff -r 7797472a9ed5 -r cabe410a7a5c langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacFiler.java --- 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 initialInputs; + + /** * Logical names of all created files. This set must be * synchronized. */ @@ -373,26 +382,30 @@ */ private final Set> aggregateGeneratedClassNames; + private final Set 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()); - generatedSourceNames = synchronizedSet(new LinkedHashSet()); - generatedSourceFileObjects = synchronizedSet(new LinkedHashSet()); + initialInputs = synchronizedSet(new LinkedHashSet<>()); + fileObjectHistory = synchronizedSet(new LinkedHashSet<>()); + generatedSourceNames = synchronizedSet(new LinkedHashSet<>()); + generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<>()); generatedClasses = synchronizedMap(new LinkedHashMap<>()); - openTypeNames = synchronizedSet(new LinkedHashSet()); + 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 initialInputs, + Collection 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(); diff -r 7797472a9ed5 -r cabe410a7a5c langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java --- 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; } diff -r 7797472a9ed5 -r cabe410a7a5c langtools/test/tools/javac/processing/OverwriteInitialInput.java --- /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 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(); + } + +}