/*
* 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.scanner;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.SuppressionHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Suppressible;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.code.Symbol;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* TODO(eaftan): I'm worried about this performance of this code,
* specifically the part that handles SuppressWarnings. We should
* profile it and see where the hotspots are.
*
* @author alexeagle@google.com (Alex Eagle)
* @author eaftan@google.com (Eddie Aftandilian)
*/
public class Scanner extends TreePathScanner<Void, VisitorState> {
private Set<String> suppressions = new HashSet<>();
private Set<Class<? extends Annotation>> customSuppressions =
new HashSet<>();
private boolean inGeneratedCode = false;
// This must be lazily initialized, because the list of custom suppression annotations will
// not be available until after the subclass's constructor has run.
private SuppressionHelper suppressionHelper;
private void initSuppressionHelper(VisitorState state) {
if (suppressionHelper == null) {
suppressionHelper = new SuppressionHelper(getCustomSuppressionAnnotations(), state);
}
}
/**
* Scan a tree from a position identified by a TreePath.
*/
@Override
public Void scan(TreePath path, VisitorState state) {
SuppressionHelper.SuppressionInfo prevSuppressionInfo =
updateSuppressions(path.getLeaf(), state);
try {
return super.scan(path, state);
} finally {
// Restore old suppression state.
suppressions = prevSuppressionInfo.suppressWarningsStrings;
customSuppressions = prevSuppressionInfo.customSuppressions;
inGeneratedCode = prevSuppressionInfo.inGeneratedCode;
}
}
/**
* Scan a single node.
* The current path is updated for the duration of the scan.
*/
@Override
public Void scan(Tree tree, VisitorState state) {
if (tree == null) {
return null;
}
SuppressionHelper.SuppressionInfo prevSuppressionInfo = updateSuppressions(tree, state);
try {
return super.scan(tree, state);
} finally {
// Restore old suppression state.
suppressions = prevSuppressionInfo.suppressWarningsStrings;
customSuppressions = prevSuppressionInfo.customSuppressions;
inGeneratedCode = prevSuppressionInfo.inGeneratedCode;
}
}
/**
* Updates current suppression state with information for the given {@code tree}. Returns
* the previous suppression state so that it can be restored when going up the tree.
*/
private SuppressionHelper.SuppressionInfo updateSuppressions(Tree tree, VisitorState state) {
SuppressionHelper.SuppressionInfo prevSuppressionInfo =
new SuppressionHelper.SuppressionInfo(suppressions, customSuppressions, inGeneratedCode);
initSuppressionHelper(state);
Symbol sym = ASTHelpers.getSymbol(tree);
if (sym != null) {
SuppressionHelper.SuppressionInfo newSuppressions =
suppressionHelper.extendSuppressionSets(
sym,
state.getSymtab().suppressWarningsType,
suppressions,
customSuppressions,
inGeneratedCode,
state);
if (newSuppressions.suppressWarningsStrings != null) {
suppressions = newSuppressions.suppressWarningsStrings;
}
if (newSuppressions.customSuppressions != null) {
customSuppressions = newSuppressions.customSuppressions;
}
inGeneratedCode = newSuppressions.inGeneratedCode;
}
return prevSuppressionInfo;
}
/**
* Returns true if this checker should be suppressed on the current tree path.
*
* @param suppressible holds information about the suppressibilty of a checker
*/
protected boolean isSuppressed(Suppressible suppressible, VisitorState state) {
initSuppressionHelper(state);
return SuppressionHelper.isSuppressed(
suppressible,
suppressions,
customSuppressions,
severityMap().get(suppressible.canonicalName()),
inGeneratedCode,
state.errorProneOptions().disableWarningsInGeneratedCode());
}
/**
* Returns a set of all the custom suppression annotation types used by the {@code BugChecker}s
* in this{@code Scanner}.
*/
protected Set<Class<? extends Annotation>> getCustomSuppressionAnnotations() {
return Collections.<Class<? extends Annotation>>emptySet();
}
protected <T extends Tree> void reportMatch(Description description, T match, VisitorState state)
{
if (description == null || description == Description.NO_MATCH) {
return;
}
state.reportMatch(description);
}
/** Handles an exception thrown by an individual check. */
protected void handleError(Suppressible s, Throwable t) {}
/**
* Returns a mapping between the canonical names of checks and their {@link SeverityLevel}.
*/
public Map<String, SeverityLevel> severityMap() {
return Collections.emptyMap();
}
}