/*
* 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.bugpatterns;
import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.assignment;
import static com.google.errorprone.matchers.Matchers.binaryTree;
import static com.google.errorprone.matchers.Matchers.inSynchronized;
import static com.google.errorprone.matchers.Matchers.kindIs;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.sameVariable;
import static com.google.errorprone.matchers.Matchers.toType;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.AssignmentTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.CompoundAssignmentTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.UnaryTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.UnaryTree;
import javax.lang.model.element.Modifier;
/** Detects non-atomic updates to volatile variables. */
@BugPattern(
name = "NonAtomicVolatileUpdate",
summary = "This update of a volatile variable is non-atomic",
explanation =
"The volatile modifier ensures that updates to a variable are propagated "
+ "predictably to other threads. A read of a volatile variable always returns the most "
+ "recent write by any thread.\n\n"
+ "However, this does not mean that all updates to a volatile variable are atomic. For "
+ "example, if you increment or decrement a volatile variable, you are actually doing "
+ "(1) a read of the variable, (2) an increment or decrement of a local copy, and (3) a "
+ "write back to the variable. Each step is atomic individually, but the whole sequence "
+ "is not, and it will cause a race condition if two threads try to increment or "
+ "decrement a volatile variable at the same time. The same is true for compound "
+ "assignment, e.g. foo += bar.\n\n"
+ "If you intended for this update to be atomic, you should wrap all update operations "
+ "on this variable in a synchronized block. If the variable is an integer, you could "
+ "use an AtomicInteger instead of a volatile int.",
category = JDK,
severity = WARNING
)
public class NonAtomicVolatileUpdate extends BugChecker
implements UnaryTreeMatcher, CompoundAssignmentTreeMatcher, AssignmentTreeMatcher {
/**
* Extracts the expression from a UnaryTree and applies a matcher to it.
*/
private static Matcher<UnaryTree> expressionFromUnaryTree(
final Matcher <ExpressionTree> exprMatcher) {
return new Matcher<UnaryTree>() {
@Override
public boolean matches(UnaryTree tree, VisitorState state) {
return exprMatcher.matches(tree.getExpression(), state);
}
};
}
/**
* Extracts the variable from a CompoundAssignmentTree and applies a matcher to it.
*/
private static Matcher<CompoundAssignmentTree> variableFromCompoundAssignmentTree(
final Matcher <ExpressionTree> exprMatcher) {
return new Matcher<CompoundAssignmentTree>() {
@Override
public boolean matches(CompoundAssignmentTree tree, VisitorState state) {
return exprMatcher.matches(tree.getVariable(), state);
}
};
}
/**
* Extracts the variable from an AssignmentTree and applies a matcher to it.
*/
private static Matcher<AssignmentTree> variableFromAssignmentTree(
final Matcher <ExpressionTree> exprMatcher) {
return new Matcher<AssignmentTree>() {
@Override
public boolean matches(AssignmentTree tree, VisitorState state) {
return exprMatcher.matches(tree.getVariable(), state);
}
};
}
/**
* Matches patterns like i++ and i-- in which i is volatile, and the pattern is not enclosed
* by a synchronized block.
*/
private static final Matcher<UnaryTree> unaryIncrementDecrementMatcher =
allOf(
expressionFromUnaryTree(Matchers.<ExpressionTree>hasModifier(Modifier.VOLATILE)),
not(inSynchronized()),
anyOf(
kindIs(Kind.POSTFIX_INCREMENT),
kindIs(Kind.PREFIX_INCREMENT),
kindIs(Kind.POSTFIX_DECREMENT),
kindIs(Kind.PREFIX_DECREMENT)));
@Override
public Description matchUnary(UnaryTree tree, VisitorState state) {
if (unaryIncrementDecrementMatcher.matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
/**
* Matches patterns like i += 1 and i -= 1 in which i is volatile, and the pattern is not
* enclosed by a synchronized block.
*/
private static final Matcher<CompoundAssignmentTree> compoundAssignmentIncrementDecrementMatcher =
allOf(
variableFromCompoundAssignmentTree(
Matchers.<ExpressionTree>hasModifier(Modifier.VOLATILE)),
not(inSynchronized()),
anyOf(
kindIs(Kind.PLUS_ASSIGNMENT),
kindIs(Kind.MINUS_ASSIGNMENT)));
@Override
public Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state) {
if (compoundAssignmentIncrementDecrementMatcher.matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
/**
* Matches patterns like i = i + 1 and i = i - 1 in which i is volatile, and the pattern is not
* enclosed by a synchronized block.
*/
private static Matcher<AssignmentTree> assignmentIncrementDecrementMatcher(
ExpressionTree variable) {
return allOf(
variableFromAssignmentTree(
Matchers.<ExpressionTree>hasModifier(Modifier.VOLATILE)),
not(inSynchronized()),
assignment(
Matchers.<ExpressionTree>anything(),
toType(
BinaryTree.class,
Matchers.<BinaryTree>allOf(
Matchers.<BinaryTree>anyOf(
kindIs(Kind.PLUS),
kindIs(Kind.MINUS)),
binaryTree(
sameVariable(variable),
Matchers.<ExpressionTree>anything())))));
}
@Override
public Description matchAssignment(AssignmentTree tree, VisitorState state) {
if (assignmentIncrementDecrementMatcher(tree.getVariable()).matches(tree, state)) {
return describeMatch(tree);
}
return Description.NO_MATCH;
}
}