org.sonar.java.se.checks.NoWayOutLoopCheck Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact 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.se.checks;
import org.sonar.check.Rule;
import org.sonar.java.cfg.CFG;
import org.sonar.java.cfg.CFGLoop;
import org.sonar.java.matcher.MethodMatcher;
import org.sonar.java.matcher.TypeCriteria;
import org.sonar.java.se.CheckerContext;
import org.sonar.java.se.ProgramState;
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.ForStatementTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import org.sonar.plugins.java.api.tree.WhileStatementTree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Rule(key = "S2189")
public class NoWayOutLoopCheck extends SECheck {
private static final MethodMatcher THREAD_RUN_MATCHER = MethodMatcher.create().typeDefinition(TypeCriteria.subtypeOf("java.lang.Thread")).name("run");
private enum UpdateType {
INCREMENT, DECREMENT, INDETERMINATE
}
Map loopStarts;
boolean threadRunMethod;
@Override
public void init(MethodTree tree, CFG cfg) {
loopStarts = CFGLoop.getCFGLoops(cfg);
threadRunMethod = THREAD_RUN_MATCHER.matches(tree);
}
@Override
public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) {
if (threadRunMethod) {
// It is OK to have an endless Thread run method
return context.getState();
}
final PreStatementVisitor visitor = new PreStatementVisitor(context);
syntaxNode.accept(visitor);
return visitor.programState;
}
private class PreStatementVisitor extends CheckerTreeNodeVisitor {
private final CheckerContext context;
protected PreStatementVisitor(CheckerContext context) {
super(context.getState());
this.context = context;
}
@Override
public void visitWhileStatement(WhileStatementTree tree) {
if (isHardCodedTrue(tree.condition())) {
CFGLoop loopBlocks = loopStarts.get(tree);
if (loopBlocks.hasNoWayOut()) {
context.reportIssue(tree, NoWayOutLoopCheck.this, "Add an end condition to this loop.");
}
}
}
@Override
public void visitForStatement(ForStatementTree tree) {
CFGLoop loopBlocks = loopStarts.get(tree);
if (tree.condition() == null) {
if (loopBlocks.hasNoWayOut()) {
context.reportIssue(tree, NoWayOutLoopCheck.this, "Add an end condition to this loop.");
}
} else if (isConditionUnreachable(tree)) {
context.reportIssue(tree, NoWayOutLoopCheck.this, "Correct this loop's end condition.");
}
}
private boolean isConditionUnreachable(ForStatementTree tree) {
UpdatesCollector collector = new UpdatesCollector();
tree.accept(collector);
ConditionType condition = new ConditionType(tree.condition(), collector);
return !condition.isMatched();
}
}
private static class Update {
private Symbol symbol = null;
private UpdateType type = null;
Update(Symbol symbol, UpdateType type) {
this.symbol = symbol;
this.type = type;
}
UpdateType type() {
return type;
}
boolean concerns(ExpressionTree operand) {
if (operand.is(Tree.Kind.IDENTIFIER)) {
return symbol.equals(((IdentifierTree) operand).symbol());
} else if (operand.is(Tree.Kind.POSTFIX_DECREMENT, Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.PREFIX_DECREMENT, Tree.Kind.PREFIX_INCREMENT)) {
UnaryExpressionTree unary = (UnaryExpressionTree) operand;
return concerns(unary.expression());
}
return false;
}
}
private static class ConditionType {
private final boolean matched;
public ConditionType(ExpressionTree condition, UpdatesCollector collector) {
if (condition.is(Tree.Kind.LESS_THAN, Tree.Kind.LESS_THAN_OR_EQUAL_TO)) {
matched = canBeMatched(((BinaryExpressionTree) condition).leftOperand(), ((BinaryExpressionTree) condition).rightOperand(), collector);
} else if (condition.is(Tree.Kind.GREATER_THAN, Tree.Kind.GREATER_THAN_OR_EQUAL_TO)) {
matched = canBeMatched(((BinaryExpressionTree) condition).rightOperand(), ((BinaryExpressionTree) condition).leftOperand(), collector);
} else {
matched = true;
}
}
protected boolean canBeMatched(ExpressionTree leftOperand, ExpressionTree rightOperand, UpdatesCollector collector) {
boolean matchFound = false;
for (Update update : collector) {
if (update.concerns(leftOperand)) {
if (!UpdateType.DECREMENT.equals(update.type())) {
return true;
}
matchFound = true;
}
if (update.concerns(rightOperand)) {
if (!UpdateType.INCREMENT.equals(update.type())) {
return true;
}
matchFound = true;
}
}
return !matchFound;
}
public boolean isMatched() {
return matched;
}
}
private static class UpdatesCollector extends BaseTreeVisitor implements Iterable {
private List updates = new ArrayList<>();
@Override
public void visitForStatement(ForStatementTree tree) {
// Updates in initializer are not of interest
scan(tree.condition());
scan(tree.update());
scan(tree.statement());
}
@Override
public void visitAssignmentExpression(AssignmentExpressionTree tree) {
ExpressionTree assign = tree.variable();
if (assign.is(Tree.Kind.IDENTIFIER)) {
UpdateType type;
if (tree.is(Tree.Kind.PLUS_ASSIGNMENT)) {
// Will not work if the target is negative
type = UpdateType.INCREMENT;
} else if (tree.is(Tree.Kind.MINUS_ASSIGNMENT)) {
// Will not work if the target is negative
type = UpdateType.DECREMENT;
} else {
// Other assignments are to complex to decide between increment or decrement
type = UpdateType.INDETERMINATE;
}
updates.add(new Update(((IdentifierTree) assign).symbol(), type));
}
super.visitAssignmentExpression(tree);
}
@Override
public void visitUnaryExpression(UnaryExpressionTree expression) {
ExpressionTree unary = expression.expression();
if (unary.is(Tree.Kind.IDENTIFIER)) {
Symbol symbol = ((IdentifierTree) unary).symbol();
if (expression.is(Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.PREFIX_INCREMENT)) {
updates.add(new Update(symbol, UpdateType.INCREMENT));
} else if (expression.is(Tree.Kind.POSTFIX_DECREMENT, Tree.Kind.PREFIX_DECREMENT)) {
updates.add(new Update(symbol, UpdateType.DECREMENT));
}
}
super.visitUnaryExpression(expression);
}
@Override
public Iterator iterator() {
return updates.iterator();
}
}
static boolean isHardCodedTrue(ExpressionTree condition) {
return condition.is(Tree.Kind.BOOLEAN_LITERAL) && Boolean.parseBoolean(((LiteralTree) condition).value());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy