Skip to content

Commit 40672af

Browse files
committed
Rust: Path resolution before variable resolution
1 parent 01b707a commit 40672af

File tree

8 files changed

+83
-59
lines changed

8 files changed

+83
-59
lines changed

rust/ql/integration-tests/hello-workspace/path-resolution.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import rust
22
import codeql.rust.internal.PathResolution
33
import utils.test.PathResolutionInlineExpectationsTest
44

5-
query predicate resolveDollarCrate(RelevantPath p, Crate c) {
5+
query predicate resolveDollarCrate(PathExt p, Crate c) {
66
c = resolvePath(p) and
77
p.isDollarCrate() and
88
p.fromSource() and

rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import codeql.util.Boolean
22
private import codeql.rust.controlflow.ControlFlowGraph
3+
private import codeql.rust.elements.internal.VariableImpl::Impl as VariableImpl
34
private import rust
45

56
newtype TCompletion =
@@ -123,13 +124,7 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
123124
*/
124125
private predicate cannotCauseMatchFailure(Pat pat) {
125126
pat instanceof RangePat or
126-
// Identifier patterns that are in fact path patterns can cause failures. For
127-
// instance `None`. Only if an `@ ...` part is present can we be sure that
128-
// it's an actual identifier pattern. As a heuristic, if the identifier starts
129-
// with a lower case letter, then we assume that it's an identifier. This
130-
// works for code that follows the Rust naming convention for enums and
131-
// constants.
132-
pat = any(IdentPat p | p.hasPat() or p.getName().getText().charAt(0).isLowercase()) or
127+
pat = any(IdentPat p | p.hasPat() or VariableImpl::variableDecl(_, p.getName(), _)) or
133128
pat instanceof WildcardPat or
134129
pat instanceof RestPat or
135130
pat instanceof RefPat or

rust/ql/lib/codeql/rust/elements/internal/CallImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ module Impl {
8282
}
8383

8484
private predicate callHasTraitQualifier(CallExpr call, Trait qualifier) {
85-
exists(RelevantPath qualifierPath |
85+
exists(PathExt qualifierPath |
8686
callHasQualifier(call, _, qualifierPath) and
8787
qualifier = resolvePath(qualifierPath) and
8888
// When the qualifier is `Self` and resolves to a trait, it's inside a

rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import rust
22
private import codeql.rust.controlflow.ControlFlowGraph
3+
private import codeql.rust.internal.PathResolution as PathResolution
34
private import codeql.rust.elements.internal.generated.ParentChild as ParentChild
45
private import codeql.rust.elements.internal.PathImpl::Impl as PathImpl
56
private import codeql.rust.elements.internal.PathExprBaseImpl::Impl as PathExprBaseImpl
@@ -101,7 +102,7 @@ module Impl {
101102
* pattern.
102103
*/
103104
cached
104-
private predicate variableDecl(AstNode definingNode, Name name, string text) {
105+
predicate variableDecl(AstNode definingNode, Name name, string text) {
105106
Cached::ref() and
106107
exists(SelfParam sp |
107108
name = sp.getName() and
@@ -120,11 +121,7 @@ module Impl {
120121
not exists(getOutermostEnclosingOrPat(pat)) and definingNode = name
121122
) and
122123
text = name.getText() and
123-
// exclude for now anything starting with an uppercase character, which may be a reference to
124-
// an enum constant (e.g. `None`). This excludes static and constant variables (UPPERCASE),
125-
// which we don't appear to recognize yet anyway. This also assumes programmers follow the
126-
// naming guidelines, which they generally do, but they're not enforced.
127-
not text.charAt(0).isUppercase() and
124+
not PathResolution::isResolvablePath(pat) and
128125
// exclude parameters from functions without a body as these are trait method declarations
129126
// without implementations
130127
not exists(Function f | not f.hasBody() and f.getAParam().getPat() = pat) and

rust/ql/lib/codeql/rust/internal/PathResolution.qll

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ abstract class ItemNode extends Locatable {
205205
pragma[nomagic]
206206
final Attr getAttr(string name) {
207207
result = this.getAnAttr() and
208-
result.getMeta().getPath().(RelevantPath).isUnqualified(name)
208+
result.getMeta().getPath().(PathExt).isUnqualified(name)
209209
}
210210

211211
final predicate hasAttr(string name) { exists(this.getAttr(name)) }
@@ -1503,17 +1503,22 @@ private predicate declares(ItemNode item, Namespace ns, string name) {
15031503
)
15041504
}
15051505

1506-
/** A path that does not access a local variable. */
1507-
class RelevantPath extends Path {
1508-
RelevantPath() { not this = any(VariableAccess va).(PathExpr).getPath() }
1506+
/**
1507+
* A `Path` or an `IdentPat`.
1508+
*
1509+
* `IdentPat`s are included in order to resolve unqualified references
1510+
* to constructors in patterns.
1511+
*/
1512+
abstract class PathExt extends AstNode {
1513+
abstract string getText();
15091514

15101515
/** Holds if this is an unqualified path with the textual value `name`. */
15111516
pragma[nomagic]
1512-
predicate isUnqualified(string name) {
1513-
not exists(this.getQualifier()) and
1514-
not this = any(UseTreeList list).getAUseTree().getPath().getQualifier*() and
1515-
name = this.getText()
1516-
}
1517+
abstract predicate isUnqualified(string name);
1518+
1519+
abstract Path getQualifier();
1520+
1521+
abstract string toStringDebug();
15171522

15181523
/**
15191524
* Holds if this is an unqualified path with the textual value `name` and
@@ -1535,6 +1540,30 @@ class RelevantPath extends Path {
15351540
predicate isDollarCrate() { this.isUnqualified("$crate", _) }
15361541
}
15371542

1543+
private class PathExtPath extends PathExt instanceof Path {
1544+
override string getText() { result = Path.super.getText() }
1545+
1546+
override predicate isUnqualified(string name) {
1547+
not exists(Path.super.getQualifier()) and
1548+
not this = any(UseTreeList list).getAUseTree().getPath().getQualifier*() and
1549+
name = Path.super.getText()
1550+
}
1551+
1552+
override Path getQualifier() { result = Path.super.getQualifier() }
1553+
1554+
override string toStringDebug() { result = Path.super.toStringDebug() }
1555+
}
1556+
1557+
private class PathExtIdentPat extends PathExt, IdentPat {
1558+
override string getText() { result = this.getName().getText() }
1559+
1560+
override predicate isUnqualified(string name) { name = this.getText() }
1561+
1562+
override Path getQualifier() { none() }
1563+
1564+
override string toStringDebug() { result = this.getText() }
1565+
}
1566+
15381567
private predicate isModule(ItemNode m) { m instanceof Module }
15391568

15401569
/** Holds if source file `source` contains the module `m`. */
@@ -1558,7 +1587,7 @@ private ItemNode getOuterScope(ItemNode i) {
15581587
pragma[nomagic]
15591588
private predicate unqualifiedPathLookup(ItemNode ancestor, string name, Namespace ns, ItemNode encl) {
15601589
// lookup in the immediately enclosing item
1561-
exists(RelevantPath path |
1590+
exists(PathExt path |
15621591
path.isUnqualified(name, encl) and
15631592
ancestor = encl and
15641593
not name = ["crate", "$crate", "super", "self"]
@@ -1594,7 +1623,7 @@ private ItemNode getASuccessor(
15941623

15951624
private predicate isSourceFile(ItemNode source) { source instanceof SourceFileItemNode }
15961625

1597-
private predicate hasCratePath(ItemNode i) { any(RelevantPath path).isCratePath(_, i) }
1626+
private predicate hasCratePath(ItemNode i) { any(PathExt path).isCratePath(_, i) }
15981627

15991628
private predicate hasChild(ItemNode parent, ItemNode child) { child.getImmediateParent() = parent }
16001629

@@ -1606,7 +1635,7 @@ private predicate sourceFileHasCratePathTc(ItemNode i1, ItemNode i2) =
16061635
* `name` may be looked up inside `ancestor`.
16071636
*/
16081637
pragma[nomagic]
1609-
private predicate keywordLookup(ItemNode ancestor, string name, RelevantPath p) {
1638+
private predicate keywordLookup(ItemNode ancestor, string name, PathExt p) {
16101639
// For `crate`, jump directly to the root module
16111640
exists(ItemNode i | p.isCratePath(name, i) |
16121641
ancestor instanceof SourceFile and
@@ -1620,7 +1649,7 @@ private predicate keywordLookup(ItemNode ancestor, string name, RelevantPath p)
16201649
}
16211650

16221651
pragma[nomagic]
1623-
private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns, SuccessorKind kind) {
1652+
private ItemNode unqualifiedPathLookup(PathExt p, Namespace ns, SuccessorKind kind) {
16241653
exists(ItemNode ancestor, string name |
16251654
result = getASuccessor(ancestor, pragma[only_bind_into](name), ns, kind, _) and
16261655
kind.isInternalOrBoth()
@@ -1635,7 +1664,7 @@ private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns, SuccessorKi
16351664
}
16361665

16371666
pragma[nomagic]
1638-
private predicate isUnqualifiedSelfPath(RelevantPath path) { path.isUnqualified("Self") }
1667+
private predicate isUnqualifiedSelfPath(PathExt path) { path.isUnqualified("Self") }
16391668

16401669
/** Provides the input to `TraitIsVisible`. */
16411670
signature predicate relevantTraitVisibleSig(Element element, Trait trait);
@@ -1718,14 +1747,14 @@ private module DollarCrateResolution {
17181747
isDollarCrateSupportedMacroExpansion(_, expansion)
17191748
}
17201749

1721-
private predicate isDollarCratePath(RelevantPath p) { p.isDollarCrate() }
1750+
private predicate isDollarCratePath(PathExt p) { p.isDollarCrate() }
17221751

1723-
private predicate isInDollarCrateMacroExpansion(RelevantPath p, AstNode expansion) =
1752+
private predicate isInDollarCrateMacroExpansion(PathExt p, AstNode expansion) =
17241753
doublyBoundedFastTC(hasParent/2, isDollarCratePath/1, isDollarCrateSupportedMacroExpansion/1)(p,
17251754
expansion)
17261755

17271756
pragma[nomagic]
1728-
private predicate isInDollarCrateMacroExpansionFromFile(File macroDefFile, RelevantPath p) {
1757+
private predicate isInDollarCrateMacroExpansionFromFile(File macroDefFile, PathExt p) {
17291758
exists(Path macroDefPath, AstNode expansion |
17301759
isDollarCrateSupportedMacroExpansion(macroDefPath, expansion) and
17311760
isInDollarCrateMacroExpansion(p, expansion) and
@@ -1740,17 +1769,17 @@ private module DollarCrateResolution {
17401769
* calls.
17411770
*/
17421771
pragma[nomagic]
1743-
predicate resolveDollarCrate(RelevantPath p, CrateItemNode crate) {
1772+
predicate resolveDollarCrate(PathExt p, CrateItemNode crate) {
17441773
isInDollarCrateMacroExpansionFromFile(crate.getASourceFile().getFile(), p)
17451774
}
17461775
}
17471776

17481777
pragma[nomagic]
1749-
private ItemNode resolvePathCand0(RelevantPath path, Namespace ns) {
1778+
private ItemNode resolvePathCand0(PathExt path, Namespace ns) {
17501779
exists(ItemNode res |
17511780
res = unqualifiedPathLookup(path, ns, _) and
17521781
if
1753-
not any(RelevantPath parent).getQualifier() = path and
1782+
not any(PathExt parent).getQualifier() = path and
17541783
isUnqualifiedSelfPath(path) and
17551784
res instanceof ImplItemNode
17561785
then result = res.(ImplItemNodeImpl).resolveSelfTyCand()
@@ -1767,7 +1796,7 @@ private ItemNode resolvePathCand0(RelevantPath path, Namespace ns) {
17671796
}
17681797

17691798
pragma[nomagic]
1770-
private ItemNode resolvePathCandQualifier(RelevantPath qualifier, RelevantPath path, string name) {
1799+
private ItemNode resolvePathCandQualifier(PathExt qualifier, PathExt path, string name) {
17711800
qualifier = path.getQualifier() and
17721801
result = resolvePathCand(qualifier) and
17731802
name = path.getText()
@@ -1815,9 +1844,7 @@ private predicate checkQualifiedVisibility(
18151844
* qualifier of `path` and `qualifier` resolves to `q`, if any.
18161845
*/
18171846
pragma[nomagic]
1818-
private ItemNode resolvePathCandQualified(
1819-
RelevantPath qualifier, ItemNode q, RelevantPath path, Namespace ns
1820-
) {
1847+
private ItemNode resolvePathCandQualified(PathExt qualifier, ItemNode q, PathExt path, Namespace ns) {
18211848
exists(string name, SuccessorKind kind, UseOption useOpt |
18221849
q = resolvePathCandQualifier(qualifier, path, name) and
18231850
result = getASuccessor(q, name, ns, kind, useOpt) and
@@ -1826,12 +1853,14 @@ private ItemNode resolvePathCandQualified(
18261853
}
18271854

18281855
/** Holds if path `p` must be looked up in namespace `n`. */
1829-
private predicate pathUsesNamespace(Path p, Namespace n) {
1856+
private predicate pathUsesNamespace(PathExt p, Namespace n) {
18301857
n.isValue() and
18311858
(
18321859
p = any(PathExpr pe).getPath()
18331860
or
18341861
p = any(TupleStructPat tsp).getPath()
1862+
or
1863+
p instanceof PathExtIdentPat
18351864
)
18361865
or
18371866
n.isType() and
@@ -1907,7 +1936,7 @@ private predicate macroUseEdge(
19071936
* result in non-monotonic recursion.
19081937
*/
19091938
pragma[nomagic]
1910-
private ItemNode resolvePathCand(RelevantPath path) {
1939+
private ItemNode resolvePathCand(PathExt path) {
19111940
exists(Namespace ns |
19121941
result = resolvePathCand0(path, ns) and
19131942
if path = any(ImplItemNode i).getSelfPath()
@@ -1920,7 +1949,10 @@ private ItemNode resolvePathCand(RelevantPath path) {
19201949
else
19211950
if path = any(PathTypeRepr p).getPath()
19221951
then result instanceof TypeItemNode
1923-
else any()
1952+
else
1953+
if path instanceof IdentPat
1954+
then result instanceof VariantItemNode or result instanceof StructItemNode
1955+
else any()
19241956
|
19251957
pathUsesNamespace(path, ns)
19261958
or
@@ -1937,7 +1969,7 @@ private ItemNode resolvePathCand(RelevantPath path) {
19371969
}
19381970

19391971
/** Get a trait that should be visible when `path` resolves to `node`, if any. */
1940-
private Trait getResolvePathTraitUsed(RelevantPath path, AssocItemNode node) {
1972+
private Trait getResolvePathTraitUsed(PathExt path, AssocItemNode node) {
19411973
exists(TypeItemNode type, ImplItemNodeImpl impl |
19421974
node = resolvePathCandQualified(_, type, path, _) and
19431975
typeImplEdge(type, impl, _, _, node, _) and
@@ -1951,7 +1983,7 @@ private predicate pathTraitUsed(Element path, Trait trait) {
19511983

19521984
/** Gets the item that `path` resolves to, if any. */
19531985
cached
1954-
ItemNode resolvePath(RelevantPath path) {
1986+
ItemNode resolvePath(PathExt path) {
19551987
result = resolvePathCand(path) and
19561988
not path = any(Path parent | exists(resolvePathCand(parent))).getQualifier() and
19571989
(
@@ -1964,29 +1996,30 @@ ItemNode resolvePath(RelevantPath path) {
19641996
or
19651997
// if `path` is the qualifier of a resolvable `parent`, then we should
19661998
// resolve `path` to something consistent with what `parent` resolves to
1967-
exists(RelevantPath parent |
1968-
resolvePathCandQualified(path, result, parent, _) = resolvePath(parent)
1969-
)
1999+
exists(PathExt parent | resolvePathCandQualified(path, result, parent, _) = resolvePath(parent))
19702000
}
19712001

1972-
private predicate isUseTreeSubPath(UseTree tree, RelevantPath path) {
2002+
// use `forceLocal` once we implement overlay support
2003+
predicate isResolvablePath(PathExt path) { exists(resolvePath(path)) }
2004+
2005+
private predicate isUseTreeSubPath(UseTree tree, PathExt path) {
19732006
path = tree.getPath()
19742007
or
1975-
exists(RelevantPath mid |
2008+
exists(PathExt mid |
19762009
isUseTreeSubPath(tree, mid) and
19772010
path = mid.getQualifier()
19782011
)
19792012
}
19802013

19812014
pragma[nomagic]
1982-
private predicate isUseTreeSubPathUnqualified(UseTree tree, RelevantPath path, string name) {
2015+
private predicate isUseTreeSubPathUnqualified(UseTree tree, PathExt path, string name) {
19832016
isUseTreeSubPath(tree, path) and
19842017
not exists(path.getQualifier()) and
19852018
name = path.getText()
19862019
}
19872020

19882021
pragma[nomagic]
1989-
private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path, SuccessorKind kind) {
2022+
private ItemNode resolveUseTreeListItem(Use use, UseTree tree, PathExt path, SuccessorKind kind) {
19902023
exists(UseOption useOpt | checkQualifiedVisibility(use, result, kind, useOpt) |
19912024
exists(UseTree midTree, ItemNode mid, string name |
19922025
mid = resolveUseTreeListItem(use, midTree) and
@@ -2003,9 +2036,7 @@ private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path
20032036
}
20042037

20052038
pragma[nomagic]
2006-
private ItemNode resolveUseTreeListItemQualifier(
2007-
Use use, UseTree tree, RelevantPath path, string name
2008-
) {
2039+
private ItemNode resolveUseTreeListItemQualifier(Use use, UseTree tree, PathExt path, string name) {
20092040
result = resolveUseTreeListItem(use, tree, path.getQualifier(), _) and
20102041
name = path.getText()
20112042
}
@@ -2137,12 +2168,12 @@ private module Debug {
21372168
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
21382169
result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
21392170
filepath.matches("%/main.rs") and
2140-
startline = 52
2171+
startline = 800
21412172
)
21422173
}
21432174

21442175
predicate debugUnqualifiedPathLookup(
2145-
RelevantPath p, string name, Namespace ns, ItemNode ancestor, string path
2176+
PathExt p, string name, Namespace ns, ItemNode ancestor, string path
21462177
) {
21472178
p = getRelevantLocatable() and
21482179
exists(ItemNode encl |
@@ -2154,7 +2185,7 @@ private module Debug {
21542185

21552186
predicate debugItemNode(ItemNode item) { item = getRelevantLocatable() }
21562187

2157-
ItemNode debugResolvePath(RelevantPath path) {
2188+
ItemNode debugResolvePath(PathExt path) {
21582189
path = getRelevantLocatable() and
21592190
result = resolvePath(path)
21602191
}

rust/ql/test/library-tests/path-resolution/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ mod patterns {
797797
Some(y) => { // $ item=Some
798798
None // $ item=None
799799
}
800-
None => // $ MISSING: item=None
800+
None => // $ item=None
801801
None // $ item=None
802802
};
803803
match y {

rust/ql/test/library-tests/path-resolution/path-resolution.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ resolvePath
404404
| main.rs:796:24:796:26 | i32 | {EXTERNAL LOCATION} | struct i32 |
405405
| main.rs:797:13:797:16 | Some | {EXTERNAL LOCATION} | Some |
406406
| main.rs:798:17:798:20 | None | {EXTERNAL LOCATION} | None |
407+
| main.rs:800:13:800:16 | None | {EXTERNAL LOCATION} | None |
407408
| main.rs:801:17:801:20 | None | {EXTERNAL LOCATION} | None |
408409
| main.rs:811:5:811:6 | my | main.rs:1:1:1:7 | mod my |
409410
| main.rs:811:5:811:14 | ...::nested | my.rs:1:1:1:15 | mod nested |

rust/ql/test/library-tests/path-resolution/path-resolution.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import TestUtils
55

66
query predicate mod(Module m) { toBeTested(m) }
77

8-
query predicate resolvePath(Path p, ItemNode i) {
8+
query predicate resolvePath(PathExt p, ItemNode i) {
99
toBeTested(p) and
1010
not p.isFromMacroExpansion() and
1111
i = resolvePath(p)

0 commit comments

Comments
 (0)