Skip to content

Commit 0d1865d

Browse files
authored
Merge pull request #18872 from paldepind/rust-ref-mut
Rust: Allow SSA and some data flow for mutable borrows
2 parents 96c0ca8 + 1225c5c commit 0d1865d

File tree

20 files changed

+1030
-453
lines changed

20 files changed

+1030
-453
lines changed

rust/ql/lib/codeql/rust/dataflow/internal/DataFlowConsistency.qll

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ private module Input implements InputSig<Location, RustDataFlow> {
1111
not exists(n.asExpr().getLocation())
1212
}
1313

14-
predicate postWithInFlowExclude(RustDataFlow::Node n) { n instanceof Node::FlowSummaryNode }
14+
predicate postWithInFlowExclude(RustDataFlow::Node n) {
15+
n instanceof Node::FlowSummaryNode
16+
or
17+
// We allow flow into post-update node for receiver expressions (from the
18+
// synthetic post receiever node).
19+
n.(Node::PostUpdateNode).getPreUpdateNode().asExpr() = any(Node::ReceiverNode r).getReceiver()
20+
}
1521

1622
predicate missingLocationExclude(RustDataFlow::Node n) { not exists(n.asExpr().getLocation()) }
1723
}

rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll

+128-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
private import codeql.util.Void
66
private import codeql.util.Unit
7+
private import codeql.util.Boolean
78
private import codeql.dataflow.DataFlow
89
private import codeql.dataflow.internal.DataFlowImpl
910
private import rust
@@ -96,6 +97,8 @@ final class ParameterPosition extends TParameterPosition {
9697
/** Gets the underlying integer position, if any. */
9798
int getPosition() { this = TPositionalParameterPosition(result) }
9899

100+
predicate hasPosition() { exists(this.getPosition()) }
101+
99102
/** Holds if this position represents the `self` position. */
100103
predicate isSelf() { this = TSelfParameterPosition() }
101104

@@ -130,21 +133,21 @@ private predicate callToMethod(CallExpr call) {
130133
)
131134
}
132135

133-
/** Holds if `arg` is an argument of `call` at the position `pos`. */
136+
/**
137+
* Holds if `arg` is an argument of `call` at the position `pos`.
138+
*
139+
* Note that this does not hold for the receiever expression of a method call
140+
* as the synthetic `ReceiverNode` is the argument for the `self` parameter.
141+
*/
134142
private predicate isArgumentForCall(ExprCfgNode arg, CallExprBaseCfgNode call, ParameterPosition pos) {
135143
if callToMethod(call.(CallExprCfgNode).getCallExpr())
136-
then (
144+
then
137145
// The first argument is for the `self` parameter
138146
arg = call.getArgument(0) and pos.isSelf()
139147
or
140148
// Succeeding arguments are shifted left
141149
arg = call.getArgument(pos.getPosition() + 1)
142-
) else (
143-
// The self argument in a method call.
144-
arg = call.(MethodCallExprCfgNode).getReceiver() and pos.isSelf()
145-
or
146-
arg = call.getArgument(pos.getPosition())
147-
)
150+
else arg = call.getArgument(pos.getPosition())
148151
}
149152

150153
/**
@@ -374,6 +377,30 @@ module Node {
374377
}
375378
}
376379

380+
/**
381+
* The receiver of a method call _after_ any implicit borrow or dereferencing
382+
* has taken place.
383+
*/
384+
final class ReceiverNode extends ArgumentNode, TReceiverNode {
385+
private MethodCallExprCfgNode n;
386+
387+
ReceiverNode() { this = TReceiverNode(n, false) }
388+
389+
ExprCfgNode getReceiver() { result = n.getReceiver() }
390+
391+
MethodCallExprCfgNode getMethodCall() { result = n }
392+
393+
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
394+
call.asMethodCallExprCfgNode() = n and pos = TSelfParameterPosition()
395+
}
396+
397+
override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }
398+
399+
override Location getLocation() { result = this.getReceiver().getLocation() }
400+
401+
override string toString() { result = "receiver for " + this.getReceiver() }
402+
}
403+
377404
final class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
378405
private FlowSummaryImpl::Private::SummaryNode receiver;
379406
private RustDataFlow::ArgumentPosition pos_;
@@ -519,6 +546,18 @@ module Node {
519546
override Location getLocation() { result = n.getLocation() }
520547
}
521548

