Skip to content

Commit 0814a90

Browse files
authored
Merge pull request #116 from microsoft/powershell-add-empty-completion
PS: Add `Emptiness` completion to get rid of CFG inconsistencies
2 parents a4e1860 + 31f232d commit 0814a90

File tree

4 files changed

+45
-8
lines changed

4 files changed

+45
-8
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ module SuccessorTypes {
108108
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
109109
final override string toString() { result = "exit" }
110110
}
111+
112+
class EmptinessSuccessor extends ConditionalSuccessor, CfgImpl::TEmptinessSuccessor {
113+
EmptinessSuccessor() { this = CfgImpl::TEmptinessSuccessor(value) }
114+
115+
/** Holds if this is an empty successor. */
116+
predicate isEmpty() { value = true }
117+
118+
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
119+
}
111120
}
112121

113122
class Split = Splitting::Split;

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ private newtype TCompletion =
1919
TContinueCompletion() or
2020
TThrowCompletion() or
2121
TExitCompletion() or
22-
TMatchingCompletion(Boolean b)
22+
TMatchingCompletion(Boolean b) or
23+
TEmptinessCompletion(Boolean isEmpty)
2324

2425
private predicate commandThrows(Cmd c, boolean unconditional) {
2526
c.getNamedArgument("ErrorAction").(StringConstExpr).getValue().getValue() = "Stop" and
@@ -68,6 +69,9 @@ abstract class Completion extends TCompletion {
6869
not isMatchingConstant(n, _) and
6970
this = TMatchingCompletion(_)
7071
)
72+
or
73+
mustHaveEmptinessCompletion(n) and
74+
this = TEmptinessCompletion(_)
7175
}
7276

7377
private predicate isValidForSpecific(Ast n) { this.isValidForSpecific0(n) }
@@ -183,6 +187,12 @@ private predicate inMatchingContext(Ast n) {
183187
n = any(CatchClause cc).getACatchType()
184188
}
185189

190+
/**
191+
* Holds if a normal completion of `cfe` must be an emptiness completion. Thats is,
192+
* whether `cfe` determines whether to execute the body of a `foreach` statement.
193+
*/
194+
private predicate mustHaveEmptinessCompletion(Ast n) { n instanceof ForEachStmt }
195+
186196
/**
187197
* A completion that represents normal evaluation of a statement or an
188198
* expression.
@@ -296,3 +306,20 @@ class ExitCompletion extends Completion, TExitCompletion {
296306

297307
override string toString() { result = "exit" }
298308
}
309+
310+
/**
311+
* A completion that represents evaluation of an emptiness test, for example
312+
* a test in a `foreach` statement.
313+
*/
314+
class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
315+
EmptinessCompletion() { this = TEmptinessCompletion(value) }
316+
317+
/** Holds if the emptiness test evaluates to `true`. */
318+
predicate isEmpty() { value = true }
319+
320+
EmptinessCompletion getDual() { result = TEmptinessCompletion(value.booleanNot()) }
321+
322+
override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
323+
324+
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
325+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ module Trees {
404404
final override predicate last(AstNode last, Completion c) {
405405
// Emptiness test exits with no more elements
406406
last = this and
407-
completionIsSimple(c)
407+
c.(EmptinessCompletion).isEmpty()
408408
or
409409
super.last(last, c)
410410
}
@@ -418,7 +418,7 @@ module Trees {
418418
// Emptiness test to variable declaration
419419
pred = this and
420420
first(super.getVarAccess(), succ) and
421-
completionIsSimple(c)
421+
c = any(EmptinessCompletion ec | not ec.isEmpty())
422422
or
423423
// Variable declaration to body
424424
last(super.getVarAccess(), pred, c) and
@@ -779,7 +779,8 @@ private module Cached {
779779
TContinueSuccessor() or
780780
TThrowSuccessor() or
781781
TExitSuccessor() or
782-
TMatchingSuccessor(Boolean b)
782+
TMatchingSuccessor(Boolean b) or
783+
TEmptinessSuccessor(Boolean b)
783784
}
784785

785786
import Cached

powershell/ql/test/library-tests/controlflow/graph/Cfg.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@
276276
| functions.ps1:28:5:28:13 | ...=... | functions.ps1:28:5:28:9 | sum | |
277277
| functions.ps1:28:12:28:13 | 0 | functions.ps1:28:12:28:13 | 0 | |
278278
| functions.ps1:28:12:28:13 | 0 | functions.ps1:29:25:29:33 | numbers | |
279-
| functions.ps1:29:5:32:6 | forach(... in ...) | functions.ps1:29:14:29:21 | number | |
280-
| functions.ps1:29:5:32:6 | forach(... in ...) | functions.ps1:33:5:33:9 | sum | |
279+
| functions.ps1:29:5:32:6 | forach(... in ...) | functions.ps1:29:14:29:21 | number | non-empty |
280+
| functions.ps1:29:5:32:6 | forach(... in ...) | functions.ps1:33:5:33:9 | sum | empty |
281281
| functions.ps1:29:14:29:21 | number | functions.ps1:29:35:32:6 | {...} | |
282282
| functions.ps1:29:25:29:33 | numbers | functions.ps1:29:5:32:6 | forach(... in ...) | |
283283
| functions.ps1:29:25:29:33 | numbers | functions.ps1:29:25:29:33 | numbers | |
@@ -480,8 +480,8 @@
480480
| loops.ps1:51:5:51:11 | ...=... | loops.ps1:51:5:51:7 | a | |
481481
| loops.ps1:51:10:51:11 | 0 | loops.ps1:51:10:51:11 | 0 | |
482482
| loops.ps1:51:10:51:11 | 0 | loops.ps1:52:25:52:37 | letterArray | |
483-
| loops.ps1:52:5:55:6 | forach(... in ...) | loops.ps1:49:23:56:2 | exit {...} (normal) | |
484-
| loops.ps1:52:5:55:6 | forach(... in ...) | loops.ps1:52:14:52:21 | letter | |
483+
| loops.ps1:52:5:55:6 | forach(... in ...) | loops.ps1:49:23:56:2 | exit {...} (normal) | empty |
484+
| loops.ps1:52:5:55:6 | forach(... in ...) | loops.ps1:52:14:52:21 | letter | non-empty |
485485
| loops.ps1:52:14:52:21 | letter | loops.ps1:53:5:55:6 | {...} | |
486486
| loops.ps1:52:25:52:37 | letterArray | loops.ps1:52:5:55:6 | forach(... in ...) | |
487487
| loops.ps1:52:25:52:37 | letterArray | loops.ps1:52:25:52:37 | letterArray | |

0 commit comments

Comments
 (0)