Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust: Allow SSA and some data flow for mutable borrows #18872

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ private module Input implements InputSig<Location, RustDataFlow> {
not exists(n.asExpr().getLocation())
}

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

predicate missingLocationExclude(RustDataFlow::Node n) { not exists(n.asExpr().getLocation()) }
}
Expand Down
140 changes: 124 additions & 16 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

private import codeql.util.Void
private import codeql.util.Unit
private import codeql.util.Boolean
private import codeql.dataflow.DataFlow
private import codeql.dataflow.internal.DataFlowImpl
private import rust
Expand Down Expand Up @@ -96,6 +97,8 @@ final class ParameterPosition extends TParameterPosition {
/** Gets the underlying integer position, if any. */
int getPosition() { this = TPositionalParameterPosition(result) }

predicate hasPosition() { exists(this.getPosition()) }

/** Holds if this position represents the `self` position. */
predicate isSelf() { this = TSelfParameterPosition() }

Expand Down Expand Up @@ -367,13 +370,41 @@ module Node {
private CallExprBaseCfgNode call_;
private RustDataFlow::ArgumentPosition pos_;

ExprArgumentNode() { isArgumentForCall(n, call_, pos_) }
ExprArgumentNode() {
isArgumentForCall(n, call_, pos_) and
// For receivers in method calls the `ReceiverNode` is the argument.
not call_.(MethodCallExprCfgNode).getReceiver() = n
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps instead remove the arg = call.(MethodCallExprCfgNode).getReceiver() and pos.isSelf() case inside isArgumentForCall, and then remove this restriction and add a e = any(MethodCallExprCfgNode mc).getReceiver() case to TExprPostUpdateNode.

}

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

/**
* The receiver of a method call _after_ any implicit borrow or dereferences
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* The receiver of a method call _after_ any implicit borrow or dereferences
* The receiver of a method call _after_ any implicit borrow or dereferencing

* has taken place.
*/
final class ReceiverNode extends ArgumentNode, TReceiverNode {
private MethodCallExprCfgNode n;

ReceiverNode() { this = TReceiverNode(n, false) }

ExprCfgNode getReceiver() { result = n.getReceiver() }

MethodCallExprCfgNode getMethodCall() { result = n }

override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
call.asMethodCallExprCfgNode() = n and pos = TSelfParameterPosition()
}

override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }

override Location getLocation() { result = n.getLocation() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use this.getReceiver().getLocation().


override string toString() { result = "receiver for " + this.getReceiver() }
}

final class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode {
private FlowSummaryImpl::Private::SummaryNode receiver;
private RustDataFlow::ArgumentPosition pos_;
Expand Down Expand Up @@ -519,6 +550,18 @@ module Node {
override Location getLocation() { result = n.getLocation() }
}

final class ReceiverPostUpdateNode extends PostUpdateNode, TReceiverNode {
private MethodCallExprCfgNode n;

ReceiverPostUpdateNode() { this = TReceiverNode(n, true) }

override Node getPreUpdateNode() { result = TReceiverNode(n, false) }

override CfgScope getCfgScope() { result = n.getAstNode().getEnclosingCfgScope() }

override Location getLocation() { result = n.getLocation() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

}

final class SummaryPostUpdateNode extends FlowSummaryNode, PostUpdateNode {
private FlowSummaryNode pre;

Expand Down Expand Up @@ -648,6 +691,14 @@ module LocalFlow {
)
or
nodeFrom.asPat().(OrPatCfgNode).getAPat() = nodeTo.asPat()
or
// Simple value step from receiver expression to receiver node, in case
// there is no implicit deref or borrow operation.
nodeFrom.asExpr() = nodeTo.(Node::ReceiverNode).getReceiver()
or
// The dual step of the above, for the post-update nodes.
nodeFrom.(Node::PostUpdateNode).getPreUpdateNode().(Node::ReceiverNode).getReceiver() =
nodeTo.(Node::PostUpdateNode).getPreUpdateNode().asExpr()
}
}

Expand Down Expand Up @@ -998,6 +1049,23 @@ predicate lambdaCallExpr(CallExprCfgNode call, LambdaCallKind kind, ExprCfgNode
exists(kind)
}

/** Holds if `mc` implicitly borrows its receiver. */
predicate implicitBorrow(MethodCallExpr mc) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

// Determining whether an implicit borrow happens depends on the type of the
// receiever as well as the target. As a heuristic we simply check if the
// target takes `self` as a borrow and limit the approximation to cases where
// the receiver is a simple variable.
mc.getReceiver() instanceof VariableAccess and
mc.getStaticTarget().getParamList().getSelfParam().isRef()
}

/** Holds if `mc` implicitly dereferences its receiver. */
predicate implicitDeref(MethodCallExpr mc) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

// Similarly to `implicitBorrow` this is an approximation.
mc.getReceiver() instanceof VariableAccess and
not mc.getStaticTarget().getParamList().getSelfParam().isRef()
}

