Files
codeql/rust/ql/lib/codeql/rust/internal/PathResolution.qll
Simon Friis Vindum 74583e4fbf Merge pull request #19227 from paldepind/rust-pr-self
Rust: Resolve `Self` path in trait type of implementation
2025-04-07 14:21:02 +02:00

1151 lines
34 KiB
Plaintext

/**
* Provides functionality for resolving paths, using the predicate `resolvePath`.
*/
private import rust
private import codeql.rust.elements.internal.generated.ParentChild
private import codeql.rust.internal.CachedStages
private newtype TNamespace =
TTypeNamespace() or
TValueNamespace()
/**
* A namespace.
*
* Either the _value_ namespace or the _type_ namespace, see
* https://doc.rust-lang.org/reference/names/namespaces.html.
*/
final class Namespace extends TNamespace {
/** Holds if this is the value namespace. */
predicate isValue() { this = TValueNamespace() }
/** Holds if this is the type namespace. */
predicate isType() { this = TTypeNamespace() }
/** Gets a textual representation of this namespace. */
string toString() {
this.isValue() and result = "value"
or
this.isType() and result = "type"
}
}
/**
* An item that may be referred to by a path, and which is a node in
* the _item graph_.
*
* The item graph is a labeled directed graph, where an edge
* `item1 --name--> item2` means that `item2` is available inside the
* scope of `item1` under the name `name`. For example, if we have
*
* ```rust
* mod m1 {
* mod m2 { }
* }
* ```
*
* then there is an edge `m1 --m2--> m1::m2`.
*
* Source files are also considered nodes in the item graph, and for
* each source file `f` there is an edge `f --name--> item` when `f`
* declares `item` with the name `name`.
*
* For imports like
*
* ```rust
* mod m1 {
* mod m2;
* use m2::foo;
* }
* ```
*
* we first generate an edge `m1::m2 --name--> f::item`, where `item` is
* any item (named `name`) inside the imported source file `f`. Using this
* edge, `m2::foo` can resolve to `f::foo`, which results in the edge
* `m1::use m2 --foo--> f::foo`. Lastly, all edges out of `use` nodes are
* lifted to predecessors in the graph, so we get an edge `m1 --foo--> f::foo`.
*
*
* References:
* - https://doc.rust-lang.org/reference/items/modules.html
* - https://doc.rust-lang.org/reference/names/scopes.html
* - https://doc.rust-lang.org/reference/paths.html
* - https://doc.rust-lang.org/reference/visibility-and-privacy.html
* - https://doc.rust-lang.org/reference/names/namespaces.html
*/
abstract class ItemNode extends Locatable {
/** Gets the (original) name of this item. */
abstract string getName();
/** Gets the namespace that this item belongs to, if any. */
abstract Namespace getNamespace();
/** Gets the visibility of this item, if any. */
abstract Visibility getVisibility();
/** Gets the `i`th type parameter of this item, if any. */
abstract TypeParam getTypeParam(int i);
/** Holds if this item is declared as `pub`. */
bindingset[this]
pragma[inline_late]
predicate isPublic() { exists(this.getVisibility()) }
/** Gets an element that has this item as immediately enclosing item. */
pragma[nomagic]
Element getADescendant() {
getImmediateParent(result) = this
or
exists(Element mid |
mid = this.getADescendant() and
getImmediateParent(result) = mid and
not mid instanceof ItemNode
)
}
/** Gets the immediately enclosing item of this item, if any. */
pragma[nomagic]
ItemNode getImmediateParent() { this = result.getADescendant() }
/** Gets the immediately enclosing module (or source file) of this item. */
pragma[nomagic]
ModuleLikeNode getImmediateParentModule() {
this = result.getAnItemInScope()
or
result = this.(SourceFileItemNode).getSuper()
}
cached
ItemNode getASuccessorRec(string name) {
Stages::PathResolutionStage::ref() and
sourceFileEdge(this, name, result)
or
this = result.getImmediateParent() and
name = result.getName()
or
fileImportEdge(this, name, result)
or
useImportEdge(this, name, result)
or
crateDefEdge(this, name, result)
or
crateDependencyEdge(this, name, result)
or
// items made available through `use` are available to nodes that contain the `use`
exists(UseItemNode use |
use = this.getASuccessorRec(_) and
result = use.(ItemNode).getASuccessorRec(name)
)
or
// items made available through macro calls are available to nodes that contain the macro call
exists(MacroCallItemNode call |
call = this.getASuccessorRec(_) and
result = call.(ItemNode).getASuccessorRec(name)
)
or
// a trait has access to the associated items of its supertraits
this =
any(TraitItemNode trait |
result = trait.resolveABound().getASuccessorRec(name) and
result instanceof AssocItemNode and
not trait.hasAssocItem(name)
)
or
// items made available by an implementation where `this` is the implementing type
exists(ItemNode node |
this = node.(ImplItemNode).resolveSelfTy() and
result = node.getASuccessorRec(name) and
result instanceof AssocItemNode
)
or
// trait items with default implementations made available in an implementation
exists(ImplItemNode impl, ItemNode trait |
this = impl and
trait = impl.resolveTraitTy() and
result = trait.getASuccessorRec(name) and
result.(AssocItemNode).hasImplementation() and
not impl.hasAssocItem(name)
)
or
// type parameters have access to the associated items of its bounds
result = this.(TypeParamItemNode).resolveABound().getASuccessorRec(name).(AssocItemNode)
}
/** Gets a successor named `name` of this item, if any. */
cached
ItemNode getASuccessor(string name) {
Stages::PathResolutionStage::ref() and
result = this.getASuccessorRec(name)
or
preludeEdge(this, name, result)
or
name = "super" and
if this instanceof Module or this instanceof SourceFile
then result = this.getImmediateParentModule()
else result = this.getImmediateParentModule().getImmediateParentModule()
or
name = "self" and
if this instanceof Module or this instanceof Enum or this instanceof Struct
then result = this
else result = this.getImmediateParentModule()
or
name = "Self" and
this = result.(ImplOrTraitItemNode).getAnItemInSelfScope()
or
name = "crate" and
this = result.(CrateItemNode).getARootModuleNode()
or
// todo: implement properly
name = "$crate" and
result = any(CrateItemNode crate | this = crate.getARootModuleNode()).(Crate).getADependency*() and
result.(CrateItemNode).isPotentialDollarCrateTarget()
}
/** Gets the location of this item. */
Location getLocation() { result = super.getLocation() }
}
/** A module or a source file. */
abstract private class ModuleLikeNode extends ItemNode {
/** Gets an item that may refer directly to items defined in this module. */
pragma[nomagic]
ItemNode getAnItemInScope() {
result.getImmediateParent() = this
or
exists(ItemNode mid |
mid = this.getAnItemInScope() and
result.getImmediateParent() = mid and
not mid instanceof ModuleLikeNode
)
}
/**
* Holds if this is a root module, meaning either a source file or
* the entry module of a crate.
*/
predicate isRoot() {
this instanceof SourceFileItemNode
or
this = any(Crate c).getModule()
}
}
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
pragma[nomagic]
ModuleLikeNode getSuper() {
result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super")
}
override string getName() { result = "(source file)" }
override Namespace getNamespace() {
result.isType() // can be referenced with `super`
}
override Visibility getVisibility() { none() }
override predicate isPublic() { any() }
override TypeParam getTypeParam(int i) { none() }
}
class CrateItemNode extends ItemNode instanceof Crate {
/**
* Gets the module node that defines this crate.
*
* This is either a source file, when the crate is defined in source code,
* or a module, when the crate is defined in a dependency.
*/
pragma[nomagic]
ModuleLikeNode getModuleNode() {
result = super.getSourceFile()
or
not exists(super.getSourceFile()) and
result = super.getModule()
}
/**
* Gets a source file that belongs to this crate, if any.
*
* This is calculated as those source files that can be reached from the entry
* file of this crate using zero or more `mod` imports, without going through
* the entry point of some other crate.
*/
pragma[nomagic]
SourceFileItemNode getASourceFile() {
result = super.getSourceFile()
or
exists(SourceFileItemNode mid, Module mod |
mid = this.getASourceFile() and
mod.getFile() = mid.getFile() and
fileImport(mod, result) and
not result = any(Crate other).getSourceFile()
)
}
/**
* Gets a root module node belonging to this crate.
*/
ModuleLikeNode getARootModuleNode() {
result = this.getASourceFile()
or
result = super.getModule()
}
pragma[nomagic]
predicate isPotentialDollarCrateTarget() {
exists(string name, RelevantPath p |
p.isDollarCrateQualifiedPath(name) and
exists(this.getASuccessor(name))
)
}
override string getName() { result = Crate.super.getName() }
override Namespace getNamespace() {
result.isType() // can be referenced with `crate`
}
override Visibility getVisibility() { none() }
override predicate isPublic() { any() }
override TypeParam getTypeParam(int i) { none() }
}
/** An item that can occur in a trait or an `impl` block. */
abstract private class AssocItemNode extends ItemNode, AssocItem {
/** Holds if this associated item has an implementation. */
abstract predicate hasImplementation();
}
private class ConstItemNode extends AssocItemNode instanceof Const {
override string getName() { result = Const.super.getName().getText() }
override predicate hasImplementation() { super.hasBody() }
override Namespace getNamespace() { result.isValue() }
override Visibility getVisibility() { result = Const.super.getVisibility() }
override TypeParam getTypeParam(int i) { none() }
}
private class EnumItemNode extends ItemNode instanceof Enum {
override string getName() { result = Enum.super.getName().getText() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { result = Enum.super.getVisibility() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
}
private class VariantItemNode extends ItemNode instanceof Variant {
override string getName() { result = Variant.super.getName().getText() }
override Namespace getNamespace() {
if super.getFieldList() instanceof StructFieldList then result.isType() else result.isValue()
}
override TypeParam getTypeParam(int i) {
result = super.getEnum().getGenericParamList().getTypeParam(i)
}
override Visibility getVisibility() { result = super.getEnum().getVisibility() }
}
class FunctionItemNode extends AssocItemNode instanceof Function {
override string getName() { result = Function.super.getName().getText() }
override predicate hasImplementation() { super.hasBody() }
override Namespace getNamespace() { result.isValue() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
override Visibility getVisibility() { result = Function.super.getVisibility() }
}
abstract class ImplOrTraitItemNode extends ItemNode {
/** Gets an item that may refer to this node using `Self`. */
pragma[nomagic]
ItemNode getAnItemInSelfScope() {
result = this
or
result.getImmediateParent() = this
or
exists(ItemNode mid |
mid = this.getAnItemInSelfScope() and
result.getImmediateParent() = mid and
not mid instanceof ImplOrTraitItemNode
)
}
/** Gets a `Self` path that refers to this item. */
Path getASelfPath() {
isUnqualifiedSelfPath(result) and
this = unqualifiedPathLookup(result, _)
}
/** Gets an associated item belonging to this trait or `impl` block. */
abstract AssocItemNode getAnAssocItem();
/** Holds if this trait or `impl` block declares an associated item named `name`. */
pragma[nomagic]
predicate hasAssocItem(string name) { name = this.getAnAssocItem().getName() }
}
pragma[nomagic]
private TypeParamItemNode resolveTypeParamPathTypeRepr(PathTypeRepr ptr) {
result = resolvePath(ptr.getPath())
}
class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
Path getSelfPath() { result = super.getSelfTy().(PathTypeRepr).getPath() }
Path getTraitPath() { result = super.getTrait().(PathTypeRepr).getPath() }
ItemNode resolveSelfTy() { result = resolvePath(this.getSelfPath()) }
TraitItemNode resolveTraitTy() { result = resolvePath(this.getTraitPath()) }
pragma[nomagic]
private TypeRepr getASelfTyArg() {
result =
this.getSelfPath().getSegment().getGenericArgList().getAGenericArg().(TypeArg).getTypeRepr()
}
/**
* Holds if this `impl` block is not fully parametric. That is, the implementing
* type is generic and the implementation is not parametrically polymorphic in all
* the implementing type's arguments.
*
* Examples:
*
* ```rust
* impl Foo { ... } // fully parametric
*
* impl<T> Foo<T> { ... } // fully parametric
*
* impl Foo<i64> { ... } // not fully parametric
*
* impl<T> Foo<Foo<T>> { ... } // not fully parametric
*
* impl<T: Trait> Foo<T> { ... } // not fully parametric
*
* impl<T> Foo<T> where T: Trait { ... } // not fully parametric
* ```
*/
pragma[nomagic]
predicate isNotFullyParametric() {
exists(TypeRepr arg | arg = this.getASelfTyArg() |
not exists(resolveTypeParamPathTypeRepr(arg))
or
resolveTypeParamPathTypeRepr(arg).hasTraitBound()
)
}
/**
* Holds if this `impl` block is fully parametric. Examples:
*
* ```rust
* impl Foo { ... } // fully parametric
*
* impl<T> Foo<T> { ... } // fully parametric
*
* impl Foo<i64> { ... } // not fully parametric
*
* impl<T> Foo<Foo<T>> { ... } // not fully parametric
*
* impl<T: Trait> Foo<T> { ... } // not fully parametric
*
* impl<T> Foo<T> where T: Trait { ... } // not fully parametric
* ```
*/
predicate isFullyParametric() { not this.isNotFullyParametric() }
override AssocItemNode getAnAssocItem() { result = super.getAssocItemList().getAnAssocItem() }
override string getName() { result = "(impl)" }
override Namespace getNamespace() {
result.isType() // can be referenced with `Self`
}
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
override Visibility getVisibility() { result = Impl.super.getVisibility() }
}
private class MacroCallItemNode extends AssocItemNode instanceof MacroCall {
override string getName() { result = "(macro call)" }
override predicate hasImplementation() { none() }
override Namespace getNamespace() { none() }
override TypeParam getTypeParam(int i) { none() }
override Visibility getVisibility() { none() }
}
private class ModuleItemNode extends ModuleLikeNode instanceof Module {
override string getName() { result = Module.super.getName().getText() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { result = Module.super.getVisibility() }
override TypeParam getTypeParam(int i) { none() }
}
private class StructItemNode extends ItemNode instanceof Struct {
override string getName() { result = Struct.super.getName().getText() }
override Namespace getNamespace() {
result.isType() // the struct itself
or
not super.getFieldList() instanceof StructFieldList and
result.isValue() // the constructor
}
override Visibility getVisibility() { result = Struct.super.getVisibility() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
}
class TraitItemNode extends ImplOrTraitItemNode instanceof Trait {
pragma[nomagic]
Path getABoundPath() {
result = super.getTypeBoundList().getABound().getTypeRepr().(PathTypeRepr).getPath()
}
pragma[nomagic]
ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }
override AssocItemNode getAnAssocItem() { result = super.getAssocItemList().getAnAssocItem() }
override string getName() { result = Trait.super.getName().getText() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { result = Trait.super.getVisibility() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
}
class TypeAliasItemNode extends AssocItemNode instanceof TypeAlias {
override string getName() { result = TypeAlias.super.getName().getText() }
override predicate hasImplementation() { super.hasTypeRepr() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { result = TypeAlias.super.getVisibility() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
}
private class UnionItemNode extends ItemNode instanceof Union {
override string getName() { result = Union.super.getName().getText() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { result = Union.super.getVisibility() }
override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) }
}
private class UseItemNode extends ItemNode instanceof Use {
override string getName() { result = "(use)" }
override Namespace getNamespace() { none() }
override Visibility getVisibility() { result = Use.super.getVisibility() }
override TypeParam getTypeParam(int i) { none() }
}
private class BlockExprItemNode extends ItemNode instanceof BlockExpr {
override string getName() { result = "(block expr)" }
override Namespace getNamespace() { none() }
override Visibility getVisibility() { none() }
override TypeParam getTypeParam(int i) { none() }
}
private class TypeParamItemNode extends ItemNode instanceof TypeParam {
pragma[nomagic]
Path getABoundPath() {
result = super.getTypeBoundList().getABound().getTypeRepr().(PathTypeRepr).getPath()
}
pragma[nomagic]
ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }
/**
* Holds if this type parameter has a trait bound. Examples:
*
* ```rust
* impl<T> Foo<T> { ... } // has no trait bound
*
* impl<T: Trait> Foo<T> { ... } // has trait bound
*
* impl<T> Foo<T> where T: Trait { ... } // has trait bound
* ```
*/
pragma[nomagic]
predicate hasTraitBound() {
exists(this.getABoundPath())
or
exists(ItemNode declaringItem, WherePred wp |
this = resolveTypeParamPathTypeRepr(wp.getTypeRepr()) and
wp = declaringItem.getADescendant() and
this = declaringItem.getADescendant()
)
}
/**
* Holds if this type parameter has no trait bound. Examples:
*
* ```rust
* impl<T> Foo<T> { ... } // has no trait bound
*
* impl<T: Trait> Foo<T> { ... } // has trait bound
*
* impl<T> Foo<T> where T: Trait { ... } // has trait bound
* ```
*/
pragma[nomagic]
predicate hasNoTraitBound() { not this.hasTraitBound() }
override string getName() { result = TypeParam.super.getName().getText() }
override Namespace getNamespace() { result.isType() }
override Visibility getVisibility() { none() }
override TypeParam getTypeParam(int i) { none() }
override Location getLocation() { result = TypeParam.super.getName().getLocation() }
}
/** Holds if `item` has the name `name` and is a top-level item inside `f`. */
private predicate sourceFileEdge(SourceFile f, string name, ItemNode item) {
item = f.getAnItem() and
name = item.getName()
}
/** Holds if `f` is available as `mod name;` inside `folder`. */
private predicate fileModule(SourceFile f, string name, Folder folder) {
exists(File file | file = f.getFile() |
file.getBaseName() = name + ".rs" and
folder = file.getParentContainer()
or
exists(Folder encl |
file.getBaseName() = "mod.rs" and
encl = file.getParentContainer() and
name = encl.getBaseName() and
folder = encl.getParentContainer()
)
)
}
/**
* Holds if `m` is a `mod name;` module declaration, where the corresponding
* module file needs to be looked up in `lookup` or one of its descandants.
*/
private predicate modImport0(Module m, string name, Folder lookup) {
exists(File f, Folder parent, string fileName |
f = m.getFile() and
not m.hasItemList() and
// TODO: handle
// ```
// #[path = "foo.rs"]
// mod bar;
// ```
not m.getAnAttr().getMeta().getPath().getText() = "path" and
name = m.getName().getText() and
parent = f.getParentContainer() and
fileName = f.getStem()
|
// sibling import
lookup = parent and
(
m.getFile() = any(CrateItemNode c).getModuleNode().(SourceFileItemNode).getFile()
or
m.getFile().getBaseName() = "mod.rs"
)
or
// child import
lookup = parent.getFolder(fileName)
)
}
/**
* Holds if `m` is a `mod name;` module declaration, which happens inside a
* nested module, and `pred -> succ` is a module edge leading to `m`.
*/
private predicate modImportNested(ModuleItemNode m, ModuleItemNode pred, ModuleItemNode succ) {
pred.getAnItemInScope() = succ and
(
modImport0(m, _, _) and
succ = m
or
modImportNested(m, succ, _)
)
}
/**
* Holds if `m` is a `mod name;` module declaration, which happens inside a
* nested module, where `ancestor` is a reflexive transitive ancestor module
* of `m` with corresponding lookup folder `lookup`.
*/
private predicate modImportNestedLookup(Module m, ModuleItemNode ancestor, Folder lookup) {
modImport0(m, _, lookup) and
modImportNested(m, ancestor, _) and
not modImportNested(m, _, ancestor)
or
exists(ModuleItemNode m1, Folder mid |
modImportNestedLookup(m, m1, mid) and
modImportNested(m, m1, ancestor) and
lookup = mid.getFolder(m1.getName())
)
}
/** Holds if `m` is a `mod name;` item importing file `f`. */
private predicate fileImport(Module m, SourceFile f) {
exists(string name, Folder parent |
modImport0(m, name, _) and
fileModule(f, name, parent)
|
// `m` is not inside a nested module
modImport0(m, name, parent) and
not modImportNested(m, _, _)
or
// `m` is inside a nested module
modImportNestedLookup(m, m, parent)
)
}
/**
* Holds if `mod` is a `mod name;` item targeting a file resulting in `item` being
* in scope under the name `name`.
*/
pragma[nomagic]
private predicate fileImportEdge(Module mod, string name, ItemNode item) {
exists(SourceFileItemNode f |
fileImport(mod, f) and
item = f.getASuccessorRec(name)
)
}
/**
* Holds if crate `c` defines the item `i` named `name`.
*/
pragma[nomagic]
private predicate crateDefEdge(CrateItemNode c, string name, ItemNode i) {
i = c.getModuleNode().getASuccessorRec(name) and
not i instanceof Crate
}
/**
* Holds if `m` depends on crate `dep` named `name`.
*/
private predicate crateDependencyEdge(ModuleLikeNode m, string name, CrateItemNode dep) {
exists(CrateItemNode c | dep = c.(Crate).getDependency(name) |
// entry module/entry source file
m = c.getModuleNode()
or
// entry/transitive source file
m = c.getASourceFile()
)
}
private predicate useTreeDeclares(UseTree tree, string name) {
not tree.isGlob() and
not exists(tree.getUseTreeList()) and
(
name = tree.getRename().getName().getText() and
name != "_"
or
not tree.hasRename() and
name = tree.getPath().getText()
)
or
exists(UseTree mid |
useTreeDeclares(mid, name) and
mid = tree.getUseTreeList().getAUseTree()
)
}
/**
* Holds if `item` explicitly declares a sub item named `name` in the
* namespace `ns`. This includes items declared by `use` statements,
* except for glob imports.
*/
pragma[nomagic]
private predicate declares(ItemNode item, Namespace ns, string name) {
exists(ItemNode child | child.getImmediateParent() = item |
child.getName() = name and
child.getNamespace() = ns
or
useTreeDeclares(child.(Use).getUseTree(), name) and
exists(ns) // `use foo::bar` can refer to both a value and a type
)
or
exists(MacroCallItemNode call |
declares(call, ns, name) and
call.getImmediateParent() = item
)
}
/** A path that does not access a local variable. */
class RelevantPath extends Path {
RelevantPath() { not this = any(VariableAccess va).(PathExpr).getPath() }
pragma[nomagic]
predicate isUnqualified(string name) {
not exists(this.getQualifier()) and
not this = any(UseTreeList list).getAUseTree().getPath() and
name = this.getText()
}
pragma[nomagic]
predicate isCratePath(string name, ItemNode encl) {
name = ["crate", "$crate"] and
this.isUnqualified(name) and
encl.getADescendant() = this
}
pragma[nomagic]
predicate isDollarCrateQualifiedPath(string name) {
this.getQualifier().(RelevantPath).isCratePath("$crate", _) and
this.getText() = name
}
}
/**
* Holds if the unqualified path `p` references an item named `name`, and `name`
* may be looked up in the `ns` namespace inside enclosing item `encl`.
*/
pragma[nomagic]
private predicate unqualifiedPathLookup(RelevantPath p, string name, Namespace ns, ItemNode encl) {
exists(ItemNode encl0 |
// lookup in the immediately enclosing item
p.isUnqualified(name) and
encl0.getADescendant() = p and
exists(ns) and
not name = ["crate", "$crate"]
or
// lookup in an outer scope, but only if the item is not declared in inner scope
exists(ItemNode mid |
unqualifiedPathLookup(p, name, ns, mid) and
not declares(mid, ns, name) and
not name = ["super", "self"] and
not (
name = "Self" and
mid = any(ImplOrTraitItemNode i).getAnItemInSelfScope()
)
|
// nested modules do not have unqualified access to items from outer modules,
// except for items declared at top-level in the root module
if mid instanceof Module
then encl0 = mid.getImmediateParent+() and encl0.(ModuleLikeNode).isRoot()
else encl0 = mid.getImmediateParent()
)
|
// functions in `impl` blocks need to use explicit `Self::` to access other
// functions in the `impl` block
if encl0 instanceof ImplOrTraitItemNode and ns.isValue()
then encl = encl0.getImmediateParent()
else encl = encl0
)
}
pragma[nomagic]
private ItemNode getASuccessor(ItemNode pred, string name, Namespace ns) {
result = pred.getASuccessor(name) and
ns = result.getNamespace()
}
private predicate isRoot(ItemNode root) { root.(ModuleLikeNode).isRoot() }
private predicate hasCratePath(ItemNode i) { any(RelevantPath path).isCratePath(_, i) }
private predicate hasChild(ItemNode parent, ItemNode child) { child.getImmediateParent() = parent }
private predicate rootHasCratePathTc(ItemNode i1, ItemNode i2) =
doublyBoundedFastTC(hasChild/2, isRoot/1, hasCratePath/1)(i1, i2)
pragma[nomagic]
private ItemNode unqualifiedPathLookup(RelevantPath path, Namespace ns) {
exists(ItemNode encl, string name |
result = getASuccessor(encl, pragma[only_bind_into](name), ns)
|
unqualifiedPathLookup(path, name, ns, encl)
or
// For `($)crate`, jump directly to the root module
exists(ItemNode i | path.isCratePath(pragma[only_bind_into](name), i) |
encl.(ModuleLikeNode).isRoot() and
encl = i
or
rootHasCratePathTc(encl, i)
)
)
}
pragma[nomagic]
private predicate isUnqualifiedSelfPath(RelevantPath path) { path.isUnqualified("Self") }
pragma[nomagic]
private ItemNode resolvePath0(RelevantPath path, Namespace ns) {
exists(ItemNode res |
res = unqualifiedPathLookup(path, ns) and
if
not any(RelevantPath parent).getQualifier() = path and
isUnqualifiedSelfPath(path) and
res instanceof ImplItemNode
then result = res.(ImplItemNode).resolveSelfTy()
else result = res
)
or
exists(ItemNode q, string name |
q = resolvePathQualifier(path, name) and
result = q.getASuccessor(name) and
ns = result.getNamespace()
)
or
result = resolveUseTreeListItem(_, _, path) and
ns = result.getNamespace()
}
/** Holds if path `p` must be looked up in namespace `n`. */
private predicate pathUsesNamespace(Path p, Namespace n) {
n.isValue() and
(
p = any(PathExpr pe).getPath()
or
p = any(TupleStructPat tsp).getPath()
)
or
n.isType() and
(
p = any(Visibility v).getPath()
or
p = any(StructExpr re).getPath()
or
p = any(PathTypeRepr ptr).getPath()
or
p = any(StructPat rp).getPath()
or
p =
any(UseTree use |
use.isGlob()
or
use.hasUseTreeList()
).getPath()
or
p = any(Path parent).getQualifier()
)
}
pragma[nomagic]
private ItemNode resolvePath1(RelevantPath path) {
exists(Namespace ns | result = resolvePath0(path, ns) |
pathUsesNamespace(path, ns)
or
not pathUsesNamespace(path, _) and
not path = any(MacroCall mc).getPath()
)
}
pragma[nomagic]
private ItemNode resolvePathPrivate(
RelevantPath path, ModuleLikeNode itemParent, ModuleLikeNode pathParent
) {
result = resolvePath1(path) and
itemParent = result.getImmediateParentModule() and
not result.isPublic() and
(
pathParent.getADescendant() = path
or
pathParent = any(ItemNode mid | path = mid.getADescendant()).getImmediateParentModule()
)
}
/**
* Gets a module that has access to private items defined inside `itemParent`.
*
* According to [The Rust Reference][1] this is either `itemParent` itself or any
* descendant of `itemParent`.
*
* [1]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#r-vis.access
*/
pragma[nomagic]
private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) {
exists(resolvePathPrivate(_, itemParent, _)) and
result.getImmediateParentModule*() = itemParent
}
/** Gets the item that `path` resolves to, if any. */
cached
ItemNode resolvePath(RelevantPath path) {
result = resolvePath1(path) and
result.isPublic()
or
exists(ModuleLikeNode itemParent, ModuleLikeNode pathParent |
result = resolvePathPrivate(path, itemParent, pathParent) and
pathParent = getAPrivateVisibleModule(itemParent)
)
}
pragma[nomagic]
private ItemNode resolvePathQualifier(RelevantPath path, string name) {
result = resolvePath(path.getQualifier()) and
name = path.getText()
}
private predicate isUseTreeSubPath(UseTree tree, RelevantPath path) {
path = tree.getPath()
or
exists(RelevantPath mid |
isUseTreeSubPath(tree, mid) and
path = mid.getQualifier()
)
}
pragma[nomagic]
private predicate isUseTreeSubPathUnqualified(UseTree tree, RelevantPath path, string name) {
isUseTreeSubPath(tree, path) and
not exists(path.getQualifier()) and
name = path.getText()
}
pragma[nomagic]
private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path) {
exists(UseTree midTree, ItemNode mid, string name |
mid = resolveUseTreeListItem(use, midTree) and
tree = midTree.getUseTreeList().getAUseTree() and
isUseTreeSubPathUnqualified(tree, path, pragma[only_bind_into](name)) and
result = mid.getASuccessor(pragma[only_bind_into](name))
)
or
exists(ItemNode q, string name |
q = resolveUseTreeListItemQualifier(use, tree, path, name) and
result = q.getASuccessor(name)
)
}
pragma[nomagic]
private ItemNode resolveUseTreeListItemQualifier(
Use use, UseTree tree, RelevantPath path, string name
) {
result = resolveUseTreeListItem(use, tree, path.getQualifier()) and
name = path.getText()
}
pragma[nomagic]
private ItemNode resolveUseTreeListItem(Use use, UseTree tree) {
tree = use.getUseTree() and
result = resolvePath(tree.getPath())
or
result = resolveUseTreeListItem(use, tree, tree.getPath())
}
/** Holds if `use` imports `item` as `name`. */
pragma[nomagic]
private predicate useImportEdge(Use use, string name, ItemNode item) {
exists(UseTree tree, ItemNode used |
used = resolveUseTreeListItem(use, tree) and
not tree.hasUseTreeList() and
if tree.isGlob()
then
exists(ItemNode encl, Namespace ns |
encl.getADescendant() = use and
item = getASuccessor(used, name, ns) and
// glob imports can be shadowed
not declares(encl, ns, name) and
not name = ["super", "self", "Self", "$crate", "crate"]
)
else (
item = used and
(
not tree.hasRename() and
name = item.getName()
or
name = tree.getRename().getName().getText() and
name != "_"
)
)
)
}
/**
* Holds if `i` is available inside `f` because it is reexported in [the prelude][1].
*
* We don't yet have access to prelude information from the extractor, so for now
* we include all the preludes for Rust: 2015, 2018, 2021, and 2024.
*
* [1]: https://doc.rust-lang.org/core/prelude/index.html
*/
private predicate preludeEdge(SourceFile f, string name, ItemNode i) {
exists(Crate core, ModuleItemNode mod, ModuleItemNode prelude, ModuleItemNode rust |
f = any(Crate c0 | core = c0.getDependency(_)).getASourceFile() and
core.getName() = "core" and
mod = core.getModule() and
prelude = mod.getASuccessorRec("prelude") and
rust = prelude.getASuccessorRec(["rust_2015", "rust_2018", "rust_2021", "rust_2024"]) and
i = rust.getASuccessorRec(name) and
not i instanceof Use
)
}
/** Provides predicates for debugging the path resolution implementation. */
private module Debug {
private Locatable getRelevantLocatable() {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
filepath.matches("%/test_logging.rs") and
startline = 163
)
}
predicate debugUnqualifiedPathLookup(
RelevantPath p, string name, Namespace ns, ItemNode encl, string path
) {
p = getRelevantLocatable() and
unqualifiedPathLookup(p, name, ns, encl) and
path = p.toStringDebug()
}
ItemNode debugResolvePath(RelevantPath path) {
path = getRelevantLocatable() and
result = resolvePath(path)
}
predicate debugUseImportEdge(Use use, string name, ItemNode item) {
use = getRelevantLocatable() and
useImportEdge(use, name, item)
}
ItemNode debugGetASuccessorRec(ItemNode i, string name) {
i = getRelevantLocatable() and
result = i.getASuccessor(name)
}
predicate debugFileImportEdge(Module mod, string name, ItemNode item) {
mod = getRelevantLocatable() and
fileImportEdge(mod, name, item)
}
predicate debugFileImport(Module m, SourceFile f) {
m = getRelevantLocatable() and
fileImport(m, f)
}
}