Skip to content

Commit ada5c5a

Browse files
committed
Rust: Allow SSA and some data flow for mutable borrows
1 parent 51ae7c6 commit ada5c5a

File tree

12 files changed

+429
-135
lines changed

12 files changed

+429
-135
lines changed

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

Lines changed: 7 additions & 1 deletion
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

Lines changed: 124 additions & 16 deletions
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

@@ -367,13 +370,41 @@ module Node {
367370
private CallExprBaseCfgNode call_;
368371
private RustDataFlow::ArgumentPosition pos_;
369372

370-
ExprArgumentNode() { isArgumentForCall(n, call_, pos_) }
373+
ExprArgumentNode() {
374+
isArgumentForCall(n, call_, pos_) and
375+
// For receivers in method calls the `ReceiverNode` is the argument.
376+
not call_.(MethodCallExprCfgNode).getReceiver() = n
377+
}
371378

372379
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
373380
call.asCallBaseExprCfgNode() = call_ and pos = pos_
374381
}
375382
}
376383

384+
/**
385+
* The receiver of a method call _after_ any implicit borrow or dereferences
386+
* has taken place.
387+
*/
388+
final class ReceiverNode extends ArgumentNode, TReceiverNode {
389+
private MethodCallExprCfgNode n;
390+
391+
ReceiverNode() { this = TReceiverNode(n, false) }
392+
393+
ExprCfgNode getReceiver() { result = n.getReceiver() }
394+
395+
MethodCallExprCfgNode getMethodCall() { result = n }
396+
397+
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
398+
call.asMethodCallExprCfgNode() = n and pos = TSelfParameterPosition()
399+
}
400+
401+
override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }
402+
403+
override Location getLocation() { result = n.getLocation() }
404+
405+
override string toString() { result = "receiver for " + this.getReceiver() }
406+
}
407+
377408
final class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
378409
private FlowSummaryImpl::Private::SummaryNode receiver;
379410
private RustDataFlow::ArgumentPosition pos_;
@@ -519,6 +550,18 @@ module Node {
519550
override Location getLocation() { result = n.getLocation() }
520551
}
521552

553+
final class ReceiverPostUpdateNode extends PostUpdateNode, TReceiverNode {
554+
private MethodCallExprCfgNode n;
555+
556+
ReceiverPostUpdateNode() { this = TReceiverNode(n, true) }
557+
558+
override Node getPreUpdateNode() { result = TReceiverNode(n, false) }
559+
560+
override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }
561+
562+
override Location getLocation() { result = n.getLocation() }
563+
}
564+
522565
final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
523566
private FlowSummaryNode pre;
524567

@@ -648,6 +691,14 @@ module LocalFlow {
648691
)
649692
or
650693
nodeFrom.asPat().(OrPatCfgNode).getAPat() = nodeTo.asPat()
694+
or
695+
// Simple value step from receiver expression to receiver node, in case
696+
// there is no implicit deref or borrow operation.
697+
nodeFrom.asExpr() = nodeTo.(Node::ReceiverNode).getReceiver()
698+
or
699+
// The dual step of the above, for the post-update nodes.
700+
nodeFrom.(Node::PostUpdateNode).getPreUpdateNode().(Node::ReceiverNode).getReceiver() =
701+
nodeTo.(Node::PostUpdateNode).getPreUpdateNode().asExpr()
651702
}
652703
}
653704

@@ -998,6 +1049,23 @@ predicate lambdaCallExpr(CallExprCfgNode call, LambdaCallKind kind, ExprCfgNode
9981049
exists(kind)
9991050
}
10001051

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

10561124
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
1125+
node instanceof Node::SsaNode or
1126+
node.(Node::FlowSummaryNode).getSummaryNode().isHidden() or
1127+
node instanceof Node::CaptureNode or
1128+
node instanceof Node::ClosureParameterNode or
1129+
node instanceof Node::ReceiverNode or
1130+
node instanceof Node::ReceiverPostUpdateNode
10641131
}
10651132

10661133
predicate neverSkipInPathGraph(Node node) {
@@ -1169,6 +1236,28 @@ module RustDataFlow implements InputSig<Location> {
11691236
node2.(Node::FlowSummaryNode).getSummaryNode())
11701237
}
11711238

