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
91 changes: 80 additions & 11 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ abstract class ItemNode extends Locatable {
// items made available through `use` are available to nodes that contain the `use`
exists(UseItemNode use |
use = this.getASuccessor(_, _) and
result = use.(ItemNode).getASuccessor(name, kind)
result = use.getASuccessor(name, kind)
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The removed explicit cast use.(ItemNode) was unnecessary since use is already declared as UseItemNode which extends ItemNode. This change improves code readability by removing redundant casting.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct.

)
or
exists(ExternCrateItemNode ec | result = ec.(ItemNode).getASuccessor(name, kind) |
Expand All @@ -240,12 +240,7 @@ abstract class ItemNode extends Locatable {
)
or
// items made available by an implementation where `this` is the implementing type
exists(ItemNode node |
this = node.(ImplItemNodeImpl).resolveSelfTyCand() and
result = node.getASuccessor(name, kind) and
kind.isExternalOrBoth() and
result instanceof AssocItemNode
)
typeImplEdge(this, _, name, kind, result)
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

Refactoring the complex inline logic into the typeImplEdge predicate improves code modularity and readability. This makes the logic reusable and easier to maintain.

Copilot uses AI. Check for mistakes.
or
// trait items with default implementations made available in an implementation
exists(ImplItemNodeImpl impl, ItemNode trait |
Expand Down Expand Up @@ -1311,6 +1306,7 @@ private predicate declares(ItemNode item, Namespace ns, string name) {
class RelevantPath extends Path {
RelevantPath() { not this = any(VariableAccess va).(PathExpr).getPath() }

/** Holds if this is an unqualified path with the textual value `name`. */
pragma[nomagic]
predicate isUnqualified(string name) {
not exists(this.getQualifier()) and
Expand Down Expand Up @@ -1421,6 +1417,35 @@ private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns, SuccessorKi
pragma[nomagic]
private predicate isUnqualifiedSelfPath(RelevantPath path) { path.isUnqualified("Self") }

/** Provides the input to `TraitIsVisible`. */
signature predicate relevantTraitVisibleSig(Element element, Trait trait);

/**
* Provides the `traitIsVisible` predicate for determining if a trait is visible
* at a given element.
*/
module TraitIsVisible<relevantTraitVisibleSig/2 relevantTraitVisible> {
/** Holds if the trait might be looked up in `encl`. */
private predicate traitLookup(ItemNode encl, Element element, Trait trait) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation of this predicate is inspired by unqualifiedPathLookup. In my original attempt I used getADescendant*(), but that seemed to be part of the performance issues in that PR.

// lookup in immediately enclosing item
relevantTraitVisible(element, trait) and
encl.getADescendant() = element
or
// lookup in an outer scope, but only if the trait is not declared in inner scope
exists(ItemNode mid |
traitLookup(mid, element, trait) and
not trait = mid.getASuccessor(_, _) and
encl = getOuterScope(mid)
)
}

/** Holds if the trait `trait` is visible at `element`. */
pragma[nomagic]
predicate traitIsVisible(Element element, Trait trait) {
exists(ItemNode encl | traitLookup(encl, element, trait) and trait = encl.getASuccessor(_, _))
}
}

pragma[nomagic]
private ItemNode resolvePathCand0(RelevantPath path, Namespace ns) {
exists(ItemNode res |
Expand All @@ -1446,6 +1471,10 @@ private ItemNode resolvePathCandQualifier(RelevantPath qualifier, RelevantPath p
name = path.getText()
}

/**
* Gets the item that `path` resolves to in `ns` when `qualifier` is the
* qualifier of `path` and `qualifier` resolves to `q`, if any.
*/
pragma[nomagic]
private ItemNode resolvePathCandQualified(
RelevantPath qualifier, ItemNode q, RelevantPath path, Namespace ns
Expand Down Expand Up @@ -1520,11 +1549,31 @@ private ItemNode resolvePathCand(RelevantPath path) {
)
}

/** Get a trait that should be visible when `path` resolves to `node`, if any. */
private Trait getResolvePathTraitUsed(RelevantPath path, AssocItemNode node) {
exists(TypeItemNode type, ImplItemNodeImpl impl |
node = resolvePathCandQualified(_, type, path, _) and
typeImplEdge(type, impl, _, _, node) and
result = impl.resolveTraitTyCand()
)
}

private predicate pathTraitUsed(Element path, Trait trait) {
trait = getResolvePathTraitUsed(path, _)
}

/** Gets the item that `path` resolves to, if any. */
cached
ItemNode resolvePath(RelevantPath path) {
result = resolvePathCand(path) and
not path = any(Path parent | exists(resolvePathCand(parent))).getQualifier()
not path = any(Path parent | exists(resolvePathCand(parent))).getQualifier() and
(
// When the result is an associated item of a trait implementation the
// implemented trait must be visible.
TraitIsVisible<pathTraitUsed/2>::traitIsVisible(path, getResolvePathTraitUsed(path, result))
or
not exists(getResolvePathTraitUsed(path, result))
)
or
// if `path` is the qualifier of a resolvable `parent`, then we should
// resolve `path` to something consistent with what `parent` resolves to
Expand Down Expand Up @@ -1606,8 +1655,16 @@ private predicate useImportEdge(Use use, string name, ItemNode item, SuccessorKi
not tree.hasRename() and
name = item.getName()
or
name = tree.getRename().getName().getText() and
name != "_"
exists(Rename rename | rename = tree.getRename() |
name = rename.getName().getText()
or
// When the rename doesn't have a name it's an underscore import. This
// makes the imported item visible but unnameable. We represent this
// by using the name `_` which can never occur in a path. See also:
// https://doc.rust-lang.org/reference/items/use-declarations.html#r-items.use.as-underscore
not rename.hasName() and
name = "_"
Comment on lines +1661 to +1666
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

Excellent documentation explaining the underscore import feature. The comment clearly explains why _ is used as a special name and includes a reference to the official Rust documentation.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@paldepind paldepind Sep 2, 2025

Choose a reason for hiding this comment

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

Thanks :copilot:.

)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note, the old disjunct here did in fact not do anything. When there is a rename it's name is never _. That is,

predicate isUnderscope(UseTree tree) { tree.getRename().getName().getText() = "_" }

is empty. Instead not rename.hasName() holds when _ is used in the source code.

)
)
)
Expand All @@ -1629,6 +1686,18 @@ private predicate externCrateEdge(ExternCrateItemNode ec, string name, CrateItem
)
}

/**
* Holds if `typeItem` is the implementing type of `impl` and the implementation
* makes `assoc` available as `name` at `kind`.
*/
private predicate typeImplEdge(
TypeItemNode typeItem, ImplItemNodeImpl impl, string name, SuccessorKind kind, AssocItemNode assoc
) {
typeItem = impl.resolveSelfTyCand() and
assoc = impl.getASuccessor(name, kind) and
kind.isExternalOrBoth()
}

pragma[nomagic]
private predicate preludeItem(string name, ItemNode i) {
exists(Crate stdOrCore, ModuleLikeNode mod, ModuleItemNode prelude, ModuleItemNode rust |
Expand Down Expand Up @@ -1693,7 +1762,7 @@ private module Debug {
useImportEdge(use, name, item, kind)
}

ItemNode debuggetASuccessor(ItemNode i, string name, SuccessorKind kind) {
ItemNode debugGetASuccessor(ItemNode i, string name, SuccessorKind kind) {
i = getRelevantLocatable() and
result = i.getASuccessor(name, kind)
}
Expand Down
44 changes: 39 additions & 5 deletions rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1891,7 +1891,7 @@ private predicate methodCandidate(Type type, string name, int arity, Impl impl)
*/
pragma[nomagic]
private predicate methodCandidateTrait(Type type, Trait trait, string name, int arity, Impl impl) {
trait = resolvePath(impl.(ImplItemNode).getTraitPath()) and
trait = impl.(ImplItemNode).resolveTraitTy() and
Copy link

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The change from resolvePath(impl.(ImplItemNode).getTraitPath()) to impl.(ImplItemNode).resolveTraitTy() suggests using a more direct method for trait resolution, which likely improves performance and code clarity.

Copilot uses AI. Check for mistakes.
methodCandidate(type, name, arity, impl)
}

Expand All @@ -1903,19 +1903,53 @@ private predicate isMethodCall(MethodCall mc, Type rootType, string name, int ar
}

private module IsInstantiationOfInput implements IsInstantiationOfInputSig<MethodCall> {
/** Holds if `mc` specifies a trait and might target a method in `impl`. */
pragma[nomagic]
predicate potentialInstantiationOf(MethodCall mc, TypeAbstraction impl, TypeMention constraint) {
private predicate methodCallTraitCandidate(MethodCall mc, Impl impl) {
exists(Type rootType, string name, int arity |
isMethodCall(mc, rootType, name, arity) and
constraint = impl.(ImplTypeAbstraction).getSelfTy()
|
methodCandidateTrait(rootType, mc.getTrait(), name, arity, impl)
or
)
}

/** Holds if `mc` does not specify a trait and might target a method in `impl`. */
pragma[nomagic]
private predicate methodCallCandidate(MethodCall mc, Impl impl) {
exists(Type rootType, string name, int arity |
not exists(mc.getTrait()) and
isMethodCall(mc, rootType, name, arity) and
methodCandidate(rootType, name, arity, impl)
)
}

private predicate relevantTraitVisible(Element mc, Trait trait) {
trait = any(ImplItemNode impl | methodCallCandidate(mc, impl)).resolveTraitTy()
}

bindingset[impl]
pragma[inline_late]
private TypeRepr getImplSelfTy(Impl impl) { result = impl.getSelfTy() }

pragma[nomagic]
predicate potentialInstantiationOf(MethodCall mc, TypeAbstraction impl, TypeMention constraint) {
constraint = getImplSelfTy(impl) and
(
methodCallTraitCandidate(mc, impl)
or
methodCallCandidate(mc, impl) and
(
not exists(impl.(ImplItemNode).resolveTraitTy())
or
// If the `impl` block implements a trait, that trait must be visible in
// order for the `impl` to be valid.
exists(Trait trait |
pragma[only_bind_into](trait) = impl.(ImplItemNode).resolveTraitTy() and
TraitIsVisible<relevantTraitVisible/2>::traitIsVisible(mc, pragma[only_bind_into](trait))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The pragma here is to prevent resolveTraitTy from being joined with traitIsVisible on the trait column.

)
)
)
}

predicate relevantTypeMention(TypeMention constraint) {
exists(Impl impl | methodCandidate(_, _, _, impl) and constraint = impl.getSelfTy())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ multipleCallTargets
| test.rs:179:30:179:68 | ...::_print(...) |
| test.rs:188:26:188:105 | ...::_print(...) |
| test.rs:229:22:229:72 | ... .read_to_string(...) |
| test.rs:513:22:513:50 | file.read_to_end(...) |
| test.rs:519:22:519:53 | file.read_to_string(...) |
| test.rs:697:18:697:38 | ...::_print(...) |
| test.rs:702:18:702:45 | ...::_print(...) |
| test.rs:706:25:706:49 | address.to_socket_addrs() |
| test.rs:720:38:720:42 | ...::_print(...) |
| test.rs:724:38:724:54 | ...::_print(...) |
| test.rs:729:38:729:51 | ...::_print(...) |
Expand Down Expand Up @@ -76,12 +73,6 @@ 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:35:26:35:63 | pinned.poll_read(...) |
| test_futures_io.rs:62:22:62:50 | pinned.poll_fill_buf(...) |
| test_futures_io.rs:69:23:69:67 | ... .poll_fill_buf(...) |
| test_futures_io.rs:93:26:93:63 | pinned.poll_read(...) |
| test_futures_io.rs:116:22:116:50 | pinned.poll_fill_buf(...) |
| test_futures_io.rs:145:26:145:49 | ...::with_capacity(...) |
| 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
53 changes: 53 additions & 0 deletions rust/ql/test/library-tests/path-resolution/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,58 @@ mod m16 {
} // I83
}

mod trait_visibility {
mod m {
pub trait Foo {
fn a_method(&self); // Foo::a_method
} // Foo

pub trait Bar {
fn a_method(&self); // Bar::a_method
} // Bar

pub struct X;
#[rustfmt::skip]
impl Foo for X { // $ item=Foo item=X
fn a_method(&self) {
println!("foo!");
} // X_Foo::a_method
}
#[rustfmt::skip]
impl Bar for X { // $ item=Bar item=X
fn a_method(&self) {
println!("bar!");
} // X_Bar::a_method
}
}

use m::X; // $ item=X

pub fn f() {
let x = X; // $ item=X
{
// Only the `Foo` trait is visible
use m::Foo; // $ item=Foo
X::a_method(&x); // $ item=X_Foo::a_method
}
{
// Only the `Bar` trait is visible
use m::Bar; // $ item=Bar
X::a_method(&x); // $ item=X_Bar::a_method
}
{
// Only the `Bar` trait is visible (but unnameable)
use m::Bar as _; // $ item=Bar
X::a_method(&x); // $ item=X_Bar::a_method
}
{
// The `Bar` trait is not visible, but we can refer to its method
// with a full path.
m::Bar::a_method(&x); // $ item=Bar::a_method
}
} // trait_visibility::f
}

mod m17 {
trait MyTrait {
fn f(&self); // I1
Expand Down Expand Up @@ -730,6 +782,7 @@ fn main() {
m11::f(); // $ item=I63
m15::f(); // $ item=I75
m16::f(); // $ item=I83
trait_visibility::f(); // $ item=trait_visibility::f
m17::f(); // $ item=I99
nested6::f(); // $ item=I116
nested8::f(); // $ item=I119
Expand Down
Loading
Loading