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
60 changes: 47 additions & 13 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ private ItemNode getAChildSuccessor(ItemNode item, string name, SuccessorKind ki
if item instanceof ImplOrTraitItemNode and result instanceof AssocItem
then kind.isExternal()
else
if result instanceof Use
then kind.isInternal()
else kind.isBoth()
if result.isPublic()
then kind.isBoth()
else kind.isInternal()
)
}

Expand Down Expand Up @@ -165,6 +165,20 @@ abstract class ItemNode extends Locatable {
/** Gets the visibility of this item, if any. */
abstract Visibility getVisibility();

/**
* Holds if this item is public.
*
* This is the case when this item either has `pub` visibility (but is not
* a `use`; a `use` itself is not visible from the outside), or when this
* item is a variant.
*/
predicate isPublic() {
exists(this.getVisibility()) and
not this instanceof Use
or
this instanceof Variant
}

/** Gets the `i`th type parameter of this item, if any. */
abstract TypeParam getTypeParam(int i);

Expand Down Expand Up @@ -380,9 +394,7 @@ abstract private class ModuleLikeNode extends ItemNode {

private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
pragma[nomagic]
ModuleLikeNode getSuper() {
result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super")
}
ModuleLikeNode getSuper() { fileImport(result.getAnItemInScope(), this) }
Copy link
Preview

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

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

The logic change from using any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor(\"super\") to fileImport(result.getAnItemInScope(), this) appears to invert the relationship. The original code finds a module that imports this file and gets its super, while the new code finds what this file imports. This could break super module resolution.

Suggested change
ModuleLikeNode getSuper() { fileImport(result.getAnItemInScope(), this) }
ModuleLikeNode getSuper() { any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super") }

Copilot uses AI. Check for mistakes.


override string getName() { result = "(source file)" }

Expand Down Expand Up @@ -1300,7 +1312,8 @@ private predicate useTreeDeclares(UseTree tree, string name) {
*/
pragma[nomagic]
private predicate declaresDirectly(ItemNode item, Namespace ns, string name) {
exists(ItemNode child, SuccessorKind kind | child = getAChildSuccessor(item, name, kind) |
exists(ItemNode child, SuccessorKind kind |
child = getAChildSuccessor(item, name, kind) and
child.getNamespace() = ns and
kind.isInternalOrBoth()
)
Expand Down Expand Up @@ -1491,6 +1504,13 @@ private ItemNode resolvePathCandQualifier(RelevantPath qualifier, RelevantPath p
name = path.getText()
}

pragma[nomagic]
private Crate getCrate0(Locatable l) { result.getASourceFile().getFile() = l.getFile() }

bindingset[l]
pragma[inline_late]
private Crate getCrate(Locatable l) { result = getCrate0(l) }

/**
* Gets the item that `path` resolves to in `ns` when `qualifier` is the
* qualifier of `path` and `qualifier` resolves to `q`, if any.
Expand All @@ -1501,8 +1521,17 @@ private ItemNode resolvePathCandQualified(
) {
exists(string name, SuccessorKind kind |
q = resolvePathCandQualifier(qualifier, path, name) and
result = getASuccessor(q, name, ns, kind) and
result = getASuccessor(q, name, ns, kind)
|
kind.isExternalOrBoth()
or
// Non-public items are visible to paths in descendant modules of the declaring
// module; the declaration may happen via a `use` statement, where the item
// being used is _not_ itself in an ancestor module, and we currently don't track
// that information in `getASuccessor`. So, for simplicity, we allow for non-public
// items when the path and the item are in the same crate.
getCrate(path) = getCrate(result) and
not result instanceof TypeParam
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is just because the case we're handling here never overlaps with type parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is to avoid the case that you originally fixed in #20096.

)
}

Expand Down Expand Up @@ -1646,10 +1675,12 @@ private ItemNode resolveUseTreeListItemQualifier(

pragma[nomagic]
private ItemNode resolveUseTreeListItem(Use use, UseTree tree) {
tree = use.getUseTree() and
result = resolvePathCand(tree.getPath())
or
result = resolveUseTreeListItem(use, tree, tree.getPath(), _)
exists(Path path | path = tree.getPath() |
tree = use.getUseTree() and
result = resolvePathCand(path)
or
result = resolveUseTreeListItem(use, tree, path, _)
)
}

/** Holds if `use` imports `item` as `name`. */
Expand All @@ -1673,7 +1704,10 @@ private predicate useImportEdge(Use use, string name, ItemNode item, SuccessorKi
item = used and
(
not tree.hasRename() and
name = item.getName()
exists(string pathName |
pathName = tree.getPath().getText() and
if pathName = "self" then name = item.getName() else name = pathName
)
Comment on lines +1707 to +1710
Copy link
Preview

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

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

This logic assumes that when pathName is not "self", the path text should be used as the import name. However, for qualified paths like foo::bar::baz, this would use "baz" as the name, but the original logic used item.getName() which might be different if the item was already renamed in an intermediate import.

Suggested change
exists(string pathName |
pathName = tree.getPath().getText() and
if pathName = "self" then name = item.getName() else name = pathName
)
name = item.getName()

Copilot uses AI. Check for mistakes.

or
exists(Rename rename | rename = tree.getRename() |
name = rename.getName().getText()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +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:664:22:664:43 | file.read(...) |
| test.rs:673:22:673:41 | f1.read(...) |
| test.rs:697:18:697:38 | ...::_print(...) |
| test.rs:702:18:702:45 | ...::_print(...) |
| test.rs:720:38:720:42 | ...::_print(...) |
Expand Down
Loading