549+
final class ReceiverPostUpdateNode extends PostUpdateNode, TReceiverNode {
550+
private MethodCallExprCfgNode n;
551+
552+
ReceiverPostUpdateNode() { this = TReceiverNode(n, true) }
553+
554+
override Node getPreUpdateNode() { result = TReceiverNode(n, false) }
555+
556+
override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }
557+
558+
override Location getLocation() { result = n.getReceiver().getLocation() }
559+
}
560+
522561
final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
523562
private FlowSummaryNode pre;
524563

@@ -648,6 +687,14 @@ module LocalFlow {
648687
)
649688
or
650689
nodeFrom.asPat().(OrPatCfgNode).getAPat() = nodeTo.asPat()
690+
or
691+
// Simple value step from receiver expression to receiver node, in case
692+
// there is no implicit deref or borrow operation.
693+
nodeFrom.asExpr() = nodeTo.(Node::ReceiverNode).getReceiver()
694+
or
695+
// The dual step of the above, for the post-update nodes.
696+
nodeFrom.(Node::PostUpdateNode).getPreUpdateNode().(Node::ReceiverNode).getReceiver() =
697+
nodeTo.(Node::PostUpdateNode).getPreUpdateNode().asExpr()
651698
}
652699
}
653700

@@ -998,6 +1045,23 @@ predicate lambdaCallExpr(CallExprCfgNode call, LambdaCallKind kind, ExprCfgNode
9981045
exists(kind)
9991046
}
10001047

