Skip to content

Rust: Take prelude into account when resolving paths #19157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust/ql/integration-tests/hello-project/summary.expected
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| Macro calls - resolved | 2 |
| Macro calls - total | 2 |
| Macro calls - unresolved | 0 |
| Taint edges - number of edges | 1674 |
| Taint edges - number of edges | 1691 |
| Taint reach - nodes tainted | 0 |
| Taint reach - per million nodes | 0 |
| Taint sinks - cryptographic operations | 0 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| Macro calls - resolved | 2 |
| Macro calls - total | 2 |
| Macro calls - unresolved | 0 |
| Taint edges - number of edges | 1674 |
| Taint edges - number of edges | 1691 |
| Taint reach - nodes tainted | 0 |
| Taint reach - per million nodes | 0 |
| Taint sinks - cryptographic operations | 0 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| Macro calls - resolved | 2 |
| Macro calls - total | 2 |
| Macro calls - unresolved | 0 |
| Taint edges - number of edges | 1674 |
| Taint edges - number of edges | 1691 |
| Taint reach - nodes tainted | 0 |
| Taint reach - per million nodes | 0 |
| Taint sinks - cryptographic operations | 0 |
Expand Down
2 changes: 0 additions & 2 deletions rust/ql/lib/codeql/rust/dataflow/internal/Content.qll
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,6 @@ cached
newtype TContent =
TTupleFieldContent(TupleField field) { Stages::DataFlowStage::ref() } or
TStructFieldContent(StructField field) or
// TODO: Remove once library types are extracted
TVariantInLibTupleFieldContent(VariantInLib::VariantInLib v, int pos) { pos = v.getAPosition() } or
TElementContent() or
TFutureContent() or
TTuplePositionContent(int pos) {
Expand Down
134 changes: 5 additions & 129 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -296,125 +296,6 @@ module LocalFlow {
}
}

/**
* Provides temporary modeling of built-in variants, for which no source code
* `Item`s are available.
*
* TODO: Remove once library code is extracted.
*/
module VariantInLib {
private import codeql.util.Option

private class CrateOrigin extends string {
CrateOrigin() { this = any(Resolvable r).getResolvedCrateOrigin() }
}

private class CrateOriginOption = Option<CrateOrigin>::Option;

private CrateOriginOption langCoreCrate() { result.asSome() = "lang:core" }

private newtype TVariantInLib =
MkVariantInLib(CrateOriginOption crate, string path, string name) {
crate = langCoreCrate() and
(
path = "crate::option::Option" and
name = "Some"
or
path = "crate::result::Result" and
name = ["Ok", "Err"]
)
}

/** An enum variant from library code, represented by the enum's canonical path and the variant's name. */
class VariantInLib extends MkVariantInLib {
CrateOriginOption crate;
string path;
string name;

VariantInLib() { this = MkVariantInLib(crate, path, name) }

int getAPosition() {
this = MkVariantInLib(langCoreCrate(), "crate::option::Option", "Some") and
result = 0
or
this = MkVariantInLib(langCoreCrate(), "crate::result::Result", ["Ok", "Err"]) and
result = 0
}

string getExtendedCanonicalPath() { result = path + "::" + name }

string toString() { result = name }
}

/** A tuple variant from library code. */
class VariantInLibTupleFieldContent extends Content, TVariantInLibTupleFieldContent {
private VariantInLib::VariantInLib v;
private int pos_;

VariantInLibTupleFieldContent() { this = TVariantInLibTupleFieldContent(v, pos_) }

VariantInLib::VariantInLib getVariantInLib(int pos) { result = v and pos = pos_ }

string getExtendedCanonicalPath() { result = v.getExtendedCanonicalPath() }

int getPosition() { result = pos_ }

final override string toString() {
// only print indices when the arity is > 1
if exists(TVariantInLibTupleFieldContent(v, 1))
then result = v.toString() + "(" + pos_ + ")"
else result = v.toString()
}

final override Location getLocation() { result instanceof EmptyLocation }
}

pragma[nomagic]
private predicate resolveExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
path = r.getResolvedPath() and
(
crate.asSome() = r.getResolvedCrateOrigin()
or
crate.isNone() and
not r.hasResolvedCrateOrigin()
)
}

