Skip to content

Commit d0681c6

Browse files
committed
C++: Divide nr of bounds between branches for phi nodes
1 parent 032c7ea commit d0681c6

File tree

2 files changed

+1666
-1653
lines changed

2 files changed

+1666
-1653
lines changed

cpp/ql/lib/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -552,34 +552,47 @@ private module BoundsEstimate {
552552
private float nrOfBoundsPhiGuard(RangeSsaDefinition def, StackVariable v) {
553553
// If we have
554554
//
555-
// if (x < c) { e1 }
556-
// e2
555+
// if (x < c) { e1 } else { e2 }
556+
// e3
557557
//
558-
// then `e2` is both a guard phi node (guarded by `x < c`) and a normal
559-
// phi node (control is merged after the `if` statement).
558+
// then `{ e1 }` and `{ e2 }` are both guard phi nodes guarded by `x < c`.
559+
// The range analysis propagates bounds on `x` into both branches, filtered
560+
// by the condition. In this case all lower bounds flow to `{ e1 }` and only
561+
// lower bounds that are smaller than `c` flow to `{ e2 }`.
560562
//
561-
// Assume `x` has `n` bounds. Then `n` bounds are propagated to the guard
562-
// phi node `{ e1 }` and, since `{ e1 }` is input to `e2` as a normal phi
563-
// node, `n` bounds are propagated to `e2`. If we also propagate the `n`
564-
// bounds to `e2` as a guard phi node, then we square the number of
565-
// bounds.
563+
// The largest bound possible for `e3` is the number of bounds on `x` plus
564+
// one. This happens when all bounds flow from `x` to `e1` to `e3` and the
565+
// bound `c` can flow to `e2` to `e3`.
566566
//
567-
// However in practice `x < c` is going to cut down the number of bounds:
568-
// The tracked bounds can't flow to both branches as that would require
569-
// them to simultaneously be greater and smaller than `c`. To approximate
570-
// this better, the contribution from a guard phi node that is also a
571-
// normal phi node is 1.
572-
exists(def.getAPhiInput(v)) and
573-
isGuardPhiWithBound(def, v, _) and
574-
result = 1
575-
or
576-
not exists(def.getAPhiInput(v)) and
577-
// If there's different `access`es, then they refer to the same variable
578-
// with the same lower bounds. Hence adding these guards make no sense (the
579-
// implementation will take the union, but they'll be removed by
580-
// deduplication). Hence we use `max` as an approximation.
581-
result =
582-
max(VariableAccess access | isGuardPhiWithBound(def, v, access) | nrOfBoundsExpr(access))
567+
// We want to optimize our bounds estimate for `e3`, as that is the estimate
568+
// that can continue propagating forward. We don't know how the existing
569+
// bounds will be split between the different branches. That depends on
570+
// whether the range analysis is tracking lower bounds or upper bounds, and
571+
// on the meaning of the condition.
572+
//
573+
// As a heuristic we divide the number of bounds on `x` by 2 to "average"
574+
// the effect of the condition and add 1 to account for the bound from the
575+
// condition itself. This will approximate estimates inside the branches,
576+
// but will give a good estimate after the branches are merged.
577+
//
578+
// This also handles cases such as this one
579+
//
580+
// if (x < c) { e1 }
581+
// e3
582+
//
583+
// where `e3` is both a guard phi node (guarded by `x < c`) and a normal
584+
// phi node (control is merged after the `if` statement). Here half of the
585+
// bounds flow into the branch and then to `e3` as a normal phi node and the
586+
// "other" half flow from the condition to `e3` as a guard phi node.
587+
exists(float varBounds |
588+
// If there's different `access`es, then they refer to the same
589+
// variable with the same lower bounds. Hence adding these guards make no
590+
// sense (the implementation will take the union, but they'll be removed by
591+
// deduplication). Hence we use `max` as an approximation.
592+
varBounds =
593+
max(VariableAccess access | isGuardPhiWithBound(def, v, access) | nrOfBoundsExpr(access)) and
594+
result = (varBounds + 1) / 2
595+
)
583596
or
584597
def.isPhiNode(v) and
585598
not isGuardPhiWithBound(def, v, _) and

0 commit comments

Comments
 (0)