Skip to content

Commit 5689506

Browse files
committed
Rust: Untangle argument/parameter data flow matching from call graph
1 parent 4d4a677 commit 5689506

File tree

4 files changed

+158
-45
lines changed

4 files changed

+158
-45
lines changed

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

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,12 @@ final class DataFlowCall extends TDataFlowCall {
8989

9090
/**
9191
* The position of a parameter in a function.
92-
*
93-
* In Rust there is a 1-to-1 correspondence between parameter positions and
94-
* arguments positions, so we use the same underlying type for both.
9592
*/
9693
final class ParameterPosition extends TParameterPosition {
9794
/** Gets the underlying integer position, if any. */
98-
int getPosition() { this = TPositionalParameterPosition(result) }
95+
int getPosition(boolean inMethod) { this = TPositionalParameterPosition(result, inMethod) }
9996

100-
predicate hasPosition() { exists(this.getPosition()) }
97+
predicate hasPosition(boolean inMethod) { exists(this.getPosition(inMethod)) }
10198

10299
/** Holds if this position represents the `self` position. */
103100
predicate isSelf() { this = TSelfParameterPosition() }
@@ -110,32 +107,88 @@ final class ParameterPosition extends TParameterPosition {
110107

111108
/** Gets a textual representation of this position. */
112109
string toString() {
113-
result = this.getPosition().toString()
110+
exists(boolean inMethod, string suffix |
111+
inMethod = true and suffix = " (method)"
112+
or
113+
inMethod = false and suffix = ""
114+
|
115+
result = this.getPosition(inMethod).toString() + suffix
116+
)
114117
or
115118
result = "self" and this.isSelf()
116119
or
117120
result = "closure self" and this.isClosureSelf()
118121
}
119122

120123
ParamBase getParameterIn(ParamList ps) {
121-
result = ps.getParam(this.getPosition())
124+
exists(boolean inMethod |
125+
result = ps.getParam(this.getPosition(inMethod)) and
126+
if ps.hasSelfParam() then inMethod = true else inMethod = false
127+
)
122128
or
123129
result = ps.getSelfParam() and this.isSelf()
124130
}
125131
}
126132

127133
/**
128134
* The position of an argument in a call.
129-
*
130-
* In Rust there is a 1-to-1 correspondence between parameter positions and
131-
* arguments positions, so we use the same underlying type for both.
132135
*/
133-
final class ArgumentPosition extends ParameterPosition {
136+
final class ArgumentPosition extends TArgumentPosition {
134137
/** Gets the argument of `call` at this position, if any. */
135138
Expr getArgument(Call call) {
136-
result = call.getPositionalArgument(this.getPosition())
139+
this.isReceiver() and
140+
(
141+
result = call.(MethodCallExpr).getReceiver()
142+
or
143+
result = call.(Operation).getOperand(0)
144+
or
145+
result = call.(IndexExpr).getBase()
146+
)
147+
or
148+
exists(boolean inMethodCall, int pos | this.getPosition(inMethodCall) = pos |
149+
result = call.(CallExpr).getArg(pos) and
150+
inMethodCall = false
151+
or
152+
result = call.(MethodCallExpr).getArg(pos) and
153+
inMethodCall = true
154+
or
155+
result = call.(Operation).getOperand(pos + 1) and
156+
pos >= 0 and
157+
inMethodCall = true
158+
or
159+
result = call.(IndexExpr).getIndex() and
160+
pos = 1 and
161+
inMethodCall = true
162+
)
163+
}
164+
165+
/** Gets the underlying integer position, if any. */
166+
int getPosition(boolean inMethodCall) { this = TPositionalArgumentPosition(result, inMethodCall) }
167+
168+
predicate hasPosition(boolean inMethodCall) { exists(this.getPosition(inMethodCall)) }
169+
170+
/** Holds if this position represents a receiver in a method call. */
171+
predicate isReceiver() { this = TReceiverArgumentPosition() }
172+
173+
/**
174+
* Holds if this position represents a reference to a closure itself. Only
175+
* used for tracking flow through captured variables.
176+
*/
177+
predicate isClosureReceiver() { this = TClosureReceiverArgumentPosition() }
178+
179+
/** Gets a textual representation of this position. */
180+
string toString() {
181+
exists(boolean inMethodCall, string suffix |
182+
inMethodCall = true and suffix = " (method call)"
183+
or
184+
inMethodCall = false and suffix = ""
185+
|
186+
result = this.getPosition(inMethodCall).toString() + suffix
187+
)
137188
or
138-
result = call.getReceiver() and this.isSelf()
189+
result = "receiver" and this.isReceiver()
190+
or
191+
result = "closure receiver" and this.isClosureReceiver()
139192
}
140193
}
141194

@@ -145,14 +198,11 @@ final class ArgumentPosition extends ParameterPosition {
145198
* Note that this does not hold for the receiever expression of a method call
146199
* as the synthetic `ReceiverNode` is the argument for the `self` parameter.
147200
*/
148-
predicate isArgumentForCall(Expr arg, Call call, ParameterPosition pos) {
201+
predicate isArgumentForCall(Expr arg, Call call, ArgumentPosition pos) {
149202
// TODO: Handle index expressions as calls in data flow.
150203
not call instanceof IndexExpr and
151-
(
152-
call.getPositionalArgument(pos.getPosition()) = arg
153-
or
154-
call.getReceiver() = arg and pos.isSelf() and not call.receiverImplicitlyBorrowed()
155-
)
204+
arg = pos.getArgument(call) and
205+
not (pos.isReceiver() and call.receiverImplicitlyBorrowed())
156206
}
157207

158208
/** Provides logic related to SSA. */
@@ -475,7 +525,21 @@ module RustDataFlow implements InputSig<Location> {
475525
* Holds if the parameter position `ppos` matches the argument position
476526
* `apos`.
477527
*/
478-
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
528+
pragma[nomagic]
529+
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
530+
exists(boolean inMethod | ppos.getPosition(inMethod) = apos.getPosition(inMethod))
531+
or
532+
// `Vec::push(vec, value))`: match `value` against parameter after `self`
533+
ppos.getPosition(true) = apos.getPosition(false) - 1
534+
or
535+
// `vec.push(value)`: match `vec` against `self` parameter
536+
ppos.isSelf() and apos.isReceiver()
537+
or
538+
// `Vec::push(vec, value))`: match `vec` against `self` parameter
539+
ppos.isSelf() and apos.getPosition(false) = 0
540+
or
541+
ppos.isClosureSelf() and apos.isClosureReceiver()
542+
}
479543

480544
/**
481545
* Holds if there is a simple local flow step from `node1` to `node2`. These
@@ -722,7 +786,7 @@ module RustDataFlow implements InputSig<Location> {
722786
or
723787
// Store in function argument
724788
exists(DataFlowCall call, int i |
725-
isArgumentNode(node1, call, TPositionalParameterPosition(i)) and
789+
isArgumentNode(node1, call, TPositionalArgumentPosition(i, false)) and
726790
lambdaCall(call, _, node2.(PostUpdateNode).getPreUpdateNode()) and
727791
c.(FunctionCallArgumentContent).getPosition() = i
728792
)
@@ -1032,16 +1096,26 @@ private module Cached {
10321096

10331097
cached
10341098
newtype TParameterPosition =
1035-
TPositionalParameterPosition(int i) {
1036-
i in [0 .. max([any(ParamList l).getNumberOfParams(), any(ArgList l).getNumberOfArgs()]) - 1]
1099+
TPositionalParameterPosition(int i, Boolean inMethod) {
1100+
i in [0 .. any(ParamList l).getNumberOfParams() - 1]
10371101
or
1038-
FlowSummaryImpl::ParsePositions::isParsedArgumentPosition(_, i)
1039-
or
1040-
FlowSummaryImpl::ParsePositions::isParsedParameterPosition(_, i)
1102+
FlowSummaryImpl::ParsePositions::isParsedArgumentPosition(_, i) and
1103+
inMethod = false
10411104
} or
10421105
TClosureSelfParameterPosition() or
10431106
TSelfParameterPosition()
10441107

1108+
cached
1109+
newtype TArgumentPosition =
1110+
TPositionalArgumentPosition(int i, Boolean inMethodCall) {
1111+
i in [0 .. any(ArgList l).getNumberOfArgs() - 1]
1112+
or
1113+
FlowSummaryImpl::ParsePositions::isParsedParameterPosition(_, i) and
1114+
inMethodCall = false
1115+
} or
1116+
TClosureReceiverArgumentPosition() or
1117+
TReceiverArgumentPosition()
1118+
10451119
cached
10461120
newtype TReturnKind = TNormalReturnKind()
10471121

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ module Input implements InputSig<Location, RustDataFlow> {
2525
final predicate callResolvesTo(string path) {
2626
path = this.getCall().getStaticTarget().(Addressable).getCanonicalPath()
2727
}
28+
29+
Expr getArgument(ParameterPosition pos) {
30+
exists(ParameterPosition pos0, ArgumentPosition apos |
31+
result = apos.getArgument(this.getCall()) and
32+
RustDataFlow::parameterMatch(pos0, apos)
33+
|
34+
not pos.hasPosition(_) and
35+
pos0 = pos
36+
or
37+
// We parse all positions as if they do not belong to a method; here we adjust if needed
38+
exists(boolean inMethod |
39+
pos0.getPosition(inMethod) = pos.getPosition(_) and
40+
if this.getCall().getStaticTarget().(Function).hasSelfParam()
41+
then inMethod = true
42+
else inMethod = false
43+
)
44+
)
45+
}
2846
}
2947

3048
abstract class SourceBase extends SourceSinkBase { }
@@ -47,14 +65,18 @@ module Input implements InputSig<Location, RustDataFlow> {
4765
override MethodCallExpr getCall() { result = call }
4866
}
4967

50-
RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { result.isClosureSelf() }
68+
RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { result.isClosureReceiver() }
5169

5270
ReturnKind getStandardReturnValueKind() { result = TNormalReturnKind() }
5371

54-
string encodeParameterPosition(ParameterPosition pos) { result = pos.toString() }
72+
string encodeParameterPosition(ParameterPosition pos) {
73+
result = pos.toString() and
74+
not pos.hasPosition(true)
75+
}
5576

5677
string encodeArgumentPosition(RustDataFlow::ArgumentPosition pos) {
57-
result = encodeParameterPosition(pos)
78+
result = pos.toString() and
79+
not pos.hasPosition(true)
5880
}
5981

6082
string encodeContent(ContentSet cs, string arg) {
@@ -111,14 +133,14 @@ module Input implements InputSig<Location, RustDataFlow> {
111133
ParameterPosition decodeUnknownParameterPosition(AccessPath::AccessPathTokenBase token) {
112134
// needed to support `Argument[x..y]` ranges
113135
token.getName() = "Argument" and
114-
result.getPosition() = AccessPath::parseInt(token.getAnArgument())
136+
result.getPosition(false) = AccessPath::parseInt(token.getAnArgument())
115137
}
116138

117139
bindingset[token]
118140
RustDataFlow::ArgumentPosition decodeUnknownArgumentPosition(AccessPath::AccessPathTokenBase token) {
119141
// needed to support `Parameter[x..y]` ranges
120142
token.getName() = "Parameter" and
121-
result.getPosition() = AccessPath::parseInt(token.getAnArgument())
143+
result.getPosition(false) = AccessPath::parseInt(token.getAnArgument())
122144
}
123145

124146
bindingset[token]
@@ -135,9 +157,9 @@ private module StepsInput implements Impl::Private::StepsInputSig {
135157

136158
/** Gets the argument of `source` described by `sc`, if any. */
137159
private Expr getSourceNodeArgument(Input::SourceBase source, Impl::Private::SummaryComponent sc) {
138-
exists(ArgumentPosition pos |
160+
exists(ParameterPosition pos |
139161
sc = Impl::Private::SummaryComponent::argument(pos) and
140-
result = pos.getArgument(source.getCall())
162+
result = source.getArgument(pos)
141163
)
142164
}
143165

@@ -165,19 +187,18 @@ private module StepsInput implements Impl::Private::StepsInputSig {
165187
exists(ArgumentPosition pos, Expr arg |
166188
s.head() = Impl::Private::SummaryComponent::parameter(pos) and
167189
arg = getSourceNodeArgument(source, s.tail().headOfSingleton()) and
168-
result.asParameter() = getCallable(arg).getParam(pos.getPosition())
190+
result.asParameter() = getCallable(arg).getParam(pos.getPosition(_))
169191
)
170192
or
171193
result.(RustDataFlow::PostUpdateNode).getPreUpdateNode().asExpr() =
172194
getSourceNodeArgument(source, s.headOfSingleton())
173195
}
174196

175197
RustDataFlow::Node getSinkNode(Input::SinkBase sink, Impl::Private::SummaryComponent sc) {
176-
exists(CallExprBase call, Expr arg, ArgumentPosition pos |
198+
exists(Expr arg, ParameterPosition ppos |
177199
result.asExpr() = arg and
178-
sc = Impl::Private::SummaryComponent::argument(pos) and
179-
call = sink.getCall() and
180-
arg = pos.getArgument(call)
200+
sc = Impl::Private::SummaryComponent::argument(ppos) and
201+
arg = sink.getArgument(ppos)
181202
)
182203
}
183204
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,19 @@ final class SummaryParameterNode extends ParameterNode, FlowSummaryNode {
190190
private ParameterPosition pos_;
191191

192192
SummaryParameterNode() {
193-
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos_)
193+
exists(ParameterPosition pos0 |
194+
FlowSummaryImpl::Private::summaryParameterNode(this.getSummaryNode(), pos0)
195+
|
196+
not pos0.hasPosition(_) and
197+
pos_ = pos0
198+
or
199+
// We parse all positions as if they do not belong to a method; here we adjust if needed
200+
exists(SummarizedCallable sc, boolean inMethod |
201+
sc = this.getSummarizedCallable() and
202+
pos_.getPosition(inMethod) = pos0.getPosition(_) and
203+
if sc.hasSelfParam() then inMethod = true else inMethod = false
204+
)
205+
)
194206
}
195207

196208
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
@@ -247,7 +259,7 @@ final class ReceiverNode extends ArgumentNode, TReceiverNode {
247259
MethodCallExpr getMethodCall() { result = n }
248260

249261
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
250-
call.asCall() = n and pos = TSelfParameterPosition()
262+
call.asCall() = n and pos = TReceiverArgumentPosition()
251263
}
252264

253265
override CfgScope getCfgScope() { result = n.getEnclosingCfgScope() }
@@ -281,7 +293,7 @@ final class ClosureArgumentNode extends ArgumentNode, ExprNode {
281293

282294
override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) {
283295
call.asCall() = call_ and
284-
pos.isClosureSelf()
296+
pos.isClosureReceiver()
285297
}
286298
}
287299

rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ module ModelGeneratorCommonInput implements
6666
string qualifierString() { result = "Argument[self]" }
6767

6868
string parameterExactAccess(R::ParamBase p) {
69-
result =
70-
"Argument[" + any(DataFlowImpl::ParameterPosition pos | p = pos.getParameterIn(_)).toString() +
71-
"]"
69+
exists(DataFlowImpl::ParameterPosition pos0, DataFlowImpl::ParameterPosition pos |
70+
p = pos0.getParameterIn(_) and
71+
result = "Argument[" + pos + "]"
72+
|
73+
not pos0.hasPosition(true) and
74+
pos = pos0
75+
or
76+
pos.getPosition(false) = pos0.getPosition(true)
77+
)
7278
}
7379

7480
string parameterApproximateAccess(R::ParamBase p) { result = parameterExactAccess(p) }
@@ -84,7 +90,7 @@ module ModelGeneratorCommonInput implements
8490

8591
bindingset[c]
8692
string paramReturnNodeAsExactOutput(QualifiedCallable c, DataFlowImpl::ParameterPosition pos) {
87-
result = parameterExactAccess(c.getFunction().getParam(pos.getPosition()))
93+
result = parameterExactAccess(c.getFunction().getParam(pos.getPosition(_)))
8894
or
8995
pos.isSelf() and result = qualifierString()
9096
}

0 commit comments

Comments
 (0)