/** Holds if path `p` resolves to variant `v`. */
private predicate pathResolveToVariantInLib(PathAstNode p, VariantInLib v) {
exists(CrateOriginOption crate, string path, string name |
resolveExtendedCanonicalPath(p, pragma[only_bind_into](crate), path + "::" + name) and
v = MkVariantInLib(pragma[only_bind_into](crate), path, name)
)
}

/** Holds if `p` destructs an enum variant `v`. */
pragma[nomagic]
private predicate tupleVariantCanonicalDestruction(TupleStructPat p, VariantInLib v) {
pathResolveToVariantInLib(p, v)
}

bindingset[pos]
predicate tupleVariantCanonicalDestruction(
TupleStructPat pat, VariantInLibTupleFieldContent c, int pos
) {
tupleVariantCanonicalDestruction(pat, c.getVariantInLib(pos))
}

/** Holds if `ce` constructs an enum value of type `v`. */
pragma[nomagic]
private predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLib v) {
pathResolveToVariantInLib(ce.getFunction().(PathExpr), v)
}

bindingset[pos]
predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLibTupleFieldContent c, int pos) {
tupleVariantCanonicalConstruction(ce, c.getVariantInLib(pos))
}
}

class VariantInLibTupleFieldContent = VariantInLib::VariantInLibTupleFieldContent;

class LambdaCallKind = Unit;

/** Holds if `creation` is an expression that creates a lambda of kind `kind`. */
Expand Down Expand Up @@ -480,6 +361,7 @@ module RustDataFlow implements InputSig<Location> {
private import Aliases
private import codeql.rust.dataflow.DataFlow
private import Node as Node
private import codeql.rust.frameworks.stdlib.Stdlib

/**
* An element, viewed as a node in a data flow graph. Either an expression
Expand Down Expand Up @@ -665,11 +547,8 @@ module RustDataFlow implements InputSig<Location> {
exists(Content c | c = cs.(SingletonContentSet).getContent() |
exists(TupleStructPatCfgNode pat, int pos |
pat = node1.asPat() and
node2.asPat() = pat.getField(pos)
|
node2.asPat() = pat.getField(pos) and
c = TTupleFieldContent(pat.getTupleStructPat().getTupleField(pos))
or
VariantInLib::tupleVariantCanonicalDestruction(pat.getPat(), c, pos)
)
or
exists(TuplePatCfgNode pat, int pos |
Expand Down Expand Up @@ -714,8 +593,8 @@ module RustDataFlow implements InputSig<Location> {
exists(TryExprCfgNode try |
node1.asExpr() = try.getExpr() and
node2.asExpr() = try and
c.(VariantInLibTupleFieldContent).getVariantInLib(0).getExtendedCanonicalPath() =
["crate::option::Option::Some", "crate::result::Result::Ok"]
c.(TupleFieldContent)
.isVariantField([any(OptionEnum o).getSome(), any(ResultEnum r).getOk()], 0)
)
or
exists(PrefixExprCfgNode deref |
Expand Down Expand Up @@ -791,11 +670,8 @@ module RustDataFlow implements InputSig<Location> {
private predicate storeContentStep(Node node1, Content c, Node node2) {
exists(CallExprCfgNode call, int pos |
node1.asExpr() = call.getArgument(pragma[only_bind_into](pos)) and
node2.asExpr() = call
|
node2.asExpr() = call and
c = TTupleFieldContent(call.getCallExpr().getTupleField(pragma[only_bind_into](pos)))
or
VariantInLib::tupleVariantCanonicalConstruction(call.getCallExpr(), c, pos)
)
or
exists(StructExprCfgNode re, string field |
Expand Down
21 changes: 14 additions & 7 deletions rust/ql/lib/codeql/rust/dataflow/internal/FlowSummaryImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private import Content

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

class SummarizedCallableBase = string;

Expand Down Expand Up @@ -66,9 +67,20 @@ module Input implements InputSig<Location, RustDataFlow> {
exists(Content c | cs = TSingletonContentSet(c) |
result = "Field" and
(
exists(Addressable a, int pos |
exists(Addressable a, int pos, string prefix |
// TODO: calculate in QL
arg = a.getExtendedCanonicalPath() + "(" + pos + ")"
arg = prefix + "(" + pos + ")" and
(
prefix = a.getExtendedCanonicalPath()
or
a = any(OptionEnum o).getSome() and
prefix = "crate::option::Option::Some"
or
exists(string name |
a = any(ResultEnum r).getVariant(name) and
prefix = "crate::result::Result::" + name
)
)
|
c.(TupleFieldContent).isStructField(a, pos)
or
Expand All @@ -84,11 +96,6 @@ module Input implements InputSig<Location, RustDataFlow> {
c.(StructFieldContent).isVariantField(a, field)
)
or
c =
any(VariantInLibTupleFieldContent v |
arg = v.getExtendedCanonicalPath() + "(" + v.getPosition() + ")"
)
or
exists(int pos |
c = TTuplePositionContent(pos) and
arg = pos.toString()
Expand Down
9 changes: 9 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/EnumImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ private import codeql.rust.elements.internal.generated.Enum
* be referenced directly.
*/
module Impl {
private import rust

// the following QLdoc is generated: if you need to edit it, do it in the schema file
/**
* A Enum. For example:
Expand All @@ -20,5 +22,12 @@ module Impl {
*/
class Enum extends Generated::Enum {
override string toStringImpl() { result = "enum " + this.getName().getText() }

/** Gets the variant named `name`, if any. */
pragma[nomagic]
Variant getVariant(string name) {
result = this.getVariantList().getAVariant() and
result.getName().getText() = name
}
}
}
12 changes: 12 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/PathImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ module Impl {
*/
pragma[nomagic]
string getText() { result = this.getSegment().getIdentifier().getText() }

/**
* Gets the full text of this path, including the qualifier.
*
* Should only be used for debugging purposes.
*/
string toStringDebug() {
not this.hasQualifier() and
result = this.getText()
or
result = this.getQualifier().toStringDebug() + "::" + this.getText()
}
}

/** A simple identifier path. */
Expand Down
45 changes: 45 additions & 0 deletions rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,48 @@ private class StartswithCall extends Path::SafeAccessCheck::Range, CfgNodes::Met
branch = true
}
}