1239+
pragma[nomagic]
1240+
private predicate implicitDerefToReceiver(Node node1, Node::ReceiverNode node2, ReferenceContent c) {
1241+
node1.asExpr() = node2.getReceiver() and
1242+
implicitDeref(node2.getMethodCall().getMethodCallExpr()) and
1243+
exists(c)
1244+
}
1245+
1246+
pragma[nomagic]
1247+
private predicate implicitBorrowToReceiver(
1248+
Node node1, Node::ReceiverNode node2, ReferenceContent c
1249+
) {
1250+
node1.asExpr() = node2.getReceiver() and
1251+
implicitBorrow(node2.getMethodCall().getMethodCallExpr()) and
1252+
exists(c)
1253+
}
1254+
1255+
pragma[nomagic]
1256+
private predicate referenceExprToExpr(Node node1, Node node2, ReferenceContent c) {
1257+
node1.asExpr() = node2.asExpr().(RefExprCfgNode).getExpr() and
1258+
exists(c)
1259+
}
1260+
11721261
/**
11731262
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
11741263
* `node1` references an object with a content `c.getAReadContent()` whose
@@ -1251,6 +1340,17 @@ module RustDataFlow implements InputSig<Location> {
12511340
node2.asExpr() = await
12521341
)
12531342
or
1343+
referenceExprToExpr(node2.(PostUpdateNode).getPreUpdateNode(),
1344+
node1.(PostUpdateNode).getPreUpdateNode(), c)
1345+
or
1346+
// Step from receiver expression to receiver node, in case of an implicit
1347+
// dereference.
1348+
implicitDerefToReceiver(node1, node2, c)
1349+
or
1350+
// A read step dual to the store step for implicit borrows.
1351+
implicitBorrowToReceiver(node2.(PostUpdateNode).getPreUpdateNode(),
1352+
node1.(PostUpdateNode).getPreUpdateNode(), c)
1353+
or
12541354
VariableCapture::readStep(node1, c, node2)
12551355
)
12561356
or
@@ -1327,11 +1427,7 @@ module RustDataFlow implements InputSig<Location> {
13271427
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase()
13281428
)
13291429
or
1330-
exists(RefExprCfgNode ref |
1331-
c instanceof ReferenceContent and
1332-
node1.asExpr() = ref.getExpr() and
1333-
node2.asExpr() = ref
1334-
)
1430+
referenceExprToExpr(node1, node2, c)
13351431
or
13361432
// Store in function argument
13371433
exists(DataFlowCall call, int i |
@@ -1341,6 +1437,10 @@ module RustDataFlow implements InputSig<Location> {
13411437
)
13421438
or
13431439
VariableCapture::storeStep(node1, c, node2)
1440+
or
1441+
// Step from receiver expression to receiver node, in case of an implicit
1442+
// borrow.
1443+
implicitBorrowToReceiver(node1, node2, c)
13441444
}
13451445

13461446
/**
@@ -1612,9 +1712,16 @@ private module Cached {
16121712
TPatNode(PatCfgNode p) or
16131713
TNameNode(NameCfgNode n) { n.getName() = any(Variable v).getName() } or
16141714
TExprPostUpdateNode(ExprCfgNode e) {
1615-
isArgumentForCall(e, _, _) or
1616-
lambdaCallExpr(_, _, e) or
1617-
lambdaCreationExpr(e.getExpr(), _) or
1715+
isArgumentForCall(e, _, _)
1716+
or
1717+
lambdaCallExpr(_, _, e)
1718+
or
1719+
lambdaCreationExpr(e.getExpr(), _)
1720+
or
1721+
// Whenever `&mut e` has a post-update node we also create one for `e`.
1722+
// E.g., for `e` in `f(..., &mut e, ...)` or `*(&mut e) = ...`.
1723+
e = any(RefExprCfgNode ref | ref.isMut() and exists(TExprPostUpdateNode(ref))).getExpr()
1724+
or
16181725
e =
16191726
[
16201727
any(IndexExprCfgNode i).getBase(), any(FieldExprCfgNode access).getExpr(),
@@ -1623,6 +1730,7 @@ private module Cached {
16231730
any(AwaitExprCfgNode a).getExpr()
16241731
]
16251732
} or
1733+
TReceiverNode(MethodCallExprCfgNode mc, Boolean isPost) or
16261734
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
16271735
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
16281736
TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c, _) } or

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

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,7 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
5454
* those that are not borrowed (either explicitly using `& mut`, or
5555
* (potentially) implicit as borrowed receivers in a method call).
5656
*/
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-
}
57+
class SourceVariable = Variable;
7058

7159
predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
7260
(
@@ -76,7 +64,12 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
7664
) and
7765
certain = true
7866
or
79-
capturedCallWrite(_, bb, i, v) and certain = false
67+
(
68+
capturedCallWrite(_, bb, i, v)
69+
or
70+
mutablyBorrows(bb.getNode(i).getAstNode(), v)
71+
) and
72+
certain = false
8073
}
8174

8275
predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) {
@@ -229,6 +222,14 @@ predicate capturedCallWrite(Expr call, BasicBlock bb, int i, Variable v) {
229222
)
230223
}
231224

225+
/** Holds if `v` may be mutably borrowed in `e`. */
226+
private predicate mutablyBorrows(Expr e, Variable v) {
227+
e = any(MethodCallExpr mc).getReceiver() and
228+
e.(VariableAccess).getVariable() = v
229+
or
230+
exists(RefExpr re | re = e and re.isMut() and re.getExpr().(VariableAccess).getVariable() = v)
231+
}
232+
232233
/**
233234
* Holds if a pseudo read of captured variable `v` should be inserted
234235
* at index `i` in exit block `bb`.
@@ -379,6 +380,12 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
379380
none() // handled in `DataFlowImpl.qll` instead
380381
}
381382

383+
predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) {
384+
exists(Variable v, BasicBlock bb, int i |
385+
def.definesAt(v, bb, i) and mutablyBorrows(bb.getNode(i).getAstNode(), v)
386+
)
387+
}
388+
382389
class Parameter = CfgNodes::ParamBaseCfgNode;
383390

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

0 commit comments

Comments
 (0)