Skip to content

Commit 0f1aee0

Browse files
authored
Merge pull request github#19051 from hvitved/rust/path-resolution-cross-crate
Rust: Path resolution improvements
2 parents 860ba2e + 3f1f37f commit 0f1aee0

File tree

20 files changed

+336
-69
lines changed

20 files changed

+336
-69
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use lib::a_module::hello;
1+
use lib::a_module::hello; // $ item=HELLO
22

33
mod a_module;
44

55
fn main() {
6-
hello();
6+
hello(); // $ item=HELLO
77
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pub fn hello() {
22
println!("Hello, world!");
3-
}
3+
} // HELLO

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

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import utils.test.PathResolutionInlineExpectationsTest

rust/ql/integration-tests/hello-workspace/rust-project.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22
"sysroot_src": "filled by the rust_project fixture",
33
"crates": [
44
{
5+
"display_name": "exe",
6+
"version": "0.1.0",
57
"root_module": "exe/src/main.rs",
68
"edition": "2021",
7-
"deps": [{"crate": 1, "name": "lib"}]
9+
"deps": [
10+
{
11+
"crate": 1,
12+
"name": "lib"
13+
}
14+
]
815
},
916
{
17+
"display_name": "lib",
18+
"version": "0.1.0",
1019
"root_module": "lib/src/lib.rs",
1120
"edition": "2021",
1221
"deps": []
1322
}
1423
]
15-
}
24+
}

rust/ql/integration-tests/hello-workspace/summary.cargo.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
| Elements extracted | 87 |
1+
| Elements extracted | 90 |
22
| Elements unextracted | 0 |
33
| Extraction errors | 0 |
44
| Extraction warnings | 0 |

