/**
 * @name Constant condition
 * @description A condition that always evaluates to 'true' or always evaluates to 'false'
 *              should be removed, and if the condition is a loop condition, the condition
 *              is likely to cause an infinite loop.
 * @kind problem
 * @problem.severity warning
 * @precision very-high
 * @id cs/constant-condition
 * @tags maintainability
 *       readability
 *       external/cwe/cwe-835
 */
import csharp
import semmle.code.csharp.commons.Assertions
import semmle.code.csharp.commons.Constants

/** A constant condition. */
abstract class ConstantCondition extends Expr {
  /** Gets the alert message for this constant condition. */
  abstract string getMessage();

  /** Holds if this constant condition is white-listed. */
  predicate isWhiteListed() { none() }
}

/** A constant Boolean condition. */
class ConstantBooleanCondition extends ConstantCondition {
  boolean b;

  ConstantBooleanCondition() {
    isConstantCondition(this, b)
  }

  override string getMessage() {
    result = "Condition always evaluates to '" + b + "'."
  }

  override predicate isWhiteListed() {
    // E.g. `x ?? false`
    this.(BoolLiteral) = any(NullCoalescingExpr nce).getRightOperand()
  }
}

/** A constant condition in an `if` statement or a conditional expression. */
class ConstantIfCondition extends ConstantBooleanCondition {
  ConstantIfCondition() {
    this = any(IfStmt is).getCondition().getAChildExpr*() or
    this = any(ConditionalExpr ce).getCondition().getAChildExpr*()
  }

  override predicate isWhiteListed() {
    ConstantBooleanCondition.super.isWhiteListed()
    or
    // It is a common pattern to use a local constant/constant field to control
    // whether code parts must be executed or not
    this instanceof AssignableRead
  }
}

/** A constant loop condition. */
class ConstantLoopCondition extends ConstantBooleanCondition {
  ConstantLoopCondition() {
    this = any(LoopStmt ls).getCondition()
  }

  override predicate isWhiteListed() {
    // Clearly intentional infinite loops are allowed
    this.(BoolLiteral).getBoolValue() = true
  }
}

/** A constant nullness condition. */
class ConstantNullnessCondition extends ConstantCondition {
  boolean b;

  ConstantNullnessCondition() {
    forex(ControlFlow::Node cfn |
      cfn = this.getAControlFlowNode() |
      exists(ControlFlow::SuccessorTypes::NullnessSuccessor t |
        exists(cfn.getASuccessorByType(t)) |
        b = t.getValue()
      ) and
      strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
    )
  }

  override string getMessage() {
    if b = true then
      result = "Expression is always 'null'."
    else
      result = "Expression is never 'null'."
  }
}

/** A constant matching condition. */
class ConstantMatchingCondition extends ConstantCondition {
  boolean b;

  ConstantMatchingCondition() {
    forex(ControlFlow::Node cfn |
      cfn = this.getAControlFlowNode() |
      exists(ControlFlow::SuccessorTypes::MatchingSuccessor t |
        exists(cfn.getASuccessorByType(t)) |
        b = t.getValue()
      ) and
      strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
    )
  }

  override string getMessage() {
    if b = true then
      result = "Pattern always matches."
    else
      result = "Pattern never matches."
  }
}

from ConstantCondition c, string msg
where msg = c.getMessage()
  and not c.isWhiteListed()
  and not isExprInAssertion(c)
select c, msg
