Skip to content

Commit 04f8010

Browse files
authored
Merge pull request #84 from microsoft/powershell-cfg-skeleton
PS: Initial CFG skeleton
2 parents 33ccf3f + f21cde2 commit 04f8010

25 files changed

+1463
-45
lines changed

powershell/ql/lib/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ groups:
66
dbscheme: semmlecode.powershell.dbscheme
77
extractor: powershell
88
library: true
9+
dependencies:
10+
codeql/controlflow: ${workspace}
911
warnOnImplicitThis: true

powershell/ql/lib/semmle/code/powershell/AssignmentStatement.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ class AssignStmt extends @assignment_statement, Stmt {
99

1010
Stmt getRightHandSide() { assignment_statement(this, _, _, result) }
1111

12-
override string toString() { result = "AssignmentStatement at: " + this.getLocation().toString() }
12+
override string toString() { result = "...=..." }
1313
}

powershell/ql/lib/semmle/code/powershell/Ast.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import powershell
33
class Ast extends @ast {
44
string toString() { none() }
55

6-
Ast getParent() { parent(result, this) }
6+
Ast getParent() { parent(this, result) } // TODO: Flip parent and child in relation in the extractor
77

88
Location getLocation() { none() }
99
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** Provides classes representing the control flow graph. */
2+
3+
import controlflow.ControlFlowGraph
4+
import controlflow.CfgNodes as CfgNodes
5+
import controlflow.BasicBlocks

powershell/ql/lib/semmle/code/powershell/CommandExpression.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import powershell
33
class CmdExpr extends @command_expression, CmdBase {
44
override SourceLocation getLocation() { command_expression_location(this, result) }
55

6-
Expr getExpression() { command_expression(this, result, _) }
6+
Expr getExpr() { command_expression(this, result, _) }
77

88
int getNumRedirections() { command_expression(this, _, result) }
99

1010
Redirection getRedirection(int i) { command_expression_redirection(this, i, result) }
1111

1212
Redirection getARedirection() { result = this.getRedirection(_) }
1313

14-
override string toString() { result = "CommandExpression at: " + this.getLocation().toString() }
14+
override string toString() { result = this.getExpr().toString() }
1515
}

powershell/ql/lib/semmle/code/powershell/ConstantExpression.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ class ConstExpr extends @constant_expression, BaseConstExpr {
77

88
StringLiteral getValue() { constant_expression_value(this, result) }
99

10-
override string toString() { result = "ConstantExpression at: " + this.getLocation().toString() }
10+
override string toString() { result = this.getValue().toString() }
1111
}

powershell/ql/lib/semmle/code/powershell/ScriptBlock.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import powershell
22

33
class ScriptBlock extends @script_block, Ast {
4-
override string toString() { result = "ScriptBlock at: " + this.getLocation().toString() }
4+
override string toString() { result = this.getLocation().getFile().getBaseName() }
55

66
override SourceLocation getLocation() { script_block_location(this, result) }
77

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/** Provides classes representing basic blocks. */
2+
3+
private import powershell
4+
private import ControlFlowGraph
5+
private import CfgNodes
6+
private import SuccessorTypes
7+
8+
/**
9+
* A basic block, that is, a maximal straight-line sequence of control flow nodes
10+
* without branches or joins.
11+
*/
12+
class BasicBlock extends TBasicBlockStart {
13+
/** Gets the scope of this basic block. */
14+
final CfgScope getScope() { result = this.getFirstNode().getScope() }
15+
16+
/** Gets an immediate successor of this basic block, if any. */
17+
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
18+
19+
/** Gets an immediate successor of this basic block of a given type, if any. */
20+
BasicBlock getASuccessor(SuccessorType t) {
21+
result.getFirstNode() = this.getLastNode().getASuccessor(t)
22+
}
23+
24+
/** Gets an immediate predecessor of this basic block, if any. */
25+
BasicBlock getAPredecessor() { result.getASuccessor() = this }
26+
27+
/** Gets an immediate predecessor of this basic block of a given type, if any. */
28+
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
29+
30+
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
31+
CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
32+
33+
/** Gets a control flow node in this basic block. */
34+
CfgNode getANode() { result = this.getNode(_) }
35+
36+
/** Gets the first control flow node in this basic block. */
37+
CfgNode getFirstNode() { this = TBasicBlockStart(result) }
38+
39+
/** Gets the last control flow node in this basic block. */
40+
CfgNode getLastNode() { result = this.getNode(this.length() - 1) }
41+
42+
/** Gets the length of this basic block. */
43+
int length() { result = strictcount(this.getANode()) }
44+
45+
/**
46+
* Holds if this basic block immediately dominates basic block `bb`.
47+
*
48+
* That is, all paths reaching basic block `bb` from some entry point
49+
* basic block must go through this basic block (which is an immediate
50+
* predecessor of `bb`).
51+
*/
52+
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
53+
54+
/**
55+
* Holds if this basic block strictly dominates basic block `bb`.
56+
*
57+
* That is, all paths reaching basic block `bb` from some entry point
58+
* basic block must go through this basic block (which must be different
59+
* from `bb`).
60+
*/
61+
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
62+
63+
/**
64+
* Holds if this basic block dominates basic block `bb`.
65+
*
66+
* That is, all paths reaching basic block `bb` from some entry point
67+
* basic block must go through this basic block.
68+
*/
69+
predicate dominates(BasicBlock bb) {
70+
bb = this or
71+
this.strictlyDominates(bb)
72+
}
73+
74+
/**
75+
* Holds if `df` is in the dominance frontier of this basic block.
76+
* That is, this basic block dominates a predecessor of `df`, but
77+
* does not dominate `df` itself.
78+
*/
79+
predicate inDominanceFrontier(BasicBlock df) {
80+
this.dominatesPredecessor(df) and
81+
not this.strictlyDominates(df)
82+
}
83+
84+
/**
85+
* Holds if this basic block dominates a predecessor of `df`.
86+
*/
87+
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
88+
89+
/**
90+
* Gets the basic block that immediately dominates this basic block, if any.
91+
*
92+
* That is, all paths reaching this basic block from some entry point
93+
* basic block must go through the result, which is an immediate basic block
94+
* predecessor of this basic block.
95+
*/
96+
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
97+
98+
/**
99+
* Holds if this basic block strictly post-dominates basic block `bb`.
100+
*
101+
* That is, all paths reaching a normal exit point basic block from basic
102+
* block `bb` must go through this basic block (which must be different
103+
* from `bb`).
104+
*/
105+
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
106+
107+
/**
108+
* Holds if this basic block post-dominates basic block `bb`.
109+
*
110+
* That is, all paths reaching a normal exit point basic block from basic
111+
* block `bb` must go through this basic block.
112+
*/
113+
predicate postDominates(BasicBlock bb) {
114+
this.strictlyPostDominates(bb) or
115+
this = bb
116+
}
117+
118+
/** Holds if this basic block is in a loop in the control flow graph. */
119+
predicate inLoop() { this.getASuccessor+() = this }
120+
121+
/** Gets a textual representation of this basic block. */
122+
string toString() { result = this.getFirstNode().toString() }
123+
124+
/** Gets the location of this basic block. */
125+
Location getLocation() { result = this.getFirstNode().getLocation() }
126+
}
127+
128+
cached
129+
private module Cached {
130+
/** Internal representation of basic blocks. */
131+
cached
132+
newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) }
133+
134+
/** Holds if `cfn` starts a new basic block. */
135+
private predicate startsBB(CfgNode cfn) {
136+
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
137+
or
138+
cfn.isJoin()
139+
or
140+
cfn.getAPredecessor().isBranch()
141+
or
142+
exists(cfn.getAPredecessor(any(SuccessorTypes::ConditionalSuccessor s)))
143+
}
144+
145+
/**
146+
* Holds if `succ` is a control flow successor of `pred` within
147+
* the same basic block.
148+
*/
149+
private predicate intraBBSucc(CfgNode pred, CfgNode succ) {
150+
succ = pred.getASuccessor() and
151+
not startsBB(succ)
152+
}
153+
154+
/**
155+
* Holds if `cfn` is the `i`th node in basic block `bb`.
156+
*
157+
* In other words, `i` is the shortest distance from a node `bb`
158+
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
159+
*/
160+
cached
161+
predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) =
162+
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
163+
164+
/**
165+
* Holds if the first node of basic block `succ` is a control flow
166+
* successor of the last node of basic block `pred`.
167+
*/
168+
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
169+
170+
/** Holds if `dom` is an immediate dominator of `bb`. */
171+
cached
172+
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
173+
idominance(entryBB/1, succBB/2)(_, dom, bb)
174+
175+
/** Holds if `pred` is a basic block predecessor of `succ`. */
176+
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
177+
178+
/** Holds if `bb` is an exit basic block that represents normal exit. */
179+
private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() }
180+
181+
/** Holds if `dom` is an immediate post-dominator of `bb`. */
182+
cached
183+
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
184+
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
185+
186+
cached
187+
predicate immediatelyControls(ConditionBlock cb, BasicBlock succ, ConditionalSuccessor s) {
188+
succ = cb.getASuccessor(s) and
189+
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != cb | succ.dominates(pred))
190+
}
191+
192+
cached
193+
predicate controls(ConditionBlock cb, BasicBlock controlled, ConditionalSuccessor s) {
194+
exists(BasicBlock succ | cb.immediatelyControls(succ, s) | succ.dominates(controlled))
195+
}
196+
}
197+
198+
private import Cached
199+
200+
/** Holds if `bb` is an entry basic block. */
201+
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode }
202+
203+
/**
204+
* An entry basic block, that is, a basic block whose first node is
205+
* an entry node.
206+
*/
207+
class EntryBasicBlock extends BasicBlock {
208+
EntryBasicBlock() { entryBB(this) }
209+
}
210+
211+
/**
212+
* An annotated exit basic block, that is, a basic block whose last node is
213+
* an annotated exit node.
214+
*/
215+
class AnnotatedExitBasicBlock extends BasicBlock {
216+
private boolean normal;
217+
218+
AnnotatedExitBasicBlock() {
219+
exists(AnnotatedExitNode n |
220+
n = this.getANode() and
221+
if n.isNormal() then normal = true else normal = false
222+
)
223+
}
224+
225+
/** Holds if this block represent a normal exit. */
226+
final predicate isNormal() { normal = true }
227+
}
228+
229+
/**
230+
* An exit basic block, that is, a basic block whose last node is
231+
* an exit node.
232+
*/
233+
class ExitBasicBlock extends BasicBlock {
234+
ExitBasicBlock() { this.getLastNode() instanceof ExitNode }
235+
}
236+
237+
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
238+
class ConditionBlock extends BasicBlock {
239+
ConditionBlock() { this.getLastNode().isCondition() }
240+
241+
/**
242+
* Holds if basic block `succ` is immediately controlled by this basic
243+
* block with conditional value `s`. That is, `succ` is an immediate
244+
* successor of this block, and `succ` can only be reached from
245+
* the callable entry point by going via the `s` edge out of this basic block.
246+
*/
247+
predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
248+
immediatelyControls(this, succ, s)
249+
}
250+
251+
/**
252+
* Holds if basic block `controlled` is controlled by this basic block with
253+
* conditional value `s`. That is, `controlled` can only be reached from
254+
* the callable entry point by going via the `s` edge out of this basic block.
255+
*/
256+
predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
257+
controls(this, controlled, s)
258+
}
259+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** Provides classes representing the control flow graph. */
2+
3+
import ControlFlowGraph
4+
import CfgNodes as CfgNodes
5+
import BasicBlocks
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/** Provides classes representing nodes in a control flow graph. */
2+
3+
private import powershell
4+
private import BasicBlocks
5+
private import ControlFlowGraph
6+
private import internal.ControlFlowGraphImpl as CfgImpl
7+
8+
/** An entry node for a given scope. */
9+
class EntryNode extends CfgNode, CfgImpl::EntryNode {
10+
override string getAPrimaryQlClass() { result = "EntryNode" }
11+
12+
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
13+
}
14+
15+
/** An exit node for a given scope, annotated with the type of exit. */
16+
class AnnotatedExitNode extends CfgNode, CfgImpl::AnnotatedExitNode {
17+
override string getAPrimaryQlClass() { result = "AnnotatedExitNode" }
18+
19+
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
20+
}
21+
22+
/** An exit node for a given scope. */
23+
class ExitNode extends CfgNode, CfgImpl::ExitNode {
24+
override string getAPrimaryQlClass() { result = "ExitNode" }
25+
}
26+
27+
/**
28+
* A node for an AST node.
29+
*
30+
* Each AST node maps to zero or more `AstCfgNode`s: zero when the node is unreachable
31+
* (dead) code or not important for control flow, and multiple when there are different
32+
* splits for the AST node.
33+
*/
34+
class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode {
35+
/** Gets the name of the primary QL class for this node. */
36+
override string getAPrimaryQlClass() { result = "AstCfgNode" }
37+
}
38+
39+
/** A control-flow node that wraps an AST expression. */
40+
class ExprCfgNode extends AstCfgNode {
41+
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
42+
43+
Expr e;
44+
45+
ExprCfgNode() { e = this.getAstNode() }
46+
47+
/** Gets the underlying expression. */
48+
Expr getExpr() { result = e }
49+
}
50+
51+
/** Provides classes for control-flow nodes that wrap AST expressions. */
52+
module ExprNodes { }

0 commit comments

Comments
 (0)