/* * Copyright 2015 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.bugpatterns; import static com.google.errorprone.BugPattern.Category.JDK; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.annotations.Var; import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; import com.google.errorprone.fixes.Fix; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.fixes.SuggestedFixes; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ForLoopTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Source; import com.sun.tools.javac.code.Symbol; import java.util.Collections; import java.util.EnumSet; import javax.lang.model.element.Modifier; /** @author cushon@google.com (Liam Miller-Cushon) */ @BugPattern( name = "Var", summary = "Non-constant variable missing @Var annotation", category = JDK, severity = WARNING ) public class VarChecker extends BugChecker implements VariableTreeMatcher { private static final String UNNECESSARY_FINAL = "Unnecessary 'final' modifier."; @Override public Description matchVariable(VariableTree tree, VisitorState state) { Symbol sym = ASTHelpers.getSymbol(tree); if (sym == null) { return Description.NO_MATCH; } if (ASTHelpers.hasAnnotation(sym, Var.class, state)) { return Description.NO_MATCH; } if (forLoopVariable(tree, state.getPath())) { // for loop indices are implicitly @Var // TODO(cushon): consider requiring @Var if the index is modified in the body of the loop return Description.NO_MATCH; } switch (sym.getKind()) { case PARAMETER: case LOCAL_VARIABLE: case EXCEPTION_PARAMETER: case RESOURCE_VARIABLE: return handleLocalOrParam(tree, state, sym); default: return Description.NO_MATCH; } } boolean forLoopVariable(VariableTree tree, TreePath path) { Tree parent = path.getParentPath().getLeaf(); if (!(parent instanceof ForLoopTree)) { return false; } ForLoopTree forLoop = (ForLoopTree) parent; return forLoop.getInitializer().contains(tree); } private Description handleLocalOrParam(VariableTree tree, VisitorState state, Symbol sym) { if (sym.getModifiers().contains(Modifier.FINAL)) { if (Source.instance(state.context).allowEffectivelyFinalInInnerClasses()) { // In Java 8, the final modifier is never necessary on locals/parameters because // effectively final variables can be used anywhere a final variable is required. Fix fix = SuggestedFixes.removeModifiers(tree, state, Modifier.FINAL); // The fix may be null for TWR variables that were not explicitly final if (fix != null) { return buildDescription(tree).setMessage(UNNECESSARY_FINAL).addFix(fix).build(); } } return Description.NO_MATCH; } if (!Collections.disjoint( sym.owner.getModifiers(), EnumSet.of(Modifier.ABSTRACT, Modifier.NATIVE))) { // flow information isn't collected for body-less methods return Description.NO_MATCH; } if ((sym.flags() & (Flags.EFFECTIVELY_FINAL | Flags.FINAL)) != 0) { return Description.NO_MATCH; } return describeMatch(tree, addVarAnnotation(tree)); } private static Fix addVarAnnotation(VariableTree tree) { return SuggestedFix.builder().prefixWith(tree, "@Var ").addImport(Var.class.getName()).build(); } }