All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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