Skip to content

Commit 79bae0c

Browse files
authored
Merge pull request #9999 from github/smowton/feature/golang-channel-flow
Go: implement conservative cross-thread dataflow
2 parents eaf3aa7 + 9f82088 commit 79bae0c

File tree

6 files changed

+46
-13
lines changed

6 files changed

+46
-13
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Fixed data-flow to captured variable references.
5+
* We now assume that if a channel-typed field is only referred to twice in the user codebase, once in a send operation and once in a receive, then data flows from the send to the receive statement. This enables finding some cross-goroutine flow.

go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,21 @@ module ControlFlow {
166166
)
167167
}
168168

169+
/**
170+
* Holds if this node writes `rhs` to `channel`.
171+
*/
172+
predicate writesToChannel(DataFlow::ExprNode channel, DataFlow::ExprNode rhs) {
173+
exists(SendStmt send |
174+
send.getChannel() = channel.asExpr() and
175+
send.getValue() = rhs.asExpr()
176+
)
177+
}
178+
169179
/**
170180
* Holds if this node sets any field or element of `base` to `rhs`.
171181
*/
172182
predicate writesComponent(DataFlow::Node base, DataFlow::Node rhs) {
173-
writesElement(base, _, rhs) or writesField(base, _, rhs)
183+
writesElement(base, _, rhs) or writesField(base, _, rhs) or writesToChannel(base, rhs)
174184
}
175185
}
176186

go/ql/lib/semmle/go/dataflow/internal/ContainerFlow.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ predicate containerStoreStep(Node node1, Node node2, Content c) {
2424
)
2525
or
2626
c instanceof CollectionContent and
27-
exists(SendStmt send |
28-
send.getChannel() = node2.(ExprNode).asExpr() and send.getValue() = node1.(ExprNode).asExpr()
29-
)
27+
exists(Write w | w.writesToChannel(node2, node1))
3028
or
3129
c instanceof MapKeyContent and
3230
node2.getType() instanceof MapType and

go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,7 @@ module Public {
634634
predicate isReceiverOf(MethodDecl m) { parm.isReceiverOf(m) }
635635
}
636636

637-
private Node getADirectlyWrittenNode() {
638-
exists(Write w | w.writesField(result, _, _) or w.writesElement(result, _, _))
639-
}
637+
private Node getADirectlyWrittenNode() { exists(Write w | w.writesComponent(result, _)) }
640638

641639
private DataFlow::Node getAccessPathPredecessor(DataFlow::Node node) {
642640
result = node.(PointerDereferenceNode).getOperand()

go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,7 @@ predicate basicLocalFlowStep(Node nodeFrom, Node nodeTo) {
7070
)
7171
or
7272
// SSA -> SSA
73-
exists(SsaDefinition pred, SsaDefinition succ |
74-
succ.(SsaVariableCapture).getSourceVariable() = pred.(SsaExplicitDefinition).getSourceVariable() or
75-
succ.(SsaPseudoDefinition).getAnInput() = pred
76-
|
73+
exists(SsaDefinition pred, SsaPseudoDefinition succ | succ.getAnInput() = pred |
7774
nodeFrom = ssaNode(pred) and
7875
nodeTo = ssaNode(succ)
7976
)
@@ -90,6 +87,12 @@ predicate basicLocalFlowStep(Node nodeFrom, Node nodeTo) {
9087
any(GlobalFunctionNode fn | fn.getFunction() = nodeTo.asExpr().(FunctionName).getTarget())
9188
}
9289

90+
pragma[noinline]
91+
private Field getASparselyUsedChannelTypedField() {
92+
result.getType() instanceof ChanType and
93+
count(result.getARead()) = 2
94+
}
95+
9396
/**
9497
* Holds if data can flow from `node1` to `node2` in a way that loses the
9598
* calling context. For example, this would happen with flow through a
@@ -102,6 +105,27 @@ predicate jumpStep(Node n1, Node n2) {
102105
w.writes(v, n1) and
103106
n2 = v.getARead()
104107
)
108+
or
109+
exists(SsaDefinition pred, SsaDefinition succ |
110+
succ.(SsaVariableCapture).getSourceVariable() = pred.(SsaExplicitDefinition).getSourceVariable()
111+
|
112+
n1 = ssaNode(pred) and
113+
n2 = ssaNode(succ)
114+
)
115+
or
116+
// If a channel-typed field is referenced exactly once in the context of
117+
// a send statement and once in a receive expression, assume the two are linked.
118+
exists(
119+
Field f, DataFlow::ReadNode recvRead, DataFlow::ReadNode sendRead, RecvExpr re, SendStmt ss
120+
|
121+
f = getASparselyUsedChannelTypedField() and
122+
recvRead = f.getARead() and
123+
sendRead = f.getARead() and
124+
recvRead.asExpr() = re.getOperand() and
125+
sendRead.asExpr() = ss.getChannel() and
126+
n1.(DataFlow::PostUpdateNode).getPreUpdateNode() = sendRead and
127+
n2 = recvRead
128+
)
105129
}
106130

107131
/**

go/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@
6060
| main.go:11:14:11:14 | z | main.go:11:9:11:15 | type conversion |
6161
| main.go:14:6:14:10 | function test2 | main.go:34:8:34:12 | test2 |
6262
| main.go:14:6:14:10 | function test2 | main.go:34:19:34:23 | test2 |
63-
| main.go:15:2:15:4 | definition of acc | main.go:16:9:16:9 | capture variable acc |
6463
| main.go:15:9:15:9 | 0 | main.go:15:2:15:4 | definition of acc |
6564
| main.go:16:9:16:9 | capture variable acc | main.go:17:3:17:5 | acc |
66-
| main.go:17:3:17:7 | definition of acc | main.go:16:9:16:9 | capture variable acc |
6765
| main.go:17:3:17:7 | definition of acc | main.go:18:10:18:12 | acc |
6866
| main.go:17:3:17:7 | rhs of increment statement | main.go:17:3:17:7 | definition of acc |
6967
| main.go:22:12:22:12 | argument corresponding to b | main.go:22:12:22:12 | definition of b |

0 commit comments

Comments
 (0)