org.sonar.java.checks.synchronization.DoubleCheckedLockingCheck Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.checks.synchronization;
import com.google.common.collect.ImmutableList;
import org.sonar.check.Rule;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.StatementTree;
import org.sonar.plugins.java.api.tree.SynchronizedStatementTree;
import org.sonar.plugins.java.api.tree.SyntaxToken;
import org.sonar.plugins.java.api.tree.Tree;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.sonar.plugins.java.api.tree.Tree.Kind.EQUAL_TO;
import static org.sonar.plugins.java.api.tree.Tree.Kind.IDENTIFIER;
import static org.sonar.plugins.java.api.tree.Tree.Kind.IF_STATEMENT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.MEMBER_SELECT;
import static org.sonar.plugins.java.api.tree.Tree.Kind.METHOD;
import static org.sonar.plugins.java.api.tree.Tree.Kind.NULL_LITERAL;
import static org.sonar.plugins.java.api.tree.Tree.Kind.SYNCHRONIZED_STATEMENT;
@Rule(key = "S2168")
public class DoubleCheckedLockingCheck extends IssuableSubscriptionVisitor {
private Deque ifFieldStack = new LinkedList<>();
private Deque synchronizedStmtStack = new LinkedList<>();
private boolean methodIsSynchronized;
@Override
public List nodesToVisit() {
return ImmutableList.of(IF_STATEMENT, SYNCHRONIZED_STATEMENT, METHOD);
}
@Override
public void visitNode(Tree tree) {
if (!hasSemantic()) {
return;
}
isIfFieldEqNull(tree).ifPresent(ifFieldEqNull -> {
ifFieldStack.push(ifFieldEqNull);
visitIfStatement(ifFieldEqNull.ifTree);
});
if (tree.is(SYNCHRONIZED_STATEMENT)) {
CriticalSection criticalSection = new CriticalSection((SynchronizedStatementTree) tree, ifFieldStack.size());
synchronizedStmtStack.push(criticalSection);
}
if (tree.is(METHOD)) {
methodIsSynchronized = ModifiersUtils.hasModifier(((MethodTree) tree).modifiers(), Modifier.SYNCHRONIZED);
}
}
@Override
public void leaveNode(Tree tree) {
if (!hasSemantic()) {
return;
}
isIfFieldEqNull(tree).ifPresent(cl -> ifFieldStack.pop());
if (tree.is(SYNCHRONIZED_STATEMENT)) {
synchronizedStmtStack.pop();
}
}
private static Optional isIfFieldEqNull(Tree tree) {
if (!tree.is(IF_STATEMENT)) {
return Optional.empty();
}
IfStatementTree ifTree = (IfStatementTree) tree;
if (!ifTree.condition().is(EQUAL_TO)) {
return Optional.empty();
}
BinaryExpressionTree eqRelation = (BinaryExpressionTree) ifTree.condition();
if (eqRelation.rightOperand().is(NULL_LITERAL)) {
return isField(eqRelation.leftOperand()).map(f -> new IfFieldEqNull(ifTree, f));
}
if (eqRelation.leftOperand().is(NULL_LITERAL)) {
return isField(eqRelation.rightOperand()).map(f -> new IfFieldEqNull(ifTree, f));
}
return Optional.empty();
}
private void visitIfStatement(IfStatementTree ifTree) {
if (insideCriticalSection()) {
Optional parentIf = sameFieldAlreadyOnStack(ifFieldStack.peek());
parentIf.ifPresent(pIf -> ifSynchronizedIfPattern(pIf, ifTree));
}
}
private void ifSynchronizedIfPattern(IfFieldEqNull parentIf, IfStatementTree nestedIf) {
if (thenStmtInitializeField(nestedIf.thenStatement(), parentIf.field)
&& !parentIf.field.isVolatile()
&& !methodIsSynchronized) {
SyntaxToken synchronizedKeyword = synchronizedStmtStack.peek().synchronizedTree.synchronizedKeyword();
reportIssue(synchronizedKeyword, "Remove this dangerous instance of double-checked locking.", createFlow(parentIf.ifTree, nestedIf), null);
}
}
private static List createFlow(IfStatementTree parentIf, IfStatementTree nestedIf) {
return Stream.of(parentIf.condition(), nestedIf.condition())
.map(c -> new JavaFileScannerContext.Location("Double-checked locking", c))
.collect(Collectors.toList());
}
private boolean insideCriticalSection() {
return !synchronizedStmtStack.isEmpty();
}
/**
* Returns if statement which is above the critical section (synchronized) and has the same condition as nestedIf
*
*/
private Optional sameFieldAlreadyOnStack(IfFieldEqNull nestedIf) {
int aboveSynchronized = ifFieldStack.size() - synchronizedStmtStack.peek().ifStackDepth;
return ifFieldStack.stream()
.skip(aboveSynchronized)
.filter(parentIf -> parentIf.field == nestedIf.field)
.findFirst();
}
private static Optional isField(ExpressionTree expressionTree) {
return symbolFromVariable(expressionTree)
.filter(s -> s.isVariableSymbol() && s.owner().isTypeSymbol());
}
private static Optional symbolFromVariable(ExpressionTree variable) {
if (variable.is(IDENTIFIER)) {
return Optional.of(((IdentifierTree) variable).symbol());
}
if (variable.is(MEMBER_SELECT)) {
return Optional.of(((MemberSelectExpressionTree) variable).identifier().symbol());
}
return Optional.empty();
}
private static boolean thenStmtInitializeField(StatementTree statementTree, Symbol field) {
AssignmentVisitor visitor = new AssignmentVisitor(field);
statementTree.accept(visitor);
return visitor.assignmentToField;
}
private static class AssignmentVisitor extends BaseTreeVisitor {
private boolean assignmentToField;
private Symbol field;
AssignmentVisitor(Symbol field) {
this.field = field;
}
@Override
public void visitAssignmentExpression(AssignmentExpressionTree assignmentTree) {
ExpressionTree variable = assignmentTree.variable();
symbolFromVariable(variable)
.filter(s -> s == field)
.ifPresent(s -> assignmentToField = true);
}
}
private static class IfFieldEqNull {
private final IfStatementTree ifTree;
private final Symbol field;
private IfFieldEqNull(IfStatementTree ifTree, Symbol field) {
this.ifTree = ifTree;
this.field = field;
}
}
private static class CriticalSection {
SynchronizedStatementTree synchronizedTree;
int ifStackDepth;
public CriticalSection(SynchronizedStatementTree synchronizedTree, int ifStackDepth) {
this.synchronizedTree = synchronizedTree;
this.ifStackDepth = ifStackDepth;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy