Skip to content

Commit

Permalink
Merge pull request #18728 from hvitved/rust/path-resolution-namespaces
Browse files Browse the repository at this point in the history
Rust: Model namespaces in path resolution
  • Loading branch information
hvitved authored Feb 11, 2025
2 parents e1c810a + c7441ab commit eaaf510
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 63 deletions.
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

0 comments on commit eaaf510

Please sign in to comment.