Skip to content

Commit 947aaa9

Browse files
committed
C++: reachableRecursive refactor for performance
The `reachable` predicate is large and slow to compute. It's part of a mutual recursion that's non-linear, meaning it has a recursive call on both sides of an `and`. This change removes a part of the base case that has no effect on recursive cases. The removed part is added back after the recursion has finished. Before, on Wireshark: ControlFlowGraph::Cached::reachable#f .......... 20.8s (executed 9800 times) ConstantExprs::successors_adapted#ff ........... 4.2s (executed 615 times) ConstantExprs::potentiallyReturningFunction#f .. 3.9s (executed 9799 times) ConstantExprs::possiblePredecessor#f ........... 2.9s (executed 788 times) After, on Wireshark: ConstantExprs::reachableRecursive#f ............ 13.2s (executed 9800 times) ConstantExprs::successors_adapted#ff ........... 4.2s (executed 615 times) ConstantExprs::potentiallyReturningFunction#f .. 4.3s (executed 9799 times) ConstantExprs::possiblePredecessor#f ........... 2.6s (executed 788 times) I've verified that this change doesn't change what's computed by checking that the output of the following query is unchanged: import cpp import semmle.code.cpp.controlflow.internal.ConstantExprs select strictcount(ControlFlowNode n | reachable(n)) as reachable, strictcount(ControlFlowNode n1, ControlFlowNode n2 | n2 = n1.getASuccessor()) as edges, strictcount(FunctionCall c | aborting(c)) as abortingCall, strictcount(Function f | abortingFunction(f)) as abortingFunction
1 parent db6a807 commit 947aaa9

File tree

1 file changed

+21
-20
lines changed

1 file changed

+21
-20
lines changed

cpp/ql/src/semmle/code/cpp/controlflow/internal/ConstantExprs.qll

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,6 @@ private module Cached {
4444
*/
4545
cached
4646
module ControlFlowGraphPublic {
47-
// The base case of `reachable` is factored out for performance. If it's
48-
// written in-line in `reachable`, the compiler inserts a `n instanceof
49-
// ControlFlowNode` check because the `not ... and not ...` case doesn't
50-
// otherwise bind `n`. The problem is that this check is inserted at the
51-
// outermost level of this predicate, so it covers all cases including the
52-
// recursive case. The optimizer doesn't eliminate the check even though it's
53-
// redundant, and having the check leads to needless extra computation and a
54-
// risk of bad join orders.
55-
private predicate reachableBaseCase(ControlFlowNode n) {
56-
exists(Function f | f.getEntryPoint() = n)
57-
or
58-
// Okay to use successors_extended directly here
59-
(not successors_extended(_,n) and not successors_extended(n,_))
60-
or
61-
n instanceof Handler
62-
}
63-
6447
/**
6548
* Holds if the control-flow node `n` is reachable, meaning that either
6649
* it is an entry point, or there exists a path in the control-flow
@@ -72,9 +55,10 @@ private module Cached {
7255
cached
7356
predicate reachable(ControlFlowNode n)
7457
{
75-
reachableBaseCase(n)
58+
// Okay to use successors_extended directly here
59+
reachableRecursive(n)
7660
or
77-
reachable(n.getAPredecessor())
61+
(not successors_extended(_,n) and not successors_extended(n,_))
7862
}
7963

8064
/** Holds if `condition` always evaluates to a nonzero value. */
@@ -169,7 +153,7 @@ private predicate potentiallyReturningFunction(Function f) {
169153
nonAnalyzableFunction(f)
170154
or
171155
// otherwise the exit-point of `f` must be reachable
172-
reachable(f)
156+
reachableRecursive(f)
173157
)
174158
}
175159

@@ -309,6 +293,23 @@ private predicate possiblePredecessor(Node pred) {
309293
potentiallyReturningFunctionCall(pred)
310294
}
311295

296+
/**
297+
* Holds if the control-flow node `n` is reachable, meaning that either
298+
* it is an entry point, or there exists a path in the control-flow
299+
* graph of its function that connects an entry point to it.
300+
* Compile-time constant conditions are taken into account, so that
301+
* the call to `f` is not reachable in `if (0) f();` even if the
302+
* `if` statement as a whole is reachable.
303+
*/
304+
private predicate reachableRecursive(ControlFlowNode n)
305+
{
306+
exists(Function f | f.getEntryPoint() = n)
307+
or
308+
n instanceof Handler
309+
or
310+
reachableRecursive(n.getAPredecessor())
311+
}
312+
312313
private predicate compileTimeConstantInt(Expr e, int val) {
313314
val = e.getFullyConverted().getValue().toInt() and
314315
not e instanceof StringLiteral and

0 commit comments

Comments
 (0)