Skip to content

Commit 8c50ecb

Browse files
committed
Rust: Take prelude into account when resolving paths
1 parent cee39cd commit 8c50ecb

File tree

7 files changed

+119
-140
lines changed

7 files changed

+119
-140
lines changed

rust/ql/lib/codeql/rust/dataflow/internal/Content.qll

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,6 @@ cached
255255
newtype TContent =
256256
TTupleFieldContent(TupleField field) { Stages::DataFlowStage::ref() } or
257257
TStructFieldContent(StructField field) or
258-
// TODO: Remove once library types are extracted
259-
TVariantInLibTupleFieldContent(VariantInLib::VariantInLib v, int pos) { pos = v.getAPosition() } or
260258
TElementContent() or
261259
TFutureContent() or
262260
TTuplePositionContent(int pos) {

rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll

Lines changed: 5 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -302,125 +302,6 @@ module LocalFlow {
302302
}
303303
}
304304

305-
/**
306-
* Provides temporary modeling of built-in variants, for which no source code
307-
* `Item`s are available.
308-
*
309-
* TODO: Remove once library code is extracted.
310-
*/
311-
module VariantInLib {
312-
private import codeql.util.Option
313-
314-
private class CrateOrigin extends string {
315-
CrateOrigin() { this = any(Resolvable r).getResolvedCrateOrigin() }
316-
}
317-
318-
private class CrateOriginOption = Option<CrateOrigin>::Option;
319-
320-
private CrateOriginOption langCoreCrate() { result.asSome() = "lang:core" }
321-
322-
private newtype TVariantInLib =
323-
MkVariantInLib(CrateOriginOption crate, string path, string name) {
324-
crate = langCoreCrate() and
325-
(
326-
path = "crate::option::Option" and
327-
name = "Some"
328-
or
329-
path = "crate::result::Result" and
330-
name = ["Ok", "Err"]
331-
)
332-
}
333-
334-
/** An enum variant from library code, represented by the enum's canonical path and the variant's name. */
335-
class VariantInLib extends MkVariantInLib {
336-
CrateOriginOption crate;
337-
string path;
338-
string name;
339-
340-
VariantInLib() { this = MkVariantInLib(crate, path, name) }
341-
342-
int getAPosition() {
343-
this = MkVariantInLib(langCoreCrate(), "crate::option::Option", "Some") and
344-
result = 0
345-
or
346-
this = MkVariantInLib(langCoreCrate(), "crate::result::Result", ["Ok", "Err"]) and
347-
result = 0
348-
}
349-
350-
string getExtendedCanonicalPath() { result = path + "::" + name }
351-
352-
string toString() { result = name }
353-
}
354-
355-
/** A tuple variant from library code. */
356-
class VariantInLibTupleFieldContent extends Content, TVariantInLibTupleFieldContent {
357-
private VariantInLib::VariantInLib v;
358-
private int pos_;
359-
360-
VariantInLibTupleFieldContent() { this = TVariantInLibTupleFieldContent(v, pos_) }
361-
362-
VariantInLib::VariantInLib getVariantInLib(int pos) { result = v and pos = pos_ }
363-
364-
string getExtendedCanonicalPath() { result = v.getExtendedCanonicalPath() }
365-
366-
int getPosition() { result = pos_ }
367-
368-
final override string toString() {
369-
// only print indices when the arity is > 1
370-
if exists(TVariantInLibTupleFieldContent(v, 1))
371-
then result = v.toString() + "(" + pos_ + ")"
372-
else result = v.toString()
373-
}
374-
375-
final override Location getLocation() { result instanceof EmptyLocation }
376-
}
377-
378-
pragma[nomagic]
379-
private predicate resolveExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
380-
path = r.getResolvedPath() and
381-
(
382-
crate.asSome() = r.getResolvedCrateOrigin()
383-
or
384-
crate.isNone() and
385-
not r.hasResolvedCrateOrigin()
386-
)
387-
}
388-
389-
/** Holds if path `p` resolves to variant `v`. */
390-
private predicate pathResolveToVariantInLib(PathAstNode p, VariantInLib v) {
391-
exists(CrateOriginOption crate, string path, string name |
392-
resolveExtendedCanonicalPath(p, pragma[only_bind_into](crate), path + "::" + name) and
393-
v = MkVariantInLib(pragma[only_bind_into](crate), path, name)
394-
)
395-
}
396-
397-
/** Holds if `p` destructs an enum variant `v`. */
398-
pragma[nomagic]
399-
private predicate tupleVariantCanonicalDestruction(TupleStructPat p, VariantInLib v) {
400-
pathResolveToVariantInLib(p, v)
401-
}
402-
403-
bindingset[pos]
404-
predicate tupleVariantCanonicalDestruction(
405-
TupleStructPat pat, VariantInLibTupleFieldContent c, int pos
406-
) {
407-
tupleVariantCanonicalDestruction(pat, c.getVariantInLib(pos))
408-
}
409-
410-
/** Holds if `ce` constructs an enum value of type `v`. */
411-
pragma[nomagic]
412-
private predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLib v) {
413-
pathResolveToVariantInLib(ce.getFunction().(PathExpr), v)
414-
}
415-
416-
bindingset[pos]
417-
predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLibTupleFieldContent c, int pos) {
418-
tupleVariantCanonicalConstruction(ce, c.getVariantInLib(pos))
419-
}
420-
}
421-
422-
class VariantInLibTupleFieldContent = VariantInLib::VariantInLibTupleFieldContent;
423-
424305
class LambdaCallKind = Unit;
425306