1048+
/** Holds if `mc` implicitly borrows its receiver. */
1049+
private predicate implicitBorrow(MethodCallExpr mc) {
1050+
// Determining whether an implicit borrow happens depends on the type of the
1051+
// receiever as well as the target. As a heuristic we simply check if the
1052+
// target takes `self` as a borrow and limit the approximation to cases where
1053+
// the receiver is a simple variable.
1054+
mc.getReceiver() instanceof VariableAccess and
1055+
mc.getStaticTarget().getParamList().getSelfParam().isRef()
1056+
}
1057+
1058+
/** Holds if `mc` implicitly dereferences its receiver. */
1059+
private predicate implicitDeref(MethodCallExpr mc) {
1060+
// Similarly to `implicitBorrow` this is an approximation.
1061+
mc.getReceiver() instanceof VariableAccess and
1062+
not mc.getStaticTarget().getParamList().getSelfParam().isRef()
1063+
}
1064+
10011065
// Defines a set of aliases needed for the `RustDataFlow` module
10021066
private module Aliases {
10031067
class DataFlowCallableAlias = DataFlowCallable;
@@ -1054,13 +1118,12 @@ module RustDataFlow implements InputSig<Location> {
10541118
DataFlowType getNodeType(Node node) { any() }
10551119

10561120
predicate nodeIsHidden(Node node) {
1057-
node instanceof Node::SsaNode
1058-
or
1059-
node.(Node::FlowSummaryNode).getSummaryNode().isHidden()
1060-
or
1061-
node instanceof Node::CaptureNode
1062-
or
1063-
node instanceof Node::ClosureParameterNode
1121+
node instanceof Node::SsaNode or
1122+
node.(Node::FlowSummaryNode).getSummaryNode().isHidden() or
1123+
node instanceof Node::CaptureNode or
1124+
node instanceof Node::ClosureParameterNode or
1125+
node instanceof Node::ReceiverNode or
1126+
node instanceof Node::ReceiverPostUpdateNode
10641127
}
10651128

10661129
predicate neverSkipInPathGraph(Node node) {
@@ -1169,6 +1232,28 @@ module RustDataFlow implements InputSig<Location> {
11691232
node2.(Node::FlowSummaryNode).getSummaryNode())
11701233
}
11711234

1235+
pragma[nomagic]
1236+
private predicate implicitDerefToReceiver(Node node1, Node::ReceiverNode node2, ReferenceContent c) {
1237+
node1.asExpr() = node2.getReceiver() and
1238+
implicitDeref(node2.getMethodCall().getMethodCallExpr()) and
1239+
exists(c)
1240+
}
1241+
1242+
pragma[nomagic]
1243+
private predicate implicitBorrowToReceiver(
1244+
Node node1, Node::ReceiverNode node2, ReferenceContent c
1245+
) {
1246+
node1.asExpr() = node2.getReceiver() and
1247+
implicitBorrow(node2.getMethodCall().getMethodCallExpr()) and
1248+
exists(c)
1249+
}
1250+
1251+
pragma[nomagic]
1252+
private predicate referenceExprToExpr(Node node1, Node node2, ReferenceContent c) {
1253+
node1.asExpr() = node2.asExpr().(RefExprCfgNode).getExpr() and
1254+
exists(c)
1255+
}
1256+
11721257
/**
11731258
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
11741259
* `node1` references an object with a content `c.getAReadContent()` whose
@@ -1251,6 +1336,17 @@ module RustDataFlow implements InputSig<Location> {
12511336
node2.asExpr() = await
12521337
)
12531338
or
1339+
referenceExprToExpr(node2.(PostUpdateNode).getPreUpdateNode(),
1340+
node1.(PostUpdateNode).getPreUpdateNode(), c)
1341+
or
1342+
// Step from receiver expression to receiver node, in case of an implicit
1343+
// dereference.
1344+
implicitDerefToReceiver(node1, node2, c)
1345+
or
1346+
// A read step dual to the store step for implicit borrows.
1347+
implicitBorrowToReceiver(node2.(PostUpdateNode).getPreUpdateNode(),
1348+
node1.(PostUpdateNode).getPreUpdateNode(), c)
1349+
or
12541350
VariableCapture::readStep(node1, c, node2)
12551351
)
12561352
or
@@ -1327,11 +1423,7 @@ module RustDataFlow implements InputSig<Location> {
13271423
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase()
13281424
)
13291425
or
1330-
exists(RefExprCfgNode ref |
1331-
c instanceof ReferenceContent and
1332-
node1.asExpr() = ref.getExpr() and
1333-
node2.asExpr() = ref
1334-
)
1426+
referenceExprToExpr(node1, node2, c)
13351427
or
13361428
// Store in function argument
13371429
exists(DataFlowCall call, int i |
@@ -1341,6 +1433,10 @@ module RustDataFlow implements InputSig<Location> {
13411433
)
13421434
or
13431435
VariableCapture::storeStep(node1, c, node2)
1436+
or
1437+
// Step from receiver expression to receiver node, in case of an implicit
1438+
// borrow.
1439+
implicitBorrowToReceiver(node1, node2, c)
13441440
}
13451441

13461442
/**
@@ -1612,17 +1708,25 @@ private module Cached {
16121708
TPatNode(PatCfgNode p) or
16131709
TNameNode(NameCfgNode n) { n.getName() = any(Variable v).getName() } or
16141710
TExprPostUpdateNode(ExprCfgNode e) {
1615-
isArgumentForCall(e, _, _) or
1616-
lambdaCallExpr(_, _, e) or
1617-
lambdaCreationExpr(e.getExpr(), _) or
1711+
isArgumentForCall(e, _, _)
1712+
or
1713+
lambdaCallExpr(_, _, e)
1714+
or
1715+
lambdaCreationExpr(e.getExpr(), _)
1716+
or
1717+
// Whenever `&mut e` has a post-update node we also create one for `e`.
1718+
// E.g., for `e` in `f(..., &mut e, ...)` or `*(&mut e) = ...`.
1719+
e = any(RefExprCfgNode ref | ref.isMut() and exists(TExprPostUpdateNode(ref))).getExpr()
1720+
or
16181721
e =
16191722
[
16201723
any(IndexExprCfgNode i).getBase(), any(FieldExprCfgNode access).getExpr(),
16211724
any(TryExprCfgNode try).getExpr(),
16221725
any(PrefixExprCfgNode pe | pe.getOperatorName() = "*").getExpr(),
1623-
any(AwaitExprCfgNode a).getExpr()
1726+
any(AwaitExprCfgNode a).getExpr(), any(MethodCallExprCfgNode mc).getReceiver()
16241727
]
16251728
} or
1729+
TReceiverNode(MethodCallExprCfgNode mc, Boolean isPost) or
16261730
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
16271731
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
16281732
TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c, _) } or

rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll

+26-21
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,7 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
4747

4848
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
4949

50-
/**
51-
* A variable amenable to SSA construction.
52-
*
53-
* All immutable variables are amenable. Mutable variables are restricted to
54-
* those that are not borrowed (either explicitly using `& mut`, or
55-
* (potentially) implicit as borrowed receivers in a method call).
56-
*/
57-
class SourceVariable extends Variable {
58-
SourceVariable() {
59-
this.isMutable()
60-
implies
61-
not exists(VariableAccess va | va = this.getAnAccess() |
62-
va = any(RefExpr re | re.isMut()).getExpr()
63-
or
64-
// receivers can be borrowed implicitly, cf.
65-
// https://doc.rust-lang.org/reference/expressions/method-call-expr.html
66-
va = any(MethodCallExpr mce).getReceiver()
67-
)
68-
}
69-
}
50+
class SourceVariable = Variable;
7051

7152
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
7253
(
@@ -76,7 +57,12 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
7657
) and
7758
certain = true
7859
or
79-
capturedCallWrite(_, bb, i, v) and certain = false
60+
(
61+
capturedCallWrite(_, bb, i, v)
62+
or
63+
mutablyBorrows(bb.getNode(i).getAstNode(), v)
64+
) and
65+
certain = false
8066
}
8167

8268
predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) {
@@ -229,6 +215,14 @@ predicate capturedCallWrite(Expr call, BasicBlock bb, int i, Variable v) {
229215
)
230216
}
231217