// Defines a set of aliases needed for the `RustDataFlow` module
private module Aliases {
class DataFlowCallableAlias = DataFlowCallable;
Expand Down Expand Up @@ -1054,13 +1122,12 @@ module RustDataFlow implements InputSig<Location> {
DataFlowType getNodeType(Node node) { any() }

predicate nodeIsHidden(Node node) {
node instanceof Node::SsaNode
or
node.(Node::FlowSummaryNode).getSummaryNode().isHidden()
or
node instanceof Node::CaptureNode
or
node instanceof Node::ClosureParameterNode
node instanceof Node::SsaNode or
node.(Node::FlowSummaryNode).getSummaryNode().isHidden() or
node instanceof Node::CaptureNode or
node instanceof Node::ClosureParameterNode or
node instanceof Node::ReceiverNode or
node instanceof Node::ReceiverPostUpdateNode
}

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

pragma[nomagic]
private predicate implicitDerefToReceiver(Node node1, Node::ReceiverNode node2, ReferenceContent c) {
node1.asExpr() = node2.getReceiver() and
implicitDeref(node2.getMethodCall().getMethodCallExpr()) and
exists(c)
}

pragma[nomagic]
private predicate implicitBorrowToReceiver(
Node node1, Node::ReceiverNode node2, ReferenceContent c
) {
node1.asExpr() = node2.getReceiver() and
implicitBorrow(node2.getMethodCall().getMethodCallExpr()) and
exists(c)
}

pragma[nomagic]
private predicate referenceExprToExpr(Node node1, Node node2, ReferenceContent c) {
node1.asExpr() = node2.asExpr().(RefExprCfgNode).getExpr() and
exists(c)
}

/**
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
* `node1` references an object with a content `c.getAReadContent()` whose
Expand Down Expand Up @@ -1251,6 +1340,17 @@ module RustDataFlow implements InputSig<Location> {
node2.asExpr() = await
)
or
referenceExprToExpr(node2.(PostUpdateNode).getPreUpdateNode(),
node1.(PostUpdateNode).getPreUpdateNode(), c)
or
// Step from receiver expression to receiver node, in case of an implicit
// dereference.
implicitDerefToReceiver(node1, node2, c)
or
// A read step dual to the store step for implicit borrows.
implicitBorrowToReceiver(node2.(PostUpdateNode).getPreUpdateNode(),
node1.(PostUpdateNode).getPreUpdateNode(), c)
or
VariableCapture::readStep(node1, c, node2)
)
or
Expand Down Expand Up @@ -1327,11 +1427,7 @@ module RustDataFlow implements InputSig<Location> {
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = index.getBase()
)
or
exists(RefExprCfgNode ref |
c instanceof ReferenceContent and
node1.asExpr() = ref.getExpr() and
node2.asExpr() = ref
)
referenceExprToExpr(node1, node2, c)
or
// Store in function argument
exists(DataFlowCall call, int i |
Expand All @@ -1341,6 +1437,10 @@ module RustDataFlow implements InputSig<Location> {
)
or
VariableCapture::storeStep(node1, c, node2)
or
// Step from receiver expression to receiver node, in case of an implicit
// borrow.
implicitBorrowToReceiver(node1, node2, c)
}

/**
Expand Down Expand Up @@ -1612,9 +1712,16 @@ private module Cached {
TPatNode(PatCfgNode p) or
TNameNode(NameCfgNode n) { n.getName() = any(Variable v).getName() } or
TExprPostUpdateNode(ExprCfgNode e) {
isArgumentForCall(e, _, _) or
lambdaCallExpr(_, _, e) or
lambdaCreationExpr(e.getExpr(), _) or
isArgumentForCall(e, _, _)
or
lambdaCallExpr(_, _, e)
or
lambdaCreationExpr(e.getExpr(), _)
or
// Whenever `&mut e` has a post-update node we also create one for `e`.
// E.g., for `e` in `f(..., &mut e, ...)` or `*(&mut e) = ...`.
e = any(RefExprCfgNode ref | ref.isMut() and exists(TExprPostUpdateNode(ref))).getExpr()
or
e =
[
any(IndexExprCfgNode i).getBase(), any(FieldExprCfgNode access).getExpr(),
Expand All @@ -1623,6 +1730,7 @@ private module Cached {
any(AwaitExprCfgNode a).getExpr()
]
} or
TReceiverNode(MethodCallExprCfgNode mc, Boolean isPost) or
TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c, _) } or
Expand Down
35 changes: 21 additions & 14 deletions rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,7 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
* those that are not borrowed (either explicitly using `& mut`, or
* (potentially) implicit as borrowed receivers in a method call).
*/
class SourceVariable extends Variable {
SourceVariable() {
this.isMutable()
implies
not exists(VariableAccess va | va = this.getAnAccess() |
va = any(RefExpr re | re.isMut()).getExpr()
or
// receivers can be borrowed implicitly, cf.
// https://doc.rust-lang.org/reference/expressions/method-call-expr.html
va = any(MethodCallExpr mce).getReceiver()
)
}
}
class SourceVariable = Variable;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The QL doc needs to be updated now (or perhaps simply be removed).


predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) {
(
Expand All @@ -76,7 +64,12 @@ module SsaInput implements SsaImplCommon::InputSig<Location> {
) and
certain = true
or
capturedCallWrite(_, bb, i, v) and certain = false
(
capturedCallWrite(_, bb, i, v)
or
mutablyBorrows(bb.getNode(i).getAstNode(), v)
) and
certain = false
}

predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) {
Expand Down Expand Up @@ -229,6 +222,14 @@ predicate capturedCallWrite(Expr call, BasicBlock bb, int i, Variable v) {
)
}

/** Holds if `v` may be mutably borrowed in `e`. */
private predicate mutablyBorrows(Expr e, Variable v) {
e = any(MethodCallExpr mc).getReceiver() and
e.(VariableAccess).getVariable() = v
or
exists(RefExpr re | re = e and re.isMut() and re.getExpr().(VariableAccess).getVariable() = v)
}

/**
* Holds if a pseudo read of captured variable `v` should be inserted
* at index `i` in exit block `bb`.
Expand Down Expand Up @@ -379,6 +380,12 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu
none() // handled in `DataFlowImpl.qll` instead
}

predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) {
exists(Variable v, BasicBlock bb, int i |
def.definesAt(v, bb, i) and mutablyBorrows(bb.getNode(i).getAstNode(), v)
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, perhaps this should be restricted to borrows passed into calls, i.e.

Suggested change
predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) {
exists(Variable v, BasicBlock bb, int i |
def.definesAt(v, bb, i) and mutablyBorrows(bb.getNode(i).getAstNode(), v)
)
}
predicate allowFlowIntoUncertainDef(UncertainWriteDefinition def) {
exists(CfgNodes::CallExprBaseCfgNode call, Variable v, BasicBlock bb, int i |
def.definesAt(v, bb, i) and
mutablyBorrows(bb.getNode(i).getAstNode(), v)
|
call.getArgument(_) = bb.getNode(i)
or
call.(CfgNodes::MethodCallExprCfgNode).getReceiver() = bb.getNode(i)
)
}


class Parameter = CfgNodes::ParamBaseCfgNode;

/** Holds if SSA definition `def` initializes parameter `p` at function entry. */
Expand Down
Loading