Skip to content
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

Rust: Flow through enum constructors #18078

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions rust/ql/lib/codeql/rust/dataflow/DataFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ module DataFlow {

final class PostUpdateNode = Node::PostUpdateNode;

final class Content = DataFlowImpl::Content;

final class ContentSet = DataFlowImpl::ContentSet;

/**
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
Expand Down
220 changes: 183 additions & 37 deletions rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
*/
ExprCfgNode asExpr() { none() }

/**
* Gets the pattern that corresponds to this node, if any.
*/
PatCfgNode asPat() { none() }

/** Gets the enclosing callable. */
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }

Expand Down Expand Up @@ -177,8 +182,7 @@

PatNode() { this = TPatNode(n) }

/** Gets the `PatCfgNode` in the CFG that this node corresponds to. */
PatCfgNode getPat() { result = n }
override PatCfgNode asPat() { result = n }
}

abstract class ParameterNode extends AstCfgFlowNode { }
Expand Down Expand Up @@ -333,18 +337,137 @@
nodeFrom.(Node::AstCfgFlowNode).getCfgNode() =
nodeTo.(Node::SsaNode).getDefinitionExt().(Ssa::WriteDefinition).getControlFlowNode()
or
nodeFrom.(Node::PositionalParameterNode).getParameter().getPat() =
nodeTo.(Node::PatNode).getPat()
nodeFrom.(Node::PositionalParameterNode).getParameter().getPat() = nodeTo.asPat()
or
SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _)
or
exists(AssignmentExprCfgNode a |
a.getRhs() = nodeFrom.getCfgNode() and
a.getLhs() = nodeTo.getCfgNode()
)
or
exists(MatchExprCfgNode match |
nodeFrom.asExpr() = match.getExpr() and
nodeTo.asPat() = match.getArmPat(_)
)
}
}

abstract class Content extends TContent {
abstract string toString();

Check warning on line 357 in rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll

View workflow job for this annotation

GitHub Actions / qldoc

Missing QLdoc for member-predicate DataFlowImpl::Content::toString/0
}

abstract private class VariantContent extends Content {
string name;

bindingset[this, name]
VariantContent() { exists(name) }
}

private class VariantTupleContent extends VariantContent, TVariantTupleContent {
private CrateOriginOption crate;
private string path;
private int i;

VariantTupleContent() { this = TVariantTupleContent(crate, path, name, i) }

final override string toString() {
if exists(TVariantTupleContent(crate, path, name, 1))
then result = name + "(" + i + ")"
else result = name
}
}

abstract class ContentSet extends TContentSet {
/** Gets a textual representation of this element. */
abstract string toString();

/** Gets a content that may be stored into when storing into this set. */
abstract Content getAStoreContent();

/** Gets a content that may be read from when reading from this set. */
abstract Content getAReadContent();
}

private class SingletonContentSet extends ContentSet, TSingletonContentSet {
private Content c;

SingletonContentSet() { this = TSingletonContentSet(c) }

Content getContent() { result = c }

override string toString() { result = c.toString() }

override Content getAStoreContent() { result = c }

override Content getAReadContent() { result = c }
}

private import codeql.util.Option

private class CrateOrigin extends string {
CrateOrigin() {
this = [any(Item i).getCrateOrigin(), any(Resolvable r).getResolvedCrateOrigin()]
}
}

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

pragma[nomagic]
private predicate hasExtendedCanonicalPath(Item i, CrateOriginOption crate, string path) {
path = i.getExtendedCanonicalPath() and
(
crate.asSome() = i.getCrateOrigin()
or
crate.isNone() and
not i.hasCrateOrigin()
)
}

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

pragma[nomagic]
private predicate callResolvesExtendedCanonicalPath(
CallExprBase call, CrateOriginOption crate, string path
) {
exists(Resolvable r | resolvesExtendedCanonicalPath(r, crate, path) |
r = call.(MethodCallExpr)
or
r = call.(CallExpr).getExpr().(PathExpr).getPath()
)
}

/** Holds if qualified path `p` resolves to variant `c`. */
private predicate pathResolvesToVariant(Path p, VariantContent c, int i) {
exists(CrateOriginOption crate, string path |
resolvesExtendedCanonicalPath(p.getQualifier(), crate, path) and
c = TVariantTupleContent(crate, path, p.getPart().getNameRef().getText(), i)
)
or
// TODO: Remove once library types are extracted
not p.hasQualifier() and
c = TVariantTupleContent(_, "crate::std::option::Option", p.getPart().getNameRef().getText(), i)
}

/** Holds if `ce` constructs an enum value of type `c`. */
private predicate variantConstructor(CallExpr ce, VariantContent c, int i) {
pathResolvesToVariant(ce.getExpr().(PathExpr).getPath(), c, i)
}

/** Holds if `p` destructs an enum value of type `c`. */
private predicate variantDestructor(TupleStructPat p, VariantContent c, int i) {
pathResolvesToVariant(p.getPath(), c, i)
}

private class DataFlowCallableAlias = DataFlowCallable;