rust/ql/integration-tests/hello-workspace/summary.rust-project.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
| Elements extracted | 87 |
1+
| Elements extracted | 90 |
22
| Elements unextracted | 0 |
33
| Extraction errors | 0 |
44
| Extraction warnings | 0 |

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ private import codeql.rust.elements.internal.generated.Crate
1313
module Impl {
1414
private import rust
1515
private import codeql.rust.elements.internal.NamedCrate
16+
private import codeql.rust.internal.PathResolution
1617

1718
class Crate extends Generated::Crate {
1819
override string toStringImpl() {
@@ -58,6 +59,14 @@ module Impl {
5859
*/
5960
Crate getADependency() { result = this.getDependency(_) }
6061

62+
/** Gets the source file that defines this crate, if any. */
63+
SourceFile getSourceFile() { result.getFile() = this.getModule().getFile() }
64+
65+
/**
66+
* Gets a source file that belongs to this crate, if any.
67+
*/
68+
SourceFile getASourceFile() { result = this.(CrateItemNode).getASourceFile() }
69+
6170
override Location getLocation() { result = this.getModule().getLocation() }
6271
}
6372
}

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

Lines changed: 178 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ final class Namespace extends TNamespace {
7373
* - https://doc.rust-lang.org/reference/visibility-and-privacy.html
7474
* - https://doc.rust-lang.org/reference/names/namespaces.html
7575
*/
76-
abstract class ItemNode extends AstNode {
76+
abstract class ItemNode extends Locatable {
7777
/** Gets the (original) name of this item. */
7878
abstract string getName();
7979

@@ -109,7 +109,11 @@ abstract class ItemNode extends AstNode {
109109

110110
/** Gets the immediately enclosing module (or source file) of this item. */
111111
pragma[nomagic]
112-
ModuleLikeNode getImmediateParentModule() { this = result.getAnItemInScope() }
112+
ModuleLikeNode getImmediateParentModule() {
113+
this = result.getAnItemInScope()
114+
or
115+
result = this.(SourceFileItemNode).getSuper()
116+
}
113117

114118
pragma[nomagic]
115119
private ItemNode getASuccessorRec(string name) {
@@ -122,6 +126,10 @@ abstract class ItemNode extends AstNode {
122126
or
123127
useImportEdge(this, name, result)
124128
or
129+
crateDefEdge(this, name, result)
130+
or
131+
crateDependencyEdge(this, name, result)
132+
or
125133
// items made available through `use` are available to nodes that contain the `use`
126134
exists(UseItemNode use |
127135
use = this.getASuccessorRec(_) and
@@ -168,19 +176,18 @@ abstract class ItemNode extends AstNode {
168176
result = this.getASuccessorRec(name)
169177
or
170178
name = "super" and
171-
if this instanceof Module
179+
if this instanceof Module or this instanceof SourceFile
172180
then result = this.getImmediateParentModule()
173181
else result = this.getImmediateParentModule().getImmediateParentModule()
174182
or
175183
name = "self" and
176-
not this instanceof Module and
177-
result = this.getImmediateParentModule()
184+
if this instanceof Module then result = this else result = this.getImmediateParentModule()
178185
or
179186
name = "Self" and
180187
this = result.(ImplOrTraitItemNode).getAnItemInSelfScope()
181188
or
182189
name = "crate" and
183-
result.(SourceFileItemNode).getFile() = this.getFile()
190+
this = result.(CrateItemNode).getASourceFile()
184191
}
185192

186193
/** Gets the location of this item. */
@@ -203,6 +210,11 @@ abstract private class ModuleLikeNode extends ItemNode {
203210
}
204211

205212
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
213+
pragma[nomagic]
214+
ModuleLikeNode getSuper() {
215+
result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super")
216+
}
217+
206218
override string getName() { result = "(source file)" }
207219

208220
override Namespace getNamespace() {
@@ -211,6 +223,55 @@ private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
211223

212224
override Visibility getVisibility() { none() }
213225

226+
override predicate isPublic() { any() }
227+
228+
override TypeParam getTypeParam(int i) { none() }
229+
}
230+
231+
class CrateItemNode extends ItemNode instanceof Crate {
232+
/**
233+
* Gets the module node that defines this crate.
234+
*
235+
* This is either a source file, when the crate is defined in source code,
236+
* or a module, when the crate is defined in a dependency.
237+
*/
238+
pragma[nomagic]
239+
ModuleLikeNode getModuleNode() {
240+
result = super.getSourceFile()
241+
or
242+
not exists(super.getSourceFile()) and
243+
result = super.getModule()
244+
}
245+
246+
/**
247+
* Gets a source file that belongs to this crate, if any.
248+
*
249+
* This is calculated as those source files that can be reached from the entry
250+
* file of this crate using zero or more `mod` imports, without going through
251+
* the entry point of some other crate.
252+
*/
253+
pragma[nomagic]
254+
SourceFileItemNode getASourceFile() {
255+
result = super.getSourceFile()
256+
or
257+
exists(SourceFileItemNode mid, Module mod |
258+
mid = this.getASourceFile() and
259+
mod.getFile() = mid.getFile() and
260+
fileImport(mod, result) and
261+
not result = any(Crate other).getSourceFile()
262+
)
263+
}
264+
265+
override string getName() { result = Crate.super.getName() }
266+
267+
override Namespace getNamespace() {
268+
result.isType() // can be referenced with `crate`
269+
}
270+
271+
override Visibility getVisibility() { none() }
272+
273+
override predicate isPublic() { any() }
274+
214275
override TypeParam getTypeParam(int i) { none() }
215276
}
216277

@@ -460,7 +521,7 @@ private class UseItemNode extends ItemNode instanceof Use {
460521

461522
override Namespace getNamespace() { none() }
462523

463-
override Visibility getVisibility() { none() }
524+
override Visibility getVisibility() { result = Use.super.getVisibility() }
464525

465526
override TypeParam getTypeParam(int i) { none() }
466527
}
@@ -586,11 +647,33 @@ private predicate fileImport(Module m, SourceFile f) {
586647
* Holds if `mod` is a `mod name;` item targeting a file resulting in `item` being
587648
* in scope under the name `name`.
588649
*/
650+
pragma[nomagic]
589651
private predicate fileImportEdge(Module mod, string name, ItemNode item) {
590-
item.isPublic() and
591-
exists(SourceFile f |
652+
exists(SourceFileItemNode f |
592653
fileImport(mod, f) and
593-
sourceFileEdge(f, name, item)
654+
item = f.getASuccessor(name)
655+
)
656+
}
657+
658+
/**
659+
* Holds if crate `c` defines the item `i` named `name`.
660+
*/
661+
pragma[nomagic]
662+
private predicate crateDefEdge(CrateItemNode c, string name, ItemNode i) {
663+
i = c.getModuleNode().getASuccessor(name) and
664+
not i instanceof Crate
665+
}
666+
667+
/**
668+
* Holds if `m` depends on crate `dep` named `name`.
669+
*/
670+
private predicate crateDependencyEdge(ModuleLikeNode m, string name, CrateItemNode dep) {
671+
exists(CrateItemNode c | dep = c.(Crate).getDependency(name) |
672+
// entry module/entry source file
673+
m = c.getModuleNode()
674+
or
675+
// entry/transitive source file
676+
m = c.getASourceFile()
594677
)
595678
}
596679

@@ -745,13 +828,53 @@ private predicate pathUsesNamespace(Path p, Namespace n) {
745828
)
746829
}
747830

748-
/** Gets the item that `path` resolves to, if any. */
749-
cached
750-
ItemNode resolvePath(RelevantPath path) {
831+
pragma[nomagic]
832+
private ItemNode resolvePath1(RelevantPath path) {
751833
exists(Namespace ns | result = resolvePath0(path, ns) |
752834
pathUsesNamespace(path, ns)
753835
or
754-
not pathUsesNamespace(path, _)
836+
not pathUsesNamespace(path, _) and
837+
not path = any(MacroCall mc).getPath()
838+
)
839+
}
840+
841+
pragma[nomagic]
842+
private ItemNode resolvePathPrivate(
843+
RelevantPath path, ModuleLikeNode itemParent, ModuleLikeNode pathParent
844+
) {
845+
result = resolvePath1(path) and
846+
itemParent = result.getImmediateParentModule() and
847+
not result.isPublic() and
848+
(
849+
pathParent.getADescendant() = path
850+
or
851+
pathParent = any(ItemNode mid | path = mid.getADescendant()).getImmediateParentModule()
852+
)
853+
}
854+
855+
/**
856+
* Gets a module that has access to private items defined inside `itemParent`.
857+
*
858+
* According to [The Rust Reference][1] this is either `itemParent` itself or any
859+
* descendant of `itemParent`.
860+
*
861+
* [1]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#r-vis.access
862+
*/
863+
pragma[nomagic]
864+
private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) {
865+
exists(resolvePathPrivate(_, itemParent, _)) and
866+
result.getImmediateParentModule*() = itemParent
867+
}
868+
869+
/** Gets the item that `path` resolves to, if any. */
870+
cached
871+
ItemNode resolvePath(RelevantPath path) {
872+
result = resolvePath1(path) and
873+
result.isPublic()
874+
or
875+
exists(ModuleLikeNode itemParent, ModuleLikeNode pathParent |
876+
result = resolvePathPrivate(path, itemParent, pathParent) and
877+
pathParent = getAPrivateVisibleModule(itemParent)
755878
)
756879
}
757880

@@ -831,3 +954,44 @@ private predicate useImportEdge(Use use, string name, ItemNode item) {
831954
name != "_"
832955
)
833956
}
957+
958+
/** Provides predicates for debugging the path resolution implementation. */
959+
private module Debug {
960+
private Locatable getRelevantLocatable() {
961+
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
962+
result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
963+
filepath.matches("%/main.rs") and
964+
startline = 1
965+
)
966+
}
967+
968+
predicate debugUnqualifiedPathLookup(RelevantPath p, string name, Namespace ns, ItemNode encl) {
969+
p = getRelevantLocatable() and
970+
unqualifiedPathLookup(p, name, ns, encl)
971+
}
972+
973+
ItemNode debugResolvePath(RelevantPath path) {
974+
path = getRelevantLocatable() and
975+
result = resolvePath(path)
976+
}
977+
978+
predicate debugUseImportEdge(Use use, string name, ItemNode item) {
979+
use = getRelevantLocatable() and
980+
useImportEdge(use, name, item)
981+
}
982+
983+
ItemNode debugGetASuccessorRec(ItemNode i, string name) {
984+
i = getRelevantLocatable() and
985+
result = i.getASuccessor(name)
986+
}
987+
988+
predicate debugFileImportEdge(Module mod, string name, ItemNode item) {
989+
mod = getRelevantLocatable() and
990+
fileImportEdge(mod, name, item)
991+
}
992+
993+
predicate debugFileImport(Module m, SourceFile f) {
994+
m = getRelevantLocatable() and
995+
fileImport(m, f)
996+
}
997+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import PathResolution
77

88
/** Holds if `p` may resolve to multiple items including `i`. */
99
query predicate multiplePathResolutions(Path p, ItemNode i) {
10+
p.fromSource() and
1011
i = resolvePath(p) and
1112
// `use foo::bar` may use both a type `bar` and a value `bar`
1213
not p =

0 commit comments

Comments
 (0)