Skip to content

Commit 5879e58

Browse files
committed
C#: Account for CFG splitting in AssignableDefinition::getAFirstRead() and AssignableRead::getANextRead()
1 parent f06a20f commit 5879e58

File tree

8 files changed

+138
-52
lines changed

8 files changed

+138
-52
lines changed

csharp/ql/src/semmle/code/csharp/Assignable.qll

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ class AssignableRead extends AssignableAccess {
100100
* - The read of `this.Field` on line 11 is next to the read on line 10.
101101
*/
102102
AssignableRead getANextRead() {
103-
Ssa::Internal::adjacentReadPairSameVar(this, result)
103+
forex(ControlFlow::Node cfn |
104+
cfn = result.getAControlFlowNode() |
105+
Ssa::Internal::adjacentReadPairSameVar(this.getAControlFlowNode(), cfn)
106+
)
104107
}
105108

106109
/**
@@ -502,9 +505,12 @@ class AssignableDefinition extends TAssignableDefinition {
502505
* `AssignableRead.getANextRead()`.
503506
*/
504507
AssignableRead getAFirstRead() {
505-
exists(Ssa::ExplicitDefinition def |
506-
def.getADefinition() = this |
507-
result = def.getAFirstRead()
508+
forex(ControlFlow::Node cfn |
509+
cfn = result.getAControlFlowNode() |
510+
exists(Ssa::ExplicitDefinition def |
511+
result = def.getAFirstReadAtNode(cfn) |
512+
this = def.(Ssa::ExplicitDefinition).getADefinition()
513+
)
508514
)
509515
}
510516

csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -423,20 +423,20 @@ private Ssa::Definition getAnUltimateDefinition(Ssa::Definition def) {
423423
}
424424

425425
/**
426-
* Holds if SSA definition `def` can reach a read `ar`, without passing
426+
* Holds if SSA definition `def` can reach a read at `cfn`, without passing
427427
* through an intermediate dereference that always (`always = true`) or
428428
* maybe (`always = false`) throws a null reference exception.
429429
*/
430-
private predicate defReaches(Ssa::Definition def, AssignableRead ar, boolean always) {
431-
ar = def.getAFirstRead() and
430+
private predicate defReaches(Ssa::Definition def, ControlFlow::Node cfn, boolean always) {
431+
exists(def.getAFirstReadAtNode(cfn)) and
432432
(always = true or always = false)
433433
or
434-
exists(AssignableRead mid |
434+
exists(ControlFlow::Node mid |
435435
defReaches(def, mid, always) |
436-
ar = mid.getANextRead() and
436+
Ssa::Internal::adjacentReadPairSameVar(mid, cfn) and
437437
not mid = any(Dereference d |
438438
if always = true then d.isAlwaysNull(def.getSourceVariable()) else d.isMaybeNull(def, _, _, _, _)
439-
)
439+
).getAControlFlowNode()
440440
)
441441
}
442442

@@ -528,7 +528,7 @@ class Dereference extends G::DereferenceableExpr {
528528
*/
529529
predicate isFirstAlwaysNull(Ssa::SourceVariable v) {
530530
this.isAlwaysNull(v) and
531-
defReaches(v.getAnSsaDefinition(), this, true)
531+
defReaches(v.getAnSsaDefinition(), this.getAControlFlowNode(), true)
532532
}
533533

534534
/**
@@ -551,6 +551,6 @@ class Dereference extends G::DereferenceableExpr {
551551
*/
552552
predicate isFirstMaybeNull(Ssa::Definition def, SourcePathNode source, SinkPathNode sink, string msg, Element reason) {
553553
this.isMaybeNull(def, source, sink, msg, reason) and
554-
defReaches(def, this, false)
554+
defReaches(def, this.getAControlFlowNode(), false)
555555
}
556556
}

csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -760,16 +760,16 @@ module Ssa {
760760

761761
private cached module Cached {
762762
/**
763-
* Holds if `read` is a last read of the non-trivial SSA definition `def`.
764-
* That is, `read` can reach the end of the enclosing callable, or another
763+
* Holds if `cfn` is a last read of the non-trivial SSA definition `def`.
764+
* That is, `cfn` can reach the end of the enclosing callable, or another
765765
* SSA definition for the underlying source variable, without passing through
766766
* another read.
767767
*/
768768
cached
769-
predicate lastRead(TrackedDefinition def, AssignableRead read) {
769+
predicate lastRead(TrackedDefinition def, ControlFlow::Node cfn) {
770770
exists(TrackedVar v, BasicBlock bb, int i, int rnk |
771-
read = def.getARead() and
772-
variableRead(bb, i, v, read.getAControlFlowNode(), _) and
771+
exists(def.getAReadAtNode(cfn)) and
772+
variableRead(bb, i, v, cfn, _) and
773773
rnk = ssaRefRank(bb, i, v, SsaRead()) |
774774
// Next reference to `v` inside `bb` is a write
775775
rnk + 1 = ssaRefRank(bb, _, v, SsaDef())
@@ -854,27 +854,29 @@ module Ssa {
854854
}
855855

856856
/**
857-
* Holds if the value defined at non-trivial SSA definition `def` can reach `read`
858-
* without passing through any other read.
857+
* Holds if the value defined at non-trivial SSA definition `def` can reach a
858+
* read at `cfn`, without passing through any other read.
859859
*/
860860
cached
861-
predicate firstReadSameVar(TrackedDefinition def, AssignableRead read) {
861+
predicate firstReadSameVar(TrackedDefinition def, ControlFlow::Node cfn) {
862862
exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 |
863863
adjacentVarRefs(v, b1, i1, b2, i2) and
864864
definesAt(def, b1, i1, v) and
865-
variableRead(b2, i2, v, read.getAControlFlowNode(), _)
865+
variableRead(b2, i2, v, cfn, _)
866866
)
867867
}
868868

869869
/**
870-
* INTERNAL: Use `AssignableRead.getANextRead()` instead.
870+
* Holds if the read at `cfn2` is a read of the same SSA definition as the
871+
* read at `cfn1`, and `cfn2` can be reached from `cfn1` without passing
872+
* through another read.
871873
*/
872874
cached
873-
predicate adjacentReadPairSameVar(AssignableRead read1, AssignableRead read2) {
875+
predicate adjacentReadPairSameVar(ControlFlow::Node cfn1, ControlFlow::Node cfn2) {
874876
exists(TrackedVar v, BasicBlock bb1, int i1, BasicBlock bb2, int i2 |
875877
adjacentVarRefs(v, bb1, i1, bb2, i2) and
876-
variableRead(bb1, i1, v, read1.getAControlFlowNode(), _) and
877-
variableRead(bb2, i2, v, read2.getAControlFlowNode(), _)
878+
variableRead(bb1, i1, v, cfn1, _) and
879+
variableRead(bb2, i2, v, cfn2, _)
878880
)
879881
}
880882
}
@@ -2074,8 +2076,45 @@ module Ssa {
20742076
* Subsequent reads can be found by following the steps defined by
20752077
* `AssignableRead.getANextRead()`.
20762078
*/
2077-
AssignableRead getAFirstRead() {
2078-
firstReadSameVar(this, result)
2079+
AssignableRead getAFirstRead() { result = this.getAFirstReadAtNode(_) }
2080+
2081+
/**
2082+
* Gets a read of the source variable underlying this SSA definition at
2083+
* control flow node `cfn` that can be reached from this SSA definition
2084+
* without passing through any other SSA definition or read. Example:
2085+
*
2086+
* ```
2087+
* int Field;
2088+
*
2089+
* void SetField(int i) {
2090+
* this.Field = i;
2091+
* Use(this.Field);
2092+
* if (i > 0)
2093+
* this.Field = i - 1;
2094+
* else if (i < 0)
2095+
* SetField(1);
2096+
* Use(this.Field);
2097+
* Use(this.Field);
2098+
* }
2099+
* ```
2100+
*
2101+
* - The read of `i` on line 4 can be reached from the explicit SSA
2102+
* definition (wrapping an implicit entry definition) on line 3.
2103+
* - The reads of `i` on lines 6 and 7 are not the first reads of any SSA
2104+
* definition.
2105+
* - The read of `this.Field` on line 5 can be reached from the explicit SSA
2106+
* definition on line 4.
2107+
* - The read of `this.Field` on line 10 can be reached from the phi node
2108+
* between lines 9 and 10.
2109+
* - The read of `this.Field` on line 11 is not the first read of any SSA
2110+
* definition.
2111+
*
2112+
* Subsequent reads can be found by following the steps defined by
2113+
* `AssignableRead.getANextRead()`.
2114+
*/
2115+
AssignableRead getAFirstReadAtNode(ControlFlow::Node cfn) {
2116+
firstReadSameVar(this, cfn) and
2117+
result.getAControlFlowNode() = cfn
20792118
}
20802119

20812120
/**
@@ -2106,8 +2145,39 @@ module Ssa {
21062145
* - The read of `this.Field` on line 11 is a last read of the phi node
21072146
* between lines 9 and 10.
21082147
*/
2109-
AssignableRead getALastRead() {
2110-
lastRead(this, result)
2148+
AssignableRead getALastRead() { result = this.getALastReadAtNode(_) }
2149+
2150+
/**
2151+
* Gets a last read of the source variable underlying this SSA definition at
2152+
* control flow node `cfn`. That is, a read that can reach the end of the
2153+
* enclosing callable, or another SSA definition for the source variable,
2154+
* without passing through any other read. Example:
2155+
*
2156+
* ```
2157+
* int Field;
2158+
*
2159+
* void SetField(int i) {
2160+
* this.Field = i;
2161+
* Use(this.Field);
2162+
* if (i > 0)
2163+
* this.Field = i - 1;
2164+
* else if (i < 0)
2165+
* SetField(1);
2166+
* Use(this.Field);
2167+
* Use(this.Field);
2168+
* }
2169+
* ```
2170+
*
2171+
* - The reads of `i` on lines 7 and 8 are the last reads for the implicit
2172+
* parameter definition on line 3.
2173+
* - The read of `this.Field` on line 5 is a last read of the definition on
2174+
* line 4.
2175+
* - The read of `this.Field` on line 11 is a last read of the phi node
2176+
* between lines 9 and 10.
2177+
*/
2178+
AssignableRead getALastReadAtNode(ControlFlow::Node cfn) {
2179+
lastRead(this, cfn) and
2180+
result.getAControlFlowNode() = cfn
21112181
}
21122182

21132183
/**

csharp/ql/test/library-tests/dataflow/ssa/DefAdjacentRead.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@
136136
| Properties.cs:75:23:75:23 | b | Properties.cs:75:23:75:35 | Action b = ... | Properties.cs:80:9:80:9 | access to local variable b |
137137
| Properties.cs:106:37:106:37 | p | Properties.cs:106:37:106:37 | p | Properties.cs:106:42:106:42 | access to parameter p |
138138
| Splitting.cs:3:18:3:18 | b | Splitting.cs:3:18:3:18 | b | Splitting.cs:6:13:6:13 | access to parameter b |
139-
| Splitting.cs:5:13:5:13 | x | Splitting.cs:7:13:7:19 | ... = ... | Splitting.cs:13:9:13:9 | access to local variable x |
140139
| Splitting.cs:5:13:5:13 | x | Splitting.cs:10:13:10:19 | ... = ... | Splitting.cs:11:13:11:13 | access to local variable x |
141140
| Splitting.cs:22:18:22:18 | b | Splitting.cs:22:18:22:18 | b | Splitting.cs:25:13:25:13 | access to parameter b |
142141
| Splitting.cs:24:13:24:13 | x | Splitting.cs:29:13:29:19 | ... = ... | Splitting.cs:30:13:30:13 | access to local variable x |
Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
11
defReadInconsistency
2-
| Splitting.cs:13:9:13:9 | access to local variable x | Splitting.cs:7:13:7:19 | ... = ... | Splitting.cs:5:13:5:13 | x | false |
32
readReadInconsistency
4-
| Splitting.cs:11:13:11:13 | access to local variable x | Splitting.cs:13:9:13:9 | access to local variable x | Splitting.cs:5:13:5:13 | x | false |
53
phiInconsistency
6-
| Splitting.cs:13:9:13:21 | ...; | Splitting.cs:7:13:7:19 | ... = ... | Splitting.cs:5:13:5:13 | x | true |
7-
| Splitting.cs:13:9:13:21 | ...; | Splitting.cs:10:13:10:19 | ... = ... | Splitting.cs:5:13:5:13 | x | true |
8-
| Splitting.cs:52:9:53:22 | if (...) ... | Splitting.cs:46:13:46:19 | ... = ... | Splitting.cs:44:13:44:13 | x | true |
9-
| Splitting.cs:52:9:53:22 | if (...) ... | Splitting.cs:49:13:49:19 | ... = ... | Splitting.cs:44:13:44:13 | x | true |
10-
| Splitting.cs:54:9:54:21 | ...; | Splitting.cs:46:13:46:19 | ... = ... | Splitting.cs:44:13:44:13 | x | false |
11-
| Splitting.cs:54:9:54:21 | ...; | Splitting.cs:49:13:49:19 | ... = ... | Splitting.cs:44:13:44:13 | x | false |

csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import csharp
22
import ControlFlow::Internal
33

4-
query predicate defReadInconsistency(
4+
class CallableWithSplitting extends Callable {
5+
CallableWithSplitting() {
6+
this = any(SplitControlFlowElement e).getEnclosingCallable()
7+
}
8+
}
9+
10+
query
11+
predicate defReadInconsistency(
512
AssignableRead ar, Expr e, PreSsa::SimpleAssignable a, boolean b
613
) {
14+
// Exclude definitions in callables with CFG splitting, as SSA definitions may be
15+
// very different from pre-SSA definitions
16+
not ar.getEnclosingCallable() instanceof CallableWithSplitting and
717
exists(AssignableDefinition def | e = def.getExpr() |
818
b = true and
919
exists(PreSsa::Definition ssaDef | ssaDef.getAssignable() = a |
@@ -31,21 +41,33 @@ query predicate defReadInconsistency(
3141
query predicate readReadInconsistency(
3242
LocalScopeVariableRead read1, LocalScopeVariableRead read2, PreSsa::SimpleAssignable a, boolean b
3343
) {
34-
b = true and
35-
a = read1.getTarget() and
36-
PreSsa::adjacentReadPairSameVar(read1, read2) and
37-
not Ssa::Internal::adjacentReadPairSameVar(read1, read2)
38-
or
39-
b = false and
40-
a = read1.getTarget() and
41-
Ssa::Internal::adjacentReadPairSameVar(read1, read2) and
42-
read1.getTarget() instanceof PreSsa::SimpleAssignable and
43-
not PreSsa::adjacentReadPairSameVar(read1, read2)
44+
// Exclude definitions in callables with CFG splitting, as SSA definitions may be
45+
// very different from pre-SSA definitions
46+
not read1.getEnclosingCallable() instanceof CallableWithSplitting and
47+
(
48+
b = true and
49+
a = read1.getTarget() and
50+
PreSsa::adjacentReadPairSameVar(read1, read2) and
51+
not Ssa::Internal::adjacentReadPairSameVar(read1.getAControlFlowNode(), read2.getAControlFlowNode())
52+
or
53+
b = false and
54+
a = read1.getTarget() and
55+
Ssa::Internal::adjacentReadPairSameVar(read1.getAControlFlowNode(), read2.getAControlFlowNode()) and
56+
read1.getTarget() instanceof PreSsa::SimpleAssignable and
57+
not PreSsa::adjacentReadPairSameVar(read1, read2) and
58+
// Exclude split CFG elements because SSA may be more precise than pre-SSA
59+
// in those cases
60+
not read1 instanceof SplitControlFlowElement and
61+
not read2 instanceof SplitControlFlowElement
62+
)
4463
}
4564

4665
query predicate phiInconsistency(
4766
ControlFlowElement cfe, Expr e, PreSsa::SimpleAssignable a, boolean b
4867
) {
68+
// Exclude definitions in callables with CFG splitting, as SSA definitions may be
69+
// very different from pre-SSA definitions
70+
not cfe.getEnclosingCallable() instanceof CallableWithSplitting and
4971
exists(AssignableDefinition adef | e = adef.getExpr() |
5072
b = true and
5173
exists(PreSsa::Definition def | a = def.getAssignable() |

csharp/ql/test/library-tests/dataflow/ssa/ReadAdjacentRead.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@
9999
| Properties.cs:104:16:104:20 | Props | Properties.cs:115:21:115:30 | access to field Props | Properties.cs:116:17:116:26 | access to field Props |
100100
| Properties.cs:104:16:104:20 | Props | Properties.cs:115:21:115:36 | access to field Props | Properties.cs:116:17:116:32 | access to field Props |
101101
| Splitting.cs:3:18:3:18 | b | Splitting.cs:6:13:6:13 | access to parameter b | Splitting.cs:15:13:15:13 | access to parameter b |
102-
| Splitting.cs:5:13:5:13 | x | Splitting.cs:11:13:11:13 | access to local variable x | Splitting.cs:13:9:13:9 | access to local variable x |
103102
| Splitting.cs:5:13:5:13 | x | Splitting.cs:13:9:13:9 | access to local variable x | Splitting.cs:14:9:14:9 | access to local variable x |
104103
| Splitting.cs:5:13:5:13 | x | Splitting.cs:14:9:14:9 | access to local variable x | Splitting.cs:17:13:17:13 | access to local variable x |
105104
| Splitting.cs:22:18:22:18 | b | Splitting.cs:25:13:25:13 | access to parameter b | Splitting.cs:35:13:35:13 | access to parameter b |

csharp/ql/test/library-tests/dataflow/ssa/SsaDefLastRead.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,11 @@
217217
| Properties.cs:114:20:114:35 | this.Props.Props | Properties.cs:113:9:113:22 | SSA call def(this.Props.Props) | Properties.cs:116:17:116:32 | access to field Props |
218218
| Properties.cs:115:21:115:39 | this.Props.Props.xs | Properties.cs:113:9:113:22 | SSA qualifier def(this.Props.Props.xs) | Properties.cs:116:17:116:35 | access to property xs |
219219
| Splitting.cs:3:18:3:18 | b | Splitting.cs:3:18:3:18 | SSA param(b) | Splitting.cs:15:13:15:13 | access to parameter b |
220-
| Splitting.cs:5:13:5:13 | x | Splitting.cs:7:13:7:19 | [b (line 3): true] SSA def(x) | Splitting.cs:14:9:14:9 | access to local variable x |
221220
| Splitting.cs:5:13:5:13 | x | Splitting.cs:7:13:7:19 | [b (line 3): true] SSA def(x) | Splitting.cs:17:13:17:13 | access to local variable x |
222221
| Splitting.cs:5:13:5:13 | x | Splitting.cs:10:13:10:19 | [b (line 3): false] SSA def(x) | Splitting.cs:14:9:14:9 | access to local variable x |
223222
| Splitting.cs:22:18:22:18 | b | Splitting.cs:22:18:22:18 | SSA param(b) | Splitting.cs:35:13:35:13 | access to parameter b |
224223
| Splitting.cs:24:13:24:13 | x | Splitting.cs:29:13:29:19 | [b (line 22): false] SSA def(x) | Splitting.cs:30:13:30:13 | access to local variable x |
225224
| Splitting.cs:24:13:24:13 | x | Splitting.cs:32:9:32:15 | [b (line 22): false] SSA def(x) | Splitting.cs:34:9:34:9 | access to local variable x |
226-
| Splitting.cs:24:13:24:13 | x | Splitting.cs:32:9:32:15 | [b (line 22): true] SSA def(x) | Splitting.cs:34:9:34:9 | access to local variable x |
227225
| Splitting.cs:24:13:24:13 | x | Splitting.cs:32:9:32:15 | [b (line 22): true] SSA def(x) | Splitting.cs:37:13:37:13 | access to local variable x |
228226
| Splitting.cs:42:18:42:18 | b | Splitting.cs:42:18:42:18 | SSA param(b) | Splitting.cs:52:13:52:13 | access to parameter b |
229227
| Splitting.cs:44:13:44:13 | x | Splitting.cs:49:13:49:19 | [b (line 42): false] SSA def(x) | Splitting.cs:50:13:50:13 | access to local variable x |

0 commit comments

Comments
 (0)