Skip to content
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
20 changes: 20 additions & 0 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,18 @@ final class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {

override Visibility getVisibility() { result = Impl.super.getVisibility() }

TypeParamItemNode getBlanketImplementationTypeParam() {
result = this.resolveSelfTy() and
// This impl block is not superseded by the expansion of an attribute macro.
not exists(super.getAttributeMacroExpansion())
}

/**
* Holds if this impl block is a blanket implementation. That is, the
* implementation targets a generic parameter of the impl block.
*/
predicate isBlanketImplementation() { exists(this.getBlanketImplementationTypeParam()) }

override predicate hasCanonicalPath(Crate c) { this.resolveSelfTy().hasCanonicalPathPrefix(c) }

/**
Expand Down Expand Up @@ -1006,6 +1018,14 @@ final class TypeParamItemNode extends TypeItemNode instanceof TypeParam {
Path getABoundPath() { result = this.getTypeBoundAt(_, _).getTypeRepr().(PathTypeRepr).getPath() }

pragma[nomagic]
ItemNode resolveBound(int index) {
result =
rank[index + 1](int i, int j |
|
resolvePath(this.getTypeBoundAt(i, j).getTypeRepr().(PathTypeRepr).getPath()) order by i, j
)
}

ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }

/**
Expand Down
130 changes: 130 additions & 0 deletions rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
methodCandidate(type, name, arity, impl)
}

/**
* Holds if `mc` has `rootType` as the root type of the receiver and the target
* method is named `name` and has arity `arity`
*/
pragma[nomagic]
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
rootType = mc.getTypeAt(TypePath::nil()) and
Expand Down Expand Up @@ -2153,6 +2157,130 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
else any()
}

