Skip to content

Commit 26f32f0

Browse files
committed
C++: Initial version of CFG.qll
This implements calculation of the control-flow graph in QL. The new code is not enabled yet as we'll need more extractor changes first. The `SyntheticDestructorCalls.qll` file is a temporary solution that can be removed when the extractor produces this information directly.
1 parent a47faa2 commit 26f32f0

File tree

97 files changed

+22665
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+22665
-0
lines changed

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

Lines changed: 1340 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**
2+
* Provides classes and predicates for approximating where compiler-generated
3+
* destructor calls should be placed. This file can be removed when the
4+
* extractor produces this information directly.
5+
*/
6+
private import cpp
7+
8+
private predicate isDeleteDestructorCall(DestructorCall c) {
9+
exists(DeleteExpr del | c = del.getDestructorCall())
10+
or
11+
exists(DeleteArrayExpr del | c = del.getDestructorCall())
12+
}
13+
14+
// Things we know about these calls
15+
// - isCompilerGenerated() always holds
16+
// - They all have a predecessor of type VariableAccess
17+
// - They all have successors
18+
// - After subtracting all the jumpy dtor calls for a particular variable
19+
// (PrematureScopeExitNode), there's at most one call left, and that will be
20+
// the ordinary one.
21+
// - Except for ConditionDeclExpr! One chain should be directly connected to
22+
// the false edge out of the parent, and the other should not.
23+
class SyntheticDestructorCall extends FunctionCall {
24+
SyntheticDestructorCall() {
25+
(
26+
this instanceof DestructorCall
27+
or
28+
// Workaround for CPP-320
29+
exists(Function target |
30+
target = this.(FunctionCall).getTarget() and
31+
not exists(target.getName())
32+
)
33+
) and
34+
not exists(this.getParent()) and
35+
not isDeleteDestructorCall(this) and
36+
not this.isUnevaluated() and
37+
this.isCompilerGenerated()
38+
}
39+
40+
VariableAccess getAccess() { successors(result, this) }
41+
42+
SyntheticDestructorCall getNext() { successors(this, result.getAccess()) }
43+
44+
SyntheticDestructorCall getPrev() { this = result.getNext() }
45+
}
46+
47+
// Things we know about these blocks
48+
// - If they follow a JumpStmt, the VariableAccesses of their calls never
49+
// have multiple predecessors.
50+
// - But after ReturnStmt, that may happen.
51+
/**
52+
* Describes a straight line of `SyntheticDestructorCall`s. Node that such
53+
* lines can share tails.
54+
*/
55+
private class SyntheticDestructorBlock extends ControlFlowNodeBase {
56+
SyntheticDestructorBlock() {
57+
this = any(SyntheticDestructorCall call |
58+
not exists(call.getPrev())
59+
or
60+
exists(ControlFlowNodeBase pred |
61+
not pred instanceof SyntheticDestructorCall and
62+
successors(pred, call.getAccess())
63+
)
64+
)
65+
}
66+
67+
SyntheticDestructorCall getCall(int i) {
68+
i = 0 and result = this
69+
or
70+
result = this.getCall(i - 1).getNext()
71+
}
72+
73+
ControlFlowNode getAPredecessor() {
74+
successors(result, this.(SyntheticDestructorCall).getAccess()) and
75+
not result instanceof SyntheticDestructorCall
76+
}
77+
78+
ControlFlowNode getSuccessor() {
79+
successors(this.getCall(max(int i | exists(this.getCall(i)))), result)
80+
}
81+
}
82+
83+
private class PrematureScopeExitNode extends ControlFlowNodeBase {
84+
PrematureScopeExitNode() {
85+
this instanceof JumpStmt
86+
or
87+
this instanceof Handler
88+
or
89+
this instanceof ThrowExpr
90+
or
91+
this instanceof ReturnStmt
92+
or
93+
this instanceof MicrosoftTryExceptStmt
94+
or
95+
// Detecting exception edges out of a MicrosoftTryExceptStmt is not
96+
// implemented. It may not be easy to do. It'll be something like finding
97+
// the first synthetic destructor call that crosses out of the scope of the
98+
// statement and does not belong to some other `PrematureScopeExitNode`.
99+
// Note that the exception destructors after __try can follow right after
100+
// ordinary cleanup from the __finally block.
101+
this instanceof MicrosoftTryFinallyStmt
102+
}
103+
104+
SyntheticDestructorBlock getSyntheticDestructorBlock() {
105+
result.getAPredecessor() = this
106+
or
107+
// StmtExpr not handled properly here.
108+
result.getAPredecessor().(Expr).getParent+() = this.(ReturnStmt)
109+
or
110+
// Only handles post-order conditions. Won't work with
111+
// short-circuiting operators.
112+
falsecond_base(this.(MicrosoftTryExceptStmt).getCondition(),
113+
result.(SyntheticDestructorCall).getAccess())
114+
}
115+
}
116+
117+
private class DestructedVariable extends LocalScopeVariable {
118+
DestructedVariable() {
119+
exists(SyntheticDestructorCall call | call.getAccess().getTarget() = this)
120+
}
121+
122+
/**
123+
* Gets the single destructor call that that corresponds to falling off the
124+
* end of the scope of this variable.
125+
*/
126+
SyntheticDestructorCall getOrdinaryCall() {
127+
exists(SyntheticDestructorBlock block |
128+
block.getCall(_) = result and
129+
not exists(PrematureScopeExitNode exit | exit.getSyntheticDestructorBlock() = block) and
130+
result.getAccess().getTarget() = this
131+
|
132+
falsecond_base(getDeclaringLoop().getCondition(), block.getCall(0).getAccess())
133+
or
134+
not exists(this.getDeclaringLoop())
135+
)
136+
}
137+
138+
predicate hasPositionInScope(int x, int y, Stmt scope) {
139+
exists(DeclStmt declStmt |
140+
this = declStmt.getDeclaration(y) and
141+
declStmt = scope.getChild(x)
142+
)
143+
or
144+
exists(ConditionDeclExpr decl |
145+
this = decl.getVariable() and
146+
// These coordinates are chosen to place the destruction correctly
147+
// relative to the destruction of other variables declared in
148+
// `decl.getParent()`. Only a `for` loop can have other declarations in
149+
// it. These show up as a `DeclStmt` with `x = 0`, so by choosing `x = 1`
150+
// here we get the `ConditionDeclExpr` placed after all variables
151+
// declared in the init statement of the `for` loop.
152+
x = 1 and
153+
y = 0 and
154+
scope = decl.getParent()
155+
)
156+
or
157+
exists(CatchBlock cb |
158+
this = cb.getParameter() and
159+
scope = cb and
160+
// A `CatchBlock` is a `Block`, so there might be other variables
161+
// declared in it. These coordinates are chosen to place the Parameter
162+
// before any such declarations.
163+
x = -1 and
164+
y = 0
165+
)
166+
}
167+
168+
SyntheticDestructorCall getInnerScopeCall() {
169+
exists(SyntheticDestructorBlock block |
170+
block.getCall(_) = result and
171+
not exists(PrematureScopeExitNode exit | exit.getSyntheticDestructorBlock() = block) and
172+
result.getAccess().getTarget() = this
173+
|
174+
exists(Loop loop | loop = this.getDeclaringLoop() |
175+
not falsecond_base(loop.getCondition(), block.getCall(0).getAccess())
176+
)
177+
)
178+
}
179+
180+
predicate hasPositionInInnerScope(int x, int y, ControlFlowNodeBase scope) {
181+
exists(ConditionDeclExpr decl |
182+
this = decl.getVariable() and
183+
// These coordinates are chosen to place the destruction correctly
184+
// relative to the destruction of other variables in `scope`. Only in the
185+
// `while` case can there be other variables in `scope`, and in that case
186+
// `scope` will be a `Block`, whose smallest `x` coordinate can be 0.
187+
x = -1 and
188+
y = 0 and
189+
(
190+
scope = decl.getParent().(ForStmt).getUpdate()
191+
or
192+
scope = decl.getParent().(WhileStmt).getStmt()
193+
)
194+
)
195+
}
196+
197+
private Loop getDeclaringLoop() {
198+
exists(ConditionDeclExpr decl | this = decl.getVariable() and result = decl.getParent())
199+
}
200+
}
201+
202+
/**
203+
* Gets the `index`'th synthetic destructor call that should follow `node`. The
204+
* exact placement of that call in the CFG depends on the type of `node` as
205+
* follows:
206+
*
207+
* - `Block`: after ordinary control flow falls off the end of the block
208+
* without jumps or exceptions.
209+
* - `ReturnStmt`: After the statement itself or after its operand (if
210+
* present).
211+
* - `ThrowExpr`: After the `throw` expression or after its operand (if
212+
* present).
213+
* - `JumpStmt` (`BreakStmt`, `ContinueStmt`, `GotoStmt`): after the statement.
214+
* - A `ForStmt`, `WhileStmt`, `SwitchStmt`, or `IfStmt`: after control flow
215+
* falls off the end of the statement without jumping. Destruction can occur
216+
* here for `for`-loops that have an initializer (`for (C x = a; ...; ...)`)
217+
* and for statements whose condition is a `ConditionDeclExpr`
218+
* (`if (C x = a)`).
219+
* - The `getUpdate()` of a `ForStmt`: after the `getUpdate()` expression. This
220+
* can happen when the condition is a `ConditionDeclExpr`
221+
* - `Handler`: On the edge out of the `Handler` for the case where the
222+
* exception was not matched and is propagated to the next handler or
223+
* function exit point.
224+
* - `MicrosoftTryExceptStmt`: After the false-edge out of the `e` in
225+
* `__except(e)`, before propagating the exception up to the next handler or
226+
* function exit point.
227+
* - `MicrosoftTryFinallyStmt`: On the edge following the `__finally` block for
228+
* the case where an exception was thrown and needs to be propagated.
229+
*/
230+
SyntheticDestructorCall getDestructorCallAfterNode(ControlFlowNodeBase node, int index) {
231+
result = rank[index + 1](SyntheticDestructorCall call, DestructedVariable var, int x, int y |
232+
call = var.getOrdinaryCall() and
233+
var.hasPositionInScope(x, y, node)
234+
or
235+
call = var.getInnerScopeCall() and
236+
var.hasPositionInInnerScope(x, y, node)
237+
|
238+
call
239+
order by
240+
x desc, y desc
241+
)
242+
or
243+
exists(SyntheticDestructorBlock block |
244+
node.(PrematureScopeExitNode).getSyntheticDestructorBlock() = block and
245+
result = block.getCall(index)
246+
)
247+
}

cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,13 @@ class RangeBasedForStmt extends Loop, @stmt_range_based_for {
702702
override Expr getCondition() { result = this.getChild(2) }
703703
override Expr getControllingExpr() { result = this.getCondition() }
704704

705+
/**
706+
* Gets a declaration statement that declares first `__begin` and then
707+
* `__end`, initializing them to the values they have before entering the
708+
* desugared loop.
709+
*/
710+
DeclStmt getBeginEndDeclaration() { result = this.getChild(1) }
711+
705712
/**
706713
* Gets the compiler-generated `++__begin` which is the update
707714
* expression of this for statement after desugaring. It will
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
typedef unsigned long size_t;
2+
3+
void * operator new[](size_t count, int arg1, int arg2);
4+
5+
template<typename T>
6+
void callNew(T arg) {
7+
new(2, 3) int[5];
8+
}
9+
10+
void callCallNew() {
11+
callNew(1);
12+
}

0 commit comments

Comments
 (0)