Title: Rust: slice elements do not flow to `for_each` closure parameters · Issue #23064 · github/codeql · GitHub
Open Graph Title: Rust: slice elements do not flow to `for_each` closure parameters · Issue #23064 · github/codeql
X Title: Rust: slice elements do not flow to `for_each` closure parameters · Issue #23064 · github/codeql
Description: Description of the issue Data flow does not reach the parameter of a closure passed to for_each on a slice iterator, as in a.iter().for_each(|x| sink(x)). The equivalent for x in a.iter() { sink(x) } loop works. The cause appears to be t...
Open Graph Description: Description of the issue Data flow does not reach the parameter of a closure passed to for_each on a slice iterator, as in a.iter().for_each(|x| sink(x)). The equivalent for x in a.iter() { sink(x)...
X Description: Description of the issue Data flow does not reach the parameter of a closure passed to for_each on a slice iterator, as in a.iter().for_each(|x| sink(x)). The equivalent for x in a.iter() { sink(x)...
Opengraph URL: https://github.com/github/codeql/issues/23064
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Rust: slice elements do not flow to `for_each` closure parameters","articleBody":"**Description of the issue**\n\nData flow does not reach the parameter of a closure passed to `for_each` on a slice iterator, as in `a.iter().for_each(|x| sink(x))`. The equivalent `for x in a.iter() { sink(x) }` loop works. The cause appears to be the model precedence rule: the callback summary added in #22287 hides the manual `Iterator::for_each` model for this method.\n\n### Example\n\n```rust\nfn source() -> String {\n String::from(\"secret\")\n}\n\nfn sink(s: &String) {\n println!(\"{s}\");\n}\n\nfn main() {\n let a = [source()];\n a.iter().for_each(|x| sink(x)); // A\n for x in a.iter() {\n sink(x); // B\n }\n}\n```\n\nThe program prints `secret` twice: in both A and B, `x` is the element of `a`.\n\nQuery: taint tracking from each `source()` call to each `sink` argument.\n\n```ql\nimport rust\nimport codeql.rust.dataflow.DataFlow\nimport codeql.rust.dataflow.TaintTracking\n\nmodule Config implements DataFlow::ConfigSig {\n predicate isSource(DataFlow::Node n) {\n exists(Call c | c.getStaticTarget().getName().getText() = \"source\" | n.asExpr() = c)\n }\n\n predicate isSink(DataFlow::Node n) {\n exists(Call c | c.getStaticTarget().getName().getText() = \"sink\" |\n n.asExpr() = c.getPositionalArgument(0)\n )\n }\n}\n\nmodule Flow = TaintTracking::Global\u003cConfig>;\n\nfrom DataFlow::Node source, DataFlow::Node sink\nwhere Flow::flow(source, sink)\nselect sink, source\n```\n\nResult, the same on CodeQL 2.27.4 (`codeql/rust-all` 0.2.25) and 2.27.2 (0.2.23):\n\n| sink | source |\n|---|---|\n| `x` at line 13 (B) | `source()` at line 10 |\n\nThere is no row for A (line 11).\n\n### Cause (our reading of the source)\n\n1. `core::slice::iter::Iter` has its own `for_each` (it overrides the trait's default method), so the call resolves to `\u003ccore::slice::iter::Iter as core::iter::traits::iterator::Iterator>::for_each`.\n2. The manual model `\u003ccore::iter::traits::iterator::Iterator>::for_each`: `Argument[self].Element` -> `Argument[0].Parameter[0]` (`rust/ql/lib/codeql/rust/frameworks/stdlib/core.model.yml`) reaches that method only as an inherited model. `interpretPath` in `ModelsAsData.qll` gives inherited models provenance `hq-generated` and `isExact = false`.\n3. `SummarizedCallableWithCallback` in `ModelsAsData.qll` (#22287) gives the same method a `heuristic-callback` summary, `Argument[0]` -> `Argument[0].Parameter[closure-self]`, with provenance `hq-generated` and `isExact = true`.\n4. `RelevantModel::isRelevantModel` in the shared `FlowSummaryImpl.qll` drops an inexact model when the same callable has an exact model with the same verification. So the inherited `Element` -> `Parameter[0]` model is dropped. The only summary left says that the closure is called, but not with what.\n\nThe comment in `interpretPath` says inherited models are generated \"so that source code definitions and exact generated models take precedence\". That fits generated models of the method itself. The callback summary carries no element flow, though, so here it hides the only model that does.\n\n### Confirming the cause\n\nAdding a manual model for the exact method restores the flow at A. The first row restores the missing element-to-parameter flow. The second row is not needed for this query; it keeps the closure edge from #22287, which a manual model on the method would otherwise suppress:\n\n```yaml\nextensions:\n - addsTo:\n pack: codeql/rust-all\n extensible: summaryModel\n data:\n - [\"\u003ccore::slice::iter::Iter as core::iter::traits::iterator::Iterator>::for_each\", \"Argument[self].Element\", \"Argument[0].Parameter[0]\", \"value\", \"manual\"]\n - [\"\u003ccore::slice::iter::Iter as core::iter::traits::iterator::Iterator>::for_each\", \"Argument[0]\", \"Argument[0].Parameter[closure-self]\", \"value\", \"manual\"]\n```\n\nWith these two rows (tested on CodeQL 2.27.2), the query finds both A (`x` at line 11) and B (`x` at line 13), each from `source()` at line 10.\n\n### Possible fix\n\nA suggestion only; we have not tested it. Don't let the exact `heuristic-callback` summary cause other, inexact summaries of the same callable to be discarded. The callback summary itself can stay subject to the normal manual and neutral-model precedence rules.\n\nChanging the callback summary to `isExact = false` instead is not enough: an exact generated summary could then suppress it, losing the captured-variable flow added by #22287.\n\n### Scope\n\nWe reproduced this only for `for_each` on `core::slice::iter::Iter`. Other iterator types that override a closure-taking `Iterator` method may have the same problem; we have not tested them.\n","author":{"url":"https://github.com/bdrodes","@type":"Person","name":"bdrodes"},"datePublished":"2026-11-09T18:23:11Z","url":"https://github.com/github/codeql/issues/23064"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:189e7faf-5d7a-4eb4-e636-05dec4310376 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 872E:29D7A5:143245B:1A96BC1:6AF890AE |
| html-safe-nonce | 87a8a8bb59f71d750744f3a53a5a35b306a1f02c8192cffeac13ec8679390915 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NzJFOjI5RDdBNToxNDMyNDVCOjFBOTZCQzE6NkFGODkwQUUiLCJ2aXNpdG9yX2lkIjoiNzM3MDczMzUzOTM5NTIxNzEwIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 4df2538b4cc732f6b6600a3a3ad612e0c7f62df9cd74ae043da61b4d1907a409 |
| hovercard-subject-tag | issue:6292084836 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/github/codeql/23064/issue_layout |
| twitter:image | https://opengraph.githubassets.com/6b3bae174e663df3a4b534d73fc6db7abe61c76afaf2e8457584886e9fb8b887/github/codeql/issues/23064 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/6b3bae174e663df3a4b534d73fc6db7abe61c76afaf2e8457584886e9fb8b887/github/codeql/issues/23064 |
| og:image:alt | Description of the issue Data flow does not reach the parameter of a closure passed to for_each on a slice iterator, as in a.iter().for_each(|x| sink(x)). The equivalent for x in a.iter() { sink(x)... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | bdrodes |
| hostname | github.com |
| expected-hostname | github.com |
| None | 612d3e6ffd693e7ea8d9fcf452370995872baef31d825b1fedef704cd3d24ac7 |
| turbo-cache-control | no-preview |
| go-import | github.com/github/codeql git https://github.com/github/codeql.git |
| octolytics-dimension-user_id | 9919 |
| octolytics-dimension-user_login | github |
| octolytics-dimension-repository_id | 143040428 |
| octolytics-dimension-repository_nwo | github/codeql |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 143040428 |
| octolytics-dimension-repository_network_root_nwo | github/codeql |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 072e34035c13c31543fba9f025cbbbda4210dae4 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width