Skip to content

Commit 2abd1f7

Browse files
committed
Go: implement conservative cross-thread dataflow
Steps into captured variables are moved into jumpStep where they always should have been, and the store/load step implementation for channels is completed. For the time being this takes a very conservative approach to identify channels that are likely connected: if there is exactly one receive site and one send site for a field, the two are presumed connected.
1 parent f2fead7 commit 2abd1f7

File tree

4 files changed

+41
-11
lines changed

4 files changed

+41
-11
lines changed

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::Node channel, DataFlow::Node rhs) {
173+
exists(SendStmt send |
174+
send.getChannel() = channel.(DataFlow::ExprNode).asExpr() and
175+
send.getValue() = rhs.(DataFlow::ExprNode).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
/**

0 commit comments

Comments
 (0)