diff --git a/rust/ql/lib/change-notes/2026-09-22-async-block-flow.md b/rust/ql/lib/change-notes/2026-09-22-async-block-flow.md new file mode 100644 index 000000000000..63525487b08c --- /dev/null +++ b/rust/ql/lib/change-notes/2026-09-22-async-block-flow.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Improve data flow for async blocks when used with `await`. \ No newline at end of file diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index bf6076e09055..60ec0fca820f 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -62,6 +62,9 @@ final class DataFlowCall extends TDataFlowCall { /** Gets the underlying call, if any. */ Call asCall() { this = TCall(result) } + /** Gets the underlying await expression, if any. */ + AwaitExpr asAwaitExpr() { this = TAwaitExpr(result) } + predicate isImplicitDerefCall(Expr e, DerefChain derefChain, int i, Function target) { this = TImplicitDerefCall(e, derefChain, i, target) } @@ -73,7 +76,8 @@ final class DataFlowCall extends TDataFlowCall { } DataFlowCallable getEnclosingCallable() { - result.asCfgScope() = this.asCall().getEnclosingCfgScope() + result.asCfgScope() = + [this.asCall().getEnclosingCfgScope(), this.asAwaitExpr().getEnclosingCfgScope()] or result.asCfgScope() = any(Expr e | this.isImplicitDerefCall(e, _, _, _)).getEnclosingCfgScope() or @@ -81,7 +85,7 @@ final class DataFlowCall extends TDataFlowCall { } string toString() { - result = this.asCall().toString() + result = [this.asCall().toString(), this.asAwaitExpr().toString()] or exists(Expr e, DerefChain derefChain, int i | this.isImplicitDerefCall(e, derefChain, i, _) and @@ -97,7 +101,7 @@ final class DataFlowCall extends TDataFlowCall { } Location getLocation() { - result = this.asCall().getLocation() + result = [this.asCall().getLocation(), this.asAwaitExpr().getLocation()] or result = any(Expr e | this.isImplicitDerefCall(e, _, _, _)).getLocation() } @@ -1004,7 +1008,11 @@ module RustDataFlowGen implements InputSig */ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { ( - receiver.asExpr() = call.asCall().(CallExprImpl::DynamicCallExpr).getFunction() + receiver.asExpr() = + [ + call.asCall().(CallExprImpl::DynamicCallExpr).getFunction(), + call.asAwaitExpr().getExpr() + ] or call.isSummaryCall(_, receiver.(FlowSummaryNode).getSummaryNode()) ) and @@ -1176,6 +1184,7 @@ private module Cached { Stages::DataFlowStage::ref() and call.hasEnclosingCfgScope() } or + TAwaitExpr(AwaitExpr await) { await.hasEnclosingCfgScope() } or TImplicitDerefCall(Expr e, DerefChain derefChain, int i, Function target) { TypeInference::implicitDerefChainBorrow(e, derefChain, _) and target = derefChain.getElement(i).getDerefFunction() and diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll b/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll index cbe638d430c4..b82c0f9221a1 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/Node.qll @@ -435,6 +435,20 @@ final class ClosureArgumentNode extends ArgumentNode, ExprNode { } } +/** + * A data flow node that represents the run-time representation of an async + * block passed into its body when awaited. + */ +final class AsyncBlockArgumentNode extends ArgumentNode, ExprNode { + private AwaitExpr await; + + AsyncBlockArgumentNode() { this.asExpr() = await.getExpr() } + + override predicate isArgumentOf(DataFlowCall call, RustDataFlow::ArgumentPosition pos) { + call.asAwaitExpr() = await and pos.isClosureSelf() + } +} + /** An SSA node. */ class SsaNode extends Node, TSsaNode { SsaImpl::DataFlowIntegration::SsaNode node; @@ -485,12 +499,17 @@ final private class ExprOutNode extends ExprNode, OutNode { not call instanceof DerefExpr and // Handled by `DerefOutNode` not call instanceof IndexExpr // Handled by `IndexOutNode` ) + or + this.asExpr() instanceof AwaitExpr } /** Gets the underlying call node that includes this out node. */ override DataFlowCall getCall(ReturnKind kind) { result.asCall() = n and kind = TNormalReturnKind() + or + result.asAwaitExpr() = n and + kind = TNormalReturnKind() } } diff --git a/rust/ql/test/library-tests/dataflow/global/inline-flow.expected b/rust/ql/test/library-tests/dataflow/global/inline-flow.expected index e13411a7ab03..6fe331bb4d9b 100644 --- a/rust/ql/test/library-tests/dataflow/global/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/global/inline-flow.expected @@ -196,7 +196,9 @@ edges | main.rs:342:9:342:9 | a | main.rs:343:10:343:10 | a | provenance | | | main.rs:342:13:342:26 | async_source(...) | main.rs:342:9:342:9 | a | provenance | | | main.rs:346:13:346:13 | c | main.rs:347:14:347:14 | c | provenance | | +| main.rs:346:13:346:13 | c | main.rs:348:9:348:9 | c | provenance | | | main.rs:346:17:346:25 | source(...) | main.rs:346:13:346:13 | c | provenance | | +| main.rs:348:9:348:9 | c | main.rs:350:10:350:16 | await b | provenance | | | main.rs:354:9:354:9 | a | main.rs:355:10:355:10 | a | provenance | | | main.rs:354:13:354:55 | ...::block_on(...) | main.rs:354:9:354:9 | a | provenance | | | main.rs:354:41:354:54 | async_source(...) | main.rs:354:13:354:55 | ...::block_on(...) | provenance | MaD:3 | @@ -443,6 +445,8 @@ nodes | main.rs:346:13:346:13 | c | semmle.label | c | | main.rs:346:17:346:25 | source(...) | semmle.label | source(...) | | main.rs:347:14:347:14 | c | semmle.label | c | +| main.rs:348:9:348:9 | c | semmle.label | c | +| main.rs:350:10:350:16 | await b | semmle.label | await b | | main.rs:354:9:354:9 | a | semmle.label | a | | main.rs:354:13:354:55 | ...::block_on(...) | semmle.label | ...::block_on(...) | | main.rs:354:41:354:54 | async_source(...) | semmle.label | async_source(...) | @@ -539,6 +543,7 @@ testFailures | main.rs:337:10:337:10 | a | main.rs:336:13:336:21 | source(...) | main.rs:337:10:337:10 | a | $@ | main.rs:336:13:336:21 | source(...) | source(...) | | main.rs:343:10:343:10 | a | main.rs:336:13:336:21 | source(...) | main.rs:343:10:343:10 | a | $@ | main.rs:336:13:336:21 | source(...) | source(...) | | main.rs:347:14:347:14 | c | main.rs:346:17:346:25 | source(...) | main.rs:347:14:347:14 | c | $@ | main.rs:346:17:346:25 | source(...) | source(...) | +| main.rs:350:10:350:16 | await b | main.rs:346:17:346:25 | source(...) | main.rs:350:10:350:16 | await b | $@ | main.rs:346:17:346:25 | source(...) | source(...) | | main.rs:355:10:355:10 | a | main.rs:336:13:336:21 | source(...) | main.rs:355:10:355:10 | a | $@ | main.rs:336:13:336:21 | source(...) | source(...) | | main.rs:404:14:404:15 | n1 | main.rs:379:13:379:21 | source(...) | main.rs:404:14:404:15 | n1 | $@ | main.rs:379:13:379:21 | source(...) | source(...) | | main.rs:408:14:408:15 | n2 | main.rs:379:13:379:21 | source(...) | main.rs:408:14:408:15 | n2 | $@ | main.rs:379:13:379:21 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/global/main.rs b/rust/ql/test/library-tests/dataflow/global/main.rs index b3a3af1be95e..2d8975e153a5 100644 --- a/rust/ql/test/library-tests/dataflow/global/main.rs +++ b/rust/ql/test/library-tests/dataflow/global/main.rs @@ -347,7 +347,7 @@ async fn test_async_await_async_part() { sink(c); // $ hasValueFlow=2 c }; - sink(b.await); // $ MISSING: hasValueFlow=2 + sink(b.await); // $ hasValueFlow=2 } fn test_async_await() { diff --git a/rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock b/rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock index b9856cfaf77d..5786eb119a9a 100644 --- a/rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock +++ b/rust/ql/test/library-tests/dataflow/lambdas/Cargo.lock @@ -2,6 +2,150 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "test" version = "0.0.1" +dependencies = [ + "futures", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" 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 08fb2a66015e..6b9075dd7b0e 100644 --- a/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/lambdas/inline-flow.expected @@ -63,6 +63,14 @@ edges | main.rs:111:26:111:36 | \|...\| ... : ... => .. [captured a] | main.rs:111:35:111:35 | a | provenance | heuristic-callback | | main.rs:112:26:112:36 | \|...\| ... : ... => .. [captured a] | main.rs:112:35:112:35 | a | provenance | heuristic-callback | | main.rs:113:26:113:36 | \|...\| ... : ... => .. [captured a] | main.rs:113:35:113:35 | a | provenance | heuristic-callback | +| main.rs:120:30:120:40 | source(...) | main.rs:121:14:121:25 | await future | provenance | | +| main.rs:125:20:125:30 | source(...) | main.rs:126:9:128:9 | { ... } : ... => .. [captured data] | provenance | | +| main.rs:126:9:128:9 | { ... } : ... => .. [captured data] | main.rs:127:18:127:21 | data | provenance | | +| main.rs:133:20:133:30 | source(...) | main.rs:135:14:135:19 | future [captured data] | provenance | | +| main.rs:135:14:135:19 | future [captured data] | main.rs:134:30:134:33 | data | provenance | | +| main.rs:135:14:135:19 | future [captured data] | main.rs:135:14:135:25 | await future | provenance | | +| main.rs:141:9:143:9 | [post] { ... } : ... => .. [captured captured] | main.rs:145:14:145:21 | captured | provenance | | +| main.rs:142:24:142:34 | source(...) | main.rs:141:9:143:9 | [post] { ... } : ... => .. [captured captured] | 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(...) | @@ -138,9 +146,22 @@ nodes | main.rs:112:35:112:35 | a | semmle.label | a | | main.rs:113:26:113:36 | \|...\| ... : ... => .. [captured a] | semmle.label | \|...\| ... : ... => .. [captured a] | | main.rs:113:35:113:35 | a | semmle.label | a | +| main.rs:120:30:120:40 | source(...) | semmle.label | source(...) | +| main.rs:121:14:121:25 | await future | semmle.label | await future | +| main.rs:125:20:125:30 | source(...) | semmle.label | source(...) | +| main.rs:126:9:128:9 | { ... } : ... => .. [captured data] | semmle.label | { ... } : ... => .. [captured data] | +| main.rs:127:18:127:21 | data | semmle.label | data | +| main.rs:133:20:133:30 | source(...) | semmle.label | source(...) | +| main.rs:134:30:134:33 | data | semmle.label | data | +| main.rs:135:14:135:19 | future [captured data] | semmle.label | future [captured data] | +| main.rs:135:14:135:25 | await future | semmle.label | await future | +| main.rs:141:9:143:9 | [post] { ... } : ... => .. [captured captured] | semmle.label | [post] { ... } : ... => .. [captured captured] | +| main.rs:142:24:142:34 | source(...) | semmle.label | source(...) | +| main.rs:145:14:145:21 | captured | semmle.label | captured | 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(...) | +| main.rs:135:14:135:19 | future [captured data] | main.rs:134:30:134:33 | data | main.rs:134:30:134:33 | data | main.rs:135:14:135:25 | await future | 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(...) | @@ -161,3 +182,7 @@ testFailures | main.rs:111:35:111:35 | a | main.rs:109:13:109:22 | source(...) | main.rs:111:35:111:35 | a | $@ | main.rs:109:13:109:22 | source(...) | source(...) | | main.rs:112:35:112:35 | a | main.rs:109:13:109:22 | source(...) | main.rs:112:35:112:35 | a | $@ | main.rs:109:13:109:22 | source(...) | source(...) | | main.rs:113:35:113:35 | a | main.rs:109:13:109:22 | source(...) | main.rs:113:35:113:35 | a | $@ | main.rs:109:13:109:22 | source(...) | source(...) | +| main.rs:121:14:121:25 | await future | main.rs:120:30:120:40 | source(...) | main.rs:121:14:121:25 | await future | $@ | main.rs:120:30:120:40 | source(...) | source(...) | +| main.rs:127:18:127:21 | data | main.rs:125:20:125:30 | source(...) | main.rs:127:18:127:21 | data | $@ | main.rs:125:20:125:30 | source(...) | source(...) | +| main.rs:135:14:135:25 | await future | main.rs:133:20:133:30 | source(...) | main.rs:135:14:135:25 | await future | $@ | main.rs:133:20:133:30 | source(...) | source(...) | +| main.rs:145:14:145:21 | captured | main.rs:142:24:142:34 | source(...) | main.rs:145:14:145:21 | captured | $@ | main.rs:142:24:142:34 | 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 742974eaab9c..650734bdd149 100644 --- a/rust/ql/test/library-tests/dataflow/lambdas/main.rs +++ b/rust/ql/test/library-tests/dataflow/lambdas/main.rs @@ -113,6 +113,61 @@ fn test_external_call() { may_invoke_callback4(|x| sink(a)); // $ hasValueFlow=81 } +mod async_blocks { + use super::{sink, source}; + + async fn async_block_flow_out() { + let future = async { source(206) }; + sink(future.await); // $ hasValueFlow=206 + } + + async fn async_block_flow_in() { + let data = source(207); + async { + sink(data); // $ hasValueFlow=207 + } + .await; + } + + async fn async_block_flow_through() { + let data = source(208); + let future = async { data }; + sink(future.await); // $ hasValueFlow=208 + } + + async fn async_block_captured_variable() { + let mut captured = 1; + sink(captured); + async { + captured = source(209); + } + .await; + sink(captured); // $ hasValueFlow=209 + } + + async fn boxed_async_block_captured_variable() { + let mut captured = 1; + sink(captured); + Box::pin(async { + captured = source(210); + }) + .await; + sink(captured); // $ MISSING: hasValueFlow=210 + } + + async fn test_async_blocks() { + async_block_flow_out().await; + async_block_flow_in().await; + async_block_flow_through().await; + async_block_captured_variable().await; + boxed_async_block_captured_variable().await; + } + + pub fn main() { + futures::executor::block_on(test_async_blocks()); + } +} + fn main() { closure_flow_out(); closure_flow_in(); @@ -123,4 +178,5 @@ fn main() { function_flows_through(); test_apply(); test_apply_wrap(); + async_blocks::main(); } diff --git a/rust/ql/test/library-tests/dataflow/lambdas/options.yml b/rust/ql/test/library-tests/dataflow/lambdas/options.yml new file mode 100644 index 000000000000..72214fcdfc77 --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/lambdas/options.yml @@ -0,0 +1,3 @@ +qltest_cargo_check: true +qltest_dependencies: + - futures = { version = "0.3" }