/**
 * @name Unbounded loop
 * @description All loops should have a fixed upper bound; the counter should also be incremented along all paths within the loop.
 *              This check excludes loops that are meant to be nonterminating (like schedulers).
 * @kind problem
 * @id cpp/jpl-c/loop-bounds
 * @problem.severity warning
 * @tags correctness
 *       external/jpl
 */

import cpp

predicate validVarForBound(Loop loop, Variable var) {
  // The variable is read in the loop controlling expression
  var.getAnAccess().getParent*() = loop.getControllingExpr() and
  // The variable is not assigned in the loop body
  not inScope(loop, var.getAnAssignment().getEnclosingStmt()) and
  // The variable is not incremented/decremented in the loop body
  not inScope(loop, var.getAnAccess().getParent().(CrementOperation).getEnclosingStmt())
}

predicate upperBoundCheck(Loop loop, VariableAccess checked) {
  exists(RelationalOperation rop | loop.getControllingExpr().getAChild*() = rop |
    checked = rop.getLesserOperand() and
    // The RHS is something "valid", i.e. a constant or
    // a variable that isn't assigned in the loop body
    (
      exists(rop.getGreaterOperand().getValue()) or
      rop.getGreaterOperand().(VariableAccess).getTarget().isConst() or
      validVarForBound(loop, rop.getGreaterOperand().(VariableAccess).getTarget())
    ) and
    not rop.getGreaterOperand() instanceof CharLiteral
  )
}

predicate lowerBoundCheck(Loop loop, VariableAccess checked) {
  exists(RelationalOperation rop | loop.getControllingExpr().getAChild*() = rop |
    checked = rop.getGreaterOperand() and
    // The RHS is something "valid", i.e. a constant or
    // a variable that isn't assigned in the loop body
    (
      exists(rop.getLesserOperand().getValue()) or
      rop.getLesserOperand().(VariableAccess).getTarget().isConst() or
      validVarForBound(loop, rop.getLesserOperand().(VariableAccess).getTarget())
    ) and
    not rop.getLesserOperand() instanceof CharLiteral
  )
}

VariableAccess getAnIncrement(Variable var) {
  result.getTarget() = var and
  (
    result.getParent() instanceof IncrementOperation
    or
    exists(AssignAddExpr a | a.getLValue() = result and a.getRValue().getValue().toInt() > 0)
    or
    exists(AssignExpr a | a.getLValue() = result |
      a.getRValue() =
        any(AddExpr ae |
          ae.getAnOperand() = var.getAnAccess() and
          ae.getAnOperand().getValue().toInt() > 0
        )
    )
  )
}

VariableAccess getADecrement(Variable var) {
  result.getTarget() = var and
  (
    result.getParent() instanceof DecrementOperation
    or
    exists(AssignSubExpr a | a.getLValue() = result and a.getRValue().getValue().toInt() > 0)
    or
    exists(AssignExpr a | a.getLValue() = result |
      a.getRValue() =
        any(SubExpr ae |
          ae.getLeftOperand() = var.getAnAccess() and
          ae.getRightOperand().getValue().toInt() > 0
        )
    )
  )
}

predicate inScope(Loop l, Stmt s) { l.getAChild*() = s }

predicate reachesNoInc(VariableAccess source, ControlFlowNode target) {
  upperBoundCheck(_, source) and source.getASuccessor() = target
  or
  exists(ControlFlowNode mid |
    reachesNoInc(source, mid) and not mid = getAnIncrement(source.getTarget())
  |
    target = mid.getASuccessor() and
    inScope(source.getEnclosingStmt(), target.getEnclosingStmt())
  )
}

predicate reachesNoDec(VariableAccess source, ControlFlowNode target) {
  lowerBoundCheck(_, source) and source.getASuccessor() = target
  or
  exists(ControlFlowNode mid |
    reachesNoDec(source, mid) and not mid = getADecrement(source.getTarget())
  |
    target = mid.getASuccessor() and
    inScope(source.getEnclosingStmt(), target.getEnclosingStmt())
  )
}

predicate hasSafeBound(Loop l) {
  exists(VariableAccess bound | upperBoundCheck(l, bound) | not reachesNoInc(bound, bound))
  or
  exists(VariableAccess bound | lowerBoundCheck(l, bound) | not reachesNoDec(bound, bound))
  or
  exists(l.getControllingExpr().getValue())
}

predicate markedAsNonterminating(Loop l) {
  exists(Comment c | c.getContents().matches("%@non-terminating@%") | c.getCommentedElement() = l)
}

from Loop loop, string msg
where
  not hasSafeBound(loop) and
  not markedAsNonterminating(loop) and
  (
    not upperBoundCheck(loop, _) and
    not lowerBoundCheck(loop, _) and
    msg = "This loop does not have a fixed bound."
    or
    exists(VariableAccess bound |
      upperBoundCheck(loop, bound) and
      reachesNoInc(bound, bound) and
      msg =
        "The loop counter " + bound.getTarget().getName() +
          " is not always incremented in the loop body."
    )
    or
    exists(VariableAccess bound |
      lowerBoundCheck(loop, bound) and
      reachesNoDec(bound, bound) and
      msg =
        "The loop counter " + bound.getTarget().getName() +
          " is not always decremented in the loop body."
    )
  )
select loop, msg