private module BlanketImplementation {
private ImplItemNode getPotentialDuplicated(
string fileName, string traitName, int arity, string tpName
) {
tpName = result.getBlanketImplementationTypeParam().getName() and
fileName = result.getLocation().getFile().getBaseName() and
traitName = result.resolveTraitTy().getName() and
arity = result.resolveTraitTy().(Trait).getNumberOfGenericParams()
}

private predicate duplicatedImpl(Impl impl1, Impl impl2) {
exists(string fileName, string traitName, int arity, string tpName |
impl1 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
impl2 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
impl1.getLocation().getFile().getAbsolutePath() <
impl2.getLocation().getFile().getAbsolutePath()
)
}

/**
* Holds if `impl` is a canonical blanket implementation.
*
* Libraries can often occur several times in the database for different
* library versions. This causes the same blanket implementations to exist
* multiple times, and these add no useful information.
*
* We detect these duplicates based on some simple heuristics (same trait
* name, file name, etc.). For these duplicates we select the one with the
* greatest file name (which usually is also the one with the greatest library
* version in the path) as the "canonical" implementation.
*/
private predicate isCanonicalImpl(Impl impl) {
not duplicatedImpl(impl, _) and impl.(ImplItemNode).isBlanketImplementation()
}

/**
* Holds if `impl` is a blanket implementation for a type parameter and
* `traitBound` is the first non-trivial trait bound of that type parameter.
*/
private predicate blanketImplementationTraitBound(ImplItemNode impl, Trait traitBound) {
traitBound =
min(Trait trait, int i |
trait = impl.getBlanketImplementationTypeParam().resolveBound(i) and
// Exclude traits that are known to not narrow things down very much.
not trait.getName().getText() =
[
"Sized", "Clone",
// The auto traits
"Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe"
]
Comment on lines +2204 to +2209
Copy link

Copilot AI Sep 10, 2025

Choose a reason for hiding this comment

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

The hardcoded list of excluded trait names should be extracted to a constant or configuration to improve maintainability and make it easier to extend.

Copilot uses AI. Check for mistakes.
|
trait order by i
)
}

/**
* Holds if `impl` is a relevant blanket implementation that requires the
* trait `traitBound` and provides `f`, a method with name `name` and arity
* `arity`.
*/
private predicate blanketImplementationMethod(
ImplItemNode impl, Trait traitBound, string name, int arity, Function f
) {
isCanonicalImpl(impl) and
blanketImplementationTraitBound(impl, traitBound) and
f.getParamList().hasSelfParam() and
arity = f.getParamList().getNumberOfParams() and
(
f = impl.getAssocItem(name)
or
// If the trait has a method with a default implementation, then that
// target is interesting as well.
not exists(impl.getAssocItem(name)) and
f = impl.resolveTraitTy().getAssocItem(name)
Comment on lines +2232 to +2233
Copy link
Contributor

Choose a reason for hiding this comment

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

How come this case is not filtered away by the constraint below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we have impl<T : Bar> Foo for T then:

  • If Foo has a super trait with a default implementation, then we want to consider that default implementation as a target. That's what this case allows for.
  • If Foo and Bar has a common super trait Baz then methods on Baz are available both through Bar and Foo, and finding them through the blanket implementation doesn't add anything. That what the below filter does.
    So the filter might remove some targets added by the case, but not all of them.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • If Foo has a super trait with a default implementation, then we want to consider that default implementation as a target. That's what this case allows for.

But f = impl.resolveTraitTy().getAssocItem(name) means that f is defined inside Foo and not in a super trait of Foo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, my example doesn't add up.

I think I added the filter for cases like this:

trait ApplicationImpl: ApplicationImplExt {
  fn activate(&self) { /* body */ }
}

impl<T: ApplicationImpl> ApplicationImplExt for T {}

Here we can get the activate method through the trait bound ApplicationImpl and hence it's not interesting to get it through the blanket implementation. I don't understand what purpose this blanket implementation serves in the first case, but it's from the gio library used in tauri.

) and
// If the method is already available through one of the trait bounds on the
// type parameter (because they implement the trait targeted by the impl
// block) then ignore it.
not impl.getBlanketImplementationTypeParam().resolveABound().(TraitItemNode).getASuccessor(name) =
f
}

pragma[nomagic]
predicate methodCallMatchesBlanketImpl(
MethodCall mc, Type t, ImplItemNode impl, Trait traitBound, Trait traitImpl, Function f
) {
// Only check method calls where we have ruled out inherent method targets.
// Ideally we would also check if non-blanket method targets have been ruled
// out.
methodCallHasNoInherentTarget(mc) and
exists(string name, int arity |
isMethodCall(mc, t, name, arity) and
blanketImplementationMethod(impl, traitBound, name, arity, f)
) and
traitImpl = impl.resolveTraitTy()
}

private predicate relevantTraitVisible(Element mc, Trait trait) {
methodCallMatchesBlanketImpl(mc, _, _, _, trait, _)
}

module SatisfiesConstraintInput implements SatisfiesConstraintInputSig<MethodCall> {
pragma[nomagic]
predicate relevantConstraint(MethodCall mc, Type constraint) {
exists(Trait traitBound, Trait traitImpl |
methodCallMatchesBlanketImpl(mc, _, _, traitBound, traitImpl, _) and
TraitIsVisible<relevantTraitVisible/2>::traitIsVisible(mc, traitImpl) and
traitBound = constraint.(TraitType).getTrait()
)
}

predicate useUniversalConditions() { none() }
}

predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait traitBound, Function f) {
SatisfiesConstraint<MethodCall, SatisfiesConstraintInput>::satisfiesConstraintType(mc,
TTrait(traitBound), _, _) and
methodCallMatchesBlanketImpl(mc, t, impl, traitBound, _, f)
}

pragma[nomagic]
Function getMethodFromBlanketImpl(MethodCall mc) { hasBlanketImpl(mc, _, _, _, result) }
}

