Skip to content

Commit 670ecb3

Browse files
authored
Merge pull request #18673 from paldepind/rust-higher-order-function-model-generation
Rust: Higher order function model generation
2 parents 0447628 + ece5557 commit 670ecb3

File tree

8 files changed

+371
-170
lines changed

8 files changed

+371
-170
lines changed

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,36 @@ final class TuplePositionContent extends Content, TTuplePositionContent {
871871
override Location getLocation() { result instanceof EmptyLocation }
872872
}
873873

874+
/**
875+
* A content for the index of an argument to at function call.
876+
*
877+
* Used by the model generator to create flow summaries for higher-order
878+
* functions.
879+
*/
880+
final class FunctionCallArgumentContent extends Content, TFunctionCallArgumentContent {
881+
private int pos;
882+
883+
FunctionCallArgumentContent() { this = TFunctionCallArgumentContent(pos) }
884+
885+
int getPosition() { result = pos }
886+
887+
override string toString() { result = "function argument at " + pos }
888+
889+
override Location getLocation() { result instanceof EmptyLocation }
890+
}
891+
892+
/**
893+
* A content for the return value of function call.
894+
*
895+
* Used by the model generator to create flow summaries for higher-order
896+
* functions.
897+
*/
898+
final class FunctionCallReturnContent extends Content, TFunctionCallReturnContent {
899+
override string toString() { result = "function return" }
900+
901+
override Location getLocation() { result instanceof EmptyLocation }
902+
}
903+
874904
/** Holds if `access` indexes a tuple at an index corresponding to `c`. */
875905
private predicate fieldTuplePositionContent(FieldExprCfgNode access, TuplePositionContent c) {
876906
access.getNameRef().getText().toInt() = c.getPosition()
@@ -1192,6 +1222,13 @@ module RustDataFlow implements InputSig<Location> {
11921222
node2.asExpr() = deref
11931223
)
11941224
or
1225+
// Read from function return
1226+
exists(DataFlowCall call |
1227+
lambdaCall(call, _, node1) and
1228+
call = node2.(OutNode).getCall(TNormalReturnKind()) and
1229+
c instanceof FunctionCallReturnContent
1230+
)
1231+
or
11951232
VariableCapture::readStep(node1, c, node2)
11961233
)
11971234
or
@@ -1273,6 +1310,13 @@ module RustDataFlow implements InputSig<Location> {
12731310
node2.asExpr() = ref
12741311
)
12751312
or
1313+
// Store in function argument
1314+
exists(DataFlowCall call, int i |
1315+
isArgumentNode(node1, call, TPositionalParameterPosition(i)) and
1316+
lambdaCall(call, _, node2.(PostUpdateNode).getPreUpdateNode()) and
1317+
c.(FunctionCallArgumentContent).getPosition() = i
1318+
)
1319+
or
12761320
VariableCapture::storeStep(node1, c, node2)
12771321
}
12781322