426307
/** Holds if `creation` is an expression that creates a lambda of kind `kind`. */
@@ -486,6 +367,7 @@ module RustDataFlow implements InputSig<Location> {
486367
private import Aliases
487368
private import codeql.rust.dataflow.DataFlow
488369
private import Node as Node
370+
private import codeql.rust.frameworks.stdlib.Stdlib
489371

490372
/**
491373
* An element, viewed as a node in a data flow graph. Either an expression
@@ -671,11 +553,8 @@ module RustDataFlow implements InputSig<Location> {
671553
exists(Content c | c = cs.(SingletonContentSet).getContent() |
672554
exists(TupleStructPatCfgNode pat, int pos |
673555
pat = node1.asPat() and
674-
node2.asPat() = pat.getField(pos)
675-
|
556+
node2.asPat() = pat.getField(pos) and
676557
c = TTupleFieldContent(pat.getTupleStructPat().getTupleField(pos))
677-
or
678-
VariantInLib::tupleVariantCanonicalDestruction(pat.getPat(), c, pos)
679558
)
680559
or
681560
exists(TuplePatCfgNode pat, int pos |
@@ -720,8 +599,8 @@ module RustDataFlow implements InputSig<Location> {
720599
exists(TryExprCfgNode try |
721600
node1.asExpr() = try.getExpr() and
722601
node2.asExpr() = try and
723-
c.(VariantInLibTupleFieldContent).getVariantInLib(0).getExtendedCanonicalPath() =
724-
["crate::option::Option::Some", "crate::result::Result::Ok"]
602+
c.(TupleFieldContent)
603+
.isVariantField([any(OptionEnum o).getSome(), any(ResultEnum r).getOk()], 0)
725604
)
726605
or
727606
exists(PrefixExprCfgNode deref |
@@ -797,11 +676,8 @@ module RustDataFlow implements InputSig<Location> {
797676
private predicate storeContentStep(Node node1, Content c, Node node2) {
798677
exists(CallExprCfgNode call, int pos |
799678
node1.asExpr() = call.getArgument(pragma[only_bind_into](pos)) and
800-
node2.asExpr() = call
801-
|
679+
node2.asExpr() = call and
802680
c = TTupleFieldContent(call.getCallExpr().getTupleField(pragma[only_bind_into](pos)))
803-
or
804-
VariantInLib::tupleVariantCanonicalConstruction(call.getCallExpr(), c, pos)
805681
)
806682
or
807683
exists(StructExprCfgNode re, string field |

rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ private import Content
1111

1212
module Input implements InputSig<Location, RustDataFlow> {
1313
private import codeql.rust.elements.internal.CallExprBaseImpl::Impl as CallExprBaseImpl
14+
private import codeql.rust.frameworks.stdlib.Stdlib
1415

1516
class SummarizedCallableBase = string;
1617

@@ -66,9 +67,20 @@ module Input implements InputSig<Location, RustDataFlow> {
6667
exists(Content c | cs = TSingletonContentSet(c) |
6768
result = "Field" and
6869
(
69-
exists(Addressable a, int pos |
70+
exists(Addressable a, int pos, string prefix |
7071
// TODO: calculate in QL
71-
arg = a.getExtendedCanonicalPath() + "(" + pos + ")"
72+
arg = prefix + "(" + pos + ")" and
73+
(
74+
prefix = a.getExtendedCanonicalPath()
75+
or
76+
a = any(OptionEnum o).getSome() and
77+
prefix = "crate::option::Option::Some"
78+
or
79+
exists(string name |
80+
a = any(ResultEnum r).getVariant(name) and
81+
prefix = "crate::result::Result::" + name
82+
)
83+
)
7284
|
7385
c.(TupleFieldContent).isStructField(a, pos)
7486
or
@@ -84,11 +96,6 @@ module Input implements InputSig<Location, RustDataFlow> {
8496
c.(StructFieldContent).isVariantField(a, field)
8597
)
8698
or
87-
c =
88-
any(VariantInLibTupleFieldContent v |
89-
arg = v.getExtendedCanonicalPath() + "(" + v.getPosition() + ")"
90-
)
91-
or
9299
exists(int pos |
93100
c = TTuplePositionContent(pos) and
94101
arg = pos.toString()

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ private import codeql.rust.elements.internal.generated.Enum
1111
* be referenced directly.
1212
*/
1313
module Impl {
14+
private import rust
15+
1416
// the following QLdoc is generated: if you need to edit it, do it in the schema file
1517
/**
1618
* A Enum. For example:
@@ -20,5 +22,12 @@ module Impl {
2022
*/
2123
class Enum extends Generated::Enum {
2224
override string toStringImpl() { result = "enum " + this.getName().getText() }
25+
26+
/** Gets the variant named `name`, if any. */
27+
pragma[nomagic]
28+
Variant getVariant(string name) {
29+
result = this.getVariantList().getAVariant() and
30+
result.getName().getText() = name
31+
}
2332
}
2433
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ module Impl {
4040
*/
4141
pragma[nomagic]
4242
string getText() { result = this.getPart().getNameRef().getText() }
43+
44+
/**
45+
* Gets the full text of this path, including the qualifier.
46+
*
47+
* Should only be used for debugging purposes.
48+
*/
49+
string toStringDebug() {
50+
not this.hasQualifier() and
51+
result = this.getText()
52+
or
53+
result = this.getQualifier().toStringDebug() + "::" + this.getText()
54+
}
4355
}
4456

4557
/** A simple identifier path. */

rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,48 @@ private class StartswithCall extends Path::SafeAccessCheck::Range, CfgNodes::Met
2121
branch = true
2222
}
2323
}
24+
25+
/**
26+
* The [`Option` enum][1].
27+
*
28+
* [1]: https://doc.rust-lang.org/std/option/enum.Option.html
29+
*/
30+
class OptionEnum extends Enum {
31+
OptionEnum() {
32+
// todo: replace with canonical path, once calculated in QL
33+
exists(Crate core, Module m |
34+
core.getName() = "core" and
35+
m = core.getModule().getItemList().getAnItem() and
36+
m.getName().getText() = "option" and
37+
this = m.getItemList().getAnItem() and
38+
this.getName().getText() = "Option"
39+
)
40+
}
41+
42+
/** Gets the `Some` variant. */
43+
Variant getSome() { result = this.getVariant("Some") }
44+
}
45+
46+
/**
47+
* The [`Result` enum][1].
48+
*
49+
* [1]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
50+
*/
51+
class ResultEnum extends Enum {
52+
ResultEnum() {
53+
// todo: replace with canonical path, once calculated in QL
54+
exists(Crate core, Module m |
55+
core.getName() = "core" and
56+
m = core.getModule().getItemList().getAnItem() and
57+
m.getName().getText() = "result" and
58+
this = m.getItemList().getAnItem() and
59+
this.getName().getText() = "Result"
60+
)
61+
}
62+
63+
/** Gets the `Ok` variant. */
64+
Variant getOk() { result = this.getVariant("Ok") }
65+
66+
/** Gets the `Err` variant. */
67+
Variant getErr() { result = this.getVariant("Err") }
68+
}

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ abstract class ItemNode extends Locatable {
175175
ItemNode getASuccessor(string name) {
176176
result = this.getASuccessorRec(name)
177177
or
178+
preludeEdge(this, name, result)
179+
or
178180
name = "super" and
179181
if this instanceof Module or this instanceof SourceFile
180182
then result = this.getImmediateParentModule()
@@ -214,6 +216,16 @@ abstract private class ModuleLikeNode extends ItemNode {
214216
not mid instanceof ModuleLikeNode
215217
)
216218
}
219+
220+
/**
221+
* Holds if this is a root module, meaning either a source file or
222+
* the entry module of a crate.
223+
*/
224+
predicate isRoot() {
225+
this instanceof SourceFileItemNode
226+
or
227+
this = any(CrateItemNode c).getModuleNode()
228+
}
217229
}
218230

