mirror of
https://github.com/github/codeql.git
synced 2026-05-01 03:35:13 +02:00
C++: Initial implementation of new range analysis
This commit is contained in:
@@ -280,6 +280,17 @@ class IRGuardCondition extends Instruction {
|
||||
ne.controls(controlled, testIsTrue.booleanNot()))
|
||||
}
|
||||
|
||||
cached predicate controlsEdge(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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,6 +21,10 @@ class Operand extends TOperand {
|
||||
result = "Operand"
|
||||
}
|
||||
|
||||
Location getLocation() {
|
||||
result = getInstruction().getLocation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Instruction` that consumes this operand.
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,6 +21,10 @@ class Operand extends TOperand {
|
||||
result = "Operand"
|
||||
}
|
||||
|
||||
Location getLocation() {
|
||||
result = getInstruction().getLocation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Instruction` that consumes this operand.
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -21,6 +21,10 @@ class Operand extends TOperand {
|
||||
result = "Operand"
|
||||
}
|
||||
|
||||
Location getLocation() {
|
||||
result = getInstruction().getLocation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Instruction` that consumes this operand.
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
|
||||
74
cpp/ql/src/semmle/code/cpp/rangeanalysis/Bound.qll
Normal file
74
cpp/ql/src/semmle/code/cpp/rangeanalysis/Bound.qll
Normal file
@@ -0,0 +1,74 @@
|
||||
import cpp
|
||||
private import semmle.code.cpp.ir.IR
|
||||
|
||||
private newtype TBound =
|
||||
TBoundZero() or
|
||||
TBoundInstruction(Instruction i) {
|
||||
i.getResultType() instanceof IntegralType or
|
||||
i.getResultType() instanceof PointerType
|
||||
} or
|
||||
TBoundOperand(Operand o) {
|
||||
o.getDefinitionInstruction().getResultType() instanceof IntegralType or
|
||||
o.getDefinitionInstruction().getResultType() instanceof PointerType
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound that may be inferred for an expression plus/minus an integer delta.
|
||||
*/
|
||||
abstract class Bound extends TBound {
|
||||
abstract string toString();
|
||||
|
||||
// FIXME: operands?
|
||||
/** 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) }
|
||||
|
||||
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
|
||||
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bound corresponding to the value of an `Instruction`.
|
||||
*/
|
||||
class InstructionBound extends Bound, TBoundInstruction {
|
||||
/** Gets the SSA variable that equals this bound. */
|
||||
override Instruction getInstruction(int delta) { this = TBoundInstruction(result) and delta = 0}
|
||||
|
||||
override string toString() { result = getInstruction().toString() }
|
||||
|
||||
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
|
||||
getInstruction().getLocation().hasLocationInfo(path, sl, sc, el, ec)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OperandBound extends Bound, TBoundOperand {
|
||||
Operand getOperand() {
|
||||
this = TBoundOperand(result)
|
||||
}
|
||||
|
||||
override Instruction getInstruction(int delta) {
|
||||
this = TBoundOperand(result.getAUse()) and
|
||||
delta = 0
|
||||
}
|
||||
|
||||
override string toString() { result = getOperand().toString() }
|
||||
|
||||
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
|
||||
getOperand().getLocation().hasLocationInfo(path, sl, sc, el, ec)
|
||||
}
|
||||
}
|
||||
591
cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeAnalysis.qll
Normal file
591
cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeAnalysis.qll
Normal file
@@ -0,0 +1,591 @@
|
||||
/**
|
||||
* Provides classes and predicates for range analysis.
|
||||
*
|
||||
* An inferred bound can either be a specific integer, the abstract value of an
|
||||
* SSA variable, or the abstract value of an interesting expression. The latter
|
||||
* category includes array lengths that are not SSA variables.
|
||||
*
|
||||
* 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 `E1 --> E2` relates two expressions
|
||||
* such that `E1 <= B` implies `E2 <= B` for any `B` (with a second separate
|
||||
* step relation handling lower bounds). Examples of such steps include
|
||||
* assignments `E2 = E1` and conditions `x <= E1` where `E2` 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 `E1 --(delta)--> E2` relates two expressions and an integer
|
||||
* such that `E1 <= B` implies `E2 <= 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 `E1` transitively
|
||||
* steps to `E2`, `delta` is the sum of deltas along the path, and `B` is an
|
||||
* interesting bound equal to the value of `E1` then `E2 <= B + delta`. This
|
||||
* corresponds to the predicate `bounded`.
|
||||
*
|
||||
* 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 `e`.
|
||||
* - `upper = true` : `e <= b + delta`
|
||||
* - `upper = false` : `e >= 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)
|
||||
}
|
||||
|
||||
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 `i` equals `bound + delta` at `use`.
|
||||
*
|
||||
* If the condition evaluates to `testIsTrue`:
|
||||
* - `isEq = true` : `i == bound + delta`
|
||||
* - `isEq = false` : `i != bound + delta`
|
||||
*/
|
||||
private IRGuardCondition eqFlowCond(Operand op, Operand bound, int delta,
|
||||
boolean isEq, boolean testIsTrue)
|
||||
{
|
||||
exists(Operand compared |
|
||||
result.ensuresEq(compared, bound, delta, op.getInstruction().getBlock(), isEq) and
|
||||
result.controls(bound.getInstruction().getBlock(), testIsTrue) and
|
||||
valueNumber(compared.getDefinitionInstruction()) = valueNumber (op.getDefinitionInstruction())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
) {
|
||||
op2.getDefinitionInstruction().getAnOperand().(CopySourceOperand) = op1 and
|
||||
(upper = true or upper = false) and
|
||||
reason = TNoReason() and
|
||||
delta = 0
|
||||
or
|
||||
exists(IRGuardCondition guard |
|
||||
guard = boundFlowCond(op2, op1, delta, upper, _) and
|
||||
reason = TCondReason(guard)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a condition that tests whether `v` is bounded by `e + delta`.
|
||||
*
|
||||
* If the condition evaluates to `testIsTrue`:
|
||||
* - `upper = true` : `v <= e + delta`
|
||||
* - `upper = false` : `v >= e + delta`
|
||||
*/
|
||||
private IRGuardCondition boundFlowCond(NonPhiOperand op, NonPhiOperand bound, int delta, boolean upper,
|
||||
boolean testIsTrue)
|
||||
{
|
||||
exists(Operand compared |
|
||||
result.comparesLt(compared, bound, delta, upper, testIsTrue) and
|
||||
result.controls(op.getInstruction().getBlock(), testIsTrue) and
|
||||
valueNumber(compared.getDefinitionInstruction()) = valueNumber(op.getDefinitionInstruction())
|
||||
)
|
||||
// TODO: strengthening through modulus library
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `comp` corresponds to:
|
||||
* - `upper = true` : `v <= e + delta` or `v < e + delta`
|
||||
* - `upper = false` : `v >= e + delta` or `v > e + delta`
|
||||
*/
|
||||
private IRGuardCondition boundFlowCondPhi(PhiOperand op, NonPhiOperand bound, int delta, boolean upper,
|
||||
boolean testIsTrue)
|
||||
{
|
||||
exists(Operand compared |
|
||||
result.comparesLt(compared, bound, delta, upper, testIsTrue) and
|
||||
result.controlsEdge(op.getPredecessorBlock().getLastInstruction(), op.getInstruction().getBlock(), testIsTrue) and
|
||||
valueNumber(compared.getDefinitionInstruction()) = valueNumber (op.getDefinitionInstruction())
|
||||
)
|
||||
or
|
||||
exists(Operand compared |
|
||||
result.comparesLt(compared, bound, delta, upper, testIsTrue) and
|
||||
result.controls(op.getPredecessorBlock(), testIsTrue) and
|
||||
valueNumber(compared.getDefinitionInstruction()) = valueNumber (op.getDefinitionInstruction())
|
||||
)
|
||||
// TODO: strengthening through modulus library
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private predicate safeCast(IntegralType fromtyp, IntegralType totyp) {
|
||||
fromtyp.getSize() <= totyp.getSize() and
|
||||
(
|
||||
fromtyp.isUnsigned()
|
||||
or
|
||||
totyp.isSigned()
|
||||
)
|
||||
}
|
||||
|
||||
private class SafeCastInstruction extends ConvertInstruction {
|
||||
SafeCastInstruction() {
|
||||
safeCast(getResultType(), getOperand().getResultType())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Operand 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 instanceof ConstantIntegerExpr` 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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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, Reason r1, Reason r2 |
|
||||
boundFlowStepSsa(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 `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 |
|
||||
guard = boundFlowCondPhi(op2, op1, delta, upper, _) and
|
||||
// Java has guardDirectlyControlsSsaRead here
|
||||
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 | // should mid be an `Operand`?
|
||||
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 `op != b + delta` at `pos`.
|
||||
*/
|
||||
private predicate unequalOperand(Operand op, Bound b, int delta, Reason reason) {
|
||||
none()
|
||||
}
|
||||
|
||||
/*
|
||||
* Notes on phi nodes in the C IR vs Java
|
||||
*
|
||||
* the IR doesn't have edges as a type, but Java uses edges rather than predecessor blocks; is
|
||||
* there anything to be gained by distinguishing different edges from the same predecessor? (does
|
||||
* Java distinguish different edges from the same predecessor? can the IR actually generate multiple
|
||||
* edges from the same predecessor?)
|
||||
*
|
||||
* Java identifies a phi operand using a tuple of a node, an input, and an edge; we might be able to
|
||||
* get away with just using a PhiOperand, which I believe uniquely identifies the phi node and
|
||||
* predecessor block.
|
||||
*
|
||||
*/
|
||||
|
||||
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 |
|
||||
boundedInstruction(op.getDefinitionInstruction(), b, d, upper, fromBackEdge0, origdelta, reason)
|
||||
or
|
||||
boundedPhiOperand(op, b, d, upper, fromBackEdge0, origdelta, reason)
|
||||
or
|
||||
b.(InstructionBound).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, InstructionBound 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)
|
||||
}
|
||||
|
||||
private predicate boundedInstruction(
|
||||
Instruction i, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason
|
||||
) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
54
cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeUtils.qll
Normal file
54
cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeUtils.qll
Normal file
@@ -0,0 +1,54 @@
|
||||
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()))
|
||||
)
|
||||
}
|
||||
|
||||
predicate backEdge(PhiInstruction phi, PhiOperand op) {
|
||||
phi.getAnOperand() = op and
|
||||
(
|
||||
phi.getBlock().dominates(op.getPredecessorBlock())
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
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) {
|
||||
boundedInstruction(i, b, delta, upper, reason)
|
||||
}
|
||||
|
||||
query predicate operandBounds(Operand op, Bound b, int delta, boolean upper, Reason reason) {
|
||||
boundedOperand(op, b, delta, upper, reason)
|
||||
}
|
||||
34
cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.c
Normal file
34
cpp/ql/test/library-tests/rangeanalysis/rangeanalysis/test.c
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
// Guards, inference, critical edges
|
||||
int test1(int x, int y) {
|
||||
if (x < y) {
|
||||
x = y;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Bounds mergers at phi nodes
|
||||
int test2(int x, int y) {
|
||||
if (x < y) {
|
||||
x = y;
|
||||
} else {
|
||||
x = x-2;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// for loops
|
||||
int test3(int x, void *p) {
|
||||
int i;
|
||||
for (i = 0; i < x; i++) {
|
||||
p[i];
|
||||
}
|
||||
}
|
||||
|
||||
// pointer bounds
|
||||
int test4(int *begin, int *end) {
|
||||
while (begin < end) {
|
||||
*begin = (*begin) + 1;
|
||||
begin++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user