Skip to content

Commit 0ddb702

Browse files
authored
Merge pull request #284 from hvitved/csharp/null-guards
C#: Teach null-guards library about pattern matching
2 parents 6e10f39 + 603c3d6 commit 0ddb702

File tree

16 files changed

+487
-65
lines changed

16 files changed

+487
-65
lines changed

csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class ConstantNullnessCondition extends ConstantCondition {
7979
cfn = this.getAControlFlowNode() |
8080
exists(ControlFlow::SuccessorTypes::NullnessSuccessor t |
8181
exists(cfn.getASuccessorByType(t)) |
82-
if t.isNull() then b = true else b = false
82+
b = t.getValue()
8383
) and
8484
strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
8585
)
@@ -102,7 +102,7 @@ class ConstantMatchingCondition extends ConstantCondition {
102102
cfn = this.getAControlFlowNode() |
103103
exists(ControlFlow::SuccessorTypes::MatchingSuccessor t |
104104
exists(cfn.getASuccessorByType(t)) |
105-
if t.isMatch() then b = true else b = false
105+
b = t.getValue()
106106
) and
107107
strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
108108
)

csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ abstract class StructuralComparisonConfiguration extends string {
8585
value = x.getValue()
8686
}
8787

88+
pragma [nomagic]
8889
private predicate sameByStructure(Element x, Element y) {
8990
// At least one of `x` and `y` must not have a value, they must have
9091
// the same kind, and the same number of children
@@ -121,6 +122,7 @@ abstract class StructuralComparisonConfiguration extends string {
121122
not (x.(Expr).hasValue() and y.(Expr).hasValue())
122123
}
123124

125+
pragma [nomagic]
124126
private predicate sameInternal(Element x, Element y) {
125127
sameByValue(x, y)
126128
or
@@ -210,6 +212,7 @@ module Internal {
210212
value = x.getValue()
211213
}
212214

215+
pragma [nomagic]
213216
private predicate sameByStructure(Element x, Element y) {
214217
// At least one of `x` and `y` must not have a value, they must have
215218
// the same kind, and the same number of children
@@ -246,6 +249,7 @@ module Internal {
246249
not (x.(Expr).hasValue() and y.(Expr).hasValue())
247250
}
248251

252+
pragma [nomagic]
249253
private predicate sameInternal(Element x, Element y) {
250254
sameByValue(x, y)
251255
or

csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import csharp
6+
private import ControlFlow::SuccessorTypes
67

78
/**
89
* A basic block, that is, a maximal straight-line sequence of control flow nodes
@@ -14,6 +15,11 @@ class BasicBlock extends TBasicBlockStart {
1415
result.getFirstNode() = getLastNode().getASuccessor()
1516
}
1617

18+
/** Gets an immediate successor of this basic block of a given flow type, if any. */
19+
BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) {
20+
result.getFirstNode() = this.getLastNode().getASuccessorByType(t)
21+
}
22+
1723
/** Gets an immediate predecessor of this basic block, if any. */
1824
BasicBlock getAPredecessor() {
1925
result.getASuccessor() = this
@@ -75,6 +81,7 @@ class BasicBlock extends TBasicBlockStart {
7581
* The node on line 2 is an immediate `null` successor of the node
7682
* `x` on line 1.
7783
*/
84+
deprecated
7885
BasicBlock getANullSuccessor() {
7986
result.getFirstNode() = getLastNode().getANullSuccessor()
8087
}
@@ -94,6 +101,7 @@ class BasicBlock extends TBasicBlockStart {
94101
* The node `x?.M()`, representing the call to `M`, is a non-`null` successor
95102
* of the node `x`.
96103
*/
104+
deprecated
97105
BasicBlock getANonNullSuccessor() {
98106
result.getFirstNode() = getLastNode().getANonNullSuccessor()
99107
}
@@ -430,7 +438,7 @@ class ConditionBlock extends BasicBlock {
430438
* the callable entry point by going via the true edge (`testIsTrue = true`)
431439
* or false edge (`testIsTrue = false`) out of this basic block.
432440
*/
433-
predicate controls(BasicBlock controlled, boolean testIsTrue) {
441+
predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
434442
/*
435443
* For this block to control the block `controlled` with `testIsTrue` the following must be true:
436444
* Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
@@ -465,7 +473,7 @@ class ConditionBlock extends BasicBlock {
465473
* directly.
466474
*/
467475
exists(BasicBlock succ |
468-
isCandidateSuccessor(succ, testIsTrue) |
476+
isCandidateSuccessor(succ, s) |
469477
succ.dominates(controlled)
470478
)
471479
}
@@ -476,11 +484,9 @@ class ConditionBlock extends BasicBlock {
476484
* the callable entry point by going via the `null` edge (`isNull = true`)
477485
* or non-`null` edge (`isNull = false`) out of this basic block.
478486
*/
487+
deprecated
479488
predicate controlsNullness(BasicBlock controlled, boolean isNull) {
480-
exists(BasicBlock succ |
481-
isCandidateSuccessorNullness(succ, isNull) |
482-
succ.dominates(controlled)
483-
)
489+
this.controls(controlled, any(NullnessSuccessor s | s.getValue() = isNull))
484490
}
485491

486492
/**
@@ -520,29 +526,11 @@ class ConditionBlock extends BasicBlock {
520526
// only `x & y` controls `A` if we do not take sub conditions into account.
521527
predicate controlsSubCond(BasicBlock controlled, boolean testIsTrue, Expr cond, boolean condIsTrue) {
522528
impliesSub(getLastNode().getElement(), cond, testIsTrue, condIsTrue) and
523-
controls(controlled, testIsTrue)
529+
controls(controlled, any(BooleanSuccessor s | s.getValue() = testIsTrue))
524530
}
525531

526-
private predicate isCandidateSuccessor(BasicBlock succ, boolean testIsTrue) {
527-
(
528-
testIsTrue = true and succ = this.getATrueSuccessor()
529-
or
530-
testIsTrue = false and succ = this.getAFalseSuccessor()
531-
)
532-
and
533-
forall(BasicBlock pred |
534-
pred = succ.getAPredecessor() and pred != this |
535-
succ.dominates(pred)
536-
)
537-
}
538-
539-
private predicate isCandidateSuccessorNullness(BasicBlock succ, boolean isNull) {
540-
(
541-
isNull = true and succ = this.getANullSuccessor()
542-
or
543-
isNull = false and succ = this.getANonNullSuccessor()
544-
)
545-
and
532+
private predicate isCandidateSuccessor(BasicBlock succ, ConditionalSuccessor s) {
533+
succ = this.getASuccessorByType(s) and
546534
forall(BasicBlock pred |
547535
pred = succ.getAPredecessor() and pred != this |
548536
succ.dominates(pred)

csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,21 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen
401401
)
402402
}
403403

404+
/**
405+
* Holds if `cfe` is the element inside case statement `cs`, belonging to `switch`
406+
* statement `ss`, that has the matching completion.
407+
*/
408+
predicate switchMatching(SwitchStmt ss, CaseStmt cs, ControlFlowElement cfe) {
409+
ss.getACase() = cs and
410+
(
411+
cfe = cs.(ConstCase).getExpr()
412+
or
413+
cfe = cs.(TypeCase).getTypeAccess() // use type access to represent the type test
414+
)
415+
}
416+
404417
private predicate mustHaveMatchingCompletion(SwitchStmt ss, ControlFlowElement cfe) {
405-
cfe = ss.getAConstCase().getExpr()
406-
or
407-
cfe = ss.getATypeCase().getTypeAccess() // use type access to represent the type test
418+
switchMatching(ss, _, cfe)
408419
}
409420

410421
/**

csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ module ControlFlow {
255255
* The node on line 2 is an immediate `null` successor of the node
256256
* `x` on line 1.
257257
*/
258+
deprecated
258259
Node getANullSuccessor() {
259260
result = getASuccessorByType(any(NullnessSuccessor t | t.isNull()))
260261
}
@@ -274,6 +275,7 @@ module ControlFlow {
274275
* The node `x?.M()`, representing the call to `M`, is a non-`null` successor
275276
* of the node `x`.
276277
*/
278+
deprecated
277279
Node getANonNullSuccessor() {
278280
result = getASuccessorByType(any(NullnessSuccessor t | not t.isNull()))
279281
}
@@ -400,7 +402,10 @@ module ControlFlow {
400402
* a nullness successor (`NullnessSuccessor`), a matching successor (`MatchingSuccessor`),
401403
* or an emptiness successor (`EmptinessSuccessor`).
402404
*/
403-
abstract class ConditionalSuccessor extends SuccessorType { }
405+
abstract class ConditionalSuccessor extends SuccessorType {
406+
/** Gets the Boolean value of this successor. */
407+
abstract boolean getValue();
408+
}
404409

405410
/**
406411
* A Boolean control flow successor.
@@ -429,8 +434,7 @@ module ControlFlow {
429434
* ```
430435
*/
431436
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
432-
/** Gets the value of this Boolean successor. */
433-
boolean getValue() { this = TBooleanSuccessor(result) }
437+
override boolean getValue() { this = TBooleanSuccessor(result) }
434438

435439
override string toString() { result = getValue().toString() }
436440

@@ -469,6 +473,8 @@ module ControlFlow {
469473
/** Holds if this is a `null` successor. */
470474
predicate isNull() { this = TNullnessSuccessor(true) }
471475

476+
override boolean getValue() { this = TNullnessSuccessor(result) }
477+
472478
override string toString() {
473479
if this.isNull() then
474480
result = "null"
@@ -520,6 +526,8 @@ module ControlFlow {
520526
/** Holds if this is a match successor. */
521527
predicate isMatch() { this = TMatchingSuccessor(true) }
522528

529+
override boolean getValue() { this = TMatchingSuccessor(result) }
530+
523531
override string toString() {
524532
if this.isMatch() then
525533
result = "match"
@@ -571,6 +579,8 @@ module ControlFlow {
571579
/** Holds if this is an empty successor. */
572580
predicate isEmpty() { this = TEmptinessSuccessor(true) }
573581

582+
override boolean getValue() { this = TEmptinessSuccessor(result) }
583+
574584
override string toString() {
575585
if this.isEmpty() then
576586
result = "empty"

0 commit comments

Comments
 (0)