private class ReturnKindAlias = ReturnKind;
Expand All @@ -353,6 +476,10 @@

private class ParameterPositionAlias = ParameterPosition;

private class ContentAlias = Content;

private class ContentSetAlias = ContentSet;

module RustDataFlow implements InputSig<Location> {
/**
* An element, viewed as a node in a data flow graph. Either an expression
Expand Down Expand Up @@ -400,12 +527,10 @@
final class ReturnKind = ReturnKindAlias;

/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall c) {
exists(Function f, string name | result.asCfgScope() = f and name = f.getName().toString() |
if f.getParamList().hasSelfParam()
then name = c.asMethodCallExprCfgNode().getNameRef().getText()
else
name = c.asCallExprCfgNode().getExpr().getExpr().(PathExpr).getPath().getPart().toString()
DataFlowCallable viableCallable(DataFlowCall call) {
exists(string path, CrateOriginOption crate |
hasExtendedCanonicalPath(result.asCfgScope(), crate, path) and
callResolvesExtendedCanonicalPath(call.asCallBaseExprCfgNode().getExpr(), crate, path)
)
}

Expand All @@ -419,30 +544,23 @@

// NOTE: For now we use the type `Unit` and do not benefit from type
// information in the data flow analysis.
final class DataFlowType = Unit;
final class DataFlowType extends Unit {
string toString() { result = "" }
}

predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }

predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }

final class Content = Void;
class Content = ContentAlias;

predicate forceHighPrecision(Content c) { none() }
class ContentSet = ContentSetAlias;

class ContentSet extends TContentSet {
/** Gets a textual representation of this element. */
string toString() { result = "ContentSet" }

/** Gets a content that may be stored into when storing into this set. */
Content getAStoreContent() { none() }

/** Gets a content that may be read from when reading from this set. */
Content getAReadContent() { none() }
}
predicate forceHighPrecision(Content c) { none() }

final class ContentApprox = Void;
final class ContentApprox = Content; // todo

ContentApprox getContentApprox(Content c) { any() }
ContentApprox getContentApprox(Content c) { result = c }

class ParameterPosition = ParameterPositionAlias;

Expand Down Expand Up @@ -475,14 +593,31 @@
* `node1` references an object with a content `c.getAReadContent()` whose
* value ends up in `node2`.
*/
predicate readStep(Node node1, ContentSet c, Node node2) { none() }
predicate readStep(Node node1, ContentSet c, Node node2) {
node1.asPat() =
any(TupleStructPatCfgNode pat, int i |
variantDestructor(pat.getPat(), c.(SingletonContentSet).getContent(), i) and
node2.asPat() = pat.getField(i)
|
pat
)
}

/**
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
* `node2` references an object with a content `c.getAStoreContent()` that
* contains the value of `node1`.
*/
predicate storeStep(Node node1, ContentSet c, Node node2) { none() }
predicate storeStep(Node node1, ContentSet c, Node node2) {
// todo: use post-update
node2.asExpr() =
any(CallExprCfgNode call, int i |
variantConstructor(call.getCallExpr(), c.(SingletonContentSet).getContent(), i) and
node1.asExpr() = call.getArgument(i)
|
call
)
}

/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
Expand Down Expand Up @@ -549,8 +684,6 @@
class DataFlowSecondLevelScope = Void;
}

final class ContentSet = RustDataFlow::ContentSet;

import MakeImpl<Location, RustDataFlow>

/** A collection of cached types and predicates to be evaluated in the same stage. */
Expand All @@ -568,14 +701,6 @@
cached
newtype TDataFlowCall = TCall(CallExprBaseCfgNode c)

cached
newtype TOptionalContentSet =
TAnyElementContent() or
TAnyContent()

cached
class TContentSet = TAnyElementContent or TAnyContent;

cached
newtype TDataFlowCallable = TCfgScope(CfgScope scope)

Expand All @@ -591,6 +716,27 @@
i in [0 .. max([any(ParamList l).getNumberOfParams(), any(ArgList l).getNumberOfArgs()]) - 1]
} or
TSelfParameterPosition()

cached
newtype TContent =
TVariantTupleContent(CrateOriginOption crate, string path, string name, int i) {
exists(Enum e, Variant v |
hasExtendedCanonicalPath(e, crate, path) and
v = e.getVariantList().getAVariant() and
name = v.getName().getText() and
i in [0 .. v.getFieldList().(TupleFieldList).getNumberOfFields() - 1]
)
or
// TODO: Remove once library types are extracted
crate.isNone() and
path = "crate::std::option::Option" and
name = "Some" and
i = 0
}

// todo: add TVariantRecordContent
cached
newtype TContentSet = TSingletonContentSet(Content c)
}

