Skip to content

Rust: Data flow through trait methods #19881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion rust/ql/.generated.list

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/ql/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions rust/ql/lib/change-notes/2025-06-26-dataflow-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Implemented support for data flow through trait functions. For the purpose of data flow, calls to trait functions dispatch to all possible implementations.
16 changes: 13 additions & 3 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,20 @@ module RustDataFlow implements InputSig<Location> {

/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall call) {
Copy link
Preview

Copilot AI Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The viableCallable definition contains deeply nested exists and or expressions; extracting the trait-dispatch logic into a helper predicate could improve readability and maintenance.

Copilot uses AI. Check for mistakes.

exists(Callable target | target = call.asCallCfgNode().getCall().getStaticTarget() |
target = result.asCfgScope()
exists(Call c | c = call.asCallCfgNode().getCall() |
result.asCfgScope() = c.getARuntimeTarget()
or
target = result.asSummarizedCallable()
exists(SummarizedCallable sc, Function staticTarget |
staticTarget = c.getStaticTarget() and
sc = result.asSummarizedCallable()
|
sc = staticTarget
or
// only apply trait models to concrete implementations when they are not
// defined in source code
staticTarget.implements(sc) and
not staticTarget.fromSource()
)
)
}

Expand Down
6 changes: 6 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/internal/ModelsAsData.qll
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
* - `Field[t(i)]`: position `i` inside the variant/struct with canonical path `v`, for example
* `Field[core::option::Option::Some(0)]`.
* - `Field[i]`: the `i`th element of a tuple.
* - `Reference`: the referenced value.
* - `Future`: the value being computed asynchronously.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot. 👍

* 3. The `kind` column is a tag that can be referenced from QL to determine to
* which classes the interpreted elements should be added. For example, for
* sources `"remote"` indicates a default remote flow source, and for summaries
Expand Down Expand Up @@ -211,6 +213,10 @@ private class SummarizedCallableFromModel extends SummarizedCallable::Range {
this.getCanonicalPath() = path
}

override predicate hasProvenance(Provenance provenance) {
summaryModel(path, _, _, _, provenance, _)
}

override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
Expand Down
17 changes: 15 additions & 2 deletions rust/ql/lib/codeql/rust/elements/internal/AssocItemImpl.qll
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// generated by codegen, remove this comment if you wish to edit this file
/**
* This module provides a hand-modifiable wrapper around the generated class `AssocItem`.
*
Expand All @@ -12,6 +11,10 @@ private import codeql.rust.elements.internal.generated.AssocItem
* be referenced directly.
*/
module Impl {
private import rust
private import codeql.rust.internal.PathResolution

// the following QLdoc is generated: if you need to edit it, do it in the schema file
/**
* An associated item in a `Trait` or `Impl`.
*
Expand All @@ -21,5 +24,15 @@ module Impl {
* // ^^^^^^^^^^^^^
* ```
*/
class AssocItem extends Generated::AssocItem { }
class AssocItem extends Generated::AssocItem {
/** Holds if this item implements trait item `other`. */
pragma[nomagic]
predicate implements(AssocItem other) {
exists(TraitItemNode t, ImplItemNode i, string name |
other = t.getAssocItem(pragma[only_bind_into](name)) and
t = i.resolveTraitTy() and
this = i.getAssocItem(pragma[only_bind_into](name))
)
}
}
}
11 changes: 11 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/CallImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ module Impl {
not exists(TypeInference::resolveMethodCallTarget(this)) and
result = this.(CallExpr).getStaticTarget()
}

/** Gets a runtime target of this call, if any. */
pragma[nomagic]
Function getARuntimeTarget() {
result.hasImplementation() and
(
result = this.getStaticTarget()
or
result.implements(this.getStaticTarget())
)
}
}

/** Holds if the call expression dispatches to a trait method. */
Expand Down
7 changes: 7 additions & 0 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,13 @@ abstract class ImplOrTraitItemNode extends ItemNode {
/** Gets an associated item belonging to this trait or `impl` block. */
abstract AssocItemNode getAnAssocItem();

/** Gets the associated item named `name` belonging to this trait or `impl` block. */
pragma[nomagic]
AssocItemNode getAssocItem(string name) {
result = this.getAnAssocItem() and
result.getName() = name
}

/** Holds if this trait or `impl` block declares an associated item named `name`. */
pragma[nomagic]
predicate hasAssocItem(string name) { name = this.getAnAssocItem().getName() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
multipleCallTargets
| main.rs:225:14:225:29 | ...::deref(...) |
| main.rs:272:14:272:29 | ...::deref(...) |
Loading