@@ -1615,6 +1659,10 @@ private module Cached {
16151659
TStructFieldContent(Struct s, string field) {
16161660
field = s.getFieldList().(RecordFieldList).getAField().getName().getText()
16171661
} or
1662+
TFunctionCallReturnContent() or
1663+
TFunctionCallArgumentContent(int pos) {
1664+
pos in [0 .. any(CallExpr c).getArgList().getNumberOfArgs() - 1]
1665+
} or
16181666
TCapturedVariableContent(VariableCapture::CapturedVariable v) or
16191667
TReferenceContent()
16201668

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ module Input implements InputSig<Location, RustDataFlow> {
5151
override MethodCallExpr getCall() { result = call }
5252
}
5353

54-
RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { none() }
54+
RustDataFlow::ArgumentPosition callbackSelfParameterPosition() { result.isClosureSelf() }
5555

5656
ReturnKind getStandardReturnValueKind() { result = TNormalReturnKind() }
5757

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ module ModelGeneratorInput implements ModelGeneratorInputSig<Location, RustDataF
8888

8989
bindingset[c]
9090
string paramReturnNodeAsContentOutput(Callable c, ParameterPosition pos) {
91-
// TODO: Implement this to support returning through parameters.
92-
result = "paramReturnNodeAsContentOutput(" + c + ", " + pos + ")"
91+
result = parameterContentAccess(c.getParamList().getParam(pos.getPosition()))
92+
or
93+
pos.isSelf() and result = qualifierString()
9394
}
9495

9596
Callable returnNodeEnclosingCallable(DataFlow::Node ret) {
@@ -131,12 +132,34 @@ module ModelGeneratorInput implements ModelGeneratorInputSig<Location, RustDataF
131132
c.(SingletonContentSet).getContent() instanceof StructFieldContent
132133
}
133134

134-
predicate isCallback(DataFlow::ContentSet c) { none() }
135+
predicate isCallback(DataFlow::ContentSet cs) {
136+
exists(Content c | c = cs.(SingletonContentSet).getContent() |
137+
c instanceof FunctionCallReturnContent or
138+
c instanceof FunctionCallArgumentContent
139+
)
140+
}
135141

136142
string getSyntheticName(DataFlow::ContentSet c) { none() }
137143

144+
private string encodeContent(ContentSet cs, string arg) {
145+
result = FlowSummary::Input::encodeContent(cs, arg)
146+
or
147+
exists(Content c | cs = TSingletonContentSet(c) |
148+
exists(int pos |
149+
pos = c.(FunctionCallArgumentContent).getPosition() and
150+
result = "Parameter" and
151+
arg = pos.toString()
152+
)
153+
or
154+
c instanceof FunctionCallReturnContent and result = "ReturnValue" and arg = ""
155+
)
156+
}
157+
138158
string printContent(DataFlow::ContentSet cs) {
139-
exists(string arg | result = FlowSummary::Input::encodeContent(cs, arg) + "[" + arg + "]")
159+
exists(string name, string arg |
160+
name = encodeContent(cs, arg) and
161+
if arg = "" then result = name else result = name + "[" + arg + "]"
162+
)
140163
}
141164

142165
string partialModelRow(Callable api, int i) {

rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ storeStep
603603
| main.rs:414:41:414:67 | default_name | captured default_name | main.rs:414:41:414:67 | \|...\| ... |
604604
| main.rs:436:27:436:27 | 0 | Some | main.rs:436:22:436:28 | Some(...) |
605605
readStep
606+
| file://:0:0:0:0 | [summary param] 0 in lang:core::_::<crate::option::Option>::unwrap_or_else | function return | file://:0:0:0:0 | [summary] read: Argument[0].ReturnValue in lang:core::_::<crate::option::Option>::unwrap_or_else |
607+
| file://:0:0:0:0 | [summary param] 0 in lang:core::_::<crate::result::Result>::unwrap_or_else | function return | file://:0:0:0:0 | [summary] read: Argument[0].ReturnValue in lang:core::_::<crate::result::Result>::unwrap_or_else |
606608
| file://:0:0:0:0 | [summary param] self in lang:core::_::<crate::option::Option>::expect | Some | file://:0:0:0:0 | [summary] read: Argument[self].Variant[crate::option::Option::Some(0)] in lang:core::_::<crate::option::Option>::expect |
607609
| file://:0:0:0:0 | [summary param] self in lang:core::_::<crate::option::Option>::unwrap | Some | file://:0:0:0:0 | [summary] read: Argument[self].Variant[crate::option::Option::Some(0)] in lang:core::_::<crate::option::Option>::unwrap |
608610
| file://:0:0:0:0 | [summary param] self in lang:core::_::<crate::option::Option>::unwrap_or | Some | file://:0:0:0:0 | [summary] read: Argument[self].Variant[crate::option::Option::Some(0)] in lang:core::_::<crate::option::Option>::unwrap_or |

rust/ql/test/library-tests/dataflow/models/main.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,34 @@ fn test_set_tuple_element() {
175175
sink(t.1); // $ hasValueFlow=11
176176
}
177177

178+
// has a flow model
179+
pub fn apply<F>(n: i64, f: F) -> i64 where F : FnOnce(i64) -> i64 {
180+
0
181+
}
182+
183+
fn test_apply_flow_in() {
184+
let s = source(83);
185+
let f = |n| {
186+
sink(n); // $ hasValueFlow=83
187+
n + 3
188+
};
189+
apply(s, f);
190+
}
191+
192+
fn test_apply_flow_out() {
193+
let s = source(86);
194+
let f = |n| if n != 0 { n } else { s };
195+
let t = apply(34, f);
196+
sink(t); // $ hasValueFlow=86
197+
}
198+
199+
fn test_apply_flow_through() {
200+
let s = source(33);
201+
let f = |n| if n != 0 { n } else { 0 };
202+
let t = apply(s, f);
203+
sink(t); // $ hasValueFlow=33
204+
}
205+
178206
impl MyFieldEnum {
179207
// has a source model
180208
fn source(&self, i: i64) -> MyFieldEnum {

0 commit comments

Comments
 (0)