import Cached
36 changes: 18 additions & 18 deletions rust/ql/test/library-tests/dataflow/barrier/inline-flow.expected
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
models
edges
| main.rs:9:13:9:19 | Param : unit | main.rs:9:30:14:1 | BlockExpr : unit | provenance | |
| main.rs:21:13:21:21 | CallExpr : unit | main.rs:22:10:22:10 | s | provenance | |
| main.rs:26:13:26:21 | CallExpr : unit | main.rs:27:22:27:22 | s : unit | provenance | |
| main.rs:27:13:27:23 | CallExpr : unit | main.rs:28:10:28:10 | s | provenance | |
| main.rs:27:22:27:22 | s : unit | main.rs:9:13:9:19 | Param : unit | provenance | |
| main.rs:27:22:27:22 | s : unit | main.rs:27:13:27:23 | CallExpr : unit | provenance | |
| main.rs:32:13:32:21 | CallExpr : unit | main.rs:33:10:33:10 | s | provenance | |
| main.rs:9:13:9:19 | Param | main.rs:9:30:14:1 | BlockExpr | provenance | |
| main.rs:21:13:21:21 | CallExpr | main.rs:22:10:22:10 | s | provenance | |
| main.rs:26:13:26:21 | CallExpr | main.rs:27:22:27:22 | s | provenance | |
| main.rs:27:13:27:23 | CallExpr | main.rs:28:10:28:10 | s | provenance | |
| main.rs:27:22:27:22 | s | main.rs:9:13:9:19 | Param | provenance | |
| main.rs:27:22:27:22 | s | main.rs:27:13:27:23 | CallExpr | provenance | |
| main.rs:32:13:32:21 | CallExpr | main.rs:33:10:33:10 | s | provenance | |
nodes
| main.rs:9:13:9:19 | Param : unit | semmle.label | Param : unit |
| main.rs:9:30:14:1 | BlockExpr : unit | semmle.label | BlockExpr : unit |
| main.rs:9:13:9:19 | Param | semmle.label | Param |
| main.rs:9:30:14:1 | BlockExpr | semmle.label | BlockExpr |
| main.rs:17:10:17:18 | CallExpr | semmle.label | CallExpr |
| main.rs:21:13:21:21 | CallExpr : unit | semmle.label | CallExpr : unit |
| main.rs:21:13:21:21 | CallExpr | semmle.label | CallExpr |
| main.rs:22:10:22:10 | s | semmle.label | s |
| main.rs:26:13:26:21 | CallExpr : unit | semmle.label | CallExpr : unit |
| main.rs:27:13:27:23 | CallExpr : unit | semmle.label | CallExpr : unit |
| main.rs:27:22:27:22 | s : unit | semmle.label | s : unit |
| main.rs:26:13:26:21 | CallExpr | semmle.label | CallExpr |
| main.rs:27:13:27:23 | CallExpr | semmle.label | CallExpr |
| main.rs:27:22:27:22 | s | semmle.label | s |
| main.rs:28:10:28:10 | s | semmle.label | s |
| main.rs:32:13:32:21 | CallExpr : unit | semmle.label | CallExpr : unit |
| main.rs:32:13:32:21 | CallExpr | semmle.label | CallExpr |
| main.rs:33:10:33:10 | s | semmle.label | s |
subpaths
| main.rs:27:22:27:22 | s : unit | main.rs:9:13:9:19 | Param : unit | main.rs:9:30:14:1 | BlockExpr : unit | main.rs:27:13:27:23 | CallExpr : unit |
| main.rs:27:22:27:22 | s | main.rs:9:13:9:19 | Param | main.rs:9:30:14:1 | BlockExpr | main.rs:27:13:27:23 | CallExpr |
testFailures
#select
| main.rs:17:10:17:18 | CallExpr | main.rs:17:10:17:18 | CallExpr | main.rs:17:10:17:18 | CallExpr | $@ | main.rs:17:10:17:18 | CallExpr | CallExpr |
| main.rs:22:10:22:10 | s | main.rs:21:13:21:21 | CallExpr : unit | main.rs:22:10:22:10 | s | $@ | main.rs:21:13:21:21 | CallExpr : unit | CallExpr : unit |
| main.rs:28:10:28:10 | s | main.rs:26:13:26:21 | CallExpr : unit | main.rs:28:10:28:10 | s | $@ | main.rs:26:13:26:21 | CallExpr : unit | CallExpr : unit |
| main.rs:33:10:33:10 | s | main.rs:32:13:32:21 | CallExpr : unit | main.rs:33:10:33:10 | s | $@ | main.rs:32:13:32:21 | CallExpr : unit | CallExpr : unit |
| main.rs:22:10:22:10 | s | main.rs:21:13:21:21 | CallExpr | main.rs:22:10:22:10 | s | $@ | main.rs:21:13:21:21 | CallExpr | CallExpr |
| main.rs:28:10:28:10 | s | main.rs:26:13:26:21 | CallExpr | main.rs:28:10:28:10 | s | $@ | main.rs:26:13:26:21 | CallExpr | CallExpr |
| main.rs:33:10:33:10 | s | main.rs:32:13:32:21 | CallExpr | main.rs:33:10:33:10 | s | $@ | main.rs:32:13:32:21 | CallExpr | CallExpr |
Loading
Loading