/**
 * Provides classes representing various classes of expression
 * and other instructions.
 */

private import CIL
private import dotnet

/**
 * An instruction that pushes a value onto the stack.
 */
class Expr extends DotNet::Expr, Instruction, @cil_expr {
  override int getPushCount() { result=1 }
  override Type getType() { result=Instruction.super.getType() }
  override Method getEnclosingCallable() { result=getImplementation().getMethod() }

  /**
   * The "parent" of a CIL expression is taken to be the instruction
   * that consumes the value pushed by this instruction.
   */
  override Expr getParent() { this=result.getAnOperand() }
}

/** An instruction that changes control flow. */
class Branch extends Instruction, @cil_jump {
  /** Gets the instruction that is jumped to. */
  Instruction getTarget() { cil_jump(this, result) }
  override string getExtra() { result=getTarget().getIndex() + ":" }
}

/** An instruction that unconditionally jumps to another instruction. */
class UnconditionalBranch extends Branch, @cil_unconditional_jump {
  override Instruction getASuccessorType(FlowType t) { t instanceof NormalFlow and result=getTarget() }
  override predicate canFlowNext() { none() }
}

/** An instruction that jumps to a target based on a condition. */
class ConditionalBranch extends Branch, @cil_conditional_jump {
  override Instruction getASuccessorType(FlowType t) {
    t instanceof TrueFlow and result = getTarget() or
    t instanceof FalseFlow and result = getImplementation().getInstruction(getIndex()+1)
  }
  override int getPushCount() { result=0 }
}

/** An expression with two operands. */
class BinaryExpr extends Expr, @cil_binary_expr {
  override int getPopCount() { result=2 }
}

/** An expression with one operand. */
class UnaryExpr extends Expr, @cil_unary_expr {
  override int getPopCount() { result=1 }
  /** Gets the operand of this unary expression. */
  Expr getOperand() { result=getOperand(0) }
}

/** A binary expression that compares two values. */
class ComparisonOperation extends BinaryExpr, @cil_comparison_operation {
  override BoolType getType() { exists(result) }
}

/** A binary arithmetic expression. */
class BinaryArithmeticExpr extends BinaryExpr, @cil_binary_arithmetic_operation {
  override Type getType() {
    exists(Type t0, Type t1 | t0=getOperand(0).getType().getUnderlyingType() and t1=getOperand(1).getType().getUnderlyingType() |
      t0=t1 and result=t0 or
      t0.getConversionIndex() < t1.getConversionIndex() and result=t1 or
      t0.getConversionIndex() > t1.getConversionIndex() and result=t0)
  }
}

/** A binary bitwise expression. */
class BinaryBitwiseOperation extends BinaryExpr, @cil_binary_bitwise_operation {
  // This is wrong but efficient - should depend on the types of the operands.
  override IntType getType() { exists(result) }
}

/** A unary bitwise expression. */
class UnaryBitwiseOperation extends UnaryExpr, @cil_unary_bitwise_operation {
  // This is wrong but efficient - should depend on the types of the operands.
  override IntType getType() { exists(result) }
}

/** A unary expression that converts a value from one primitive type to another. */
class Conversion extends UnaryExpr, @cil_conversion_operation {
  /** Gets the expression being converted. */
  Expr getExpr() { result=getOperand(0) }
}

/** A branch that leaves the scope of a `Handler`. */
class Leave extends UnconditionalBranch, @cil_leave_any {
}

/** An expression that pushes a literal value onto the stack. */
class Literal extends DotNet::Literal, Expr, @cil_literal
{
  /** Gets the pushed value. */
  override string getValue() { cil_value(this, result) }

  override string getExtra() { result = getValue() }
}

/** An integer literal. */
class IntLiteral extends Literal, @cil_ldc_i {
  override string getExtra() { none() }
  override IntType getType() { exists(result) }
}

/** An expression that pushes a `float`/`Single`. */
class FloatLiteral extends Literal, @cil_ldc_r {
}

