From 92528f2b49fbbb01da1ad7818e6b5b0f2aa9dccb Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 10:38:46 +0100 Subject: [PATCH 01/12] Rust: Add debug predicate --- .../queries/telemetry/RustAnalyzerComparison.qll | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll b/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll index 8a0a4db14623..d62e5ec33639 100644 --- a/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll +++ b/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll @@ -59,6 +59,19 @@ private module Compare RustAnalyzer, CompareSig Date: Mon, 17 Mar 2025 15:02:27 +0100 Subject: [PATCH 02/12] Rust: Add path resolution test for reexported items --- rust/ql/test/library-tests/path-resolution/main.rs | 1 + .../test/library-tests/path-resolution/my2/mod.rs | 2 ++ .../library-tests/path-resolution/my2/nested2.rs | 8 ++++++++ .../path-resolution/path-resolution.expected | 14 +++++++++----- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index 1f047ffa23e9..c91c9f385f48 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -495,4 +495,5 @@ fn main() { m15::f(); // $ item=I75 m16::f(); // $ item=I83 m17::f(); // $ item=I99 + nested6::f(); // $ MISSING: item=I116 } diff --git a/rust/ql/test/library-tests/path-resolution/my2/mod.rs b/rust/ql/test/library-tests/path-resolution/my2/mod.rs index fcd82bf02c88..95aa3a3f9dbf 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/mod.rs @@ -4,3 +4,5 @@ fn g() { println!("mod.rs::g"); nested2::nested3::nested4::f(); // $ item=I12 } // I9 + +pub use nested2::nested5::*; // $ item=I114 diff --git a/rust/ql/test/library-tests/path-resolution/my2/nested2.rs b/rust/ql/test/library-tests/path-resolution/my2/nested2.rs index dec8b8a2377e..6b69d068c86d 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/nested2.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/nested2.rs @@ -9,3 +9,11 @@ pub mod nested3 { } // I13 } // I11 } // I10 + +pub mod nested5 { + pub mod nested6 { + pub fn f() { + println!("nested2.rs::nested5::nested6::f"); + } // I116 + } // I115 +} // I114 diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index d90732257ca5..bdfbfc89349d 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -24,6 +24,8 @@ mod | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/nested2.rs:2:5:10:5 | mod nested4 | +| my2/nested2.rs:13:1:19:1 | mod nested5 | +| my2/nested2.rs:14:5:18:5 | mod nested6 | | my.rs:1:1:1:15 | mod nested | | my/nested.rs:1:1:17:1 | mod nested1 | | my/nested.rs:2:5:11:5 | mod nested2 | @@ -46,7 +48,7 @@ resolvePath | main.rs:30:17:30:21 | super | main.rs:18:5:36:5 | mod m2 | | main.rs:30:17:30:24 | ...::f | main.rs:19:9:21:9 | fn f | | main.rs:33:17:33:17 | f | main.rs:19:9:21:9 | fn f | -| main.rs:40:9:40:13 | super | main.rs:1:1:498:2 | SourceFile | +| main.rs:40:9:40:13 | super | main.rs:1:1:499:2 | SourceFile | | main.rs:40:9:40:17 | ...::m1 | main.rs:13:1:37:1 | mod m1 | | main.rs:40:9:40:21 | ...::m2 | main.rs:18:5:36:5 | mod m2 | | main.rs:40:9:40:24 | ...::g | main.rs:23:9:27:9 | fn g | @@ -58,7 +60,7 @@ resolvePath | main.rs:61:17:61:19 | Foo | main.rs:59:9:59:21 | struct Foo | | main.rs:64:13:64:15 | Foo | main.rs:53:5:53:17 | struct Foo | | main.rs:66:5:66:5 | f | main.rs:55:5:62:5 | fn f | -| main.rs:68:5:68:8 | self | main.rs:1:1:498:2 | SourceFile | +| main.rs:68:5:68:8 | self | main.rs:1:1:499:2 | SourceFile | | main.rs:68:5:68:11 | ...::i | main.rs:71:1:83:1 | fn i | | main.rs:74:13:74:15 | Foo | main.rs:48:1:48:13 | struct Foo | | main.rs:81:17:81:19 | Foo | main.rs:77:9:79:9 | struct Foo | @@ -72,7 +74,7 @@ resolvePath | main.rs:87:57:87:66 | ...::g | my2/nested2.rs:7:9:9:9 | fn g | | main.rs:87:80:87:86 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | | main.rs:100:5:100:22 | f_defined_in_macro | main.rs:99:18:99:42 | fn f_defined_in_macro | -| main.rs:117:13:117:17 | super | main.rs:1:1:498:2 | SourceFile | +| main.rs:117:13:117:17 | super | main.rs:1:1:499:2 | SourceFile | | main.rs:117:13:117:21 | ...::m5 | main.rs:103:1:107:1 | mod m5 | | main.rs:118:9:118:9 | f | main.rs:104:5:106:5 | fn f | | main.rs:118:9:118:9 | f | main.rs:110:5:112:5 | fn f | @@ -127,7 +129,7 @@ resolvePath | main.rs:274:16:274:16 | T | main.rs:268:7:268:7 | T | | main.rs:275:14:275:17 | Self | main.rs:266:5:276:5 | trait MyParamTrait | | main.rs:275:14:275:33 | ...::AssociatedType | main.rs:270:9:270:28 | TypeAlias | -| main.rs:284:13:284:17 | crate | main.rs:1:1:498:2 | SourceFile | +| main.rs:284:13:284:17 | crate | main.rs:1:1:499:2 | SourceFile | | main.rs:284:13:284:22 | ...::m13 | main.rs:279:1:292:1 | mod m13 | | main.rs:284:13:284:25 | ...::f | main.rs:280:5:280:17 | fn f | | main.rs:284:13:284:25 | ...::f | main.rs:280:19:281:19 | struct f | @@ -218,7 +220,7 @@ resolvePath | main.rs:479:5:479:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f | | main.rs:480:5:480:5 | f | my2/nested2.rs:3:9:5:9 | fn f | | main.rs:481:5:481:5 | g | my2/nested2.rs:7:9:9:9 | fn g | -| main.rs:482:5:482:9 | crate | main.rs:1:1:498:2 | SourceFile | +| main.rs:482:5:482:9 | crate | main.rs:1:1:499:2 | SourceFile | | main.rs:482:5:482:12 | ...::h | main.rs:50:1:69:1 | fn h | | main.rs:483:5:483:6 | m1 | main.rs:13:1:37:1 | mod m1 | | main.rs:483:5:483:10 | ...::m2 | main.rs:18:5:36:5 | mod m2 | @@ -253,6 +255,8 @@ resolvePath | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/mod.rs:5:5:5:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | | my2/mod.rs:5:5:5:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f | +| my2/mod.rs:8:9:8:15 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | +| my2/mod.rs:8:9:8:24 | ...::nested5 | my2/nested2.rs:13:1:19:1 | mod nested5 | | my.rs:3:5:3:10 | nested | my.rs:1:1:1:15 | mod nested | | my.rs:3:5:3:13 | ...::g | my/nested.rs:19:1:22:1 | fn g | | my.rs:11:5:11:5 | g | my/nested.rs:19:1:22:1 | fn g | From c5106f78ac62cbaf8cef0b83f6d6a1e6c3ced697 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 10:52:03 +0100 Subject: [PATCH 03/12] Rust: Handle `pub use` reexports in path resolution --- rust/ql/lib/codeql/rust/internal/PathResolution.qll | 7 ++++--- rust/ql/test/library-tests/path-resolution/main.rs | 2 +- .../library-tests/path-resolution/path-resolution.expected | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index b49db6d40e33..868fb26f0568 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -460,7 +460,7 @@ private class UseItemNode extends ItemNode instanceof Use { override Namespace getNamespace() { none() } - override Visibility getVisibility() { none() } + override Visibility getVisibility() { result = Use.super.getVisibility() } override TypeParam getTypeParam(int i) { none() } } @@ -586,11 +586,12 @@ private predicate fileImport(Module m, SourceFile f) { * Holds if `mod` is a `mod name;` item targeting a file resulting in `item` being * in scope under the name `name`. */ +pragma[nomagic] private predicate fileImportEdge(Module mod, string name, ItemNode item) { item.isPublic() and - exists(SourceFile f | + exists(SourceFileItemNode f | fileImport(mod, f) and - sourceFileEdge(f, name, item) + item = f.getASuccessor(name) ) } diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index c91c9f385f48..f748ec8afda9 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -495,5 +495,5 @@ fn main() { m15::f(); // $ item=I75 m16::f(); // $ item=I83 m17::f(); // $ item=I99 - nested6::f(); // $ MISSING: item=I116 + nested6::f(); // $ item=I116 } diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index bdfbfc89349d..d6651423b6d8 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -251,6 +251,8 @@ resolvePath | main.rs:496:5:496:10 | ...::f | main.rs:417:5:441:5 | fn f | | main.rs:497:5:497:7 | m17 | main.rs:444:1:474:1 | mod m17 | | main.rs:497:5:497:10 | ...::f | main.rs:468:5:473:5 | fn f | +| main.rs:498:5:498:11 | nested6 | my2/nested2.rs:14:5:18:5 | mod nested6 | +| main.rs:498:5:498:14 | ...::f | my2/nested2.rs:15:9:17:9 | fn f | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/mod.rs:5:5:5:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | From bd4c85a5bce2b513c4abca13879b060c1813dc0f Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 10:20:57 +0100 Subject: [PATCH 04/12] Rust: Add cross-crate path resolution test --- .../hello-workspace/exe/src/main.rs | 4 +- .../hello-workspace/lib/src/a_module/mod.rs | 2 +- .../hello-workspace/path-resolution.expected | 0 .../hello-workspace/path-resolution.ql | 1 + .../hello-workspace/rust-project.json | 13 ++++- .../hello-workspace/summary.cargo.expected | 2 +- .../summary.rust-project.expected | 2 +- .../PathResolutionInlineExpectationsTest.qll | 48 +++++++++++++++++++ .../path-resolution/path-resolution.ql | 43 +---------------- 9 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 rust/ql/integration-tests/hello-workspace/path-resolution.expected create mode 100644 rust/ql/integration-tests/hello-workspace/path-resolution.ql create mode 100644 rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll diff --git a/rust/ql/integration-tests/hello-workspace/exe/src/main.rs b/rust/ql/integration-tests/hello-workspace/exe/src/main.rs index 3c46784465ab..0e1b4371b9f9 100644 --- a/rust/ql/integration-tests/hello-workspace/exe/src/main.rs +++ b/rust/ql/integration-tests/hello-workspace/exe/src/main.rs @@ -1,7 +1,7 @@ -use lib::a_module::hello; +use lib::a_module::hello; // $ MISSING: item=HELLO mod a_module; fn main() { - hello(); + hello(); // $ MISSING: item=HELLO } diff --git a/rust/ql/integration-tests/hello-workspace/lib/src/a_module/mod.rs b/rust/ql/integration-tests/hello-workspace/lib/src/a_module/mod.rs index fda8464098ec..dca921ab6432 100644 --- a/rust/ql/integration-tests/hello-workspace/lib/src/a_module/mod.rs +++ b/rust/ql/integration-tests/hello-workspace/lib/src/a_module/mod.rs @@ -1,3 +1,3 @@ pub fn hello() { println!("Hello, world!"); -} +} // HELLO diff --git a/rust/ql/integration-tests/hello-workspace/path-resolution.expected b/rust/ql/integration-tests/hello-workspace/path-resolution.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rust/ql/integration-tests/hello-workspace/path-resolution.ql b/rust/ql/integration-tests/hello-workspace/path-resolution.ql new file mode 100644 index 000000000000..bf0a548fbb68 --- /dev/null +++ b/rust/ql/integration-tests/hello-workspace/path-resolution.ql @@ -0,0 +1 @@ +import utils.test.PathResolutionInlineExpectationsTest diff --git a/rust/ql/integration-tests/hello-workspace/rust-project.json b/rust/ql/integration-tests/hello-workspace/rust-project.json index c8461b098bd8..d8c73fa41b6d 100644 --- a/rust/ql/integration-tests/hello-workspace/rust-project.json +++ b/rust/ql/integration-tests/hello-workspace/rust-project.json @@ -2,14 +2,23 @@ "sysroot_src": "filled by the rust_project fixture", "crates": [ { + "display_name": "exe", + "version": "0.1.0", "root_module": "exe/src/main.rs", "edition": "2021", - "deps": [{"crate": 1, "name": "lib"}] + "deps": [ + { + "crate": 1, + "name": "lib" + } + ] }, { + "display_name": "lib", + "version": "0.1.0", "root_module": "lib/src/lib.rs", "edition": "2021", "deps": [] } ] -} +} \ No newline at end of file diff --git a/rust/ql/integration-tests/hello-workspace/summary.cargo.expected b/rust/ql/integration-tests/hello-workspace/summary.cargo.expected index 3fbea6c46417..e7810e8a5126 100644 --- a/rust/ql/integration-tests/hello-workspace/summary.cargo.expected +++ b/rust/ql/integration-tests/hello-workspace/summary.cargo.expected @@ -1,4 +1,4 @@ -| Elements extracted | 87 | +| Elements extracted | 90 | | Elements unextracted | 0 | | Extraction errors | 0 | | Extraction warnings | 0 | diff --git a/rust/ql/integration-tests/hello-workspace/summary.rust-project.expected b/rust/ql/integration-tests/hello-workspace/summary.rust-project.expected index 3fbea6c46417..e7810e8a5126 100644 --- a/rust/ql/integration-tests/hello-workspace/summary.rust-project.expected +++ b/rust/ql/integration-tests/hello-workspace/summary.rust-project.expected @@ -1,4 +1,4 @@ -| Elements extracted | 87 | +| Elements extracted | 90 | | Elements unextracted | 0 | | Extraction errors | 0 | | Extraction warnings | 0 | diff --git a/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll b/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll new file mode 100644 index 000000000000..54d6023b2642 --- /dev/null +++ b/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll @@ -0,0 +1,48 @@ +/** + * Provides an inline expectations test for path resolution. + */ + +private import rust +private import codeql.rust.internal.PathResolution +private import codeql.rust.internal.TypeInference +private import utils.test.InlineExpectationsTest + +private module ResolveTest implements TestSig { + string getARelevantTag() { result = "item" } + + private predicate itemAt(ItemNode i, string filepath, int line, boolean inMacro) { + i.getLocation().hasLocationInfo(filepath, _, _, line, _) and + if i.(AstNode).isInMacroExpansion() then inMacro = true else inMacro = false + } + + private predicate commmentAt(string text, string filepath, int line) { + exists(Comment c | + c.getLocation().hasLocationInfo(filepath, line, _, _, _) and + c.getCommentText() = text + ) + } + + private predicate item(ItemNode i, string value) { + exists(string filepath, int line, boolean inMacro | itemAt(i, filepath, line, inMacro) | + commmentAt(value, filepath, line) and inMacro = false + or + not (commmentAt(_, filepath, line) and inMacro = false) and + value = i.getName() + ) + } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(AstNode n | + not n = any(Path parent).getQualifier() and + location = n.getLocation() and + element = n.toString() and + tag = "item" + | + item(resolvePath(n), value) + or + item(n.(MethodCallExpr).getStaticTarget(), value) + ) + } +} + +import MakeTest diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.ql b/rust/ql/test/library-tests/path-resolution/path-resolution.ql index 342931d54dea..e8aebeaa0ef6 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.ql +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.ql @@ -1,49 +1,8 @@ import rust import codeql.rust.internal.PathResolution -import codeql.rust.internal.TypeInference -import utils.test.InlineExpectationsTest +import utils.test.PathResolutionInlineExpectationsTest import TestUtils query predicate mod(Module m) { toBeTested(m) } query predicate resolvePath(Path p, ItemNode i) { toBeTested(p) and i = resolvePath(p) } - -module ResolveTest implements TestSig { - string getARelevantTag() { result = "item" } - - private predicate itemAt(ItemNode i, string filepath, int line, boolean inMacro) { - i.getLocation().hasLocationInfo(filepath, _, _, line, _) and - if i.isInMacroExpansion() then inMacro = true else inMacro = false - } - - private predicate commmentAt(string text, string filepath, int line) { - exists(Comment c | - c.getLocation().hasLocationInfo(filepath, line, _, _, _) and - c.getCommentText() = text - ) - } - - private predicate item(ItemNode i, string value) { - exists(string filepath, int line, boolean inMacro | itemAt(i, filepath, line, inMacro) | - commmentAt(value, filepath, line) and inMacro = false - or - not (commmentAt(_, filepath, line) and inMacro = false) and - value = i.getName() - ) - } - - predicate hasActualResult(Location location, string element, string tag, string value) { - exists(AstNode n | - not n = any(Path parent).getQualifier() and - location = n.getLocation() and - element = n.toString() and - tag = "item" - | - item(resolvePath(n), value) - or - item(n.(MethodCallExpr).getStaticTarget(), value) - ) - } -} - -import MakeTest From c91176116f8e30d14d8aff7418d11b316df582e1 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 10:52:31 +0100 Subject: [PATCH 05/12] Rust: Cross-crate path resolution --- .../hello-workspace/exe/src/main.rs | 4 +- .../rust/elements/internal/CrateImpl.qll | 9 ++ .../codeql/rust/internal/PathResolution.qll | 120 +++++++++++++++++- .../PathResolutionConsistency.expected | 3 + .../PathResolutionConsistency.expected | 3 + .../path-resolution/path-resolution.expected | 4 +- 6 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 rust/ql/test/extractor-tests/canonical_path/CONSISTENCY/PathResolutionConsistency.expected create mode 100644 rust/ql/test/extractor-tests/canonical_path_disabled/CONSISTENCY/PathResolutionConsistency.expected diff --git a/rust/ql/integration-tests/hello-workspace/exe/src/main.rs b/rust/ql/integration-tests/hello-workspace/exe/src/main.rs index 0e1b4371b9f9..ea26a90c3192 100644 --- a/rust/ql/integration-tests/hello-workspace/exe/src/main.rs +++ b/rust/ql/integration-tests/hello-workspace/exe/src/main.rs @@ -1,7 +1,7 @@ -use lib::a_module::hello; // $ MISSING: item=HELLO +use lib::a_module::hello; // $ item=HELLO mod a_module; fn main() { - hello(); // $ MISSING: item=HELLO + hello(); // $ item=HELLO } diff --git a/rust/ql/lib/codeql/rust/elements/internal/CrateImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/CrateImpl.qll index 77fc4b8ff502..d8321fce4bf4 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/CrateImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/CrateImpl.qll @@ -13,6 +13,7 @@ private import codeql.rust.elements.internal.generated.Crate module Impl { private import rust private import codeql.rust.elements.internal.NamedCrate + private import codeql.rust.internal.PathResolution class Crate extends Generated::Crate { override string toStringImpl() { @@ -58,6 +59,14 @@ module Impl { */ Crate getADependency() { result = this.getDependency(_) } + /** Gets the source file that defines this crate, if any. */ + SourceFile getSourceFile() { result.getFile() = this.getModule().getFile() } + + /** + * Gets a source file that belongs to this crate, if any. + */ + SourceFile getASourceFile() { result = this.(CrateItemNode).getASourceFile() } + override Location getLocation() { result = this.getModule().getLocation() } } } diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 868fb26f0568..1723967a2f16 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -73,7 +73,7 @@ final class Namespace extends TNamespace { * - https://doc.rust-lang.org/reference/visibility-and-privacy.html * - https://doc.rust-lang.org/reference/names/namespaces.html */ -abstract class ItemNode extends AstNode { +abstract class ItemNode extends Locatable { /** Gets the (original) name of this item. */ abstract string getName(); @@ -122,6 +122,10 @@ abstract class ItemNode extends AstNode { or useImportEdge(this, name, result) or + crateDefEdge(this, name, result) + or + crateDependencyEdge(this, name, result) + or // items made available through `use` are available to nodes that contain the `use` exists(UseItemNode use | use = this.getASuccessorRec(_) and @@ -180,7 +184,7 @@ abstract class ItemNode extends AstNode { this = result.(ImplOrTraitItemNode).getAnItemInSelfScope() or name = "crate" and - result.(SourceFileItemNode).getFile() = this.getFile() + this = result.(CrateItemNode).getASourceFile() } /** Gets the location of this item. */ @@ -214,6 +218,51 @@ private class SourceFileItemNode extends ModuleLikeNode, SourceFile { override TypeParam getTypeParam(int i) { none() } } +class CrateItemNode extends ItemNode instanceof Crate { + /** + * Gets the module node that defines this crate. + * + * This is either a source file, when the crate is defined in source code, + * or a module, when the crate is defined in a dependency. + */ + pragma[nomagic] + ModuleLikeNode getModuleNode() { + result = super.getSourceFile() + or + not exists(super.getSourceFile()) and + result = super.getModule() + } + + /** + * Gets a source file that belongs to this crate, if any. + * + * This is calculated as those source files that can be reached from the entry + * file of this crate using zero or more `mod` imports, without going through + * the entry point of some other crate. + */ + pragma[nomagic] + SourceFileItemNode getASourceFile() { + result = super.getSourceFile() + or + exists(SourceFileItemNode mid, Module mod | + mid = this.getASourceFile() and + mod.getFile() = mid.getFile() and + fileImport(mod, result) and + not result = any(Crate other).getSourceFile() + ) + } + + override string getName() { result = Crate.super.getName() } + + override Namespace getNamespace() { + result.isType() // can be referenced with `crate` + } + + override Visibility getVisibility() { none() } + + override TypeParam getTypeParam(int i) { none() } +} + /** An item that can occur in a trait or an `impl` block. */ abstract private class AssocItemNode extends ItemNode, AssocItem { /** Holds if this associated item has an implementation. */ @@ -595,6 +644,28 @@ private predicate fileImportEdge(Module mod, string name, ItemNode item) { ) } +/** + * Holds if crate `c` defines the item `i` named `name`. + */ +pragma[nomagic] +private predicate crateDefEdge(CrateItemNode c, string name, ItemNode i) { + i = c.getModuleNode().getASuccessor(name) and + not i instanceof Crate +} + +/** + * Holds if `m` depends on crate `dep` named `name`. + */ +private predicate crateDependencyEdge(ModuleLikeNode m, string name, CrateItemNode dep) { + exists(CrateItemNode c | dep = c.(Crate).getDependency(name) | + // entry module/entry source file + m = c.getModuleNode() + or + // entry/transitive source file + m = c.getASourceFile() + ) +} + private predicate useTreeDeclares(UseTree tree, string name) { not tree.isGlob() and not exists(tree.getUseTreeList()) and @@ -832,3 +903,48 @@ private predicate useImportEdge(Use use, string name, ItemNode item) { name != "_" ) } + +/** Provides predicates for debugging the path resolution implementation. */ +private module Debug { + private Locatable getRelevantLocatable() { + exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and + // filepath.matches("%/compile.rs") and + // startline = 1986 + // filepath.matches("%/build_steps/mod.rs") and + // startline = 17 + filepath.matches("%/main.rs") and + startline = 1 + ) + } + + predicate debugUnqualifiedPathLookup(RelevantPath p, string name, Namespace ns, ItemNode encl) { + p = getRelevantLocatable() and + unqualifiedPathLookup(p, name, ns, encl) + } + + ItemNode debugResolvePath(RelevantPath path) { + path = getRelevantLocatable() and + result = resolvePath(path) + } + + predicate debugUseImportEdge(Use use, string name, ItemNode item) { + use = getRelevantLocatable() and + useImportEdge(use, name, item) + } + + ItemNode debugGetASuccessorRec(ItemNode i, string name) { + i = getRelevantLocatable() and + result = i.getASuccessor(name) + } + + predicate debugFileImportEdge(Module mod, string name, ItemNode item) { + mod = getRelevantLocatable() and + fileImportEdge(mod, name, item) + } + + predicate debugFileImport(Module m, SourceFile f) { + m = getRelevantLocatable() and + fileImport(m, f) + } +} diff --git a/rust/ql/test/extractor-tests/canonical_path/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/extractor-tests/canonical_path/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 000000000000..15c7cfcb4107 --- /dev/null +++ b/rust/ql/test/extractor-tests/canonical_path/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,3 @@ +multipleStaticCallTargets +| regular.rs:29:5:29:9 | s.g(...) | anonymous.rs:15:9:15:22 | fn g | +| regular.rs:29:5:29:9 | s.g(...) | regular.rs:13:5:13:18 | fn g | diff --git a/rust/ql/test/extractor-tests/canonical_path_disabled/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/extractor-tests/canonical_path_disabled/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 000000000000..0a1a09d5c702 --- /dev/null +++ b/rust/ql/test/extractor-tests/canonical_path_disabled/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,3 @@ +multipleStaticCallTargets +| regular.rs:32:5:32:9 | s.g(...) | anonymous.rs:18:9:18:22 | fn g | +| regular.rs:32:5:32:9 | s.g(...) | regular.rs:16:5:16:18 | fn g | diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index d6651423b6d8..a8fbee75fc91 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -129,7 +129,7 @@ resolvePath | main.rs:274:16:274:16 | T | main.rs:268:7:268:7 | T | | main.rs:275:14:275:17 | Self | main.rs:266:5:276:5 | trait MyParamTrait | | main.rs:275:14:275:33 | ...::AssociatedType | main.rs:270:9:270:28 | TypeAlias | -| main.rs:284:13:284:17 | crate | main.rs:1:1:499:2 | SourceFile | +| main.rs:284:13:284:17 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | | main.rs:284:13:284:22 | ...::m13 | main.rs:279:1:292:1 | mod m13 | | main.rs:284:13:284:25 | ...::f | main.rs:280:5:280:17 | fn f | | main.rs:284:13:284:25 | ...::f | main.rs:280:19:281:19 | struct f | @@ -220,7 +220,7 @@ resolvePath | main.rs:479:5:479:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f | | main.rs:480:5:480:5 | f | my2/nested2.rs:3:9:5:9 | fn f | | main.rs:481:5:481:5 | g | my2/nested2.rs:7:9:9:9 | fn g | -| main.rs:482:5:482:9 | crate | main.rs:1:1:499:2 | SourceFile | +| main.rs:482:5:482:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | | main.rs:482:5:482:12 | ...::h | main.rs:50:1:69:1 | fn h | | main.rs:483:5:483:6 | m1 | main.rs:13:1:37:1 | mod m1 | | main.rs:483:5:483:10 | ...::m2 | main.rs:18:5:36:5 | mod m2 | From 8f8f6f74bc150f610d84b3388bdd7039b367920a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 11:10:55 +0100 Subject: [PATCH 06/12] Rust: Add `use`+`self` path resolution test --- rust/ql/test/library-tests/path-resolution/main.rs | 1 + rust/ql/test/library-tests/path-resolution/my2/mod.rs | 2 ++ .../test/library-tests/path-resolution/my2/nested2.rs | 8 ++++++++ .../path-resolution/path-resolution.expected | 11 ++++++++--- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index f748ec8afda9..cafaf85dd5c9 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -496,4 +496,5 @@ fn main() { m16::f(); // $ item=I83 m17::f(); // $ item=I99 nested6::f(); // $ item=I116 + nested8::f(); // $ MISSING: item=I119 } diff --git a/rust/ql/test/library-tests/path-resolution/my2/mod.rs b/rust/ql/test/library-tests/path-resolution/my2/mod.rs index 95aa3a3f9dbf..358b740ce1e3 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/mod.rs @@ -6,3 +6,5 @@ fn g() { } // I9 pub use nested2::nested5::*; // $ item=I114 + +pub use nested2::nested7::nested8::{self}; // $ item=I118 diff --git a/rust/ql/test/library-tests/path-resolution/my2/nested2.rs b/rust/ql/test/library-tests/path-resolution/my2/nested2.rs index 6b69d068c86d..47fed6d42998 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/nested2.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/nested2.rs @@ -17,3 +17,11 @@ pub mod nested5 { } // I116 } // I115 } // I114 + +pub mod nested7 { + pub mod nested8 { + pub fn f() { + println!("nested2.rs::nested7::nested8::f"); + } // I119 + } // I118 +} // I117 diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index a8fbee75fc91..3269a2a765a0 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -26,6 +26,8 @@ mod | my2/nested2.rs:2:5:10:5 | mod nested4 | | my2/nested2.rs:13:1:19:1 | mod nested5 | | my2/nested2.rs:14:5:18:5 | mod nested6 | +| my2/nested2.rs:21:1:27:1 | mod nested7 | +| my2/nested2.rs:22:5:26:5 | mod nested8 | | my.rs:1:1:1:15 | mod nested | | my/nested.rs:1:1:17:1 | mod nested1 | | my/nested.rs:2:5:11:5 | mod nested2 | @@ -48,7 +50,7 @@ resolvePath | main.rs:30:17:30:21 | super | main.rs:18:5:36:5 | mod m2 | | main.rs:30:17:30:24 | ...::f | main.rs:19:9:21:9 | fn f | | main.rs:33:17:33:17 | f | main.rs:19:9:21:9 | fn f | -| main.rs:40:9:40:13 | super | main.rs:1:1:499:2 | SourceFile | +| main.rs:40:9:40:13 | super | main.rs:1:1:500:2 | SourceFile | | main.rs:40:9:40:17 | ...::m1 | main.rs:13:1:37:1 | mod m1 | | main.rs:40:9:40:21 | ...::m2 | main.rs:18:5:36:5 | mod m2 | | main.rs:40:9:40:24 | ...::g | main.rs:23:9:27:9 | fn g | @@ -60,7 +62,7 @@ resolvePath | main.rs:61:17:61:19 | Foo | main.rs:59:9:59:21 | struct Foo | | main.rs:64:13:64:15 | Foo | main.rs:53:5:53:17 | struct Foo | | main.rs:66:5:66:5 | f | main.rs:55:5:62:5 | fn f | -| main.rs:68:5:68:8 | self | main.rs:1:1:499:2 | SourceFile | +| main.rs:68:5:68:8 | self | main.rs:1:1:500:2 | SourceFile | | main.rs:68:5:68:11 | ...::i | main.rs:71:1:83:1 | fn i | | main.rs:74:13:74:15 | Foo | main.rs:48:1:48:13 | struct Foo | | main.rs:81:17:81:19 | Foo | main.rs:77:9:79:9 | struct Foo | @@ -74,7 +76,7 @@ resolvePath | main.rs:87:57:87:66 | ...::g | my2/nested2.rs:7:9:9:9 | fn g | | main.rs:87:80:87:86 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | | main.rs:100:5:100:22 | f_defined_in_macro | main.rs:99:18:99:42 | fn f_defined_in_macro | -| main.rs:117:13:117:17 | super | main.rs:1:1:499:2 | SourceFile | +| main.rs:117:13:117:17 | super | main.rs:1:1:500:2 | SourceFile | | main.rs:117:13:117:21 | ...::m5 | main.rs:103:1:107:1 | mod m5 | | main.rs:118:9:118:9 | f | main.rs:104:5:106:5 | fn f | | main.rs:118:9:118:9 | f | main.rs:110:5:112:5 | fn f | @@ -259,6 +261,9 @@ resolvePath | my2/mod.rs:5:5:5:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f | | my2/mod.rs:8:9:8:15 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:8:9:8:24 | ...::nested5 | my2/nested2.rs:13:1:19:1 | mod nested5 | +| my2/mod.rs:10:9:10:15 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | +| my2/mod.rs:10:9:10:24 | ...::nested7 | my2/nested2.rs:21:1:27:1 | mod nested7 | +| my2/mod.rs:10:9:10:33 | ...::nested8 | my2/nested2.rs:22:5:26:5 | mod nested8 | | my.rs:3:5:3:10 | nested | my.rs:1:1:1:15 | mod nested | | my.rs:3:5:3:13 | ...::g | my/nested.rs:19:1:22:1 | fn g | | my.rs:11:5:11:5 | g | my/nested.rs:19:1:22:1 | fn g | From 7c2bafeb17dd7c4937f55d3be61bb79bbce025f7 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 18 Mar 2025 11:30:43 +0100 Subject: [PATCH 07/12] Rust: Handle `self` in `use` statements --- rust/ql/lib/codeql/rust/internal/PathResolution.qll | 3 +-- rust/ql/test/library-tests/path-resolution/main.rs | 2 +- .../library-tests/path-resolution/path-resolution.expected | 3 +++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 1723967a2f16..8a7549f28748 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -177,8 +177,7 @@ abstract class ItemNode extends Locatable { else result = this.getImmediateParentModule().getImmediateParentModule() or name = "self" and - not this instanceof Module and - result = this.getImmediateParentModule() + if this instanceof Module then result = this else result = this.getImmediateParentModule() or name = "Self" and this = result.(ImplOrTraitItemNode).getAnItemInSelfScope() diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index cafaf85dd5c9..1340537bcb77 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -496,5 +496,5 @@ fn main() { m16::f(); // $ item=I83 m17::f(); // $ item=I99 nested6::f(); // $ item=I116 - nested8::f(); // $ MISSING: item=I119 + nested8::f(); // $ item=I119 } diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index 3269a2a765a0..ffb89b235ac3 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -255,6 +255,8 @@ resolvePath | main.rs:497:5:497:10 | ...::f | main.rs:468:5:473:5 | fn f | | main.rs:498:5:498:11 | nested6 | my2/nested2.rs:14:5:18:5 | mod nested6 | | main.rs:498:5:498:14 | ...::f | my2/nested2.rs:15:9:17:9 | fn f | +| main.rs:499:5:499:11 | nested8 | my2/nested2.rs:22:5:26:5 | mod nested8 | +| main.rs:499:5:499:14 | ...::f | my2/nested2.rs:23:9:25:9 | fn f | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/mod.rs:5:5:5:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | @@ -264,6 +266,7 @@ resolvePath | my2/mod.rs:10:9:10:15 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:10:9:10:24 | ...::nested7 | my2/nested2.rs:21:1:27:1 | mod nested7 | | my2/mod.rs:10:9:10:33 | ...::nested8 | my2/nested2.rs:22:5:26:5 | mod nested8 | +| my2/mod.rs:10:37:10:40 | self | my2/nested2.rs:22:5:26:5 | mod nested8 | | my.rs:3:5:3:10 | nested | my.rs:1:1:1:15 | mod nested | | my.rs:3:5:3:13 | ...::g | my/nested.rs:19:1:22:1 | fn g | | my.rs:11:5:11:5 | g | my/nested.rs:19:1:22:1 | fn g | From 8044b0d03c74908fbc82150943114893a3b370e8 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 19 Mar 2025 13:38:29 +0100 Subject: [PATCH 08/12] Rust: Exclude macro call paths from `resolvePath` --- rust/ql/lib/codeql/rust/internal/PathResolution.qll | 3 ++- rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 8a7549f28748..d192e5d98db4 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -822,7 +822,8 @@ ItemNode resolvePath(RelevantPath path) { exists(Namespace ns | result = resolvePath0(path, ns) | pathUsesNamespace(path, ns) or - not pathUsesNamespace(path, _) + not pathUsesNamespace(path, _) and + not path = any(MacroCall mc).getPath() ) } diff --git a/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll b/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll index 9766b97cafca..75d4ac8a6e83 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll @@ -7,6 +7,7 @@ private import PathResolution /** Holds if `p` may resolve to multiple items including `i`. */ query predicate multiplePathResolutions(Path p, ItemNode i) { + p.fromSource() and i = resolvePath(p) and // `use foo::bar` may use both a type `bar` and a value `bar` not p = From 57dfbf4faa33b537400fb2b1db2dcf909e8cfa91 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 19 Mar 2025 13:41:08 +0100 Subject: [PATCH 09/12] Rust: Add path resolution test for `super` in `use` statement --- rust/ql/test/library-tests/path-resolution/main.rs | 1 + rust/ql/test/library-tests/path-resolution/my2/mod.rs | 4 +++- .../ql/test/library-tests/path-resolution/my2/my3/mod.rs | 8 ++++++++ .../path-resolution/path-resolution.expected | 9 ++++++--- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index 1340537bcb77..7857ddc6cf5f 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -497,4 +497,5 @@ fn main() { m17::f(); // $ item=I99 nested6::f(); // $ item=I116 nested8::f(); // $ item=I119 + my3::f(); // $ item=I200 } diff --git a/rust/ql/test/library-tests/path-resolution/my2/mod.rs b/rust/ql/test/library-tests/path-resolution/my2/mod.rs index 358b740ce1e3..64291a53af53 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/mod.rs @@ -1,10 +1,12 @@ pub mod nested2; // I8 fn g() { - println!("mod.rs::g"); + println!("my2/mod.rs::g"); nested2::nested3::nested4::f(); // $ item=I12 } // I9 pub use nested2::nested5::*; // $ item=I114 pub use nested2::nested7::nested8::{self}; // $ item=I118 + +pub mod my3; diff --git a/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs b/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs new file mode 100644 index 000000000000..ac3ca4f5d885 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs @@ -0,0 +1,8 @@ +pub fn f() { + println!("my2/my3/mod.rs::f"); + g(); // $ MISSING: item=I9 + h(); // $ MISSING: item=I25 +} // I200 + +use super::super::h; // $ MISSING: item=I25 +use super::g; // $ MISSING: item=I9 diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index ffb89b235ac3..231a9c6d0d9e 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -22,6 +22,7 @@ mod | main.rs:350:1:442:1 | mod m16 | | main.rs:444:1:474:1 | mod m17 | | my2/mod.rs:1:1:1:16 | mod nested2 | +| my2/mod.rs:12:1:12:12 | mod my3 | | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/nested2.rs:2:5:10:5 | mod nested4 | | my2/nested2.rs:13:1:19:1 | mod nested5 | @@ -50,7 +51,7 @@ resolvePath | main.rs:30:17:30:21 | super | main.rs:18:5:36:5 | mod m2 | | main.rs:30:17:30:24 | ...::f | main.rs:19:9:21:9 | fn f | | main.rs:33:17:33:17 | f | main.rs:19:9:21:9 | fn f | -| main.rs:40:9:40:13 | super | main.rs:1:1:500:2 | SourceFile | +| main.rs:40:9:40:13 | super | main.rs:1:1:501:2 | SourceFile | | main.rs:40:9:40:17 | ...::m1 | main.rs:13:1:37:1 | mod m1 | | main.rs:40:9:40:21 | ...::m2 | main.rs:18:5:36:5 | mod m2 | | main.rs:40:9:40:24 | ...::g | main.rs:23:9:27:9 | fn g | @@ -62,7 +63,7 @@ resolvePath | main.rs:61:17:61:19 | Foo | main.rs:59:9:59:21 | struct Foo | | main.rs:64:13:64:15 | Foo | main.rs:53:5:53:17 | struct Foo | | main.rs:66:5:66:5 | f | main.rs:55:5:62:5 | fn f | -| main.rs:68:5:68:8 | self | main.rs:1:1:500:2 | SourceFile | +| main.rs:68:5:68:8 | self | main.rs:1:1:501:2 | SourceFile | | main.rs:68:5:68:11 | ...::i | main.rs:71:1:83:1 | fn i | | main.rs:74:13:74:15 | Foo | main.rs:48:1:48:13 | struct Foo | | main.rs:81:17:81:19 | Foo | main.rs:77:9:79:9 | struct Foo | @@ -76,7 +77,7 @@ resolvePath | main.rs:87:57:87:66 | ...::g | my2/nested2.rs:7:9:9:9 | fn g | | main.rs:87:80:87:86 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | | main.rs:100:5:100:22 | f_defined_in_macro | main.rs:99:18:99:42 | fn f_defined_in_macro | -| main.rs:117:13:117:17 | super | main.rs:1:1:500:2 | SourceFile | +| main.rs:117:13:117:17 | super | main.rs:1:1:501:2 | SourceFile | | main.rs:117:13:117:21 | ...::m5 | main.rs:103:1:107:1 | mod m5 | | main.rs:118:9:118:9 | f | main.rs:104:5:106:5 | fn f | | main.rs:118:9:118:9 | f | main.rs:110:5:112:5 | fn f | @@ -257,6 +258,8 @@ resolvePath | main.rs:498:5:498:14 | ...::f | my2/nested2.rs:15:9:17:9 | fn f | | main.rs:499:5:499:11 | nested8 | my2/nested2.rs:22:5:26:5 | mod nested8 | | main.rs:499:5:499:14 | ...::f | my2/nested2.rs:23:9:25:9 | fn f | +| main.rs:500:5:500:7 | my3 | my2/mod.rs:12:1:12:12 | mod my3 | +| main.rs:500:5:500:10 | ...::f | my2/my3/mod.rs:1:1:5:1 | fn f | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | | my2/mod.rs:5:5:5:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 | From b2fc7e771fba8d806cea16008309be09320b7c08 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 19 Mar 2025 13:51:56 +0100 Subject: [PATCH 10/12] Rust: Handle `super` in `use` statements --- .../lib/codeql/rust/internal/PathResolution.qll | 16 ++++++++++++++-- .../library-tests/path-resolution/my2/my3/mod.rs | 8 ++++---- .../path-resolution/path-resolution.expected | 7 +++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index d192e5d98db4..a2203d199d1a 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -109,7 +109,11 @@ abstract class ItemNode extends Locatable { /** Gets the immediately enclosing module (or source file) of this item. */ pragma[nomagic] - ModuleLikeNode getImmediateParentModule() { this = result.getAnItemInScope() } + ModuleLikeNode getImmediateParentModule() { + this = result.getAnItemInScope() + or + result = this.(SourceFileItemNode).getSuper() + } pragma[nomagic] private ItemNode getASuccessorRec(string name) { @@ -172,7 +176,7 @@ abstract class ItemNode extends Locatable { result = this.getASuccessorRec(name) or name = "super" and - if this instanceof Module + if this instanceof Module or this instanceof SourceFile then result = this.getImmediateParentModule() else result = this.getImmediateParentModule().getImmediateParentModule() or @@ -206,6 +210,14 @@ abstract private class ModuleLikeNode extends ItemNode { } private class SourceFileItemNode extends ModuleLikeNode, SourceFile { + pragma[nomagic] + ModuleLikeNode getSuper() { + exists(ModuleItemNode mod | + fileImport(mod, this) and + result = mod.getASuccessor("super") + ) + } + override string getName() { result = "(source file)" } override Namespace getNamespace() { diff --git a/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs b/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs index ac3ca4f5d885..6b54377728bb 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/my3/mod.rs @@ -1,8 +1,8 @@ pub fn f() { println!("my2/my3/mod.rs::f"); - g(); // $ MISSING: item=I9 - h(); // $ MISSING: item=I25 + g(); // $ item=I9 + h(); // $ item=I25 } // I200 -use super::super::h; // $ MISSING: item=I25 -use super::g; // $ MISSING: item=I9 +use super::super::h; // $ item=I25 +use super::g; // $ item=I9 diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index 231a9c6d0d9e..445eb3d9403a 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -270,6 +270,13 @@ resolvePath | my2/mod.rs:10:9:10:24 | ...::nested7 | my2/nested2.rs:21:1:27:1 | mod nested7 | | my2/mod.rs:10:9:10:33 | ...::nested8 | my2/nested2.rs:22:5:26:5 | mod nested8 | | my2/mod.rs:10:37:10:40 | self | my2/nested2.rs:22:5:26:5 | mod nested8 | +| my2/my3/mod.rs:3:5:3:5 | g | my2/mod.rs:3:1:6:1 | fn g | +| my2/my3/mod.rs:4:5:4:5 | h | main.rs:50:1:69:1 | fn h | +| my2/my3/mod.rs:7:5:7:9 | super | my2/mod.rs:1:1:12:13 | SourceFile | +| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:501:2 | SourceFile | +| my2/my3/mod.rs:7:5:7:19 | ...::h | main.rs:50:1:69:1 | fn h | +| my2/my3/mod.rs:8:5:8:9 | super | my2/mod.rs:1:1:12:13 | SourceFile | +| my2/my3/mod.rs:8:5:8:12 | ...::g | my2/mod.rs:3:1:6:1 | fn g | | my.rs:3:5:3:10 | nested | my.rs:1:1:1:15 | mod nested | | my.rs:3:5:3:13 | ...::g | my/nested.rs:19:1:22:1 | fn g | | my.rs:11:5:11:5 | g | my/nested.rs:19:1:22:1 | fn g | From 3142dbb040a2d0df286fe3db3f618db0f46d1aa7 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 19 Mar 2025 15:08:33 +0100 Subject: [PATCH 11/12] Rust: Rework visibility handling in path resolution --- .../codeql/rust/internal/PathResolution.qll | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index a2203d199d1a..ad3595e3e175 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -226,6 +226,8 @@ private class SourceFileItemNode extends ModuleLikeNode, SourceFile { override Visibility getVisibility() { none() } + override predicate isPublic() { any() } + override TypeParam getTypeParam(int i) { none() } } @@ -271,6 +273,8 @@ class CrateItemNode extends ItemNode instanceof Crate { override Visibility getVisibility() { none() } + override predicate isPublic() { any() } + override TypeParam getTypeParam(int i) { none() } } @@ -648,7 +652,6 @@ private predicate fileImport(Module m, SourceFile f) { */ pragma[nomagic] private predicate fileImportEdge(Module mod, string name, ItemNode item) { - item.isPublic() and exists(SourceFileItemNode f | fileImport(mod, f) and item = f.getASuccessor(name) @@ -828,9 +831,8 @@ private predicate pathUsesNamespace(Path p, Namespace n) { ) } -/** Gets the item that `path` resolves to, if any. */ -cached -ItemNode resolvePath(RelevantPath path) { +pragma[nomagic] +private ItemNode resolvePath1(RelevantPath path) { exists(Namespace ns | result = resolvePath0(path, ns) | pathUsesNamespace(path, ns) or @@ -839,6 +841,47 @@ ItemNode resolvePath(RelevantPath path) { ) } +pragma[nomagic] +private ItemNode resolvePathPrivate( + RelevantPath path, ModuleLikeNode itemParent, ModuleLikeNode pathParent +) { + result = resolvePath1(path) and + itemParent = result.getImmediateParentModule() and + not result.isPublic() and + ( + pathParent.getADescendant() = path + or + pathParent = any(ItemNode mid | path = mid.getADescendant()).getImmediateParentModule() + ) +} + +/** + * Gets a module that has access to private items defined inside `itemParent`. + * + * According to + * + * https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/ch07-02-controlling-visibility-with-pub.html#privacy-rules + * + * this is either `itemParent` itself or any (transitive) child of `itemParent`. + */ +pragma[nomagic] +private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) { + exists(resolvePathPrivate(_, itemParent, _)) and + result.getImmediateParentModule*() = itemParent +} + +/** Gets the item that `path` resolves to, if any. */ +cached +ItemNode resolvePath(RelevantPath path) { + result = resolvePath1(path) and + result.isPublic() + or + exists(ModuleLikeNode itemParent, ModuleLikeNode pathParent | + result = resolvePathPrivate(path, itemParent, pathParent) and + pathParent = getAPrivateVisibleModule(itemParent) + ) +} + pragma[nomagic] private ItemNode resolvePathQualifier(RelevantPath path, string name) { result = resolvePath(path.getQualifier()) and From 3f1f37fd5fef725c74ae39535a3bbd85a66ea1f4 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 24 Mar 2025 10:53:38 +0100 Subject: [PATCH 12/12] Address review comments --- .../lib/codeql/rust/internal/PathResolution.qll | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index ad3595e3e175..2128bb8e7a82 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -212,10 +212,7 @@ abstract private class ModuleLikeNode extends ItemNode { private class SourceFileItemNode extends ModuleLikeNode, SourceFile { pragma[nomagic] ModuleLikeNode getSuper() { - exists(ModuleItemNode mod | - fileImport(mod, this) and - result = mod.getASuccessor("super") - ) + result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super") } override string getName() { result = "(source file)" } @@ -858,11 +855,10 @@ private ItemNode resolvePathPrivate( /** * Gets a module that has access to private items defined inside `itemParent`. * - * According to - * - * https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/ch07-02-controlling-visibility-with-pub.html#privacy-rules + * According to [The Rust Reference][1] this is either `itemParent` itself or any + * descendant of `itemParent`. * - * this is either `itemParent` itself or any (transitive) child of `itemParent`. + * [1]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#r-vis.access */ pragma[nomagic] private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) { @@ -964,10 +960,6 @@ private module Debug { private Locatable getRelevantLocatable() { exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and - // filepath.matches("%/compile.rs") and - // startline = 1986 - // filepath.matches("%/build_steps/mod.rs") and - // startline = 17 filepath.matches("%/main.rs") and startline = 1 )