8067747: javac throws exception during compilation when annotation processing is enabled

Enforcing Filer rules regarding initial inputs, to avoid downstream problems.

Reviewed-by: darcy, jjg
This commit is contained in:
Jan Lahoda 2017-01-26 14:11:38 +01:00
parent 01b7f9ed5e
commit a57e6e77d2
5 changed files with 245 additions and 18 deletions

View File

@ -190,7 +190,7 @@ public class JavacTaskImpl extends BasicJavacTask {
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<>();

View File

@ -921,7 +921,7 @@ public class JavaCompiler {
start_msec = now();
try {
initProcessAnnotations(processors);
initProcessAnnotations(processors, sourceFileObjects, classnames);
for (String className : classnames) {
int sep = className.indexOf('/');
@ -1123,7 +1123,9 @@ public class JavaCompiler {
* @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 @@ public class JavaCompiler {
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();
}

View File

@ -51,9 +51,11 @@ import static javax.tools.StandardLocation.SOURCE_OUTPUT;
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 @@ public class JavacFiler implements Filer, Closeable {
}
JavaFileManager fileManager;
JavacElements elementUtils;
Log log;
Modules modules;
Names names;
@ -330,6 +333,12 @@ public class JavacFiler implements Filer, Closeable {
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 @@ public class JavacFiler implements Filer, Closeable {
*/
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 @@ public class JavacFiler implements Filer, Closeable {
// 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 @@ public class JavacFiler implements Filer, Closeable {
* 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 {
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());
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...
}
}
}
if (addToHistory)
fileObjectHistory.add(fileObject);
for(FileObject veteran : fileObjectHistory) {
if (fileManager.isSameFile(veteran, fileObject)) {
return true;
}
}
return false;
}
public boolean newFiles() {
@ -656,9 +706,17 @@ public class JavacFiler implements Filer, Closeable {
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();

View File

@ -1628,7 +1628,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
}
@DefinedBy(Api.ANNOTATION_PROCESSING)
public Filer getFiler() {
public JavacFiler getFiler() {
return filer;
}

View File

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