/* * Copyright 2012 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.Preconditions.checkNotNull; import static com.google.common.base.Verify.verify; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.base.Throwables; import com.google.errorprone.scanner.ErrorProneScannerTransformer; import com.google.errorprone.scanner.ScannerSupplier; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Tree; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskEvent.Kind; import com.sun.source.util.TaskListener; import com.sun.source.util.TreePath; import com.sun.tools.javac.api.ClientCodeWrapper.Trusted; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.code.Symbol.CompletionFailure; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.PropagatedException; import java.util.HashSet; import java.util.Set; /** A {@link TaskListener} that runs Error Prone over attributed compilation units. */ @Trusted public class ErrorProneAnalyzer implements TaskListener { // The set of trees that have already been scanned. private final Set<Tree> seen = new HashSet<>(); private final Supplier<CodeTransformer> transformer; private final ErrorProneOptions errorProneOptions; private final Context context; private final DescriptionListener.Factory descriptionListenerFactory; public static ErrorProneAnalyzer createByScanningForPlugins( ScannerSupplier scannerSupplier, ErrorProneOptions errorProneOptions, Context context) { return new ErrorProneAnalyzer( scansPlugins(scannerSupplier, errorProneOptions, context), errorProneOptions, context, JavacErrorDescriptionListener.provider()); } private static Supplier<CodeTransformer> scansPlugins( ScannerSupplier scannerSupplier, ErrorProneOptions errorProneOptions, Context context) { return Suppliers.memoize( () -> { // we can't load plugins from the processorpath until the filemanager has been // initialized, so do it lazily try { return ErrorProneScannerTransformer.create( ErrorPronePlugins.loadPlugins(scannerSupplier, context) .applyOverrides(errorProneOptions) .get()); } catch (InvalidCommandLineOptionException e) { throw new PropagatedException(e); } }); } static ErrorProneAnalyzer createWithCustomDescriptionListener( CodeTransformer codeTransformer, ErrorProneOptions errorProneOptions, Context context, DescriptionListener.Factory descriptionListenerFactory) { return new ErrorProneAnalyzer( Suppliers.ofInstance(codeTransformer), errorProneOptions, context, descriptionListenerFactory); } // Used by Bazel still @SuppressWarnings("unused") public ErrorProneAnalyzer( ScannerSupplier scannerSupplier, ErrorProneOptions errorProneOptions, Context context) { this( scansPlugins(scannerSupplier, errorProneOptions, context), errorProneOptions, context, JavacErrorDescriptionListener.provider()); } private ErrorProneAnalyzer( Supplier<CodeTransformer> transformer, ErrorProneOptions errorProneOptions, Context context, DescriptionListener.Factory descriptionListenerFactory) { this.transformer = checkNotNull(transformer); this.errorProneOptions = checkNotNull(errorProneOptions); this.context = checkNotNull(context); this.descriptionListenerFactory = checkNotNull(descriptionListenerFactory); } @Override public void finished(TaskEvent taskEvent) { if (taskEvent.getKind() != Kind.ANALYZE) { return; } if (JavaCompiler.instance(context).errorCount() > 0) { return; } TreePath path = JavacTrees.instance(context).getPath(taskEvent.getTypeElement()); if (path == null) { path = new TreePath(taskEvent.getCompilationUnit()); } // Assert that the event is unique and scan the current tree. verify(seen.add(path.getLeaf()), "Duplicate FLOW event for: %s", taskEvent.getTypeElement()); Context subContext = new SubContext(context); subContext.put(ErrorProneOptions.class, errorProneOptions); Log log = Log.instance(context); JCCompilationUnit compilation = (JCCompilationUnit) path.getCompilationUnit(); DescriptionListener descriptionListener = descriptionListenerFactory.getDescriptionListener(log, compilation); try { if (path.getLeaf().getKind() == Tree.Kind.COMPILATION_UNIT) { // We only get TaskEvents for compilation units if they contain no package declarations // (e.g. package-info.java files). In this case it's safe to analyze the // CompilationUnitTree immediately. transformer.get().apply(path, subContext, descriptionListener); } else if (finishedCompilation(path.getCompilationUnit())) { // Otherwise this TaskEvent is for a ClassTree, and we can scan the whole // CompilationUnitTree once we've seen all the enclosed classes. transformer.get().apply(new TreePath(compilation), subContext, descriptionListener); } } catch (ErrorProneError e) { e.logFatalError(log); // let the exception propagate to javac's main, where it will cause the compilation to // terminate with Result.ABNORMAL throw e; } catch (CompletionFailure e) { // A CompletionFailure can be triggered when error-prone tries to complete a symbol // that isn't on the compilation classpath. This can occur when a check performs an // instanceof test on a symbol, which requires inspecting the transitive closure of the // symbol's supertypes. If javac didn't need to check the symbol's assignability // then a normal compilation would have succeeded, and no diagnostics will have been // reported yet, but we don't want to crash javac. log.error("proc.cant.access", e.sym, e.getDetailValue(), Throwables.getStackTraceAsString(e)); } } /** * Returns true if all declarations inside the given compilation unit have been visited. */ private boolean finishedCompilation(CompilationUnitTree tree) { OUTER: for (Tree decl : tree.getTypeDecls()) { switch (decl.getKind()) { case EMPTY_STATEMENT: // ignore ";" at the top level, which counts as an empty type decl continue OUTER; case IMPORT: // The spec disallows mixing imports and empty top-level declarations (";"), but // javac has a bug that causes it to accept empty declarations interspersed with imports: // http://mail.openjdk.java.net/pipermail/compiler-dev/2013-August/006968.html // // Any import declarations after the first semi are incorrectly added to the list // of type declarations, so we have to skip over them here. continue OUTER; default: break; } if (!seen.contains(decl)) { return false; } } return true; } }