diff --git a/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll b/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll index 5867b8e5c59c..5e2d4bd14508 100644 --- a/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll +++ b/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll @@ -280,6 +280,32 @@ class IRGuardCondition extends Instruction { ne.controls(controlled, testIsTrue.booleanNot())) } + /** + * Holds if `branch` jumps directly to `succ` when this condition is `testIsTrue`. + * + * This predicate is intended to help with situations in which an inference can only be made + * based on an edge between a block with multiple successors and a block with multiple + * predecessors. For example, in the following situation, an inference can be made about the + * value of `x` at the end of the `if` statement, but there is no block which is controlled by + * the `if` statement when `x >= y`. + * ``` + * if (x < y) { + * x = y; + * } + * return x; + * ``` + */ + predicate hasBranchEdge(ConditionalBranchInstruction branch, IRBlock succ, boolean testIsTrue) { + branch.getCondition() = this and + ( + testIsTrue = true and + succ.getFirstInstruction() = branch.getTrueSuccessor() + or + testIsTrue = false and + succ.getFirstInstruction() = branch.getFalseSuccessor() + ) + } + /** Holds if (determined by this guard) `left < right + k` evaluates to `isLessThan` if this expression evaluates to `testIsTrue`. */ cached predicate comparesLt(Operand left, Operand right, int k, boolean isLessThan, boolean testIsTrue) { compares_lt(this, left, right, k, isLessThan, testIsTrue) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index d956421a268f..dcad960d8dfd 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -130,7 +130,7 @@ module InstructionSanity { query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { blockCount = count(instr.getBlock()) and blockCount != 1 - } + } } /** @@ -750,6 +750,15 @@ class BinaryInstruction extends Instruction { final Instruction getRightOperand() { result = getAnOperand().(RightOperand).getDefinitionInstruction() } + + /** + * Holds if this instruction's operands are `op1` and `op2`, in either order. + */ + final predicate hasOperands(Operand op1, Operand op2) { + op1 = getAnOperand().(LeftOperand) and op2 = getAnOperand().(RightOperand) + or + op1 = getAnOperand().(RightOperand) and op2 = getAnOperand().(LeftOperand) + } } class AddInstruction extends BinaryInstruction { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index edf8e141764e..929ba988c9b6 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -21,6 +21,10 @@ class Operand extends TOperand { result = "Operand" } + Location getLocation() { + result = getInstruction().getLocation() + } + /** * Gets the `Instruction` that consumes this operand. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll index cff349a07b44..c03ddf991626 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll @@ -83,6 +83,10 @@ class ValueNumber extends TValueNumber { instr order by instr.getBlock().getDisplayIndex(), instr.getDisplayIndexInBlock() ) } + + final Operand getAUse() { + this = valueNumber(result.getDefinitionInstruction()) + } } /** @@ -107,6 +111,7 @@ private class CongruentCopyInstruction extends CopyInstruction { def = this.getSourceValue() and ( def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + def.getResultMemoryAccess() instanceof PhiMemoryAccess or not def.hasMemoryResult() ) ) @@ -211,7 +216,7 @@ private predicate uniqueValueNumber(Instruction instr, FunctionIR funcIR) { /** * Gets the value number assigned to `instr`, if any. Returns at most one result. */ -ValueNumber valueNumber(Instruction instr) { +cached ValueNumber valueNumber(Instruction instr) { result = nonUniqueValueNumber(instr) or exists(FunctionIR funcIR | uniqueValueNumber(instr, funcIR) and @@ -219,6 +224,13 @@ ValueNumber valueNumber(Instruction instr) { ) } +/** + * Gets the value number assigned to `instr`, if any. Returns at most one result. + */ +ValueNumber valueNumberOfOperand(Operand op) { + result = valueNumber(op.getDefinitionInstruction()) +} + /** * Gets the value number assigned to `instr`, if any, unless that instruction is assigned a unique * value number. diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index d956421a268f..449d7544a04b 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -130,7 +130,7 @@ module InstructionSanity { query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { blockCount = count(instr.getBlock()) and blockCount != 1 - } + } } /** @@ -750,6 +750,12 @@ class BinaryInstruction extends Instruction { final Instruction getRightOperand() { result = getAnOperand().(RightOperand).getDefinitionInstruction() } + + final predicate hasOperands(Operand op1, Operand op2) { + op1 = getAnOperand().(LeftOperand) and op2 = getAnOperand().(RightOperand) + or + op1 = getAnOperand().(RightOperand) and op2 = getAnOperand().(LeftOperand) + } } class AddInstruction extends BinaryInstruction { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index edf8e141764e..929ba988c9b6 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -21,6 +21,10 @@ class Operand extends TOperand { result = "Operand" } + Location getLocation() { + result = getInstruction().getLocation() + } + /** * Gets the `Instruction` that consumes this operand. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll index cff349a07b44..d5c6752479eb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll @@ -107,6 +107,7 @@ private class CongruentCopyInstruction extends CopyInstruction { def = this.getSourceValue() and ( def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + def.getResultMemoryAccess() instanceof PhiMemoryAccess or not def.hasMemoryResult() ) ) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index d956421a268f..449d7544a04b 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -130,7 +130,7 @@ module InstructionSanity { query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { blockCount = count(instr.getBlock()) and blockCount != 1 - } + } } /** @@ -750,6 +750,12 @@ class BinaryInstruction extends Instruction { final Instruction getRightOperand() { result = getAnOperand().(RightOperand).getDefinitionInstruction() } + + final predicate hasOperands(Operand op1, Operand op2) { + op1 = getAnOperand().(LeftOperand) and op2 = getAnOperand().(RightOperand) + or + op1 = getAnOperand().(RightOperand) and op2 = getAnOperand().(LeftOperand) + } } class AddInstruction extends BinaryInstruction { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index edf8e141764e..929ba988c9b6 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -21,6 +21,10 @@ class Operand extends TOperand { result = "Operand" } + Location getLocation() { + result = getInstruction().getLocation() + } + /** * Gets the `Instruction` that consumes this operand. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll index cff349a07b44..d5c6752479eb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll @@ -107,6 +107,7 @@ private class CongruentCopyInstruction extends CopyInstruction { def = this.getSourceValue() and ( def.getResultMemoryAccess() instanceof IndirectMemoryAccess or + def.getResultMemoryAccess() instanceof PhiMemoryAccess or not def.hasMemoryResult() ) ) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/Bound.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/Bound.qll new file mode 100644 index 000000000000..54a825e2ffce --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/Bound.qll @@ -0,0 +1,80 @@ +import cpp +private import semmle.code.cpp.ir.IR +private import semmle.code.cpp.ir.ValueNumbering + +private newtype TBound = + TBoundZero() or + TBoundValueNumber(ValueNumber vn) { + exists(Instruction i | + vn.getAnInstruction() = i and + ( + i.getResultType() instanceof IntegralType or + i.getResultType() instanceof PointerType + ) and + not vn.getAnInstruction() instanceof ConstantInstruction + | + i instanceof PhiInstruction + or + i instanceof InitializeParameterInstruction + or + i instanceof CallInstruction + or + i instanceof VariableAddressInstruction + or + i instanceof FieldAddressInstruction + or + i.(LoadInstruction).getSourceAddress() instanceof VariableAddressInstruction + or + i.(LoadInstruction).getSourceAddress() instanceof FieldAddressInstruction + or + i.getAUse() instanceof ArgumentOperand + ) + } + +/** + * A bound that may be inferred for an expression plus/minus an integer delta. + */ +abstract class Bound extends TBound { + abstract string toString(); + + /** Gets an expression that equals this bound plus `delta`. */ + abstract Instruction getInstruction(int delta); + + /** Gets an expression that equals this bound. */ + Instruction getInstruction() { result = getInstruction(0) } + + abstract Location getLocation(); +} + +/** + * The bound that corresponds to the integer 0. This is used to represent all + * integer bounds as bounds are always accompanied by an added integer delta. + */ +class ZeroBound extends Bound, TBoundZero { + override string toString() { result = "0" } + + override Instruction getInstruction(int delta) { result.(ConstantValueInstruction).getValue().toInt() = delta } + + override Location getLocation() { + result instanceof UnknownDefaultLocation + } +} +/** + * A bound corresponding to the value of an `Instruction`. + */ +class ValueNumberBound extends Bound, TBoundValueNumber { + ValueNumber vn; + + ValueNumberBound() { + this = TBoundValueNumber(vn) + } + + /** Gets the SSA variable that equals this bound. */ + override Instruction getInstruction(int delta) { this = TBoundValueNumber(valueNumber(result)) and delta = 0} + + override string toString() { result = vn.getExampleInstruction().toString() } + + override Location getLocation() { + result = vn.getLocation() + } +} diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeAnalysis.qll new file mode 100644 index 000000000000..32b3d4ee4fcd --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeAnalysis.qll @@ -0,0 +1,605 @@ +/** + * Provides classes and predicates for range analysis. + * + * An inferred bound can either be a specific integer or a `ValueNumber` + * representing the abstract value of a set of `Instruction`s. + * + * If an inferred bound relies directly on a condition, then this condition is + * reported as the reason for the bound. + */ + +/* + * This library tackles range analysis as a flow problem. Consider e.g.: + * ``` + * len = arr.length; + * if (x < len) { ... y = x-1; ... y ... } + * ``` + * In this case we would like to infer `y <= arr.length - 2`, and this is + * accomplished by tracking the bound through a sequence of steps: + * ``` + * arr.length --> len = .. --> x < len --> x-1 --> y = .. --> y + * ``` + * + * In its simplest form the step relation `I1 --> I2` relates two `Instruction`s + * such that `I1 <= B` implies `I2 <= B` for any `B` (with a second separate + * step relation handling lower bounds). Examples of such steps include + * assignments `I2 = I1` and conditions `x <= I1` where `I2` is a use of `x` + * guarded by the condition. + * + * In order to handle subtractions and additions with constants, and strict + * comparisons, the step relation is augmented with an integer delta. With this + * generalization `I1 --(delta)--> I2` relates two `Instruction`s and an integer + * such that `I1 <= B` implies `I2 <= B + delta` for any `B`. This corresponds + * to the predicate `boundFlowStep`. + * + * The complete range analysis is then implemented as the transitive closure of + * the step relation summing the deltas along the way. If `I1` transitively + * steps to `I2`, `delta` is the sum of deltas along the path, and `B` is an + * interesting bound equal to the value of `I1` then `I2 <= B + delta`. This + * corresponds to the predicate `boundedInstruction`. + * + * Bounds come in two forms: either they are relative to zero (and thus provide + * a constant bound), or they are relative to some program value. This value is + * represented by the `ValueNumber` class, each instance of which represents a + * set of `Instructions` that must have the same value. + * + * Phi nodes need a little bit of extra handling. Consider `x0 = phi(x1, x2)`. + * There are essentially two cases: + * - If `x1 <= B + d1` and `x2 <= B + d2` then `x0 <= B + max(d1,d2)`. + * - If `x1 <= B + d1` and `x2 <= x0 + d2` with `d2 <= 0` then `x0 <= B + d1`. + * The first case is for whenever a bound can be proven without taking looping + * into account. The second case is relevant when `x2` comes from a back-edge + * where we can prove that the variable has been non-increasing through the + * loop-iteration as this means that any upper bound that holds prior to the + * loop also holds for the variable during the loop. + * This generalizes to a phi node with `n` inputs, so if + * `x0 = phi(x1, ..., xn)` and `xi <= B + delta` for one of the inputs, then we + * also have `x0 <= B + delta` if we can prove either: + * - `xj <= B + d` with `d <= delta` or + * - `xj <= x0 + d` with `d <= 0` + * for each input `xj`. + * + * As all inferred bounds can be related directly to a path in the source code + * the only source of non-termination is if successive redundant (and thereby + * increasingly worse) bounds are calculated along a loop in the source code. + * We prevent this by weakening the bound to a small finite set of bounds when + * a path follows a second back-edge (we postpone weakening till the second + * back-edge as a precise bound might require traversing a loop once). +*/ + +import cpp + +private import semmle.code.cpp.ir.IR +private import semmle.code.cpp.controlflow.IRGuards +private import semmle.code.cpp.ir.ValueNumbering +private import RangeUtils +private import SignAnalysis +import Bound + +cached private module RangeAnalysisCache { + + cached module RangeAnalysisPublic { + /** + * Holds if `b + delta` is a valid bound for `i`. + * - `upper = true` : `i <= b + delta` + * - `upper = false` : `i >= b + delta` + * + * The reason for the bound is given by `reason` and may be either a condition + * or `NoReason` if the bound was proven directly without the use of a bounding + * condition. + */ + cached predicate boundedInstruction(Instruction i, Bound b, int delta, boolean upper, Reason reason) { + boundedInstruction(i, b, delta, upper, _, _, reason) + } + + /** + * Holds if `b + delta` is a valid bound for `op`. + * - `upper = true` : `op <= b + delta` + * - `upper = false` : `op >= b + delta` + * + * The reason for the bound is given by `reason` and may be either a condition + * or `NoReason` if the bound was proven directly without the use of a bounding + * condition. + */ + cached predicate boundedOperand(Operand op, Bound b, int delta, boolean upper, Reason reason) { + boundedNonPhiOperand(op, b, delta, upper, _, _, reason) + or + boundedPhiOperand(op, b, delta, upper, _, _, reason) + } + } + + /** + * Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`. + */ + cached predicate possibleReason(IRGuardCondition guard) { + guard = boundFlowCond(_, _, _, _, _) + or + guard = eqFlowCond(_, _, _, _, _) + } + +} +private import RangeAnalysisCache +import RangeAnalysisPublic + +/** + * Gets a condition that tests whether `vn` equals `bound + delta`. + * + * If the condition evaluates to `testIsTrue`: + * - `isEq = true` : `vn == bound + delta` + * - `isEq = false` : `vn != bound + delta` + */ +private IRGuardCondition eqFlowCond(ValueNumber vn, Operand bound, int delta, + boolean isEq, boolean testIsTrue) +{ + result.comparesEq(vn.getAUse(), bound, delta, isEq, testIsTrue) +} + +/** + * Holds if `op1 + delta` is a valid bound for `op2`. + * - `upper = true` : `op2 <= op1 + delta` + * - `upper = false` : `op2 >= op1 + delta` + */ +private predicate boundFlowStepSsa( + NonPhiOperand op2, Operand op1, int delta, boolean upper, Reason reason +) { + exists(IRGuardCondition guard, boolean testIsTrue | + guard = boundFlowCond(valueNumberOfOperand(op2), op1, delta, upper, testIsTrue) and + guard.controls(op2.getInstruction().getBlock(), testIsTrue) and + reason = TCondReason(guard) + ) +} + +/** + * Gets a condition that tests whether `vn` is bounded by `bound + delta`. + * + * If the condition evaluates to `testIsTrue`: + * - `upper = true` : `vn <= bound + delta` + * - `upper = false` : `vn >= bound + delta` + */ +private IRGuardCondition boundFlowCond(ValueNumber vn, NonPhiOperand bound, int delta, boolean upper, + boolean testIsTrue) +{ + exists(int d | + result.comparesLt(vn.getAUse(), bound, d, upper, testIsTrue) and + // `comparesLt` provides bounds of the form `x < y + k` or `x >= y + k`, but we need + // `x <= y + k` so we strengthen here. `testIsTrue` has the same semantics in `comparesLt` as + // it does here, so we don't need to account for it. + if upper = true + then delta = d-1 + else delta = d + ) + or + result = eqFlowCond(vn, bound, delta, true, testIsTrue) and + (upper = true or upper = false) +} + +private newtype TReason = + TNoReason() or + TCondReason(IRGuardCondition guard) { possibleReason(guard) } + +/** + * A reason for an inferred bound. This can either be `CondReason` if the bound + * is due to a specific condition, or `NoReason` if the bound is inferred + * without going through a bounding condition. + */ +abstract class Reason extends TReason { + abstract string toString(); +} +class NoReason extends Reason, TNoReason { + override string toString() { result = "NoReason" } +} +class CondReason extends Reason, TCondReason { + IRGuardCondition getCond() { this = TCondReason(result) } + override string toString() { result = getCond().toString() } +} + +/** + * Holds if a cast from `fromtyp` to `totyp` can be ignored for the purpose of + * range analysis. + */ +pragma[inline] +private predicate safeCast(IntegralType fromtyp, IntegralType totyp) { + fromtyp.getSize() < totyp.getSize() and + ( + fromtyp.isUnsigned() + or + totyp.isSigned() + ) or + fromtyp.getSize() <= totyp.getSize() and + ( + fromtyp.isSigned() and + totyp.isSigned() + or + fromtyp.isUnsigned() and + totyp.isUnsigned() + ) +} + +private class SafeCastInstruction extends ConvertInstruction { + SafeCastInstruction() { + safeCast(getResultType(), getOperand().getResultType()) + or + getResultType() instanceof PointerType and + getOperand().getResultType() instanceof PointerType + } +} + +/** + * Holds if `typ` is a small integral type with the given lower and upper bounds. + */ +private predicate typeBound(IntegralType typ, int lowerbound, int upperbound) { + typ.isSigned() and typ.getSize() = 1 and lowerbound = -128 and upperbound = 127 + or + typ.isUnsigned() and typ.getSize() = 1 and lowerbound = 0 and upperbound = 255 + or + typ.isSigned() and typ.getSize() = 2 and lowerbound = -32768 and upperbound = 32767 + or + typ.isUnsigned() and typ.getSize() = 2 and lowerbound = 0 and upperbound = 65535 +} + +/** + * A cast to a small integral type that may overflow or underflow. + */ +private class NarrowingCastInstruction extends ConvertInstruction { + NarrowingCastInstruction() { + not this instanceof SafeCastInstruction and + typeBound(getResultType(), _, _) + } + /** Gets the lower bound of the resulting type. */ + int getLowerBound() { typeBound(getResultType(), result, _) } + /** Gets the upper bound of the resulting type. */ + int getUpperBound() { typeBound(getResultType(), _, result) } +} + +/** + * Holds if `op + delta` is a valid bound for `i`. + * - `upper = true` : `i <= op + delta` + * - `upper = false` : `i >= op + delta` + */ +private predicate boundFlowStep(Instruction i, NonPhiOperand op, int delta, boolean upper) { + valueFlowStep(i, op, delta) and + (upper = true or upper = false) + or + i.(SafeCastInstruction).getAnOperand() = op and + delta = 0 and + (upper = true or upper = false) + or + exists(Operand x | + i.(AddInstruction).getAnOperand() = op and + i.(AddInstruction).getAnOperand() = x and + op != x + | + not exists(getValue(getConstantValue(op.getInstruction()))) and + not exists(getValue(getConstantValue(x.getInstruction()))) and + if(strictlyPositive(x)) + then ( + upper = false and delta = 1 + ) else + if positive(x) + then ( + upper = false and delta = 0 + ) else + if strictlyNegative(x) + then ( + upper = true and delta = -1 + ) else if negative(x) then (upper = true and delta = 0) else none() + ) + or + exists(Operand x | + exists(SubInstruction sub | + i = sub and + sub.getAnOperand().(LeftOperand) = op and + sub.getAnOperand().(RightOperand) = x + ) + | + // `x` with constant value is covered by valueFlowStep + not exists(getValue(getConstantValue(x.getInstruction()))) and + if strictlyPositive(x) + then ( + upper = true and delta = -1 + ) else + if positive(x) + then ( + upper = true and delta = 0 + ) else + if strictlyNegative(x) + then ( + upper = false and delta = 1 + ) else if negative(x) then (upper = false and delta = 0) else none() + ) + or + i.(RemInstruction).getAnOperand().(RightOperand) = op and positive(op) and delta = -1 and upper = true + or + i.(RemInstruction).getAnOperand().(LeftOperand) = op and positive(op) and delta = 0 and upper = true + or + i.(BitAndInstruction).getAnOperand() = op and positive(op) and delta = 0 and upper = true + or + i.(BitOrInstruction).getAnOperand() = op and positiveInstruction(i) and delta = 0 and upper = false + // TODO: min, max, rand +} + +private predicate boundFlowStepMul(Instruction i1, Operand op, int factor) { + exists(Instruction c, int k | k = getValue(getConstantValue(c)) and k > 0 | + i1.(MulInstruction).hasOperands(op, c.getAUse()) and factor = k + or + exists(ShiftLeftInstruction i | + i = i1 and i.getAnOperand().(LeftOperand) = op and i.getAnOperand().(RightOperand) = c.getAUse() and factor = 2.pow(k) + ) + ) +} + +private predicate boundFlowStepDiv(Instruction i1, Operand op, int factor) { + exists(Instruction c, int k | k = getValue(getConstantValue(c)) and k > 0 | + exists(DivInstruction i | + i = i1 and i.getAnOperand().(LeftOperand) = op and i.getRightOperand() = c and factor = k + ) + or + exists(ShiftRightInstruction i | + i = i1 and i.getAnOperand().(LeftOperand) = op and i.getRightOperand() = c and factor = 2.pow(k) + ) + ) +} + +/** + * Holds if `b` is a valid bound for `op` + */ +pragma[noinline] +private predicate boundedNonPhiOperand(NonPhiOperand op, Bound b, int delta, boolean upper, + boolean fromBackEdge, int origdelta, Reason reason +) { + exists(NonPhiOperand op2, int d1, int d2 | + boundFlowStepSsa(op, op2, d1, upper, reason) and + boundedNonPhiOperand(op2, b, d2, upper, fromBackEdge, origdelta, _) and + delta = d1 + d2 + ) + or + boundedInstruction(op.getDefinitionInstruction(), b, delta, upper, fromBackEdge, origdelta, reason) + or + exists(int d, Reason r1, Reason r2 | + boundedNonPhiOperand(op, b, d, upper, fromBackEdge, origdelta, r2) + | + unequalOperand(op, b, d, r1) and + ( + upper = true and delta = d - 1 + or upper = false and delta = d + 1 + ) and + ( + reason = r1 + or + reason = r2 and not r2 instanceof NoReason + ) + ) +} + +/** + * Holds if `op1 + delta` is a valid bound for `op2`. + * - `upper = true` : `op2 <= op1 + delta` + * - `upper = false` : `op2 >= op1 + delta` + */ +private predicate boundFlowStepPhi( + PhiOperand op2, Operand op1, int delta, boolean upper, Reason reason +) { + op2.getDefinitionInstruction().getAnOperand().(CopySourceOperand) = op1 and + (upper = true or upper = false) and + reason = TNoReason() and + delta = 0 + or + exists(IRGuardCondition guard, boolean testIsTrue | + guard = boundFlowCond(valueNumberOfOperand(op2), op1, delta, upper, testIsTrue) and + ( + guard.hasBranchEdge(op2.getPredecessorBlock().getLastInstruction(), op2.getInstruction().getBlock(), testIsTrue) + or + guard.controls(op2.getPredecessorBlock(), testIsTrue) + ) and + reason = TCondReason(guard) + ) +} + + +private predicate boundedPhiOperand( + PhiOperand op, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason +) { + exists(NonPhiOperand op2, int d1, int d2, Reason r1, Reason r2 | + boundFlowStepPhi(op, op2, d1, upper, r1) and + boundedNonPhiOperand(op2, b, d2, upper, fromBackEdge, origdelta, r2) and + delta = d1 + d2 and + (if r1 instanceof NoReason then reason = r2 else reason = r1) + ) + or + boundedInstruction(op.getDefinitionInstruction(), b, delta, upper, fromBackEdge, origdelta, reason) + or + exists(int d, Reason r1, Reason r2 | + boundedInstruction(op.getDefinitionInstruction(), b, d, upper, fromBackEdge, origdelta, r2) + | + unequalOperand(op, b, d, r1) and + ( + upper = true and delta = d - 1 + or upper = false and delta = d + 1 + ) and + ( + reason = r1 + or + reason = r2 and not r2 instanceof NoReason + ) + ) +} + +/** Holds if `op2 != op1 + delta` at `pos`. */ +private predicate unequalFlowStep( + Operand op2, Operand op1, int delta, Reason reason +) { + exists(IRGuardCondition guard, boolean testIsTrue | + guard = eqFlowCond(valueNumberOfOperand(op2), op1, delta, false, testIsTrue) and + guard.controls(op2.getInstruction().getBlock(), testIsTrue) and + reason = TCondReason(guard) + ) +} + +/** + * Holds if `op != b + delta` at `pos`. + */ +private predicate unequalOperand(Operand op, Bound b, int delta, Reason reason) { + exists(Operand op2, int d1, int d2 | + unequalFlowStep(op, op2, d1, reason) and + boundedNonPhiOperand(op2, b, d2, true, _, _, _) and + boundedNonPhiOperand(op2, b, d2, false, _, _, _) and + delta = d1 + d2 + ) +} + +private predicate boundedPhiCandValidForEdge( + PhiInstruction phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason, PhiOperand op +) { + boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and + ( + exists(int d | boundedPhiInp1(phi, op, b, d, upper) | upper = true and d <= delta) + or + exists(int d | boundedPhiInp1(phi, op, b, d, upper) | upper = false and d >= delta) + or + selfBoundedPhiInp(phi, op, upper) + ) +} + +/** Weakens a delta to lie in the range `[-1..1]`. */ +bindingset[delta, upper] +private int weakenDelta(boolean upper, int delta) { + delta in [-1 .. 1] and result = delta + or + upper = true and result = -1 and delta < -1 + or + upper = false and result = 1 and delta > 1 +} + +private predicate boundedPhiInp( + PhiInstruction phi, PhiOperand op, Bound b, int delta, boolean upper, boolean fromBackEdge, + int origdelta, Reason reason +) { + phi.getAnOperand() = op and + exists(int d, boolean fromBackEdge0 | + boundedPhiOperand(op, b, d, upper, fromBackEdge0, origdelta, reason) + or + b.(ValueNumberBound).getInstruction() = op.getDefinitionInstruction() and + d = 0 and + (upper = true or upper = false) and + fromBackEdge0 = false and + origdelta = 0 and + reason = TNoReason() + | + if backEdge(phi, op) + then + fromBackEdge = true and + ( + fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta + or + fromBackEdge0 = false and delta = d + ) + else ( + delta = d and fromBackEdge = fromBackEdge0 + ) + ) +} + +pragma[noinline] +private predicate boundedPhiInp1( + PhiInstruction phi, PhiOperand op, Bound b, int delta, boolean upper +) { + boundedPhiInp(phi, op, b, delta, upper, _, _, _) +} + +private predicate selfBoundedPhiInp(PhiInstruction phi, PhiOperand op, boolean upper) { + exists(int d, ValueNumberBound phibound | + phibound.getInstruction() = phi and + boundedPhiInp(phi, op, phibound, d, upper, _, _, _) and + ( + upper = true and d <= 0 + or + upper = false and d >= 0 + ) + ) +} + +pragma[noinline] +private predicate boundedPhiCand( + PhiInstruction phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, + Reason reason +) { + exists(PhiOperand op | + boundedPhiInp(phi, op, b, delta, upper, fromBackEdge, origdelta, reason) + ) +} + +/** + * Holds if the value being cast has an upper (for `upper = true`) or lower + * (for `upper = false`) bound within the bounds of the resulting type. + * For `upper = true` this means that the cast will not overflow and for + * `upper = false` this means that the cast will not underflow. + */ +private predicate safeNarrowingCast(NarrowingCastInstruction cast, boolean upper) { + exists(int bound | boundedNonPhiOperand(cast.getAnOperand(), any(ZeroBound zb), bound, upper, _, _, _) | + upper = true and bound <= cast.getUpperBound() + or + upper = false and bound >= cast.getLowerBound() + ) +} + +pragma[noinline] +private predicate boundedCastExpr( + NarrowingCastInstruction cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason +) { + boundedNonPhiOperand(cast.getAnOperand(), b, delta, upper, fromBackEdge, origdelta, reason) +} +/** + * Holds if `b + delta` is a valid bound for `i`. + * - `upper = true` : `i <= b + delta` + * - `upper = false` : `i >= b + delta` + */ +private predicate boundedInstruction( + Instruction i, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason +) { + isReducibleCFG(i.getFunction()) and + ( + i instanceof PhiInstruction and + forex(PhiOperand op | op = i.getAnOperand() | + boundedPhiCandValidForEdge(i, b, delta, upper, fromBackEdge, origdelta, reason, op) + ) + or + i = b.getInstruction(delta) and + (upper = true or upper = false) and + fromBackEdge = false and + origdelta = delta and + reason = TNoReason() + or + exists(Operand mid, int d1, int d2 | + boundFlowStep(i, mid, d1, upper) and + boundedNonPhiOperand(mid, b, d2, upper, fromBackEdge, origdelta, reason) and + delta = d1 + d2 and + not exists(getValue(getConstantValue(i))) + ) + or + exists(Operand mid, int factor, int d | + boundFlowStepMul(i, mid, factor) and + boundedNonPhiOperand(mid, b, d, upper, fromBackEdge, origdelta, reason) and + b instanceof ZeroBound and + delta = d*factor and + not exists(getValue(getConstantValue(i))) + ) + or + exists(Operand mid, int factor, int d | + boundFlowStepDiv(i, mid, factor) and + boundedNonPhiOperand(mid, b, d, upper, fromBackEdge, origdelta, reason) and + d >= 0 and + b instanceof ZeroBound and + delta = d / factor and + not exists(getValue(getConstantValue(i))) + ) + or + exists(NarrowingCastInstruction cast | + cast = i and + safeNarrowingCast(cast, upper.booleanNot()) and + boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason) + ) + ) +} diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeUtils.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeUtils.qll new file mode 100644 index 000000000000..b02914033a74 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeUtils.qll @@ -0,0 +1,86 @@ +import cpp + +private import semmle.code.cpp.ir.IR +// TODO: move this dependency +import semmle.code.cpp.ir.internal.IntegerConstant + +// TODO: move this out of test code +language[monotonicAggregates] +IntValue getConstantValue(Instruction instr) { + result = instr.(IntegerConstantInstruction).getValue().toInt() or + exists(BinaryInstruction binInstr, IntValue left, IntValue right | + binInstr = instr and + left = getConstantValue(binInstr.getLeftOperand()) and + right = getConstantValue(binInstr.getRightOperand()) and + ( + binInstr instanceof AddInstruction and result = add(left, right) or + binInstr instanceof SubInstruction and result = sub(left, right) or + binInstr instanceof MulInstruction and result = mul(left, right) or + binInstr instanceof DivInstruction and result = div(left, right) + ) + ) or + result = getConstantValue(instr.(CopyInstruction).getSourceValue()) or + exists(PhiInstruction phi | + phi = instr and + result = max(PhiOperand operand | operand = phi.getAnOperand() | getConstantValue(operand.getDefinitionInstruction())) and + result = min(PhiOperand operand | operand = phi.getAnOperand() | getConstantValue(operand.getDefinitionInstruction())) + ) +} + +predicate valueFlowStep(Instruction i, Operand op, int delta) { + i.getAnOperand().(CopySourceOperand) = op and delta = 0 + or + exists(Operand x | + i.(AddInstruction).getAnOperand() = op and + i.(AddInstruction).getAnOperand() = x and + op != x + | + delta = getValue(getConstantValue(x.getDefinitionInstruction())) + ) + or + exists(Operand x | + i.(SubInstruction).getAnOperand().(LeftOperand) = op and + i.(SubInstruction).getAnOperand().(RightOperand) = x + | + delta = -getValue(getConstantValue(x.getDefinitionInstruction())) + ) + or + exists(Operand x | + i.(PointerAddInstruction).getAnOperand() = op and + i.(PointerAddInstruction).getAnOperand() = x and + op != x + | + delta = i.(PointerAddInstruction).getElementSize() * + getValue(getConstantValue(x.getDefinitionInstruction())) + ) + or + exists(Operand x | + i.(PointerSubInstruction).getAnOperand().(LeftOperand) = op and + i.(PointerSubInstruction).getAnOperand().(RightOperand) = x + | + delta = i.(PointerSubInstruction).getElementSize() * + -getValue(getConstantValue(x.getDefinitionInstruction())) + ) +} + +predicate isReducibleCFG(Function f) { + not exists(LabelStmt l, GotoStmt goto | + goto.getTarget() = l and + l.getLocation().isBefore(goto.getLocation()) and + l.getEnclosingFunction() = f + ) and + not exists(LabelStmt ls, Loop l | + ls.getParent*() = l and + l.getEnclosingFunction() = f + ) and + not exists(SwitchCase cs | + cs.getSwitchStmt().getStmt() != cs.getParentStmt() and + cs.getEnclosingFunction() = f + ) +} + +predicate backEdge(PhiInstruction phi, PhiOperand op) { + phi.getAnOperand() = op and + phi.getBlock().dominates(op.getPredecessorBlock()) + // TODO: identify backedges during IR construction +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/inline_assembly.c b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/inline_assembly.c similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/inline_assembly.c rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/inline_assembly.c diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/lowerBound.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/lowerBound.expected rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/lowerBound.ql b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.ql similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/lowerBound.ql rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.ql diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/minmax.c b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/minmax.c similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/minmax.c rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/minmax.c diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryLower.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryLower.expected rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryLower.ql b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.ql similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryLower.ql rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.ql diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryUpper.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryUpper.expected rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryUpper.ql b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.ql similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/ternaryUpper.ql rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.ql diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.c b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.c rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp new file mode 100644 index 000000000000..9a930772b8d7 --- /dev/null +++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp @@ -0,0 +1,70 @@ +template +class vector { +public: + T& operator[](int); + const T& operator[](int) const; +}; + +int test1(vector vec, int b) { + int x = -1; + if (b) { + x = vec[3]; + } + return x; +} + +// Regression test for ODASA-6013. +int test2(int x) { + int x0 = static_cast(x); + return x0; +} + +// Tests for conversion to bool +bool test3(bool b, int x, int y) { + // The purpose the assignments to `x` below is to generate a lot of + // potential upper and lower bounds for `x`, so that the logic in + // boolConversionLowerBound and boolConversionUpperBound gets exercized. + if (y == 0) { + x = 0; + } + if (y == -1) { + x = -1; + } + if (y == 1) { + x = 1; + } + if (y == -128) { + x = -128; + } + if (y == 128) { + x = 128; + } + if (y == -1024) { + x = -1024; + } + if (y == 1024) { + x = 1024; + } + + int t = 0; + + if (x == 0) { + bool xb = (bool)x; // (bool)x == false + t += (int)xb; + } + + if (x > 0) { + bool xb = (bool)x; // (bool)x == true + t += (int)xb; + } + + if (x < 0) { + bool xb = (bool)x; // (bool)x == true + t += (int)xb; + } + + bool xb = (bool)x; // Value of (bool)x is unknown. + t += (int)xb; + + return b || (bool)t; +} diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/upperBound.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/upperBound.expected rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/upperBound.ql b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.ql similarity index 100% rename from cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/upperBound.ql rename to cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.ql diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected new file mode 100644 index 000000000000..d48b37bbaf19 --- /dev/null +++ b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected @@ -0,0 +1,56 @@ +| test.cpp:10:10:10:10 | Store: x | test.cpp:6:15:6:15 | InitializeParameter: x | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:10:10:10:10 | Store: x | test.cpp:6:22:6:22 | InitializeParameter: y | 0 | false | CompareLT: ... < ... | test.cpp:7:7:7:11 | test.cpp:7:7:7:11 | +| test.cpp:10:10:10:10 | Store: x | test.cpp:6:22:6:22 | InitializeParameter: y | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:20:10:20:10 | Store: x | test.cpp:14:15:14:15 | InitializeParameter: x | -2 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:20:10:20:10 | Store: x | test.cpp:14:22:14:22 | InitializeParameter: y | -2 | false | CompareLT: ... < ... | test.cpp:15:7:15:11 | test.cpp:15:7:15:11 | +| test.cpp:27:10:27:10 | Load: i | file://:0:0:0:0 | 0 | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:27:10:27:10 | Load: i | test.cpp:24:15:24:15 | InitializeParameter: x | -1 | true | CompareLT: ... < ... | test.cpp:26:14:26:18 | test.cpp:26:14:26:18 | +| test.cpp:30:10:30:10 | Load: i | file://:0:0:0:0 | 0 | 1 | false | CompareGT: ... > ... | test.cpp:29:14:29:18 | test.cpp:29:14:29:18 | +| test.cpp:30:10:30:10 | Load: i | test.cpp:24:15:24:15 | InitializeParameter: x | 0 | true | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:30:10:30:10 | Load: i | test.cpp:26:14:26:14 | Phi: i | 0 | true | CompareLT: ... < ... | test.cpp:26:14:26:18 | test.cpp:26:14:26:18 | +| test.cpp:33:10:33:10 | Load: i | file://:0:0:0:0 | 0 | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:33:10:33:10 | Load: i | test.cpp:24:15:24:15 | InitializeParameter: x | 1 | true | CompareLT: ... < ... | test.cpp:32:14:32:22 | test.cpp:32:14:32:22 | +| test.cpp:33:10:33:10 | Load: i | test.cpp:26:14:26:14 | Phi: i | 1 | true | CompareLT: ... < ... | test.cpp:32:14:32:22 | test.cpp:32:14:32:22 | +| test.cpp:33:10:33:10 | Load: i | test.cpp:29:14:29:14 | Phi: i | 0 | false | CompareGT: ... > ... | test.cpp:29:14:29:18 | test.cpp:29:14:29:18 | +| test.cpp:40:10:40:14 | Load: begin | test.cpp:38:16:38:20 | InitializeParameter: begin | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:40:10:40:14 | Load: begin | test.cpp:38:28:38:30 | InitializeParameter: end | -1 | true | CompareLT: ... < ... | test.cpp:39:10:39:20 | test.cpp:39:10:39:20 | +| test.cpp:49:12:49:12 | Load: x | test.cpp:46:22:46:22 | InitializeParameter: y | -1 | true | CompareLT: ... < ... | test.cpp:48:9:48:13 | test.cpp:48:9:48:13 | +| test.cpp:49:12:49:12 | Load: x | test.cpp:46:29:46:29 | InitializeParameter: z | -2 | true | CompareLT: ... < ... | test.cpp:48:9:48:13 | test.cpp:48:9:48:13 | +| test.cpp:54:12:54:12 | Load: x | test.cpp:46:22:46:22 | InitializeParameter: y | -1 | true | CompareLT: ... < ... | test.cpp:52:7:52:11 | test.cpp:52:7:52:11 | +| test.cpp:62:10:62:13 | Load: iter | test.cpp:60:17:60:17 | InitializeParameter: p | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:62:10:62:13 | Load: iter | test.cpp:60:17:60:17 | InitializeParameter: p | 3 | true | CompareLT: ... < ... | test.cpp:61:32:61:51 | test.cpp:61:32:61:51 | +| test.cpp:62:10:62:13 | Load: iter | test.cpp:61:39:61:51 | Convert: (char *)... | -1 | true | CompareLT: ... < ... | test.cpp:61:32:61:51 | test.cpp:61:32:61:51 | +| test.cpp:67:10:67:13 | Load: iter | test.cpp:60:17:60:17 | InitializeParameter: p | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:67:10:67:13 | Load: iter | test.cpp:60:17:60:17 | InitializeParameter: p | 3 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | +| test.cpp:67:10:67:13 | Load: iter | test.cpp:61:32:61:35 | Phi: iter | -1 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | +| test.cpp:67:10:67:13 | Load: iter | test.cpp:61:39:61:51 | Convert: (char *)... | -1 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | +| test.cpp:77:12:77:12 | Load: i | file://:0:0:0:0 | 0 | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:77:12:77:12 | Load: i | test.cpp:72:15:72:15 | InitializeParameter: x | -1 | true | CompareLT: ... < ... | test.cpp:76:20:76:24 | test.cpp:76:20:76:24 | +| test.cpp:77:12:77:12 | Load: i | test.cpp:72:22:72:22 | InitializeParameter: y | -1 | true | CompareLT: ... < ... | test.cpp:76:20:76:24 | test.cpp:76:20:76:24 | +| test.cpp:85:10:85:10 | Load: x | file://:0:0:0:0 | 0 | 2 | false | CompareGT: ... > ... | test.cpp:84:7:84:11 | test.cpp:84:7:84:11 | +| test.cpp:87:10:87:10 | Load: x | file://:0:0:0:0 | 0 | 1 | true | CompareGT: ... > ... | test.cpp:84:7:84:11 | test.cpp:84:7:84:11 | +| test.cpp:90:10:90:10 | Load: x | file://:0:0:0:0 | 0 | 1 | false | CompareGE: ... >= ... | test.cpp:89:7:89:12 | test.cpp:89:7:89:12 | +| test.cpp:92:10:92:10 | Load: x | file://:0:0:0:0 | 0 | 0 | true | CompareGE: ... >= ... | test.cpp:89:7:89:12 | test.cpp:89:7:89:12 | +| test.cpp:95:10:95:10 | Load: x | file://:0:0:0:0 | 0 | 0 | true | CompareLT: ... < ... | test.cpp:94:7:94:11 | test.cpp:94:7:94:11 | +| test.cpp:97:10:97:10 | Load: x | file://:0:0:0:0 | 0 | 1 | false | CompareLT: ... < ... | test.cpp:94:7:94:11 | test.cpp:94:7:94:11 | +| test.cpp:100:10:100:10 | Load: x | file://:0:0:0:0 | 0 | 1 | true | CompareLE: ... <= ... | test.cpp:99:7:99:12 | test.cpp:99:7:99:12 | +| test.cpp:102:10:102:10 | Load: x | file://:0:0:0:0 | 0 | 2 | false | CompareLE: ... <= ... | test.cpp:99:7:99:12 | test.cpp:99:7:99:12 | +| test.cpp:107:5:107:10 | Phi: test10 | test.cpp:114:3:114:6 | Phi: call to sink | -1 | true | CompareLT: ... < ... | test.cpp:115:18:115:22 | test.cpp:115:18:115:22 | +| test.cpp:140:10:140:10 | Store: i | file://:0:0:0:0 | 0 | 1 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:140:10:140:10 | Store: i | test.cpp:135:16:135:16 | InitializeParameter: x | 0 | false | CompareLT: ... < ... | test.cpp:139:11:139:15 | test.cpp:139:11:139:15 | +| test.cpp:140:10:140:10 | Store: i | test.cpp:138:5:138:5 | Phi: i | 1 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:140:10:140:10 | Store: i | test.cpp:138:5:138:5 | Phi: i | 1 | true | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | +| test.cpp:156:12:156:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | false | CompareEQ: ... == ... | test.cpp:155:9:155:16 | test.cpp:155:9:155:16 | +| test.cpp:156:12:156:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | true | CompareEQ: ... == ... | test.cpp:155:9:155:16 | test.cpp:155:9:155:16 | +| test.cpp:156:12:156:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | true | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | +| test.cpp:158:12:158:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -2 | true | CompareEQ: ... == ... | test.cpp:155:9:155:16 | test.cpp:155:9:155:16 | +| test.cpp:158:12:158:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -2 | true | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | +| test.cpp:161:12:161:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -2 | true | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | +| test.cpp:161:12:161:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -2 | true | CompareNE: ... != ... | test.cpp:160:9:160:16 | test.cpp:160:9:160:16 | +| test.cpp:163:12:163:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | false | CompareNE: ... != ... | test.cpp:160:9:160:16 | test.cpp:160:9:160:16 | +| test.cpp:163:12:163:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | true | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | +| test.cpp:163:12:163:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | -1 | true | CompareNE: ... != ... | test.cpp:160:9:160:16 | test.cpp:160:9:160:16 | +| test.cpp:167:12:167:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | 0 | false | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | +| test.cpp:167:12:167:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | 1 | false | CompareEQ: ... == ... | test.cpp:166:9:166:16 | test.cpp:166:9:166:16 | +| test.cpp:167:12:167:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | 1 | true | CompareEQ: ... == ... | test.cpp:166:9:166:16 | test.cpp:166:9:166:16 | +| test.cpp:169:12:169:12 | Load: x | test.cpp:153:23:153:23 | InitializeParameter: y | 0 | false | CompareLT: ... < ... | test.cpp:154:6:154:10 | test.cpp:154:6:154:10 | diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.ql b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.ql new file mode 100644 index 000000000000..fe0d3871451e --- /dev/null +++ b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.ql @@ -0,0 +1,25 @@ +import semmle.code.cpp.rangeanalysis.RangeAnalysis +import semmle.code.cpp.ir.IR +import semmle.code.cpp.controlflow.IRGuards +import semmle.code.cpp.ir.ValueNumbering + +query predicate instructionBounds(Instruction i, Bound b, int delta, boolean upper, Reason reason, + Location reasonLoc) +{ + ( + i.getAUse() instanceof ArgumentOperand + or + i.getAUse() instanceof ReturnValueOperand + ) and + ( + upper = true and + delta = min(int d | boundedInstruction(i, b, d, upper, reason)) + or + upper = false and + delta = max(int d | boundedInstruction(i, b, d, upper, reason)) + ) and + not valueNumber(b.getInstruction()) = valueNumber(i) + and if reason instanceof CondReason + then reasonLoc = reason.(CondReason).getCond().getLocation() + else reasonLoc instanceof UnknownDefaultLocation +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.cpp b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.cpp index 9a930772b8d7..24f95205fcf8 100644 --- a/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.cpp +++ b/cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.cpp @@ -1,70 +1,172 @@ -template -class vector { -public: - T& operator[](int); - const T& operator[](int) const; -}; - -int test1(vector vec, int b) { - int x = -1; - if (b) { - x = vec[3]; + +void sink(...); +int source(); + +// Guards, inference, critical edges +int test1(int x, int y) { + if (x < y) { + x = y; } return x; } -// Regression test for ODASA-6013. -int test2(int x) { - int x0 = static_cast(x); - return x0; +// Bounds mergers at phi nodes +int test2(int x, int y) { + if (x < y) { + x = y; + } else { + x = x-2; + } + return x; } -// Tests for conversion to bool -bool test3(bool b, int x, int y) { - // The purpose the assignments to `x` below is to generate a lot of - // potential upper and lower bounds for `x`, so that the logic in - // boolConversionLowerBound and boolConversionUpperBound gets exercized. - if (y == 0) { - x = 0; +// for loops +int test3(int x) { + int i; + for(i = 0; i < x; i++) { + sink(i); } - if (y == -1) { - x = -1; + for(i = x; i > 0; i--) { + sink(i); } - if (y == 1) { - x = 1; + for(i = 0; i < x + 2; i++) { + sink(i); + } +} + +// pointer bounds +int test4(int *begin, int *end) { + while (begin < end) { + sink(begin); + begin++; } - if (y == -128) { - x = -128; +} + +// bound propagation through conditionals +int test5(int x, int y, int z) { + if (y < z) { + if (x < y) { + sink(x); + } } - if (y == 128) { - x = 128; + if (x < y) { + if (y < z) { + sink(x); // x < z is not inferred here + } } - if (y == -1024) { - x = -1024; +} + +// pointer arithmetic and sizes +void test6(int *p) { + for (char *iter = (char *)p; iter < (char *)(p+1); iter++) { + sink(iter); } - if (y == 1024) { - x = 1024; + + char *end = (char *)(p+1); + for (char *iter = (char *)p; iter < end; iter++) { + sink(iter); } +} + +// inference from equality +int test8(int x, int y) { + int *p = new int[x]; - int t = 0; + if (x == y) { + for(int i = 0; i < y; ++i) { + sink(i); + } + } +} - if (x == 0) { - bool xb = (bool)x; // (bool)x == false - t += (int)xb; +// >, >=, <= +void test9(int x) { + if (x > 1) { + sink(x); + } else { + sink(x); + } + if (x >= 1) { + sink(x); + } else { + sink(x); + } + if (x < 1) { + sink(x); + } else { + sink(x); } + if (x <= 1) { + sink(x); + } else { + sink(x); + } +} - if (x > 0) { - bool xb = (bool)x; // (bool)x == true - t += (int)xb; +// Phi nodes as bounds +int test10(int y, int z, bool use_y) { + int x; + if(use_y) { + x = y; + } else { + x = z; + } + sink(); + for(int i = 0; i < x; i++) { + return i; } +} - if (x < 0) { - bool xb = (bool)x; // (bool)x == true - t += (int)xb; +// Irreducible CFGs +int test11(int y, int x) { + int i = 0; + if (x < y) { + x = y; + } else { + goto inLoop; + } + for(i = 0; i < x; i++) { + inLoop: + sink(i); } +} - bool xb = (bool)x; // Value of (bool)x is unknown. - t += (int)xb; +// do-while +int test12(int x) { + int i = 0; + do { + i++; + } while(i < x); + return i; +} - return b || (bool)t; +// do while false +int test13(int x) { + int i = 0; + do { + i++; + } while(false); + return i; +} + +// unequal bound narrowing +int test14(int x, int y) { + if(x < y) { + if (x == y-1) { + sink(x); + } else { + sink(x); + } + if (x != y-1) { + sink(x); + } else { + sink(x); + } + } else { + if (y == x-1) { + sink(x); + } else { + sink(x); + } + } } diff --git a/cpp/ql/test/library-tests/rangeanalysis/signanalysis/SignAnalysis.expected b/cpp/ql/test/library-tests/rangeanalysis/signanalysis/SignAnalysis.expected index 138d6c33a6e2..4a1f172d16df 100644 --- a/cpp/ql/test/library-tests/rangeanalysis/signanalysis/SignAnalysis.expected +++ b/cpp/ql/test/library-tests/rangeanalysis/signanalysis/SignAnalysis.expected @@ -65,9 +65,9 @@ | test.c:34:5:34:14 | Store: ... += ... | positive | | test.c:34:14:34:14 | Load: i | positive | | test.c:36:10:36:14 | Load: total | positive | -| test.c:36:10:36:18 | Add: ... + ... | positive | -| test.c:36:10:36:18 | Store: ... + ... | positive | -| test.c:36:18:36:18 | Load: i | positive | +| test.c:36:10:36:18 | Add: ... + ... | positive strictlyPositive | +| test.c:36:10:36:18 | Store: ... + ... | positive strictlyPositive | +| test.c:36:18:36:18 | Load: i | positive strictlyPositive | | test.c:42:15:42:15 | Load: i | positive | | test.c:42:15:42:15 | Phi: i | positive | | test.c:42:15:42:15 | Phi: i | positive | @@ -81,9 +81,9 @@ | test.c:43:5:43:14 | Store: ... += ... | positive | | test.c:43:14:43:14 | Load: i | positive | | test.c:45:10:45:14 | Load: total | positive | -| test.c:45:10:45:18 | Add: ... + ... | positive | -| test.c:45:10:45:18 | Store: ... + ... | positive | -| test.c:45:18:45:18 | Load: i | positive | +| test.c:45:10:45:18 | Add: ... + ... | positive strictlyPositive | +| test.c:45:10:45:18 | Store: ... + ... | positive strictlyPositive | +| test.c:45:18:45:18 | Load: i | positive strictlyPositive | | test.c:51:15:51:15 | Load: i | positive | | test.c:51:15:51:15 | Phi: i | positive | | test.c:51:15:51:15 | Phi: i | positive | @@ -448,10 +448,10 @@ | test.c:343:5:343:7 | Constant: ... ++ | positive strictlyPositive | | test.c:343:5:343:7 | Load: ... ++ | positive | | test.c:343:5:343:7 | Store: ... ++ | positive strictlyPositive | -| test.c:345:3:345:7 | Store: ... = ... | positive | -| test.c:345:7:345:7 | Load: i | positive | +| test.c:345:3:345:7 | Store: ... = ... | positive strictlyPositive | +| test.c:345:7:345:7 | Load: i | positive strictlyPositive | | test.c:346:7:346:7 | Load: x | positive | -| test.c:347:9:347:9 | Load: d | positive | +| test.c:347:9:347:9 | Load: d | positive strictlyPositive | | test.c:348:14:348:14 | Constant: 1 | positive strictlyPositive | | test.c:348:14:348:14 | Store: 1 | positive strictlyPositive | | test.c:355:42:355:42 | InitializeParameter: x | positive | @@ -674,6 +674,8 @@ | test.cpp:45:12:45:15 | Constant: 1024 | positive strictlyPositive | | test.cpp:46:5:46:12 | Store: ... = ... | positive strictlyPositive | | test.cpp:46:9:46:12 | Constant: 1024 | positive strictlyPositive | +| test.cpp:57:21:57:21 | Load: x | positive strictlyPositive | +| test.cpp:62:21:62:21 | Load: x | negative strictlyNegative | | test.cpp:69:10:69:21 | Constant: ... \|\| ... | positive strictlyPositive | | test.cpp:69:10:69:21 | Load: ... \|\| ... | positive | | test.cpp:69:10:69:21 | Phi: ... \|\| ... | positive | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected index dd3c96a23adf..877cdb36750b 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -365,7 +365,7 @@ test.cpp: # 53| Block 1 # 53| m1_0(unsigned int) = Phi : from 0:m0_11, from 8:m8_4 -# 53| valnum = unique +# 53| valnum = m1_0 # 53| r1_1(glval) = VariableAddress[str] : # 53| valnum = r0_3 # 53| r1_2(char *) = Load : r1_1, m0_4 @@ -395,11 +395,11 @@ test.cpp: # 56| Block 3 # 56| m3_0(char *) = Phi : from 2:m2_3, from 5:m5_4 -# 56| valnum = unique +# 56| valnum = m3_0 # 56| r3_1(glval) = VariableAddress[ptr] : # 56| valnum = r0_7 # 56| r3_2(char *) = Load : r3_1, m3_0 -# 56| valnum = unique +# 56| valnum = m3_0 # 56| r3_3(char) = Load : r3_2, m0_1 # 56| valnum = unique # 56| r3_4(int) = Convert : r3_3 @@ -422,7 +422,7 @@ test.cpp: # 56| r4_0(glval) = VariableAddress[ptr] : # 56| valnum = r0_7 # 56| r4_1(char *) = Load : r4_0, m3_0 -# 56| valnum = unique +# 56| valnum = m3_0 # 56| r4_2(char) = Load : r4_1, m0_1 # 56| valnum = unique # 56| r4_3(int) = Convert : r4_2 @@ -439,7 +439,7 @@ test.cpp: # 56| r5_0(glval) = VariableAddress[ptr] : # 56| valnum = r0_7 # 56| r5_1(char *) = Load : r5_0, m3_0 -# 56| valnum = unique +# 56| valnum = m3_0 # 56| r5_2(int) = Constant[1] : # 56| valnum = unique # 56| r5_3(char *) = PointerAdd[1] : r5_1, r5_2 @@ -452,7 +452,7 @@ test.cpp: # 59| r6_0(glval) = VariableAddress[ptr] : # 59| valnum = r0_7 # 59| r6_1(char *) = Load : r6_0, m3_0 -# 59| valnum = unique +# 59| valnum = m3_0 # 59| r6_2(char) = Load : r6_1, m0_1 # 59| valnum = unique # 59| r6_3(int) = Convert : r6_2 @@ -473,7 +473,7 @@ test.cpp: # 62| r8_0(glval) = VariableAddress[result] : # 62| valnum = r0_9 # 62| r8_1(unsigned int) = Load : r8_0, m1_0 -# 62| valnum = unique +# 62| valnum = m1_0 # 62| r8_2(unsigned int) = Constant[1] : # 62| valnum = unique # 62| r8_3(unsigned int) = Add : r8_1, r8_2 @@ -489,9 +489,9 @@ test.cpp: # 65| r9_2(glval) = VariableAddress[result] : # 65| valnum = r0_9 # 65| r9_3(unsigned int) = Load : r9_2, m1_0 -# 65| valnum = r9_3 +# 65| valnum = m1_0 # 65| m9_4(unsigned int) = Store : r9_1, r9_3 -# 65| valnum = r9_3 +# 65| valnum = m1_0 # 49| r9_5(glval) = VariableAddress[#return] : # 49| valnum = r9_1 # 49| v9_6(void) = ReturnValue : r9_5, m9_4 @@ -639,15 +639,15 @@ test.cpp: # 88| Block 3 # 88| m3_0(int) = Phi : from 1:m1_3, from 2:m2_3 -# 88| valnum = unique +# 88| valnum = m3_0 # 88| r3_1(glval) = VariableAddress[#temp88:7] : # 88| valnum = r1_2 # 88| r3_2(int) = Load : r3_1, m3_0 -# 88| valnum = r3_2 +# 88| valnum = m3_0 # 88| r3_3(glval) = VariableAddress[v] : # 88| valnum = r0_9 # 88| m3_4(int) = Store : r3_3, r3_2 -# 88| valnum = r3_2 +# 88| valnum = m3_0 # 89| v3_5(void) = NoOp : # 84| v3_6(void) = ReturnVoid : # 84| v3_7(void) = UnmodeledUse : mu*