Skip to content

Commit a8c57ec

Browse files
committed
C++: Prevent false negatives caused by incorrectly concluding that a loop variant condition refutes itself across loop iterations.
1 parent d308dd2 commit a8c57ec

File tree

1 file changed

+74
-44
lines changed

1 file changed

+74
-44
lines changed

cpp/ql/src/semmle/code/cpp/controlflow/StackVariableReachability.qll

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,77 @@ private predicate revBbSuccessorEntryReaches0(
192192
)
193193
}
194194

195+
private predicate successorExitsLoop(BasicBlock pred, BasicBlock succ, Loop loop) {
196+
pred.getASuccessor() = succ and
197+
bbDominates(loop.getStmt(), pred) and
198+
not bbDominates(loop.getStmt(), succ)
199+
}
200+
201+
private predicate successorExitsFirstDisjunct(BasicBlock pred, BasicBlock succ) {
202+
exists(LogicalOrExpr orExpr | orExpr instanceof GuardCondition |
203+
pred.getAFalseSuccessor() = succ and
204+
pred.contains(orExpr.getLeftOperand())
205+
)
206+
}
207+
208+
/**
209+
* When we exit a loop, we filter out the conditions that arise from the loop's guard.
210+
* To see why this is necessary, consider this example:
211+
* ```
212+
* (1) source();
213+
* while (b) { (2) ... }
214+
* (3) sink();
215+
* ```
216+
* If we keep all the conditions when we transition from (2) to (3) we learn that `b` is true at
217+
* (3), but since we exited the loop we also learn that `b` is false at 3.
218+
* Thus, when we transition from (2) to (3) we discard all those conditions that are true at (2),
219+
* but NOT true at (3).
220+
*/
221+
private predicate isLoopCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
222+
exists(Loop loop, boolean testIsTrue | successorExitsLoop(pred, bb, loop) |
223+
// the resulting `Condition` holds inside the loop
224+
cond.controls(pred, testIsTrue) and
225+
// but not prior to the loop.
226+
not cond.controls(loop.getBasicBlock(), testIsTrue)
227+
)
228+
}
229+
230+
/**
231+
* When we leave the first disjunct we throw away the condition that says the the first disjunct is
232+
* false. To see why this is necessary, consider this example:
233+
* ```
234+
* if((1) b1 || (2) b2) { (3) ... }
235+
* ```
236+
* it holds that `b1 == false` controls (2), and since (2) steps to (3) we learn that `b1 == false `
237+
* holds at (3). So we filter out the conditions that we learn from leaving taking the false
238+
* branch in a disjunction.
239+
*/
240+
private predicate isDisjunctionCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
241+
exists(boolean testIsTrue | successorExitsFirstDisjunct(pred, bb) |
242+
// the resulting `Condition` holds after evaluating the left-hand side
243+
cond.controls(bb, testIsTrue) and
244+
// but not before evaluating the left-hand side.
245+
not cond.controls(pred, testIsTrue)
246+
)
247+
}
248+
249+
private predicate isLoopVariantCondition(LogicalGuardCondition cond, BasicBlock pred, BasicBlock bb) {
250+
exists(Loop loop |
251+
bb.getEnd() = loop.getCondition() and
252+
pred.getASuccessor() = bb and
253+
bbDominates(bb, pred) and
254+
loopVariant(cond.getAChild*(), loop)
255+
)
256+
}
257+
258+
private predicate loopVariant(VariableAccess e, Loop loop) {
259+
exists(SsaDefinition d | d.getAUse(e.getTarget()) = e |
260+
d.getAnUltimateDefiningValue(e.getTarget()) = loop.getCondition().getAChild*() or
261+
d.getAnUltimateDefiningValue(e.getTarget()).getEnclosingStmt().getParent*() = loop.getStmt() or
262+
d.getAnUltimateDefiningValue(e.getTarget()) = loop.(ForStmt).getUpdate().getAChild*()
263+
)
264+
}
265+
195266
/**
196267
* A reachability analysis for control-flow nodes involving stack variables.
197268
* This defines sources, sinks, and any other configurable aspect of the
@@ -296,19 +367,6 @@ abstract class StackVariableReachability extends string {
296367
)
297368
}
298369

299-
private predicate successorExitsLoop(BasicBlock pred, BasicBlock succ, Loop loop) {
300-
pred.getASuccessor() = succ and
301-
bbDominates(loop.getStmt(), pred) and
302-
not bbDominates(loop.getStmt(), succ)
303-
}
304-
305-
private predicate successorExitsFirstDisjunct(BasicBlock pred, BasicBlock succ) {
306-
exists(LogicalOrExpr orExpr | orExpr instanceof GuardCondition |
307-
pred.getAFalseSuccessor() = succ and
308-
pred.contains(orExpr.getLeftOperand())
309-
)
310-
}
311-
312370
/** Holds if `cond` is a condition with a known truth value in `bb`. */
313371
private Condition getACondition(ControlFlowNode source, SemanticStackVariable v, BasicBlock bb) {
314372
revBbSuccessorEntryReaches0(source, pragma[only_bind_into](bb), v, _, this) and
@@ -325,37 +383,9 @@ abstract class StackVariableReachability extends string {
325383
result = cond and
326384
revBbSuccessorEntryReaches0(source, pragma[only_bind_into](pred), v, _, this) and
327385
pred.getASuccessor() = bb and
328-
// When we exit a loop, we filter out the conditions that arise from the loop's guard.
329-
// To see why this is necessary, consider this example:
330-
// ```
331-
// (1) source();
332-
// while (b) { (2) /* ... */ }
333-
// (3) sink();
334-
// ```
335-
// If we keep all the conditions when we transition from (2) to (3) we learn that `b` is true at
336-
// (3), but since we exited the loop we also learn that `b` is false at 3.
337-
// Thus, when we transition from (2) to (3) we discard all those conditions that are true at (2),
338-
// but NOT true at (3).
339-
not exists(Loop loop, boolean testIsTrue | successorExitsLoop(pred, bb, loop) |
340-
// the resulting `Condition` holds inside the loop
341-
cond.getCondition().controls(pred, testIsTrue) and
342-
// but not prior to the loop.
343-
not cond.getCondition().controls(loop.getBasicBlock(), testIsTrue)
344-
) and
345-
// Similarly, in this example:
346-
// ```
347-
// if((1) b1 || (2) b2) { (3) /* ... */ }
348-
// ```
349-
// it holds that `b1 == false` controls (2), and since (2) steps to (3) we learn that `b1 == false `
350-
// holds at (3). So we filter out the conditions that we learn from leaving taking the false
351-
// branch in a disjunction.
352-
not exists(boolean testIsTrue |
353-
successorExitsFirstDisjunct(pred, bb) and
354-
// the resulting `Condition` holds after evaluating the left-hand side
355-
cond.getCondition().controls(bb, testIsTrue) and
356-
// but not before evaluating the left-hand side.
357-
not cond.getCondition().controls(pred, testIsTrue)
358-
)
386+
not isLoopCondition(cond.getCondition(), pred, bb) and
387+
not isDisjunctionCondition(cond.getCondition(), pred, bb) and
388+
not isLoopVariantCondition(cond.getCondition(), pred, bb)
359389
)
360390
)
361391
}

0 commit comments

Comments
 (0)