Skip to content

Rust: Model namespaces in path resolution #18728

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 2 commits into from
Feb 11, 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
6 changes: 6 additions & 0 deletions rust/ql/lib/codeql/rust/AstConsistency.qll
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ private import codeql.rust.elements.internal.PathResolution
/** Holds if `p` may resolve to multiple items including `i`. */
query predicate multiplePathResolutions(Path p, ItemNode i) {
i = resolvePath(p) and
// `use foo::bar` may use both a type `bar` and a value `bar`
not p =
any(UseTree use |
not use.isGlob() and
not use.hasUseTreeList()
).getPath() and
strictcount(resolvePath(p)) > 1
}

Expand Down
185 changes: 161 additions & 24 deletions rust/ql/lib/codeql/rust/elements/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@
private import rust
private import codeql.rust.elements.internal.generated.ParentChild

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_.
Expand Down Expand Up @@ -46,11 +71,15 @@ private import codeql.rust.elements.internal.generated.ParentChild
* - 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 AstNode {
/** 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();

Expand Down Expand Up @@ -143,30 +172,44 @@ abstract private class ModuleLikeNode extends ItemNode {
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
override string getName() { result = "(source file)" }

override Namespace getNamespace() {
result.isType() // can be referenced with `super`
}

override Visibility getVisibility() { none() }
}

private class ConstItemNode extends ItemNode instanceof Const {
override string getName() { result = Const.super.getName().getText() }

override Namespace getNamespace() { result.isValue() }

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

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() }
}

private class VariantItemNode extends ItemNode instanceof Variant {
override string getName() { result = Variant.super.getName().getText() }

override Namespace getNamespace() {
if super.getFieldList() instanceof RecordFieldList then result.isType() else result.isValue()
}

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

private class FunctionItemNode extends ItemNode instanceof Function {
override string getName() { result = Function.super.getName().getText() }

override Namespace getNamespace() { result.isValue() }

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

Expand All @@ -184,57 +227,92 @@ abstract private class ImplOrTraitItemNode extends ItemNode {
}
}

private class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
ItemNode resolveSelfTy() { result = resolvePath(super.getSelfTy().(PathTypeRepr).getPath()) }

override string getName() { result = "(impl)" }

override Namespace getNamespace() {
result.isType() // can be referenced with `Self`
}

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

private class MacroCallItemNode extends ItemNode instanceof MacroCall {
override string getName() { result = "(macro call)" }

override Namespace getNamespace() { 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() }
}

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 RecordFieldList and
result.isValue() // the constructor
}

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

private class TraitItemNode extends ImplOrTraitItemNode instanceof Trait {
class TraitItemNode extends ImplOrTraitItemNode instanceof Trait {
override string getName() { result = Trait.super.getName().getText() }

override Namespace getNamespace() { result.isType() }

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

class TypeAliasItemNode extends ItemNode instanceof TypeAlias {
override string getName() { result = TypeAlias.super.getName().getText() }

override Namespace getNamespace() { result.isType() }

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

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() }
}

private class UseItemNode extends ItemNode instanceof Use {
override string getName() { result = "(use)" }

override Namespace getNamespace() { none() }

override Visibility getVisibility() { none() }
}

private class BlockExprItemNode extends ItemNode instanceof BlockExpr {
override string getName() { result = "(block expr)" }

override Namespace getNamespace() { none() }

override Visibility getVisibility() { none() }
}

private class TypeParamItemNode extends ItemNode instanceof TypeParam {
override string getName() { result = TypeParam.super.getName().getText() }

override Namespace getNamespace() { result.isType() }

override Visibility getVisibility() { none() }
}

Expand Down Expand Up @@ -320,19 +398,22 @@ private predicate useTreeDeclares(UseTree tree, string name) {
}

/**
* Holds if `item` explicitly declares a sub item named `name`. This includes
* items declared by `use` statements, except for glob imports.
* 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, string name) {
private predicate declares(ItemNode item, Namespace ns, string name) {
exists(ItemNode child | child.getImmediateParent() = item |
child.getName() = name
child.getName() = name and
child.getNamespace() = ns
or
useTreeDeclares(child.(Use).getUseTree(), name)
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, name) and
declares(call, ns, name) and
call.getImmediateParent() = item
)
}
Expand All @@ -351,19 +432,20 @@ private class RelevantPath extends Path {

/**
* Holds if the unqualified path `p` references an item named `name`, and `name`
* may be looked up inside enclosing item `encl`.
* may be looked up in the `ns` namespace inside enclosing item `encl`.
*/
pragma[nomagic]
private predicate unqualifiedPathLookup(RelevantPath p, string name, ItemNode encl) {
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
encl0.getADescendant() = p and
exists(ns)
or
// lookup in an outer scope, but only if the item is not declared in inner scope
exists(ItemNode mid |
unqualifiedPathLookup(p, name, mid) and
not declares(mid, name)
unqualifiedPathLookup(p, name, ns, mid) and
not declares(mid, ns, name)
|
// nested modules do not have unqualified access to items from outer modules,
// except for items declared at top-level in the source file
Expand All @@ -374,16 +456,30 @@ private predicate unqualifiedPathLookup(RelevantPath p, string name, ItemNode en
|
// functions in `impl` blocks need to use explicit `Self::` to access other
// functions in the `impl` block
if encl0 instanceof ImplOrTraitItemNode then encl = encl0.getImmediateParent() else encl = encl0
if encl0 instanceof ImplOrTraitItemNode and ns.isValue()
then encl = encl0.getImmediateParent()
else encl = encl0
)
}

/** Gets the item that `path` resolves to, if any. */
cached
ItemNode resolvePath(RelevantPath path) {
exists(ItemNode encl, string name |
unqualifiedPathLookup(path, name, encl) and
result = encl.getASuccessor(name)
pragma[nomagic]
private ItemNode getASuccessor(ItemNode pred, string name, Namespace ns) {
result = pred.getASuccessor(name) and
ns = result.getNamespace()
}

pragma[nomagic]
private ItemNode resolvePath0(RelevantPath path) {
exists(ItemNode encl, Namespace ns, string name, ItemNode res |
unqualifiedPathLookup(path, name, ns, encl) and
res = getASuccessor(encl, name, ns)
|
if
not any(RelevantPath parent).getQualifier() = path and
name = "Self" and
res instanceof ImplItemNode
then result = res.(ImplItemNode).resolveSelfTy()
else result = res
)
or
exists(ItemNode q, string name |
Expand All @@ -394,6 +490,47 @@ ItemNode resolvePath(RelevantPath path) {
result = resolveUseTreeListItem(_, _, path)
}

/** 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(RecordExpr re).getPath()
or
p = any(PathTypeRepr ptr).getPath()
or
p = any(RecordPat rp).getPath()
or
p =
any(UseTree use |
use.isGlob()
or
use.hasUseTreeList()
).getPath()
or
p = any(Path parent).getQualifier()
)
}

/** Gets the item that `path` resolves to, if any. */
cached
ItemNode resolvePath(RelevantPath path) {
result = resolvePath0(path) and
(
pathUsesNamespace(path, result.getNamespace())
or
not pathUsesNamespace(path, _)
)
}

pragma[nomagic]
private ItemNode resolvePathQualifier(RelevantPath path, string name) {
result = resolvePath(path.getQualifier()) and
Expand Down Expand Up @@ -452,14 +589,14 @@ pragma[nomagic]
private predicate useImportEdge(Use use, string name, ItemNode item) {
exists(UseTree tree, ItemNode used |
used = resolveUseTreeListItem(use, tree) and
not exists(tree.getUseTreeList()) and
not tree.hasUseTreeList() and
if tree.isGlob()
then
exists(ItemNode encl |
exists(ItemNode encl, Namespace ns |
encl.getADescendant() = use and
item = used.getASuccessor(name) and
item = getASuccessor(used, name, ns) and
// glob imports can be shadowed
not declares(encl, name)
not declares(encl, ns, name)
)
else item = used
|
Expand Down
Loading