diff --git a/rust/ql/.generated.list b/rust/ql/.generated.list index 0bd2931256e7..4b48d90fc58f 100644 --- a/rust/ql/.generated.list +++ b/rust/ql/.generated.list @@ -192,7 +192,6 @@ lib/codeql/rust/elements/YeetExpr.qll 4172bf70de31cab17639da6eed4a12a7afcefd7aa9 lib/codeql/rust/elements/YieldExpr.qll de2dc096a077f6c57bba9d1c2b2dcdbecce501333753b866d77c3ffbe06aa516 1f3e8949689c09ed356ff4777394fe39f2ed2b1e6c381fd391790da4f5d5c76a lib/codeql/rust/elements/internal/AbiConstructor.qll 4484538db49d7c1d31c139f0f21879fceb48d00416e24499a1d4b1337b4141ac 460818e397f2a1a8f2e5466d9551698b0e569d4640fcb87de6c4268a519b3da1 lib/codeql/rust/elements/internal/AbiImpl.qll 01439712ecadc9dc8da6f74d2e19cee13c77f8e1e25699055da675b2c88cb02d dcc9395ef8abd1af3805f3e7fcbc2d7ce30affbce654b6f5e559924768db403c -lib/codeql/rust/elements/internal/AddressableImpl.qll e01a6104980960f5708d5a0ada774ba21db9a344e33deeaf3d3239c627268c77 b8bfc711b267df305ac9fe5f6a994f051ddeca7fc95dacd76d1bae2d4fa7adde lib/codeql/rust/elements/internal/ArgListConstructor.qll a73685c8792ae23a2d628e7357658efb3f6e34006ff6e9661863ef116ec0b015 0bee572a046e8dfc031b1216d729843991519d94ae66280f5e795d20aea07a22 lib/codeql/rust/elements/internal/ArgListImpl.qll 19664651c06b46530f0ae5745ccb3233afc97b9152e053761d641de6e9c62d38 40af167e571f5c255f264b3be7cc7f5ff42ec109661ca03dcee94e92f8facfc6 lib/codeql/rust/elements/internal/ArrayExprInternal.qll 07a219b3d3fba3ff8b18e77686b2f58ab01acd99e0f5d5cad5d91af937e228f5 7528fc0e2064c481f0d6cbff3835950a044e429a2cd00c4d8442d2e132560d37 diff --git a/rust/ql/.gitattributes b/rust/ql/.gitattributes index 382565f3b86b..61a269649d5f 100644 --- a/rust/ql/.gitattributes +++ b/rust/ql/.gitattributes @@ -194,7 +194,6 @@ /lib/codeql/rust/elements/YieldExpr.qll linguist-generated /lib/codeql/rust/elements/internal/AbiConstructor.qll linguist-generated /lib/codeql/rust/elements/internal/AbiImpl.qll linguist-generated -/lib/codeql/rust/elements/internal/AddressableImpl.qll linguist-generated /lib/codeql/rust/elements/internal/ArgListConstructor.qll linguist-generated /lib/codeql/rust/elements/internal/ArgListImpl.qll linguist-generated /lib/codeql/rust/elements/internal/ArrayExprInternal.qll linguist-generated diff --git a/rust/ql/lib/codeql/files/FileSystem.qll b/rust/ql/lib/codeql/files/FileSystem.qll index 175f50c7c9ea..5a60d28418eb 100644 --- a/rust/ql/lib/codeql/files/FileSystem.qll +++ b/rust/ql/lib/codeql/files/FileSystem.qll @@ -34,6 +34,8 @@ class Container = Impl::Container; class Folder = Impl::Folder; +module Folder = Impl::Folder; + /** A file. */ class File extends Container, Impl::File { /** Holds if this file was extracted from ordinary source code. */ diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowConsistency.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowConsistency.qll index f8e24c4c34a9..f0dc961a9f93 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowConsistency.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowConsistency.qll @@ -29,3 +29,25 @@ private module Input implements InputSig { } import MakeConsistency +private import codeql.rust.dataflow.internal.ModelsAsData + +query predicate missingMadSummaryCanonicalPath(string crate, string path, Addressable a) { + summaryModel(crate, path, _, _, _, _, _) and + a.getCrateOrigin() = crate and + a.getExtendedCanonicalPath() = path and + not exists(a.getCanonicalPath()) +} + +query predicate missingMadSourceCanonicalPath(string crate, string path, Addressable a) { + sourceModel(crate, path, _, _, _, _) and + a.getCrateOrigin() = crate and + a.getExtendedCanonicalPath() = path and + not exists(a.getCanonicalPath()) +} + +query predicate missingMadSinkCanonicalPath(string crate, string path, Addressable a) { + sinkModel(crate, path, _, _, _, _) and + a.getCrateOrigin() = crate and + a.getExtendedCanonicalPath() = path and + not exists(a.getCanonicalPath()) +} diff --git a/rust/ql/lib/codeql/rust/elements/internal/AddressableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/AddressableImpl.qll index b3fe47b294a2..cea40b66aeea 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/AddressableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/AddressableImpl.qll @@ -1,4 +1,3 @@ -// generated by codegen, remove this comment if you wish to edit this file /** * This module provides a hand-modifiable wrapper around the generated class `Addressable`. * @@ -12,10 +11,43 @@ private import codeql.rust.elements.internal.generated.Addressable * be referenced directly. */ module Impl { + private import rust + private import codeql.rust.internal.PathResolution + + // the following QLdoc is generated: if you need to edit it, do it in the schema file /** * Something that can be addressed by a path. * * TODO: This does not yet include all possible cases. */ - class Addressable extends Generated::Addressable { } + class Addressable extends Generated::Addressable { + /** + * Gets the canonical path of this item, if any. + * + * The crate `c` is the root of the path. + * + * See [The Rust Reference][1] for more details. + * + * [1]: https://doc.rust-lang.org/reference/paths.html#canonical-paths + */ + string getCanonicalPath(Crate c) { result = this.(ItemNode).getCanonicalPath(c) } + + /** + * Gets the canonical path of this item, if any. + * + * See [The Rust Reference][1] for more details. + * + * [1]: https://doc.rust-lang.org/reference/paths.html#canonical-paths + */ + string getCanonicalPath() { result = this.getCanonicalPath(_) } + + /** + * Holds if this item has a canonical path. + * + * See [The Rust Reference][1] for more details. + * + * [1]: https://doc.rust-lang.org/reference/paths.html#canonical-paths + */ + predicate hasCanonicalPath() { exists(this.getCanonicalPath()) } + } } diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll index 0ba90bc2e346..84ee379773a1 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll @@ -28,16 +28,7 @@ private class StartswithCall extends Path::SafeAccessCheck::Range, CfgNodes::Met * [1]: https://doc.rust-lang.org/std/option/enum.Option.html */ class OptionEnum extends Enum { - OptionEnum() { - // todo: replace with canonical path, once calculated in QL - exists(Crate core, Module m | - core.getName() = "core" and - m = core.getModule().getItemList().getAnItem() and - m.getName().getText() = "option" and - this = m.getItemList().getAnItem() and - this.getName().getText() = "Option" - ) - } + OptionEnum() { this.getCanonicalPath() = "core::option::Option" } /** Gets the `Some` variant. */ Variant getSome() { result = this.getVariant("Some") } @@ -49,16 +40,7 @@ class OptionEnum extends Enum { * [1]: https://doc.rust-lang.org/stable/std/result/enum.Result.html */ class ResultEnum extends Enum { - ResultEnum() { - // todo: replace with canonical path, once calculated in QL - exists(Crate core, Module m | - core.getName() = "core" and - m = core.getModule().getItemList().getAnItem() and - m.getName().getText() = "result" and - this = m.getItemList().getAnItem() and - this.getName().getText() = "Result" - ) - } + ResultEnum() { this.getCanonicalPath() = "core::result::Result" } /** Gets the `Ok` variant. */ Variant getOk() { result = this.getVariant("Ok") } diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 91d7e87704c6..db92ad453d68 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -202,6 +202,45 @@ abstract class ItemNode extends Locatable { result.(CrateItemNode).isPotentialDollarCrateTarget() } + /** Holds if this item has a canonical path belonging to the crate `c`. */ + abstract predicate hasCanonicalPath(Crate c); + + /** Holds if this node provides a canonical path prefix for `child` in crate `c`. */ + pragma[nomagic] + predicate providesCanonicalPathPrefixFor(Crate c, ItemNode child) { + child.getImmediateParent() = this and + this.hasCanonicalPath(c) + } + + /** Holds if this node has a canonical path prefix in crate `c`. */ + pragma[nomagic] + final predicate hasCanonicalPathPrefix(Crate c) { + any(ItemNode parent).providesCanonicalPathPrefixFor(c, this) + } + + /** + * Gets the canonical path of this item, if any. + * + * See [The Rust Reference][1] for more details. + * + * [1]: https://doc.rust-lang.org/reference/paths.html#canonical-paths + */ + cached + abstract string getCanonicalPath(Crate c); + + /** Gets the canonical path prefix that this node provides for `child`. */ + pragma[nomagic] + string getCanonicalPathPrefixFor(Crate c, ItemNode child) { + this.providesCanonicalPathPrefixFor(c, child) and + result = this.getCanonicalPath(c) + } + + /** Gets the canonical path prefix of this node, if any. */ + pragma[nomagic] + final string getCanonicalPathPrefix(Crate c) { + result = any(ItemNode parent).getCanonicalPathPrefixFor(c, this) + } + /** Gets the location of this item. */ Location getLocation() { result = super.getLocation() } } @@ -248,6 +287,10 @@ private class SourceFileItemNode extends ModuleLikeNode, SourceFile { override predicate isPublic() { any() } override TypeParam getTypeParam(int i) { none() } + + override predicate hasCanonicalPath(Crate c) { none() } + + override string getCanonicalPath(Crate c) { none() } } class CrateItemNode extends ItemNode instanceof Crate { @@ -312,12 +355,48 @@ class CrateItemNode extends ItemNode instanceof Crate { override predicate isPublic() { any() } override TypeParam getTypeParam(int i) { none() } + + override predicate hasCanonicalPath(Crate c) { c = this } + + override predicate providesCanonicalPathPrefixFor(Crate c, ItemNode child) { + this.hasCanonicalPath(c) and + exists(ModuleLikeNode m | + child.getImmediateParent() = m and + not m = child.(SourceFileItemNode).getSuper() + | + m = super.getModule() // the special `crate` root module inserted by the extractor + or + m = super.getSourceFile() + ) + } + + override string getCanonicalPath(Crate c) { c = this and result = Crate.super.getName() } } /** 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. */ abstract predicate hasImplementation(); + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } private class ConstItemNode extends AssocItemNode instanceof Const { @@ -340,6 +419,26 @@ private class EnumItemNode extends ItemNode instanceof Enum { override Visibility getVisibility() { result = Enum.super.getVisibility() } override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } private class VariantItemNode extends ItemNode instanceof Variant { @@ -354,6 +453,26 @@ private class VariantItemNode extends ItemNode instanceof Variant { } override Visibility getVisibility() { result = super.getEnum().getVisibility() } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } class FunctionItemNode extends AssocItemNode instanceof Function { @@ -477,6 +596,49 @@ class ImplItemNode extends ImplOrTraitItemNode instanceof Impl { override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) } override Visibility getVisibility() { result = Impl.super.getVisibility() } + + override predicate hasCanonicalPath(Crate c) { this.resolveSelfTy().hasCanonicalPathPrefix(c) } + + pragma[nomagic] + private string getCanonicalPathTraitPart(Crate c) { + exists(Crate c2, TraitItemNode trait | + trait = this.resolveTraitTy() and + result = trait.getCanonicalPath(c2) and + if this.hasCanonicalPath(c2) then c = c2 else c2 = c.getADependency() + ) + } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = "<" + or + i = 1 and + result = this.resolveSelfTy().getCanonicalPath(c) + or + if exists(this.getTraitPath()) + then + i = 2 and + result = " as " + or + i = 3 and + result = this.getCanonicalPathTraitPart(c) + or + i = 4 and + result = ">" + else ( + i = 2 and + result = ">" + ) + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + exists(int m | if exists(this.getTraitPath()) then m = 4 else m = 2 | + result = strictconcat(int i | i in [0 .. m] | this.getCanonicalPathPart(c, i) order by i) + ) + } } private class MacroCallItemNode extends AssocItemNode instanceof MacroCall { @@ -489,6 +651,20 @@ private class MacroCallItemNode extends AssocItemNode instanceof MacroCall { override TypeParam getTypeParam(int i) { none() } override Visibility getVisibility() { none() } + + override predicate providesCanonicalPathPrefixFor(Crate c, ItemNode child) { + any(ItemNode parent).providesCanonicalPathPrefixFor(c, this) and + child.getImmediateParent() = this + } + + override string getCanonicalPathPrefixFor(Crate c, ItemNode child) { + result = this.getCanonicalPathPrefix(c) and + this.providesCanonicalPathPrefixFor(c, child) + } + + override predicate hasCanonicalPath(Crate c) { none() } + + override string getCanonicalPath(Crate c) { none() } } private class ModuleItemNode extends ModuleLikeNode instanceof Module { @@ -499,6 +675,43 @@ private class ModuleItemNode extends ModuleLikeNode instanceof Module { override Visibility getVisibility() { result = Module.super.getVisibility() } override TypeParam getTypeParam(int i) { none() } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + override predicate providesCanonicalPathPrefixFor(Crate c, ItemNode child) { + this.hasCanonicalPath(c) and + ( + exists(SourceFile f | + fileImport(this, f) and + sourceFileEdge(f, _, child) + ) + or + this = child.getImmediateParent() + or + exists(ItemNode mid | + this.providesCanonicalPathPrefixFor(c, mid) and + mid.(MacroCallItemNode) = child.getImmediateParent() + ) + ) + } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } private class StructItemNode extends ItemNode instanceof Struct { @@ -514,6 +727,26 @@ private class StructItemNode extends ItemNode instanceof Struct { override Visibility getVisibility() { result = Struct.super.getVisibility() } override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } class TraitItemNode extends ImplOrTraitItemNode instanceof Trait { @@ -534,6 +767,43 @@ class TraitItemNode extends ImplOrTraitItemNode instanceof Trait { override Visibility getVisibility() { result = Trait.super.getVisibility() } override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + override predicate providesCanonicalPathPrefixFor(Crate c, ItemNode child) { + this.hasCanonicalPath(c) and + child = this.getAnAssocItem() + } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = "<_ as " + or + i = 1 and + result = this.getCanonicalPathPrefix(c) + or + i = 2 and + result = "::" + or + i = 3 and + result = this.getName() + or + i = 4 and + result = ">" + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [1 .. 3] | this.getCanonicalPathPart(c, i) order by i) + } + + language[monotonicAggregates] + override string getCanonicalPathPrefixFor(Crate c, ItemNode child) { + this.providesCanonicalPathPrefixFor(c, child) and + result = strictconcat(int i | i in [0 .. 4] | this.getCanonicalPathPart(c, i) order by i) + } } class TypeAliasItemNode extends AssocItemNode instanceof TypeAlias { @@ -556,6 +826,26 @@ private class UnionItemNode extends ItemNode instanceof Union { override Visibility getVisibility() { result = Union.super.getVisibility() } override TypeParam getTypeParam(int i) { result = super.getGenericParamList().getTypeParam(i) } + + override predicate hasCanonicalPath(Crate c) { this.hasCanonicalPathPrefix(c) } + + bindingset[c] + private string getCanonicalPathPart(Crate c, int i) { + i = 0 and + result = this.getCanonicalPathPrefix(c) + or + i = 1 and + result = "::" + or + i = 2 and + result = this.getName() + } + + language[monotonicAggregates] + override string getCanonicalPath(Crate c) { + this.hasCanonicalPath(c) and + result = strictconcat(int i | i in [0 .. 2] | this.getCanonicalPathPart(c, i) order by i) + } } private class UseItemNode extends ItemNode instanceof Use { @@ -566,6 +856,10 @@ private class UseItemNode extends ItemNode instanceof Use { override Visibility getVisibility() { result = Use.super.getVisibility() } override TypeParam getTypeParam(int i) { none() } + + override predicate hasCanonicalPath(Crate c) { none() } + + override string getCanonicalPath(Crate c) { none() } } private class BlockExprItemNode extends ItemNode instanceof BlockExpr { @@ -576,6 +870,10 @@ private class BlockExprItemNode extends ItemNode instanceof BlockExpr { override Visibility getVisibility() { none() } override TypeParam getTypeParam(int i) { none() } + + override predicate hasCanonicalPath(Crate c) { none() } + + override string getCanonicalPath(Crate c) { none() } } private class TypeParamItemNode extends ItemNode instanceof TypeParam { @@ -632,6 +930,10 @@ private class TypeParamItemNode extends ItemNode instanceof TypeParam { override TypeParam getTypeParam(int i) { none() } override Location getLocation() { result = TypeParam.super.getName().getLocation() } + + override predicate hasCanonicalPath(Crate c) { none() } + + override string getCanonicalPath(Crate c) { none() } } /** Holds if `item` has the name `name` and is a top-level item inside `f`. */ @@ -655,6 +957,11 @@ private predicate fileModule(SourceFile f, string name, Folder folder) { ) } +private Meta getPathAttrMeta(Module m) { + result = m.getAnAttr().getMeta() and + result.getPath().getText() = "path" +} + /** * Holds if `m` is a `mod name;` module declaration, where the corresponding * module file needs to be looked up in `lookup` or one of its descandants. @@ -663,12 +970,7 @@ private predicate modImport0(Module m, string name, Folder lookup) { exists(File f, Folder parent, string fileName | f = m.getFile() and not m.hasItemList() and - // TODO: handle - // ``` - // #[path = "foo.rs"] - // mod bar; - // ``` - not m.getAnAttr().getMeta().getPath().getText() = "path" and + not exists(getPathAttrMeta(m)) and name = m.getName().getText() and parent = f.getParentContainer() and fileName = f.getStem() @@ -717,6 +1019,16 @@ private predicate modImportNestedLookup(Module m, ModuleItemNode ancestor, Folde ) } +private predicate pathAttrImport(Folder f, Module m, string relativePath) { + exists(Meta meta | + f = m.getFile().getParentContainer() and + meta = getPathAttrMeta(m) and + relativePath = meta.getExpr().(LiteralExpr).getTextValue().regexpCapture("\"(.+)\"", 1) + ) +} + +private predicate append(Folder f, string relativePath) { pathAttrImport(f, _, relativePath) } + /** Holds if `m` is a `mod name;` item importing file `f`. */ private predicate fileImport(Module m, SourceFile f) { exists(string name, Folder parent | @@ -730,6 +1042,11 @@ private predicate fileImport(Module m, SourceFile f) { // `m` is inside a nested module modImportNestedLookup(m, m, parent) ) + or + exists(Folder folder, string relativePath | + pathAttrImport(folder, m, relativePath) and + f.getFile() = Folder::Append::append(folder, relativePath) + ) } /** @@ -1110,8 +1427,8 @@ 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("%/test_logging.rs") and - startline = 163 + filepath.matches("%/clean/types.rs") and + startline = [5, 350] ) } @@ -1147,4 +1464,14 @@ private module Debug { m = getRelevantLocatable() and fileImport(m, f) } + + predicate debugPreludeEdge(SourceFile f, string name, ItemNode i) { + preludeEdge(f, name, i) and + f = getRelevantLocatable() + } + + string debugGetCanonicalPath(ItemNode i, Crate c) { + result = i.getCanonicalPath(c) and + i = getRelevantLocatable() + } } diff --git a/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll b/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll index a8f581aabdf9..2175dea37133 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolutionConsistency.qll @@ -38,6 +38,12 @@ query predicate multipleTupleFields(FieldExpr fe, TupleField field) { strictcount(fe.getTupleField()) > 1 } +/** Holds if `p` may resolve to multiple items including `i`. */ +query predicate multipleCanonicalPaths(ItemNode i, Crate c, string path) { + path = i.getCanonicalPath(c) and + strictcount(i.getCanonicalPath(c)) > 1 +} + /** * Gets counts of path resolution inconsistencies of each type. */ @@ -53,4 +59,7 @@ int getPathResolutionInconsistencyCounts(string type) { or type = "Multiple tuple fields" and result = count(FieldExpr fe | multipleTupleFields(fe, _) | fe) + or + type = "Multiple canonical paths" and + result = count(ItemNode i | multipleCanonicalPaths(i, _, _) | i) } diff --git a/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll b/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll index d62e5ec33639..e68306a3cf9e 100644 --- a/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll +++ b/rust/ql/src/queries/telemetry/RustAnalyzerComparison.qll @@ -145,3 +145,8 @@ private module QlCallGraph implements CompareSig { } module CallGraphCompare = Compare; + +predicate qlMissingCanonicalPath(Addressable a, string path) { + path = a.getExtendedCanonicalPath() and + not exists(a.getCanonicalPath(_)) +} diff --git a/rust/ql/test/extractor-tests/canonical_path/canonical_paths.expected b/rust/ql/test/extractor-tests/canonical_path/canonical_paths.expected index 8395c20a00a5..69ea1bb7b0e3 100644 --- a/rust/ql/test/extractor-tests/canonical_path/canonical_paths.expected +++ b/rust/ql/test/extractor-tests/canonical_path/canonical_paths.expected @@ -1,3 +1,26 @@ +canonicalPath +| anonymous.rs:3:1:32:1 | fn canonicals | test::anonymous::canonicals | +| anonymous.rs:34:1:36:1 | fn other | test::anonymous::other | +| lib.rs:1:1:1:14 | mod anonymous | test::anonymous | +| lib.rs:2:1:2:12 | mod regular | test::regular | +| regular.rs:1:1:2:18 | struct Struct | test::regular::Struct | +| regular.rs:4:1:6:1 | trait Trait | test::regular::Trait | +| regular.rs:5:5:5:16 | fn f | <_ as test::regular::Trait>::f | +| regular.rs:8:1:10:1 | impl Trait for Struct { ... } | | +| regular.rs:9:5:9:18 | fn f | ::f | +| regular.rs:12:1:14:1 | impl Struct { ... } | | +| regular.rs:13:5:13:18 | fn g | ::g | +| regular.rs:16:1:18:1 | trait TraitWithBlanketImpl | test::regular::TraitWithBlanketImpl | +| regular.rs:17:5:17:16 | fn h | <_ as test::regular::TraitWithBlanketImpl>::h | +| regular.rs:24:1:24:12 | fn free | test::regular::free | +| regular.rs:26:1:32:1 | fn usage | test::regular::usage | +| regular.rs:34:1:38:1 | enum MyEnum | test::regular::MyEnum | +| regular.rs:35:5:35:12 | Variant1 | test::regular::MyEnum::Variant1 | +| regular.rs:36:5:36:19 | Variant2 | test::regular::MyEnum::Variant2 | +| regular.rs:37:5:37:25 | Variant3 | test::regular::MyEnum::Variant3 | +| regular.rs:40:1:46:1 | fn enum_qualified_usage | test::regular::enum_qualified_usage | +| regular.rs:48:1:55:1 | fn enum_unqualified_usage | test::regular::enum_unqualified_usage | +| regular.rs:57:1:63:1 | fn enum_match | test::regular::enum_match | canonicalPaths | anonymous.rs:1:1:1:26 | use ...::Trait | None | None | | anonymous.rs:3:1:32:1 | fn canonicals | repo::test | crate::anonymous::canonicals | diff --git a/rust/ql/test/extractor-tests/canonical_path/canonical_paths.ql b/rust/ql/test/extractor-tests/canonical_path/canonical_paths.ql index 7488d699087c..16aa82eee21c 100644 --- a/rust/ql/test/extractor-tests/canonical_path/canonical_paths.ql +++ b/rust/ql/test/extractor-tests/canonical_path/canonical_paths.ql @@ -1,6 +1,11 @@ import rust import TestUtils +query predicate canonicalPath(Addressable a, string path) { + toBeTested(a) and + path = a.getCanonicalPath(_) +} + query predicate canonicalPaths(Item i, string origin, string path) { toBeTested(i) and ( diff --git a/rust/ql/test/extractor-tests/canonical_path_disabled/canonical_paths.expected b/rust/ql/test/extractor-tests/canonical_path_disabled/canonical_paths.expected index 878cb1fc7c9d..2605a806f6f7 100644 --- a/rust/ql/test/extractor-tests/canonical_path_disabled/canonical_paths.expected +++ b/rust/ql/test/extractor-tests/canonical_path_disabled/canonical_paths.expected @@ -1,3 +1,26 @@ +canonicalPath +| anonymous.rs:6:1:35:1 | fn canonicals | test::anonymous::canonicals | +| anonymous.rs:37:1:39:1 | fn other | test::anonymous::other | +| lib.rs:1:1:1:14 | mod anonymous | test::anonymous | +| lib.rs:2:1:2:12 | mod regular | test::regular | +| regular.rs:4:1:5:18 | struct Struct | test::regular::Struct | +| regular.rs:7:1:9:1 | trait Trait | test::regular::Trait | +| regular.rs:8:5:8:16 | fn f | <_ as test::regular::Trait>::f | +| regular.rs:11:1:13:1 | impl Trait for Struct { ... } | | +| regular.rs:12:5:12:18 | fn f | ::f | +| regular.rs:15:1:17:1 | impl Struct { ... } | | +| regular.rs:16:5:16:18 | fn g | ::g | +| regular.rs:19:1:21:1 | trait TraitWithBlanketImpl | test::regular::TraitWithBlanketImpl | +| regular.rs:20:5:20:16 | fn h | <_ as test::regular::TraitWithBlanketImpl>::h | +| regular.rs:27:1:27:12 | fn free | test::regular::free | +| regular.rs:29:1:35:1 | fn usage | test::regular::usage | +| regular.rs:37:1:41:1 | enum MyEnum | test::regular::MyEnum | +| regular.rs:38:5:38:12 | Variant1 | test::regular::MyEnum::Variant1 | +| regular.rs:39:5:39:19 | Variant2 | test::regular::MyEnum::Variant2 | +| regular.rs:40:5:40:25 | Variant3 | test::regular::MyEnum::Variant3 | +| regular.rs:43:1:49:1 | fn enum_qualified_usage | test::regular::enum_qualified_usage | +| regular.rs:51:1:58:1 | fn enum_unqualified_usage | test::regular::enum_unqualified_usage | +| regular.rs:60:1:66:1 | fn enum_match | test::regular::enum_match | canonicalPaths | anonymous.rs:4:1:4:26 | use ...::Trait | None | None | | anonymous.rs:6:1:35:1 | fn canonicals | None | None | 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 64291a53af53..43c1a05e91fe 100644 --- a/rust/ql/test/library-tests/path-resolution/my2/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/my2/mod.rs @@ -10,3 +10,8 @@ pub use nested2::nested5::*; // $ item=I114 pub use nested2::nested7::nested8::{self}; // $ item=I118 pub mod my3; + +#[path = "renamed.rs"] +mod mymod; + +use mymod::f; // $ item=I1001 diff --git a/rust/ql/test/library-tests/path-resolution/my2/renamed.rs b/rust/ql/test/library-tests/path-resolution/my2/renamed.rs new file mode 100644 index 000000000000..fa37691c1366 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/my2/renamed.rs @@ -0,0 +1 @@ +pub fn f() {} // I1001 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 1d480f2f5ad9..7fbbca66c39c 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -30,6 +30,7 @@ mod | main.rs:523:1:548:1 | mod m23 | | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:12:1:12:12 | mod my3 | +| my2/mod.rs:14:1:15:10 | mod mymod | | 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 | @@ -306,12 +307,14 @@ 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/mod.rs:17:5:17:9 | mymod | my2/mod.rs:14:1:15:10 | mod mymod | +| my2/mod.rs:17:5:17:12 | ...::f | my2/renamed.rs:1:1:1:13 | fn f | | 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:9 | super | my2/mod.rs:1:1:17:30 | SourceFile | | my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:578: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:9 | super | my2/mod.rs:1:1:17:30 | 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 | diff --git a/shared/util/codeql/util/FileSystem.qll b/shared/util/codeql/util/FileSystem.qll index a9eb21279b63..261139dcf41b 100644 --- a/shared/util/codeql/util/FileSystem.qll +++ b/shared/util/codeql/util/FileSystem.qll @@ -218,6 +218,53 @@ module Make { /** Gets the URL of this file. */ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } } + + /** Provides logic related to `Folder`s. */ + module Folder { + /** Holds if `relativePath` needs to be appended to `f`. */ + signature predicate appendSig(Folder f, string relativePath); + + /** Provides the `append` predicate for appending a relative path onto a folder. */ + module Append { + pragma[nomagic] + private string getComponent(string relativePath, int i) { + app(_, relativePath) and + result = relativePath.replaceAll("\\", "/").regexpFind("[^/]+", i, _) + } + + pragma[nomagic] + private Container appendStep(Folder f, string relativePath, int i) { + i = -1 and + app(f, relativePath) and + result = f + or + exists(Container mid, string comp | + mid = appendStep(f, relativePath, i - 1) and + comp = getComponent(relativePath, i) and + if comp = ".." + then result = mid.getParentContainer() + else + if comp = "." + then result = mid + else ( + result = mid.getAChildContainer() and + result.getBaseName() = comp + ) + ) + } + + /** + * Gets the file or folder obtained by appending `relativePath` onto `f`. + */ + pragma[nomagic] + Container append(Folder f, string relativePath) { + exists(int components | + components = (-1).maximum(max(int comp | exists(getComponent(relativePath, comp)) | comp)) and + result = appendStep(f, relativePath, components) + ) + } + } + } } /** A file. */