/** An expression that pushes a `null` value onto the stack. */
class NullLiteral extends Literal, @cil_ldnull {
}

/** A branch with one operand. */
class UnaryBranch extends ConditionalBranch, @cil_unary_jump {
  override int getPopCount() { result=1 }
  override int getPushCount() { result=0 }
}

/** A branch with two operands. */
class BinaryBranch extends ConditionalBranch, @cil_binary_jump {
  override int getPopCount() { result=2 }
  override int getPushCount() { result=0 }
}

/** A call. */
class Call extends Expr, DotNet::Call, @cil_call_any {

  /** Gets the method that is called. */
  override Method getTarget() { cil_access(this, result) }

  override Method getARuntimeTarget() {
    result = getTarget().getAnOverrider*()
  }

  override string getExtra() { result=getTarget().getQualifiedName() }

  /**
   * Gets the return type of the call. Methods that do not return a value
   * return the `void` type, `System.Void`, although the value of `getPushCount` is
   * 0 in this case.
   */
  override Type getType() { result=getTarget().getReturnType() }

  // The number of items popped/pushed from the stack
  // depends on the target of the call.
  override int getPopCount() { result=getTarget().getCallPopCount() }
  override int getPushCount() { result=getTarget().getCallPushCount() }

  /**
   * Holds if this is a "tail call", meaning that control does not return to the
   * calling method.
   */
  predicate isTailCall() {
    getImplementation().getInstruction(getIndex()-1) instanceof Opcodes::Tail
  }

  /** Holds if this call is virtual and could go to an overriding method. */
  predicate isVirtual() { none() }

  override Expr getRawArgument(int i) { result=getOperand(getPopCount() - i - 1) }

  /** Gets the qualifier of this call, if any. */
  Expr getQualifier() { result=getRawArgument(0) and not getTarget().isStatic() }

  override Expr getArgument(int i) {
    if getTarget().isStatic()
    then result=getRawArgument(i)
    else (result=getRawArgument(i+1) and i>=0)
  }

  override Expr getArgumentForParameter(DotNet::Parameter param) {
    exists(int index |
      result = this.getRawArgument(index) and param = this.getTarget().getRawParameter(index)
    )
  }
}

/** A tail call. */
class TailCall extends Call {
  TailCall() { this.isTailCall() }
  override predicate canFlowNext() { none() }
}

/** A call to a static target. */
class StaticCall extends Call {
  StaticCall() { not this.isVirtual() }
}

/** A call to a virtual target. */
class VirtualCall extends Call {
  VirtualCall() { this.isVirtual() }
}

/** A read of an array element. */
class ReadArrayElement extends BinaryExpr, @cil_read_array {

  /** Gets the array being read. */
  Expr getArray() { result=getOperand(1) }

  /** Gets the index into the array. */
  Expr getArrayIndex() { result = getOperand(0) }
}

/** A write of an array element. */
class WriteArrayElement extends Instruction, @cil_write_array {
  override int getPushCount() { result=0 }
  override int getPopCount() { result=3 }
}

/** A `return` statement. */
class Return extends Instruction, @cil_ret {
  /** Gets the expression being returned, if any. */
  Expr getExpr() { result=getOperand(0) }
  override predicate canFlowNext() { none() }
}

/** A `throw` statement. */
class Throw extends Instruction, DotNet::Throw, @cil_throw_any {
  override Expr getExpr() { result=getOperand(0) }
  override predicate canFlowNext() { none() }
}

/** Stores a value at an address/location. */
class StoreIndirect extends Instruction, @cil_stind {
  override int getPopCount() { result=2 }

  /** Gets the location to store the value at. */
  Expr getAddress() { result=getOperand(1) }

  /** Gets the value to store. */
  Expr getExpr() { result=getOperand(0) }
}

/** Loads a value from an address/location. */
class LoadIndirect extends UnaryExpr, @cil_ldind {
}