219231
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
@@ -807,9 +819,9 @@ private predicate unqualifiedPathLookup(RelevantPath p, string name, Namespace n
807819
)
808820
|
809821
// nested modules do not have unqualified access to items from outer modules,
810-
// except for items declared at top-level in the source file
822+
// except for items declared at top-level in the root module
811823
if mid instanceof Module
812-
then encl0.(SourceFileItemNode) = mid.getImmediateParent+()
824+
then encl0 = mid.getImmediateParent+() and encl0.(ModuleLikeNode).isRoot()
813825
else encl0 = mid.getImmediateParent()
814826
)
815827
|
@@ -1021,6 +1033,26 @@ private predicate useImportEdge(Use use, string name, ItemNode item) {
10211033
)
10221034
}
10231035

1036+
/**
1037+
* Holds if `i` is available inside `f` because it is reexported in [the prelude][1].
1038+
*
1039+
* We don't yet have access to prelude information from the extractor, so for now
1040+
* we include all the preludes for Rust 2015, 2018, 2021, and 2024.
1041+
*
1042+
* [1]: https://doc.rust-lang.org/core/prelude/index.html
1043+
*/
1044+
private predicate preludeEdge(SourceFile f, string name, ItemNode i) {
1045+
exists(Crate core, ModuleItemNode mod, ModuleItemNode prelude, ModuleItemNode rust |
1046+
f = any(Crate c0 | core = c0.getDependency(_)).getASourceFile() and
1047+
core.getName() = "core" and
1048+
mod = core.getModule() and
1049+
prelude = mod.getASuccessorRec("prelude") and
1050+
rust = prelude.getASuccessorRec(["rust_2015", "rust_2018", "rust_2021", "rust_2024"]) and
1051+
i = rust.getASuccessorRec(name) and
1052+
not i instanceof Use
1053+
)
1054+
}
1055+
10241056
/** Provides predicates for debugging the path resolution implementation. */
10251057
private module Debug {
10261058
private Locatable getRelevantLocatable() {

0 commit comments

Comments
 (0)