218+
/** Holds if `v` may be mutably borrowed in `e`. */
219+
private predicate mutablyBorrows(Expr e, Variable v) {
220+
e = any(MethodCallExpr mc).getReceiver() and
221+
e.(VariableAccess).getVariable() = v
222+
or
223+
exists(RefExpr re | re = e and re.isMut() and re.getExpr().(VariableAccess).getVariable() = v)
224+
}
225+
232226
/**
233227
* Holds if a pseudo read of captured variable `v` should be inserted
234228
* at index `i` in exit block `bb`.
@@ -379,6 +373,17 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
379373
none() // handled in `DataFlowImpl.qll` instead
380374
}
381375

376+
predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) {
377+
exists(CfgNodes::CallExprBaseCfgNode call, Variable v, BasicBlock bb, int i |
378+
def.definesAt(v, bb, i) and
379+
mutablyBorrows(bb.getNode(i).getAstNode(), v)
380+
|
381+
call.getArgument(_) = bb.getNode(i)
382+
or
383+
call.(CfgNodes::MethodCallExprCfgNode).getReceiver() = bb.getNode(i)
384+
)
385+
}
386+
382387
class Parameter = CfgNodes::ParamBaseCfgNode;
383388

384389
/** Holds if SSA definition `def` initializes parameter `p` at function entry. */

0 commit comments

Comments
 (0)