/**
* The [`Option` enum][1].
*
* [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"
)
}

/** Gets the `Some` variant. */
Variant getSome() { result = this.getVariant("Some") }
}

/**
* The [`Result` enum][1].
*
* [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"
)
}

/** Gets the `Ok` variant. */
Variant getOk() { result = this.getVariant("Ok") }

/** Gets the `Err` variant. */
Variant getErr() { result = this.getVariant("Err") }
}
35 changes: 33 additions & 2 deletions rust/ql/lib/codeql/rust/internal/CachedStages.qll
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,48 @@ module Stages {
}
}

/**
* The path resolution stage.
*/
cached
module PathResolutionStage {
private import codeql.rust.internal.PathResolution

/**
* Always holds.
* Ensures that a predicate is evaluated as part of the path resolution stage.
*/
cached
predicate ref() { 1 = 1 }

/**
* DO NOT USE!
*
* Contains references to each predicate that use the above `ref` predicate.
*/
cached
predicate backref() {
1 = 1
or
exists(resolvePath(_))
or
exists(any(ItemNode i).getASuccessor(_))
or
exists(any(ItemNode i).getASuccessorRec(_))
}
}

/**
* The type inference stage.
*/
cached
module TypeInference {
module TypeInferenceStage {
private import codeql.rust.internal.Type
private import codeql.rust.internal.TypeInference

/**
* Always holds.
* Ensures that a predicate is evaluated as part of the CFG stage.
* Ensures that a predicate is evaluated as part of the type inference stage.
*/
cached
predicate ref() { 1 = 1 }
Expand Down
Loading