Skip to content

Commit

Permalink
Rust: Flow through enum constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
hvitved committed Nov 25, 2024
1 parent 067b3ab commit 966b0f4
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 86 deletions.
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
282 changes: 202 additions & 80 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 @@ module Node {
*/
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 @@ module Node {

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,27 +337,167 @@ module LocalFlow {
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(_)
)
or
nodeFrom.asPat().(OrPatCfgNode).getAPat() = nodeTo.asPat()
}
}

/**
* A reference contained in an object. For example a field in a struct.
*/
abstract class Content extends TContent {
/** Gets a textual representation of this content. */
abstract string toString();
}

/** A variant of an `enum`. */
abstract private class VariantContent extends Content {
string name;

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

/** A tuple variant of an `enum`. */
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() {
// only print indices when the arity is > 1
if exists(TVariantTupleContent(crate, path, name, 1))
then result = name + "(" + i + ")"
else result = name
}
}

private class DataFlowCallableAlias = DataFlowCallable;
/** A value that represents a set of `Content`s. */
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`. */
pragma[nomagic]
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`. */
pragma[nomagic]
private predicate variantDestructor(TupleStructPat p, VariantContent c, int i) {
pathResolvesToVariant(p.getPath(), c, i)
}

// Defines a set of aliases needed for the `RustDataFlow` module
private module Aliases {
class DataFlowCallableAlias = DataFlowCallable;

private class ReturnKindAlias = ReturnKind;
class ReturnKindAlias = ReturnKind;

private class DataFlowCallAlias = DataFlowCall;
class DataFlowCallAlias = DataFlowCall;

private class ParameterPositionAlias = ParameterPosition;
class ParameterPositionAlias = ParameterPosition;

class ContentAlias = Content;

class ContentSetAlias = ContentSet;
}

module RustDataFlow implements InputSig<Location> {
private import Aliases

/**
* An element, viewed as a node in a data flow graph. Either an expression
* (`ExprNode`) or a parameter (`ParameterNode`).
Expand Down Expand Up @@ -399,55 +543,11 @@ module RustDataFlow implements InputSig<Location> {

final class ReturnKind = ReturnKindAlias;

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(
DataFlowCallable c, CrateOriginOption crate, string path
) {
exists(Item i |
i = c.asCfgScope() and
path = i.getExtendedCanonicalPath()
|
crate.asSome() = i.getCrateOrigin()
or
crate.isNone() and
not i.hasCrateOrigin()
)
}

pragma[nomagic]
private predicate resolvesExtendedCanonicalPath(
DataFlowCall c, CrateOriginOption crate, string path
) {
exists(Resolvable r |
path = r.getResolvedPath() and
(
r = c.asMethodCallExprCfgNode().getExpr()
or
r = c.asCallExprCfgNode().getExpr().(PathExprCfgNode).getPath()
)
|
crate.asSome() = r.getResolvedCrateOrigin()
or
crate.isNone() and
not r.hasResolvedCrateOrigin()
)
}

/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall call) {
exists(string path, CrateOriginOption crate |
hasExtendedCanonicalPath(result, crate, path) and
resolvesExtendedCanonicalPath(call, crate, path)
hasExtendedCanonicalPath(result.asCfgScope(), crate, path) and
callResolvesExtendedCanonicalPath(call.asCallBaseExprCfgNode().getExpr(), crate, path)
)
}

Expand All @@ -469,24 +569,15 @@ module RustDataFlow implements InputSig<Location> {

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

final class Content = Void;

predicate forceHighPrecision(Content c) { none() }

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

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

/** 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 @@ -519,14 +610,34 @@ module RustDataFlow implements InputSig<Location> {
* `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 cs, Node node2) {
exists(Content c | c = cs.(SingletonContentSet).getContent() |
node1.asPat() =
any(TupleStructPatCfgNode pat, int i |
variantDestructor(pat.getPat(), c, 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 cs, Node node2) {
exists(Content c | c = cs.(SingletonContentSet).getContent() |
node2.asExpr() =
any(CallExprCfgNode call, int i |
variantConstructor(call.getCallExpr(), c, 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 @@ -593,8 +704,6 @@ module RustDataFlow implements InputSig<Location> {
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 @@ -612,14 +721,6 @@ private module Cached {
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 @@ -635,6 +736,27 @@ private module Cached {
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
Loading

0 comments on commit 966b0f4

Please sign in to comment.