/* * Copyright 2014 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.errorprone; import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import com.google.common.collect.ImmutableList; import com.google.errorprone.RefactoringCollection.RefactoringResult; import com.google.errorprone.scanner.ErrorProneScannerTransformer; import com.google.errorprone.scanner.ScannerSupplier; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskEvent.Kind; import com.sun.source.util.TaskListener; import com.sun.tools.javac.api.JavacTaskImpl; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JavacMessages; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Log.WriterKind; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; import javax.lang.model.SourceVersion; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; /** An Error Prone compiler that implements {@link javax.tools.JavaCompiler}. */ public class BaseErrorProneJavaCompiler implements JavaCompiler { private final JavaCompiler javacTool; private final ScannerSupplier scannerSupplier; public BaseErrorProneJavaCompiler(ScannerSupplier scannerSupplier) { this(JavacTool.create(), scannerSupplier); } BaseErrorProneJavaCompiler(JavaCompiler javacTool, ScannerSupplier scannerSupplier) { this.javacTool = javacTool; this.scannerSupplier = scannerSupplier; } @Override public CompilationTask getTask( Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits) { ErrorProneOptions errorProneOptions = ErrorProneOptions.processArgs(options); List<String> remainingOptions = Arrays.asList(errorProneOptions.getRemainingArgs()); ImmutableList<String> javacOpts = ImmutableList.copyOf(remainingOptions); javacOpts = defaultToLatestSupportedLanguageLevel(javacOpts); javacOpts = setCompilePolicyToByFile(javacOpts); final JavacTaskImpl task = (JavacTaskImpl) javacTool.getTask( out, fileManager, diagnosticListener, javacOpts, classes, compilationUnits); setupMessageBundle(task.getContext()); RefactoringCollection[] refactoringCollection = {null}; task.addTaskListener( createAnalyzer( scannerSupplier, errorProneOptions, task.getContext(), refactoringCollection)); if (refactoringCollection[0] != null) { task.addTaskListener(new RefactoringTask(task.getContext(), refactoringCollection[0])); } return task; } @Override public StandardJavaFileManager getStandardFileManager( DiagnosticListener<? super JavaFileObject> diagnosticListener, Locale locale, Charset charset) { return javacTool.getStandardFileManager(diagnosticListener, locale, charset); } @Override public int isSupportedOption(String option) { int numberOfArgs = javacTool.isSupportedOption(option); if (numberOfArgs != -1) { return numberOfArgs; } return ErrorProneOptions.isSupportedOption(option); } @Override public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) { return javacTool.run(in, out, err, arguments); } @Override public Set<SourceVersion> getSourceVersions() { Set<SourceVersion> filtered = EnumSet.noneOf(SourceVersion.class); for (SourceVersion version : javacTool.getSourceVersions()) { if (version.compareTo(SourceVersion.RELEASE_6) >= 0) { filtered.add(version); } } return filtered; } /** * Default to compiling with the same -source and -target as the host's javac. * * <p>This prevents, e.g., targeting Java 8 by default when using error-prone on JDK7. */ private static ImmutableList<String> defaultToLatestSupportedLanguageLevel( ImmutableList<String> args) { String overrideLanguageLevel; switch (JAVA_SPECIFICATION_VERSION.value()) { case "1.7": overrideLanguageLevel = "7"; break; case "1.8": overrideLanguageLevel = "8"; break; default: return args; } return ImmutableList.<String>builder() .add( // suppress xlint 'options' warnings to avoid diagnostics like: // 'bootstrap class path not set in conjunction with -source 1.7' "-Xlint:-options", "-source", overrideLanguageLevel, "-target", overrideLanguageLevel) .addAll(args) .build(); } /** * Sets javac's {@code -XDcompilePolicy} flag to ensure that all classes in a file are attributed * before any of them are lowered. Error Prone depends on this behavior when analyzing files that * contain multiple top-level classes. * * @throws InvalidCommandLineOptionException if the {@code -XDcompilePolicy} flag is passed in the * existing arguments with an unsupported value */ private static ImmutableList<String> setCompilePolicyToByFile(ImmutableList<String> args) { for (String arg : args) { if (arg.startsWith("-XDcompilePolicy")) { String value = arg.substring(arg.indexOf('=') + 1); switch (value) { case "byfile": case "simple": break; default: throw new InvalidCommandLineOptionException( String.format("-XDcompilePolicy=%s is not supported by Error Prone", value)); } // don't do anything if a valid policy is already set return args; } } return ImmutableList.<String>builder().addAll(args).add("-XDcompilePolicy=byfile").build(); } /** Registers our message bundle. */ public static void setupMessageBundle(Context context) { ResourceBundle bundle = ResourceBundle.getBundle("com.google.errorprone.errors"); JavacMessages.instance(context).add(l -> bundle); } static ErrorProneAnalyzer createAnalyzer( ScannerSupplier scannerSupplier, ErrorProneOptions epOptions, Context context, RefactoringCollection[] refactoringCollection) { if (!epOptions.patchingOptions().doRefactor()) { return ErrorProneAnalyzer.createByScanningForPlugins(scannerSupplier, epOptions, context); } refactoringCollection[0] = RefactoringCollection.refactor(epOptions.patchingOptions()); // Refaster refactorer or using builtin checks CodeTransformer codeTransformer = epOptions .patchingOptions() .customRefactorer() .or( () -> { ScannerSupplier toUse = scannerSupplier; Set<String> namedCheckers = epOptions.patchingOptions().namedCheckers(); if (!namedCheckers.isEmpty()) { toUse = scannerSupplier.filter(bci -> namedCheckers.contains(bci.canonicalName())); } return ErrorProneScannerTransformer.create(toUse.applyOverrides(epOptions).get()); }) .get(); return ErrorProneAnalyzer.createWithCustomDescriptionListener( codeTransformer, epOptions, context, refactoringCollection[0]); } static class RefactoringTask implements TaskListener { private final Context context; private final RefactoringCollection refactoringCollection; public RefactoringTask(Context context, RefactoringCollection refactoringCollection) { this.context = context; this.refactoringCollection = refactoringCollection; } @Override public void started(TaskEvent event) {} @Override public void finished(TaskEvent event) { if (event.getKind() != Kind.GENERATE) { return; } RefactoringResult refactoringResult; try { refactoringResult = refactoringCollection.applyChanges(event.getSourceFile().toUri()); } catch (Exception e) { PrintWriter out = Log.instance(context).getWriter(WriterKind.ERROR); out.append(e.getMessage()); out.flush(); return; } if (refactoringResult.type() == RefactoringCollection.RefactoringResultType.CHANGED) { PrintWriter out = Log.instance(context).getWriter(WriterKind.NOTICE); out.println(refactoringResult.message()); out.flush(); } } } }