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:
parent
01b7f9ed5e
commit
a57e6e77d2
@ -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<>();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -1628,7 +1628,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
|
||||
}
|
||||
|
||||
@DefinedBy(Api.ANNOTATION_PROCESSING)
|
||||
public Filer getFiler() {
|
||||
public JavacFiler getFiler() {
|
||||
return filer;
|
||||
}
|
||||
|
||||
|
166
langtools/test/tools/javac/processing/OverwriteInitialInput.java
Normal file
166
langtools/test/tools/javac/processing/OverwriteInitialInput.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user