Skip to content

Commit 39977e9

Browse files
authored
Merge pull request github#4724 from hvitved/csharp/cfg/not-pattern
C#: Implement CFG for `not` patterns
2 parents 0381190 + 71d25c1 commit 39977e9

26 files changed

+1360
-395
lines changed

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

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -232,36 +232,57 @@ private Expr getQualifier(QualifiableExpr e) {
232232
result = e.getChildExpr(-1)
233233
}
234234

235+
pragma[noinline]
236+
private predicate typePatternMustHaveMatchingCompletion(
237+
TypePatternExpr tpe, Type t, Type strippedType
238+
) {
239+
exists(Expr e, Expr stripped | mustHaveMatchingCompletion(e, tpe) |
240+
stripped = e.stripCasts() and
241+
t = tpe.getCheckedType() and
242+
strippedType = stripped.getType() and
243+
not t.containsTypeParameters() and
244+
not strippedType.containsTypeParameters()
245+
)
246+
}
247+
248+
pragma[noinline]
249+
private Type typePatternCommonSubTypeLeft(Type t) {
250+
typePatternMustHaveMatchingCompletion(_, t, _) and
251+
result.isImplicitlyConvertibleTo(t) and
252+
not result instanceof DynamicType
253+
}
254+
255+
pragma[noinline]
256+
private Type typePatternCommonSubTypeRight(Type strippedType) {
257+
typePatternMustHaveMatchingCompletion(_, _, strippedType) and
258+
result.isImplicitlyConvertibleTo(strippedType) and
259+
not result instanceof DynamicType
260+
}
261+
262+
pragma[noinline]
263+
private predicate typePatternCommonSubType(Type t, Type strippedType) {
264+
typePatternCommonSubTypeLeft(t) = typePatternCommonSubTypeRight(strippedType)
265+
}
266+
235267
/**
236-
* Holds if expression `e` constantly matches (`value = true`) or constantly
237-
* non-matches (`value = false`).
268+
* Holds if pattern expression `pe` constantly matches (`value = true`) or
269+
* constantly non-matches (`value = false`).
238270
*/
239-
private predicate isMatchingConstant(Expr e, boolean value) {
240-
exists(Switch s | mustHaveMatchingCompletion(s, e) |
241-
exists(Expr stripped | stripped = s.getExpr().stripCasts() |
242-
exists(Case c, string strippedValue |
243-
c = s.getACase() and
244-
e = c.getPattern() and
245-
strippedValue = stripped.getValue()
246-
|
247-
if strippedValue = e.getValue() then value = true else value = false
248-
)
249-
or
250-
exists(Case c, TypePatternExpr tpe, Type t, Type strippedType | c = s.getACase() |
251-
tpe = c.getPattern() and
252-
e = tpe and
253-
t = tpe.getCheckedType() and
254-
strippedType = stripped.getType() and
255-
not t.isImplicitlyConvertibleTo(strippedType) and
256-
not t instanceof Interface and
257-
not t.containsTypeParameters() and
258-
not strippedType.containsTypeParameters() and
259-
value = false
260-
)
261-
)
262-
or
263-
e instanceof DiscardPatternExpr and
264-
value = true
271+
private predicate isMatchingConstant(PatternExpr pe, boolean value) {
272+
exists(Expr e, string exprValue, string patternValue |
273+
mustHaveMatchingCompletion(e, pe) and
274+
exprValue = e.stripCasts().getValue() and
275+
patternValue = pe.getValue() and
276+
if exprValue = patternValue then value = true else value = false
277+
)
278+
or
279+
pe instanceof DiscardPatternExpr and
280+
value = true
281+
or
282+
exists(Type t, Type strippedType |
283+
typePatternMustHaveMatchingCompletion(pe, t, strippedType) and
284+
not typePatternCommonSubType(t, strippedType) and
285+
value = false
265286
)
266287
}
267288

@@ -518,7 +539,20 @@ predicate switchMatching(Switch s, Case c, PatternExpr pe) {
518539
pe = c.getPattern()
519540
}
520541

521-
private predicate mustHaveMatchingCompletion(Switch s, PatternExpr pe) { switchMatching(s, _, pe) }
542+
/**
543+
* Holds if `pe` must have a matching completion, and `e` is the expression
544+
* that is being matched.
545+
*/
546+
private predicate mustHaveMatchingCompletion(Expr e, PatternExpr pe) {
547+
exists(Switch s |
548+
switchMatching(s, _, pe) and
549+
e = s.getExpr()
550+
)
551+
or
552+
pe = any(IsExpr ie | inBooleanContext(ie) and e = ie.getExpr()).getPattern()
553+
or
554+
pe = any(UnaryPatternExpr upe | mustHaveMatchingCompletion(e, upe)).getPattern()
555+
}
522556

523557
/**
524558
* Holds if a normal completion of `cfe` must be a matching completion. Thats is,
@@ -565,7 +599,13 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
565599
* completion (`NullnessCompletion`), a matching completion (`MatchingCompletion`),
566600
* or an emptiness completion (`EmptinessCompletion`).
567601
*/
568-
abstract class ConditionalCompletion extends NonNestedNormalCompletion { }
602+
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
603+
/** Gets the Boolean value of this completion. */
604+
abstract boolean getValue();
605+
606+
/** Gets the dual completion. */
607+
abstract ConditionalCompletion getDual();
608+
}
569609

570610
/**
571611
* A completion that represents evaluation of an expression
@@ -576,10 +616,9 @@ class BooleanCompletion extends ConditionalCompletion {
576616

577617
BooleanCompletion() { this = TBooleanCompletion(value) }
578618

579-
/** Gets the Boolean value of this completion. */
580-
boolean getValue() { result = value }
619+
override boolean getValue() { result = value }
581620

582-
BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
621+
override BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
583622

584623
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
585624

@@ -611,6 +650,10 @@ class NullnessCompletion extends ConditionalCompletion, TNullnessCompletion {
611650
/** Holds if the last sub expression of this expression evaluates to a non-`null` value. */
612651
predicate isNonNull() { value = false }
613652

653+
override boolean getValue() { result = value }
654+
655+
override NullnessCompletion getDual() { result = TNullnessCompletion(value.booleanNot()) }
656+
614657
override NullnessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
615658

616659
override string toString() { if this.isNull() then result = "null" else result = "non-null" }
@@ -631,6 +674,10 @@ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
631674
/** Holds if there is not a match. */
632675
predicate isNonMatch() { value = false }
633676

677+
override boolean getValue() { result = value }
678+
679+
override MatchingCompletion getDual() { result = TMatchingCompletion(value.booleanNot()) }
680+
634681
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
635682

636683
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
@@ -648,6 +695,10 @@ class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
648695
/** Holds if the emptiness test evaluates to `true`. */
649696
predicate isEmpty() { value = true }
650697

698+
override boolean getValue() { result = value }
699+
700+
override EmptinessCompletion getDual() { result = TEmptinessCompletion(value.booleanNot()) }
701+
651702
override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
652703

653704
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }

0 commit comments

Comments
 (0)