/** Gets a method from an `impl` block that matches the method call `mc`. */
pragma[nomagic]
private Function getMethodFromImpl(MethodCall mc) {
Expand Down Expand Up @@ -2188,6 +2316,8 @@ private Function resolveMethodCallTarget(MethodCall mc) {
// The method comes from an `impl` block targeting the type of the receiver.
result = getMethodFromImpl(mc)
or
result = BlanketImplementation::getMethodFromBlanketImpl(mc)
or
// The type of the receiver is a type parameter and the method comes from a
// trait bound on the type parameter.
result = getTypeParameterMethod(mc.getTypeAt(TypePath::nil()), mc.getMethodName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1005,12 +1005,15 @@ readStep
| main.rs:458:5:458:11 | mut_arr | file://:0:0:0:0 | element | main.rs:458:5:458:14 | mut_arr[1] |
| main.rs:459:13:459:19 | mut_arr | file://:0:0:0:0 | element | main.rs:459:13:459:22 | mut_arr[1] |
| main.rs:461:10:461:16 | mut_arr | file://:0:0:0:0 | element | main.rs:461:10:461:19 | mut_arr[0] |
| main.rs:467:24:467:33 | [post] receiver for source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | [post] source(...) |
| main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.0 | main.rs:468:10:468:13 | cond |
| main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.1 | main.rs:468:16:468:19 | name |
| main.rs:468:25:468:29 | names | file://:0:0:0:0 | element | main.rs:468:9:468:20 | TuplePat |
| main.rs:470:41:470:67 | [post] \|...\| ... | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | [post] default_name |
| main.rs:470:44:470:55 | [post] receiver for default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | [post] default_name |
| main.rs:470:44:470:55 | this | main.rs:467:9:467:20 | captured default_name | main.rs:470:44:470:55 | default_name |
| main.rs:471:18:471:18 | [post] receiver for n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | [post] n |
| main.rs:494:13:494:13 | [post] receiver for a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | [post] a |
| main.rs:495:13:495:13 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | [post] b |
| main.rs:496:18:496:18 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | [post] b |
| main.rs:507:10:507:11 | vs | file://:0:0:0:0 | element | main.rs:507:10:507:14 | vs[0] |
Expand Down Expand Up @@ -1110,8 +1113,11 @@ storeStep
| main.rs:455:27:455:27 | 2 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] |
| main.rs:455:30:455:30 | 3 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] |
| main.rs:458:18:458:27 | source(...) | file://:0:0:0:0 | element | main.rs:458:5:458:11 | [post] mut_arr |
| main.rs:467:24:467:33 | source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | receiver for source(...) |
| main.rs:470:41:470:67 | default_name | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | \|...\| ... |
| main.rs:470:44:470:55 | default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | receiver for default_name |
| main.rs:471:18:471:18 | n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | receiver for n |
| main.rs:494:13:494:13 | a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | receiver for a |
| main.rs:495:13:495:13 | b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | receiver for b |
| main.rs:496:18:496:18 | b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | receiver for b |
| main.rs:505:15:505:24 | source(...) | file://:0:0:0:0 | element | main.rs:505:14:505:34 | [...] |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ multipleCallTargets
| test.rs:977:14:977:29 | ...::_print(...) |
| test.rs:979:27:979:36 | ...::_print(...) |
| test.rs:980:28:980:41 | ...::_print(...) |
| test_futures_io.rs:45:27:45:84 | ...::read(...) |
| test_futures_io.rs:49:27:49:51 | reader.read(...) |
| test_futures_io.rs:83:22:83:39 | reader2.fill_buf() |
| test_futures_io.rs:103:27:103:85 | ...::read(...) |
| test_futures_io.rs:107:27:107:52 | reader2.read(...) |
| test_futures_io.rs:125:22:125:39 | reader2.fill_buf() |
| test_futures_io.rs:132:27:132:62 | reader2.read_until(...) |
| test_futures_io.rs:139:27:139:54 | reader2.read_line(...) |
| test_futures_io.rs:146:27:146:58 | reader2.read_to_end(...) |
| test_futures_io.rs:152:32:152:46 | reader2.lines() |
| test_futures_io.rs:153:14:153:32 | lines_stream.next() |
| test_futures_io.rs:154:32:154:50 | lines_stream.next() |
| web_frameworks.rs:13:14:13:22 | a.as_str() |
| web_frameworks.rs:13:14:13:23 | a.as_str() |
| web_frameworks.rs:14:14:14:24 | a.as_bytes() |
Expand Down
Loading
Loading