From 0728692e93f26b9d43ac05c0bd51a067a17d1163 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 29 Sep 2025 14:23:16 +0200 Subject: [PATCH 1/3] Rust: Add tests for functions as lambdas --- .../dataflow/closures/inline-flow.expected | 50 ------------------- .../dataflow/{closures => lambdas}/Cargo.lock | 0 .../dataflow/lambdas/inline-flow.expected | 50 +++++++++++++++++++ .../{closures => lambdas}/inline-flow.ql | 0 .../dataflow/{closures => lambdas}/main.rs | 50 +++++++++++++++---- 5 files changed, 91 insertions(+), 59 deletions(-) delete mode 100644 rust/ql/test/library-tests/dataflow/closures/inline-flow.expected rename rust/ql/test/library-tests/dataflow/{closures => lambdas}/Cargo.lock (100%) create mode 100644 rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected rename rust/ql/test/library-tests/dataflow/{closures => lambdas}/inline-flow.ql (100%) rename rust/ql/test/library-tests/dataflow/{closures => lambdas}/main.rs (53%) diff --git a/rust/ql/test/library-tests/dataflow/closures/inline-flow.expected b/rust/ql/test/library-tests/dataflow/closures/inline-flow.expected deleted file mode 100644 index a2cee10f246d..000000000000 --- a/rust/ql/test/library-tests/dataflow/closures/inline-flow.expected +++ /dev/null @@ -1,50 +0,0 @@ -models -edges -| main.rs:11:20:11:52 | if cond {...} else {...} | main.rs:12:10:12:16 | f(...) | provenance | | -| main.rs:11:30:11:39 | source(...) | main.rs:11:20:11:52 | if cond {...} else {...} | provenance | | -| main.rs:16:20:16:23 | ... | main.rs:18:18:18:21 | data | provenance | | -| main.rs:22:9:22:9 | a | main.rs:23:13:23:13 | a | provenance | | -| main.rs:22:13:22:22 | source(...) | main.rs:22:9:22:9 | a | provenance | | -| main.rs:23:13:23:13 | a | main.rs:16:20:16:23 | ... | provenance | | -| main.rs:27:20:27:23 | ... | main.rs:28:9:32:9 | if cond {...} else {...} | provenance | | -| main.rs:33:9:33:9 | a | main.rs:34:21:34:21 | a | provenance | | -| main.rs:33:13:33:22 | source(...) | main.rs:33:9:33:9 | a | provenance | | -| main.rs:34:9:34:9 | b | main.rs:35:10:35:10 | b | provenance | | -| main.rs:34:13:34:22 | f(...) | main.rs:34:9:34:9 | b | provenance | | -| main.rs:34:21:34:21 | a | main.rs:27:20:27:23 | ... | provenance | | -| main.rs:34:21:34:21 | a | main.rs:34:13:34:22 | f(...) | provenance | | -| main.rs:42:16:42:25 | source(...) | main.rs:44:5:44:5 | [post] f [captured capt] | provenance | | -| main.rs:44:5:44:5 | [post] f [captured capt] | main.rs:45:10:45:13 | capt | provenance | | -| main.rs:44:5:44:5 | [post] f [captured capt] | main.rs:49:5:49:5 | g [captured capt] | provenance | | -| main.rs:49:5:49:5 | g [captured capt] | main.rs:47:14:47:17 | capt | provenance | | -nodes -| main.rs:11:20:11:52 | if cond {...} else {...} | semmle.label | if cond {...} else {...} | -| main.rs:11:30:11:39 | source(...) | semmle.label | source(...) | -| main.rs:12:10:12:16 | f(...) | semmle.label | f(...) | -| main.rs:16:20:16:23 | ... | semmle.label | ... | -| main.rs:18:18:18:21 | data | semmle.label | data | -| main.rs:22:9:22:9 | a | semmle.label | a | -| main.rs:22:13:22:22 | source(...) | semmle.label | source(...) | -| main.rs:23:13:23:13 | a | semmle.label | a | -| main.rs:27:20:27:23 | ... | semmle.label | ... | -| main.rs:28:9:32:9 | if cond {...} else {...} | semmle.label | if cond {...} else {...} | -| main.rs:33:9:33:9 | a | semmle.label | a | -| main.rs:33:13:33:22 | source(...) | semmle.label | source(...) | -| main.rs:34:9:34:9 | b | semmle.label | b | -| main.rs:34:13:34:22 | f(...) | semmle.label | f(...) | -| main.rs:34:21:34:21 | a | semmle.label | a | -| main.rs:35:10:35:10 | b | semmle.label | b | -| main.rs:42:16:42:25 | source(...) | semmle.label | source(...) | -| main.rs:44:5:44:5 | [post] f [captured capt] | semmle.label | [post] f [captured capt] | -| main.rs:45:10:45:13 | capt | semmle.label | capt | -| main.rs:47:14:47:17 | capt | semmle.label | capt | -| main.rs:49:5:49:5 | g [captured capt] | semmle.label | g [captured capt] | -subpaths -| main.rs:34:21:34:21 | a | main.rs:27:20:27:23 | ... | main.rs:28:9:32:9 | if cond {...} else {...} | main.rs:34:13:34:22 | f(...) | -testFailures -#select -| main.rs:12:10:12:16 | f(...) | main.rs:11:30:11:39 | source(...) | main.rs:12:10:12:16 | f(...) | $@ | main.rs:11:30:11:39 | source(...) | source(...) | -| main.rs:18:18:18:21 | data | main.rs:22:13:22:22 | source(...) | main.rs:18:18:18:21 | data | $@ | main.rs:22:13:22:22 | source(...) | source(...) | -| main.rs:35:10:35:10 | b | main.rs:33:13:33:22 | source(...) | main.rs:35:10:35:10 | b | $@ | main.rs:33:13:33:22 | source(...) | source(...) | -| main.rs:45:10:45:13 | capt | main.rs:42:16:42:25 | source(...) | main.rs:45:10:45:13 | capt | $@ | main.rs:42:16:42:25 | source(...) | source(...) | -| main.rs:47:14:47:17 | capt | main.rs:42:16:42:25 | source(...) | main.rs:47:14:47:17 | capt | $@ | main.rs:42:16:42:25 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/closures/Cargo.lock b/rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock similarity index 100% rename from rust/ql/test/library-tests/dataflow/closures/Cargo.lock rename to rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock diff --git a/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected new file mode 100644 index 000000000000..ef9a97677e1b --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected @@ -0,0 +1,50 @@ +models +edges +| main.rs:10:20:10:52 | if cond {...} else {...} | main.rs:11:10:11:16 | f(...) | provenance | | +| main.rs:10:30:10:39 | source(...) | main.rs:10:20:10:52 | if cond {...} else {...} | provenance | | +| main.rs:15:20:15:23 | ... | main.rs:17:18:17:21 | data | provenance | | +| main.rs:22:9:22:9 | a | main.rs:23:13:23:13 | a | provenance | | +| main.rs:22:13:22:22 | source(...) | main.rs:22:9:22:9 | a | provenance | | +| main.rs:23:13:23:13 | a | main.rs:15:20:15:23 | ... | provenance | | +| main.rs:27:20:27:23 | ... | main.rs:27:26:27:52 | if cond {...} else {...} | provenance | | +| main.rs:28:9:28:9 | a | main.rs:29:21:29:21 | a | provenance | | +| main.rs:28:13:28:22 | source(...) | main.rs:28:9:28:9 | a | provenance | | +| main.rs:29:9:29:9 | b | main.rs:30:10:30:10 | b | provenance | | +| main.rs:29:13:29:22 | f(...) | main.rs:29:9:29:9 | b | provenance | | +| main.rs:29:21:29:21 | a | main.rs:27:20:27:23 | ... | provenance | | +| main.rs:29:21:29:21 | a | main.rs:29:13:29:22 | f(...) | provenance | | +| main.rs:37:16:37:25 | source(...) | main.rs:39:5:39:5 | [post] f [captured capt] | provenance | | +| main.rs:39:5:39:5 | [post] f [captured capt] | main.rs:40:10:40:13 | capt | provenance | | +| main.rs:39:5:39:5 | [post] f [captured capt] | main.rs:44:5:44:5 | g [captured capt] | provenance | | +| main.rs:44:5:44:5 | g [captured capt] | main.rs:42:14:42:17 | capt | provenance | | +nodes +| main.rs:10:20:10:52 | if cond {...} else {...} | semmle.label | if cond {...} else {...} | +| main.rs:10:30:10:39 | source(...) | semmle.label | source(...) | +| main.rs:11:10:11:16 | f(...) | semmle.label | f(...) | +| main.rs:15:20:15:23 | ... | semmle.label | ... | +| main.rs:17:18:17:21 | data | semmle.label | data | +| main.rs:22:9:22:9 | a | semmle.label | a | +| main.rs:22:13:22:22 | source(...) | semmle.label | source(...) | +| main.rs:23:13:23:13 | a | semmle.label | a | +| main.rs:27:20:27:23 | ... | semmle.label | ... | +| main.rs:27:26:27:52 | if cond {...} else {...} | semmle.label | if cond {...} else {...} | +| main.rs:28:9:28:9 | a | semmle.label | a | +| main.rs:28:13:28:22 | source(...) | semmle.label | source(...) | +| main.rs:29:9:29:9 | b | semmle.label | b | +| main.rs:29:13:29:22 | f(...) | semmle.label | f(...) | +| main.rs:29:21:29:21 | a | semmle.label | a | +| main.rs:30:10:30:10 | b | semmle.label | b | +| main.rs:37:16:37:25 | source(...) | semmle.label | source(...) | +| main.rs:39:5:39:5 | [post] f [captured capt] | semmle.label | [post] f [captured capt] | +| main.rs:40:10:40:13 | capt | semmle.label | capt | +| main.rs:42:14:42:17 | capt | semmle.label | capt | +| main.rs:44:5:44:5 | g [captured capt] | semmle.label | g [captured capt] | +subpaths +| main.rs:29:21:29:21 | a | main.rs:27:20:27:23 | ... | main.rs:27:26:27:52 | if cond {...} else {...} | main.rs:29:13:29:22 | f(...) | +testFailures +#select +| main.rs:11:10:11:16 | f(...) | main.rs:10:30:10:39 | source(...) | main.rs:11:10:11:16 | f(...) | $@ | main.rs:10:30:10:39 | source(...) | source(...) | +| main.rs:17:18:17:21 | data | main.rs:22:13:22:22 | source(...) | main.rs:17:18:17:21 | data | $@ | main.rs:22:13:22:22 | source(...) | source(...) | +| main.rs:30:10:30:10 | b | main.rs:28:13:28:22 | source(...) | main.rs:30:10:30:10 | b | $@ | main.rs:28:13:28:22 | source(...) | source(...) | +| main.rs:40:10:40:13 | capt | main.rs:37:16:37:25 | source(...) | main.rs:40:10:40:13 | capt | $@ | main.rs:37:16:37:25 | source(...) | source(...) | +| main.rs:42:14:42:17 | capt | main.rs:37:16:37:25 | source(...) | main.rs:42:14:42:17 | capt | $@ | main.rs:37:16:37:25 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/closures/inline-flow.ql b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.ql similarity index 100% rename from rust/ql/test/library-tests/dataflow/closures/inline-flow.ql rename to rust/ql/test/library-tests/dataflow/lambdas/inline-flow.ql diff --git a/rust/ql/test/library-tests/dataflow/closures/main.rs b/rust/ql/test/library-tests/dataflow/lambdas/main.rs similarity index 53% rename from rust/ql/test/library-tests/dataflow/closures/main.rs rename to rust/ql/test/library-tests/dataflow/lambdas/main.rs index 66ce59a3b041..573e93311c5c 100644 --- a/rust/ql/test/library-tests/dataflow/closures/main.rs +++ b/rust/ql/test/library-tests/dataflow/lambdas/main.rs @@ -6,30 +6,25 @@ fn sink(s: i64) { println!("{}", s); } - fn closure_flow_out() { let f = |cond| if cond { source(92) } else { 0 }; sink(f(true)); // $ hasValueFlow=92 } fn closure_flow_in() { - let f = |cond, data| + let f = |cond, data| { if cond { sink(data); // $ hasValueFlow=87 } else { sink(0) - }; + } + }; let a = source(87); f(true, a); } fn closure_flow_through() { - let f = |cond, data| - if cond { - data - } else { - 0 - }; + let f = |cond, data| if cond { data } else { 0 }; let a = source(43); let b = f(true, a); sink(b); // $ hasValueFlow=43 @@ -49,9 +44,46 @@ fn closure_captured_variable() { g(); } +fn get_from_source() -> i64 { + source(93) +} + +fn pass_to_sink(data: i64) { + sink(data); // $ MISSING: hasValueFlow=34 +} + +fn function_flow_out() { + let f = get_from_source; + sink(f()); // $ MISSING: hasValueFlow=93 +} + +fn function_flow_in() { + let f = pass_to_sink; + let a = source(34); + f(a); +} + +fn get_arg(cond: bool, data: i64) -> i64 { + if cond { + data + } else { + 0 + } +} + +fn function_flows_through() { + let f = get_arg; + let a = source(56); + let b = f(true, a); + sink(b); // $ MISSING: hasValueFlow=56 +} + fn main() { closure_flow_out(); closure_flow_in(); closure_flow_through(); closure_captured_variable(); + function_flow_in(); + function_flow_out(); + function_flows_through(); } From 37ffe82ac94023148caa6f818bf81b4621b4fea0 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 29 Sep 2025 14:49:04 +0200 Subject: [PATCH 2/3] Rust: Handle functions as lambdas --- .../rust/dataflow/internal/DataFlowImpl.qll | 24 ++++++++------ .../codeql/rust/dataflow/internal/Node.qll | 4 +-- .../dataflow/lambdas/inline-flow.expected | 33 +++++++++++++++++++ .../library-tests/dataflow/lambdas/main.rs | 6 ++-- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index 4c252dfcd0f0..9fa0f0a5a834 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -295,13 +295,10 @@ module LocalFlow { class LambdaCallKind = Unit; /** Holds if `creation` is an expression that creates a lambda of kind `kind`. */ -predicate lambdaCreationExpr(Expr creation, LambdaCallKind kind) { - ( - creation instanceof ClosureExpr - or - creation instanceof Scope::AsyncBlockScope - ) and - exists(kind) +predicate lambdaCreationExpr(Expr creation) { + creation instanceof ClosureExpr + or + creation instanceof Scope::AsyncBlockScope } /** @@ -810,8 +807,15 @@ module RustDataFlow implements InputSig { /** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { - exists(Expr e | - e = creation.asExpr().getExpr() and lambdaCreationExpr(e, kind) and e = c.asCfgScope() + exists(kind) and + exists(Expr e | e = creation.asExpr().getExpr() | + lambdaCreationExpr(e) and e = c.asCfgScope() + or + // A path expression, that resolves to a function, evaluates to a function + // pointer. Except if the path occurs directly in a call, then it's just a + // call to the function and not a function being passed as data. + resolvePath(e.(PathExpr).getPath()) = c.asCfgScope() and + not any(CallExpr call).getFunction() = e ) } @@ -931,7 +935,7 @@ module VariableCapture { } class ClosureExpr extends Expr instanceof ExprCfgNode { - ClosureExpr() { lambdaCreationExpr(super.getExpr(), _) } + ClosureExpr() { lambdaCreationExpr(super.getExpr()) } predicate hasBody(Callable body) { body = super.getExpr() } diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll b/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll index a1bdc367d0aa..e46b4375c04c 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll @@ -454,7 +454,7 @@ newtype TNode = or lambdaCallExpr(_, _, e) or - lambdaCreationExpr(e.getExpr(), _) + 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) = ...`. @@ -478,5 +478,5 @@ newtype TNode = } or TSsaNode(SsaImpl::DataFlowIntegration::SsaNode node) or TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or - TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c, _) } or + TClosureSelfReferenceNode(CfgScope c) { lambdaCreationExpr(c) } or TCaptureNode(VariableCapture::Flow::SynthesizedCaptureNode cn) diff --git a/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected index ef9a97677e1b..7c99702aec63 100644 --- a/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected @@ -17,6 +17,19 @@ edges | main.rs:39:5:39:5 | [post] f [captured capt] | main.rs:40:10:40:13 | capt | provenance | | | main.rs:39:5:39:5 | [post] f [captured capt] | main.rs:44:5:44:5 | g [captured capt] | provenance | | | main.rs:44:5:44:5 | g [captured capt] | main.rs:42:14:42:17 | capt | provenance | | +| main.rs:47:29:49:1 | { ... } | main.rs:57:10:57:12 | f(...) | provenance | | +| main.rs:48:5:48:14 | source(...) | main.rs:47:29:49:1 | { ... } | provenance | | +| main.rs:51:17:51:25 | ...: i64 | main.rs:52:10:52:13 | data | provenance | | +| main.rs:62:9:62:9 | a | main.rs:63:7:63:7 | a | provenance | | +| main.rs:62:13:62:22 | source(...) | main.rs:62:9:62:9 | a | provenance | | +| main.rs:63:7:63:7 | a | main.rs:51:17:51:25 | ...: i64 | provenance | | +| main.rs:66:24:66:32 | ...: i64 | main.rs:66:42:72:1 | { ... } | provenance | | +| main.rs:76:9:76:9 | a | main.rs:77:21:77:21 | a | provenance | | +| main.rs:76:13:76:22 | source(...) | main.rs:76:9:76:9 | a | provenance | | +| main.rs:77:9:77:9 | b | main.rs:78:10:78:10 | b | provenance | | +| main.rs:77:13:77:22 | f(...) | main.rs:77:9:77:9 | b | provenance | | +| main.rs:77:21:77:21 | a | main.rs:66:24:66:32 | ...: i64 | provenance | | +| main.rs:77:21:77:21 | a | main.rs:77:13:77:22 | f(...) | provenance | | nodes | main.rs:10:20:10:52 | if cond {...} else {...} | semmle.label | if cond {...} else {...} | | main.rs:10:30:10:39 | source(...) | semmle.label | source(...) | @@ -39,8 +52,25 @@ nodes | main.rs:40:10:40:13 | capt | semmle.label | capt | | main.rs:42:14:42:17 | capt | semmle.label | capt | | main.rs:44:5:44:5 | g [captured capt] | semmle.label | g [captured capt] | +| main.rs:47:29:49:1 | { ... } | semmle.label | { ... } | +| main.rs:48:5:48:14 | source(...) | semmle.label | source(...) | +| main.rs:51:17:51:25 | ...: i64 | semmle.label | ...: i64 | +| main.rs:52:10:52:13 | data | semmle.label | data | +| main.rs:57:10:57:12 | f(...) | semmle.label | f(...) | +| main.rs:62:9:62:9 | a | semmle.label | a | +| main.rs:62:13:62:22 | source(...) | semmle.label | source(...) | +| main.rs:63:7:63:7 | a | semmle.label | a | +| main.rs:66:24:66:32 | ...: i64 | semmle.label | ...: i64 | +| main.rs:66:42:72:1 | { ... } | semmle.label | { ... } | +| main.rs:76:9:76:9 | a | semmle.label | a | +| main.rs:76:13:76:22 | source(...) | semmle.label | source(...) | +| main.rs:77:9:77:9 | b | semmle.label | b | +| main.rs:77:13:77:22 | f(...) | semmle.label | f(...) | +| main.rs:77:21:77:21 | a | semmle.label | a | +| main.rs:78:10:78:10 | b | semmle.label | b | subpaths | main.rs:29:21:29:21 | a | main.rs:27:20:27:23 | ... | main.rs:27:26:27:52 | if cond {...} else {...} | main.rs:29:13:29:22 | f(...) | +| main.rs:77:21:77:21 | a | main.rs:66:24:66:32 | ...: i64 | main.rs:66:42:72:1 | { ... } | main.rs:77:13:77:22 | f(...) | testFailures #select | main.rs:11:10:11:16 | f(...) | main.rs:10:30:10:39 | source(...) | main.rs:11:10:11:16 | f(...) | $@ | main.rs:10:30:10:39 | source(...) | source(...) | @@ -48,3 +78,6 @@ testFailures | main.rs:30:10:30:10 | b | main.rs:28:13:28:22 | source(...) | main.rs:30:10:30:10 | b | $@ | main.rs:28:13:28:22 | source(...) | source(...) | | main.rs:40:10:40:13 | capt | main.rs:37:16:37:25 | source(...) | main.rs:40:10:40:13 | capt | $@ | main.rs:37:16:37:25 | source(...) | source(...) | | main.rs:42:14:42:17 | capt | main.rs:37:16:37:25 | source(...) | main.rs:42:14:42:17 | capt | $@ | main.rs:37:16:37:25 | source(...) | source(...) | +| main.rs:52:10:52:13 | data | main.rs:62:13:62:22 | source(...) | main.rs:52:10:52:13 | data | $@ | main.rs:62:13:62:22 | source(...) | source(...) | +| main.rs:57:10:57:12 | f(...) | main.rs:48:5:48:14 | source(...) | main.rs:57:10:57:12 | f(...) | $@ | main.rs:48:5:48:14 | source(...) | source(...) | +| main.rs:78:10:78:10 | b | main.rs:76:13:76:22 | source(...) | main.rs:78:10:78:10 | b | $@ | main.rs:76:13:76:22 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/lambdas/main.rs b/rust/ql/test/library-tests/dataflow/lambdas/main.rs index 573e93311c5c..252b132ec744 100644 --- a/rust/ql/test/library-tests/dataflow/lambdas/main.rs +++ b/rust/ql/test/library-tests/dataflow/lambdas/main.rs @@ -49,12 +49,12 @@ fn get_from_source() -> i64 { } fn pass_to_sink(data: i64) { - sink(data); // $ MISSING: hasValueFlow=34 + sink(data); // $ hasValueFlow=34 } fn function_flow_out() { let f = get_from_source; - sink(f()); // $ MISSING: hasValueFlow=93 + sink(f()); // $ hasValueFlow=93 } fn function_flow_in() { @@ -75,7 +75,7 @@ fn function_flows_through() { let f = get_arg; let a = source(56); let b = f(true, a); - sink(b); // $ MISSING: hasValueFlow=56 + sink(b); // $ hasValueFlow=56 } fn main() { From 98a20f982037f5159a2474a48152b949e8a422dd Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 29 Sep 2025 14:58:34 +0200 Subject: [PATCH 3/3] Rust: Add change note --- .../lib/change-notes/2025-09-29-data-flow-function-pointer.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rust/ql/lib/change-notes/2025-09-29-data-flow-function-pointer.md diff --git a/rust/ql/lib/change-notes/2025-09-29-data-flow-function-pointer.md b/rust/ql/lib/change-notes/2025-09-29-data-flow-function-pointer.md new file mode 100644 index 000000000000..7d1adb06e746 --- /dev/null +++ b/rust/ql/lib/change-notes/2025-09-29-data-flow-function-pointer.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improve data flow through functions being passed as function pointers. \ No newline at end of file