Skip to content

Commit 4c59de4

Browse files
committed
PS: Implement CFG for if statements and switches.
1 parent fd29c47 commit 4c59de4

File tree

3 files changed

+164
-10
lines changed

3 files changed

+164
-10
lines changed

powershell/ql/lib/semmle/code/powershell/controlflow/ControlFlowGraph.qll

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,25 @@ module SuccessorTypes {
6969
* A conditional control flow successor. Either a Boolean successor (`BooleanSuccessor`)
7070
* or a matching successor (`MatchingSuccessor`)
7171
*/
72-
class ConditionalSuccessor extends SuccessorType {
72+
abstract class ConditionalSuccessor extends SuccessorType {
7373
boolean value;
7474

75-
ConditionalSuccessor() { this = CfgImpl::TBooleanSuccessor(value) }
75+
bindingset[value]
76+
ConditionalSuccessor() { any() }
7677

7778
/** Gets the Boolean value of this successor. */
7879
final boolean getValue() { result = value }
7980

8081
override string toString() { result = this.getValue().toString() }
8182
}
8283

83-
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
84+
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor {
85+
BooleanSuccessor() { this = CfgImpl::TBooleanSuccessor(value) }
86+
}
87+
88+
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
89+
MatchingSuccessor() { this = CfgImpl::TMatchingSuccessor(value) }
90+
}
8491

8592
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
8693
final override string toString() { result = "return" }
@@ -94,8 +101,8 @@ module SuccessorTypes {
94101
final override string toString() { result = "continue" }
95102
}
96103

97-
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
98-
final override string toString() { result = "raise" }
104+
class ThrowSuccessor extends SuccessorType, CfgImpl::TThrowSuccessor {
105+
final override string toString() { result = "throw" }
99106
}
100107

101108
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {

powershell/ql/lib/semmle/code/powershell/controlflow/internal/Completion.qll

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ private import powershell
88
private import semmle.code.powershell.controlflow.ControlFlowGraph
99
private import ControlFlowGraphImpl as CfgImpl
1010
private import SuccessorTypes
11+
private import codeql.util.Boolean
1112

1213
// TODO: We most likely need a TrapCompletion as well
1314
private newtype TCompletion =
1415
TSimpleCompletion() or
15-
TBooleanCompletion(boolean b) { b in [false, true] } or
16+
TBooleanCompletion(Boolean b) or
1617
TReturnCompletion() or
1718
TBreakCompletion() or
1819
TContinueCompletion() or
1920
TRaiseCompletion() or
20-
TExitCompletion()
21+
TExitCompletion() or
22+
TMatchingCompletion(Boolean b)
2123

2224
pragma[noinline]
2325
private predicate completionIsValidForStmt(Ast n, Completion c) {
@@ -40,6 +42,14 @@ abstract class Completion extends TCompletion {
4042
not isBooleanConstant(n, _) and
4143
this = TBooleanCompletion(_)
4244
)
45+
or
46+
mustHaveMatchingCompletion(n) and
47+
(
48+
exists(boolean value | isMatchingConstant(n, value) | this = TMatchingCompletion(value))
49+
or
50+
not isMatchingConstant(n, _) and
51+
this = TMatchingCompletion(_)
52+
)
4353
}
4454

4555
private predicate isValidForSpecific(Ast n) { this.isValidForSpecific0(n) }
@@ -74,11 +84,18 @@ private predicate isBooleanConstant(Ast n, boolean value) {
7484
none() // TODO
7585
}
7686

87+
private predicate isMatchingConstant(Ast n, boolean value) {
88+
inMatchingContext(n) and
89+
none() // TODO
90+
}
91+
7792
/**
7893
* Holds if a normal completion of `n` must be a Boolean completion.
7994
*/
8095
private predicate mustHaveBooleanCompletion(Ast n) { inBooleanContext(n) }
8196

97+
private predicate mustHaveMatchingCompletion(Ast n) { inMatchingContext(n) }
98+
8299
/**
83100
* Holds if `n` is used in a Boolean context. That is, the value
84101
* that `n` evaluates to determines a true/false branch successor.
@@ -128,6 +145,20 @@ private predicate inBooleanContext(Ast n) {
128145
)
129146
}
130147

148+
/**
149+
* From: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_switch?view=powershell-7.4:
150+
* ```
151+
* switch [-regex | -wildcard | -exact] [-casesensitive] (<test-expression>)
152+
* {
153+
* "string" | number | variable | { <value-scriptblock> } { <action-scriptblock> }
154+
* default { <action-scriptblock> } # optional
155+
* }
156+
* ```
157+
*/
158+
private predicate inMatchingContext(Ast n) {
159+
n = any(SwitchStmt switch).getAPattern()
160+
}
161+
131162
/**
132163
* A completion that represents normal evaluation of a statement or an
133164
* expression.
@@ -159,7 +190,7 @@ abstract class ConditionalCompletion extends NormalCompletion {
159190
* A completion that represents evaluation of an expression
160191
* with a Boolean value.
161192
*/
162-
class BooleanCompletion extends ConditionalCompletion, NormalCompletion, TBooleanCompletion {
193+
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
163194
BooleanCompletion() { this = TBooleanCompletion(value) }
164195

165196
/** Gets the dual Boolean completion. */
@@ -180,6 +211,18 @@ class FalseCompletion extends BooleanCompletion {
180211
FalseCompletion() { this.getValue() = false }
181212
}
182213

214+
class MatchCompletion extends ConditionalCompletion, TMatchingCompletion {
215+
MatchCompletion() { this = TMatchingCompletion(value) }
216+
217+
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
218+
219+
predicate isMatch() { this.getValue() = true }
220+
221+
predicate isNonMatch() { this.getValue() = false }
222+
223+
override string toString() { if this.isMatch() then result = "match" else result = "nonmatch" }
224+
}
225+
183226
/**
184227
* A completion that represents evaluation of a statement or an
185228
* expression resulting in a return.

powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
private import powershell
77
private import codeql.controlflow.Cfg as CfgShared
8+
private import codeql.util.Boolean
89
private import semmle.code.powershell.controlflow.ControlFlowGraph
910
private import Completion
1011

@@ -418,6 +419,108 @@ module Trees {
418419
}
419420
}
420421

422+
class IfStmtTree extends PreOrderTree instanceof IfStmt {
423+
final override predicate propagatesAbnormal(AstNode child) {
424+
child = super.getACondition()
425+
or
426+
child = super.getAThen()
427+
or
428+
child = super.getElse()
429+
}
430+
431+
final override predicate last(AstNode last, Completion c) {
432+
last(super.getAThen(), last, c)
433+
or
434+
last(super.getElse(), last, c)
435+
}
436+
437+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
438+
this = pred and
439+
first(super.getCondition(0), succ) and
440+
completionIsSimple(c)
441+
or
442+
exists(int i, boolean value |
443+
last(super.getCondition(i), pred, c) and value = c.(BooleanCompletion).getValue()
444+
|
445+
value = true and
446+
first(super.getThen(i), succ)
447+
or
448+
value = false and
449+
(
450+
first(super.getCondition(i + 1), succ)
451+
or
452+
i = super.getNumberOfConditions() - 1 and
453+
first(super.getElse(), succ)
454+
)
455+
)
456+
}
457+
}
458+
459+
class SwitchStmtTree extends PreOrderTree instanceof SwitchStmt {
460+
final override predicate propagatesAbnormal(AstNode child) {
461+
child = super.getCondition()
462+
or
463+
child = super.getACase()
464+
or
465+
child = super.getDefault()
466+
or
467+
child = super.getAPattern()
468+
}
469+
470+
final override predicate last(AstNode last, Completion c) {
471+
// There are no cases and no default
472+
not exists(super.getACase()) and
473+
not exists(super.getDefault()) and
474+
last(super.getCondition(), last, c) and
475+
completionIsNormal(c)
476+
or
477+
// The last element can be the last statement in the default block
478+
last(super.getDefault(), last, c)
479+
or
480+
// ... or any of the last elements in a case block
481+
last(super.getACase(), last, c)
482+
or
483+
// No default and we reached the final pattern and failed to match
484+
not exists(super.getDefault()) and
485+
last(super.getPattern(super.getNumberOfCases() - 1), last, c) and
486+
c.(MatchCompletion).isNonMatch()
487+
}
488+
489+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
490+
// Preorder: Flow from the switch to the condition
491+
pred = this and
492+
first(super.getCondition(), succ) and
493+
completionIsSimple(c)
494+
or
495+
// Flow from the condition to the first pattern
496+
last(super.getCondition(), pred, c) and
497+
completionIsNormal(c) and
498+
first(super.getPattern(0), succ)
499+
or
500+
// Flow from a match to:
501+
// 1. the corresponding case if the match succeeds, or
502+
// 2. the next pattern if the match failed, or
503+
// 3. the default case if this is the last pattern and the match failed.
504+
exists(int i, boolean match |
505+
last(super.getPattern(i), pred, c) and c.(MatchCompletion).getValue() = match
506+
|
507+
// Case 1
508+
match = true and
509+
first(super.getCase(i), succ)
510+
or
511+
match = false and
512+
(
513+
// Case 2
514+
first(super.getPattern(i + 1), succ)
515+
or
516+
// Case 3
517+
i = super.getNumberOfCases() - 1 and
518+
first(super.getDefault(), succ)
519+
)
520+
)
521+
}
522+
}
523+
421524
class GotoStmtTree extends LeafTree instanceof GotoStmt { }
422525

423526
class FunctionStmtTree extends LeafTree instanceof Function { }
@@ -470,12 +573,13 @@ private module Cached {
470573
cached
471574
newtype TSuccessorType =
472575
TSuccessorSuccessor() or
473-
TBooleanSuccessor(boolean b) { b in [false, true] } or
576+
TBooleanSuccessor(Boolean b) or
474577
TReturnSuccessor() or
475578
TBreakSuccessor() or
476579
TContinueSuccessor() or
477580
TRaiseSuccessor() or
478-
TExitSuccessor()
581+
TExitSuccessor() or
582+
TMatchingSuccessor(Boolean b)
479583
}
480584

481585
import Cached

0 commit comments

Comments
 (0)