Skip to content

Commit 966b0f4

Browse files
committed
Rust: Flow through enum constructors
1 parent 067b3ab commit 966b0f4

File tree

6 files changed

+283
-86
lines changed

6 files changed

+283
-86
lines changed

rust/ql/lib/codeql/rust/dataflow/DataFlow.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ module DataFlow {
1919

2020
final class PostUpdateNode = Node::PostUpdateNode;
2121

22+
final class Content = DataFlowImpl::Content;
23+
24+
final class ContentSet = DataFlowImpl::ContentSet;
25+
2226
/**
2327
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
2428
* (intra-procedural) step.

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

Lines changed: 202 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ module Node {
115115
*/
116116
ExprCfgNode asExpr() { none() }
117117

118+
/**
119+
* Gets the pattern that corresponds to this node, if any.
120+
*/
121+
PatCfgNode asPat() { none() }
122+
118123
/** Gets the enclosing callable. */
119124
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }
120125

@@ -177,8 +182,7 @@ module Node {
177182

178183
PatNode() { this = TPatNode(n) }
179184

180-
/** Gets the `PatCfgNode` in the CFG that this node corresponds to. */
181-
PatCfgNode getPat() { result = n }
185+
override PatCfgNode asPat() { result = n }
182186
}
183187

184188
abstract class ParameterNode extends AstCfgFlowNode { }
@@ -333,27 +337,167 @@ module LocalFlow {
333337
nodeFrom.(Node::AstCfgFlowNode).getCfgNode() =
334338
nodeTo.(Node::SsaNode).getDefinitionExt().(Ssa::WriteDefinition).getControlFlowNode()
335339
or
336-
nodeFrom.(Node::PositionalParameterNode).getParameter().getPat() =
337-
nodeTo.(Node::PatNode).getPat()
340+
nodeFrom.(Node::PositionalParameterNode).getParameter().getPat() = nodeTo.asPat()
338341
or
339342
SsaFlow::localFlowStep(_, nodeFrom, nodeTo, _)
340343
or
341344
exists(AssignmentExprCfgNode a |
342345
a.getRhs() = nodeFrom.getCfgNode() and
343346
a.getLhs() = nodeTo.getCfgNode()
344347
)
348+
or
349+
exists(MatchExprCfgNode match |
350+
nodeFrom.asExpr() = match.getExpr() and
351+
nodeTo.asPat() = match.getArmPat(_)
352+
)
353+
or
354+
nodeFrom.asPat().(OrPatCfgNode).getAPat() = nodeTo.asPat()
355+
}
356+
}
357+
358+
/**
359+
* A reference contained in an object. For example a field in a struct.
360+
*/
361+
abstract class Content extends TContent {
362+
/** Gets a textual representation of this content. */
363+
abstract string toString();
364+
}
365+
366+
/** A variant of an `enum`. */
367+
abstract private class VariantContent extends Content {
368+
string name;
369+
370+
bindingset[this, name]
371+
VariantContent() { exists(name) }
372+
}
373+
374+
/** A tuple variant of an `enum`. */
375+
private class VariantTupleContent extends VariantContent, TVariantTupleContent {
376+
private CrateOriginOption crate;
377+
private string path;
378+
private int i;
379+
380+
VariantTupleContent() { this = TVariantTupleContent(crate, path, name, i) }
381+
382+
final override string toString() {
383+
// only print indices when the arity is > 1
384+
if exists(TVariantTupleContent(crate, path, name, 1))
385+
then result = name + "(" + i + ")"
386+
else result = name
345387
}
346388
}
347389

348-
private class DataFlowCallableAlias = DataFlowCallable;
390+
/** A value that represents a set of `Content`s. */
391+
abstract class ContentSet extends TContentSet {
392+
/** Gets a textual representation of this element. */
393+
abstract string toString();
394+
395+
/** Gets a content that may be stored into when storing into this set. */
396+
abstract Content getAStoreContent();
397+
398+
/** Gets a content that may be read from when reading from this set. */
399+
abstract Content getAReadContent();
400+
}
401+
402+
private class SingletonContentSet extends ContentSet, TSingletonContentSet {
403+
private Content c;
404+
405+
SingletonContentSet() { this = TSingletonContentSet(c) }
406+
407+
Content getContent() { result = c }
408+
409+
override string toString() { result = c.toString() }
410+
411+
override Content getAStoreContent() { result = c }
412+
413+
override Content getAReadContent() { result = c }
414+
}
415+
416+
private import codeql.util.Option
417+
418+
private class CrateOrigin extends string {
419+
CrateOrigin() {
420+
this = [any(Item i).getCrateOrigin(), any(Resolvable r).getResolvedCrateOrigin()]
421+
}
422+
}
423+
424+
private class CrateOriginOption = Option<CrateOrigin>::Option;
425+
426+
pragma[nomagic]
427+
private predicate hasExtendedCanonicalPath(Item i, CrateOriginOption crate, string path) {
428+
path = i.getExtendedCanonicalPath() and
429+
(
430+
crate.asSome() = i.getCrateOrigin()
431+
or
432+
crate.isNone() and
433+
not i.hasCrateOrigin()
434+
)
435+
}
436+
437+
pragma[nomagic]
438+
private predicate resolvesExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
439+
path = r.getResolvedPath() and
440+
(
441+
crate.asSome() = r.getResolvedCrateOrigin()
442+
or
443+
crate.isNone() and
444+
not r.hasResolvedCrateOrigin()
445+
)
446+
}
447+
448+
pragma[nomagic]
449+
private predicate callResolvesExtendedCanonicalPath(
450+
CallExprBase call, CrateOriginOption crate, string path
451+
) {
452+
exists(Resolvable r | resolvesExtendedCanonicalPath(r, crate, path) |
453+
r = call.(MethodCallExpr)
454+
or
455+
r = call.(CallExpr).getExpr().(PathExpr).getPath()
456+
)
457+
}
458+
459+
/** Holds if qualified path `p` resolves to variant `c`. */
460+
private predicate pathResolvesToVariant(Path p, VariantContent c, int i) {
461+
exists(CrateOriginOption crate, string path |
462+
resolvesExtendedCanonicalPath(p.getQualifier(), crate, path) and
463+
c = TVariantTupleContent(crate, path, p.getPart().getNameRef().getText(), i)
464+
)
465+
or
466+
// TODO: Remove once library types are extracted
467+
not p.hasQualifier() and
468+
c = TVariantTupleContent(_, "crate::std::option::Option", p.getPart().getNameRef().getText(), i)
469+
}
470+
471+
/** Holds if `ce` constructs an enum value of type `c`. */
472+
pragma[nomagic]
473+
private predicate variantConstructor(CallExpr ce, VariantContent c, int i) {
474+
pathResolvesToVariant(ce.getExpr().(PathExpr).getPath(), c, i)
475+
}
476+
477+
/** Holds if `p` destructs an enum value of type `c`. */
478+
pragma[nomagic]
479+
private predicate variantDestructor(TupleStructPat p, VariantContent c, int i) {
480+
pathResolvesToVariant(p.getPath(), c, i)
481+
}
482+
483+
// Defines a set of aliases needed for the `RustDataFlow` module
484+
private module Aliases {
485+
class DataFlowCallableAlias = DataFlowCallable;
349486

350-
private class ReturnKindAlias = ReturnKind;
487+
class ReturnKindAlias = ReturnKind;
351488

352-
private class DataFlowCallAlias = DataFlowCall;
489+
class DataFlowCallAlias = DataFlowCall;
353490

354-
private class ParameterPositionAlias = ParameterPosition;
491+
class ParameterPositionAlias = ParameterPosition;
492+
493+
class ContentAlias = Content;
494+
495+
class ContentSetAlias = ContentSet;
496+
}
355497

356498
module RustDataFlow implements InputSig<Location> {
499+
private import Aliases
500+
357501
/**
358502
* An element, viewed as a node in a data flow graph. Either an expression
359503
* (`ExprNode`) or a parameter (`ParameterNode`).
@@ -399,55 +543,11 @@ module RustDataFlow implements InputSig<Location> {
399543

400544
final class ReturnKind = ReturnKindAlias;
401545

402-
private import codeql.util.Option
403-
404-
private class CrateOrigin extends string {
405-
CrateOrigin() {
406-
this = [any(Item i).getCrateOrigin(), any(Resolvable r).getResolvedCrateOrigin()]
407-
}
408-
}
409-
410-
private class CrateOriginOption = Option<CrateOrigin>::Option;
411-
412-
pragma[nomagic]
413-
private predicate hasExtendedCanonicalPath(
414-
DataFlowCallable c, CrateOriginOption crate, string path
415-
) {
416-
exists(Item i |
417-
i = c.asCfgScope() and
418-
path = i.getExtendedCanonicalPath()
419-
|
420-
crate.asSome() = i.getCrateOrigin()
421-
or
422-
crate.isNone() and
423-
not i.hasCrateOrigin()
424-
)
425-
}
426-
427-
pragma[nomagic]
428-
private predicate resolvesExtendedCanonicalPath(
429-
DataFlowCall c, CrateOriginOption crate, string path
430-
) {
431-
exists(Resolvable r |
432-
path = r.getResolvedPath() and
433-
(
434-
r = c.asMethodCallExprCfgNode().getExpr()
435-
or
436-
r = c.asCallExprCfgNode().getExpr().(PathExprCfgNode).getPath()
437-
)
438-
|
439-
crate.asSome() = r.getResolvedCrateOrigin()
440-
or
441-
crate.isNone() and
442-
not r.hasResolvedCrateOrigin()
443-
)
444-
}
445-
446546
/** Gets a viable implementation of the target of the given `Call`. */
447547
DataFlowCallable viableCallable(DataFlowCall call) {
448548
exists(string path, CrateOriginOption crate |
449-
hasExtendedCanonicalPath(result, crate, path) and
450-
resolvesExtendedCanonicalPath(call, crate, path)
549+
hasExtendedCanonicalPath(result.asCfgScope(), crate, path) and
550+
callResolvesExtendedCanonicalPath(call.asCallBaseExprCfgNode().getExpr(), crate, path)
451551
)
452552
}
453553

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

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

472-
final class Content = Void;
473-
474-
predicate forceHighPrecision(Content c) { none() }
475-
476-
class ContentSet extends TContentSet {
477-
/** Gets a textual representation of this element. */
478-
string toString() { result = "ContentSet" }
572+
class Content = ContentAlias;
479573

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

483-
/** Gets a content that may be read from when reading from this set. */
484-
Content getAReadContent() { none() }
485-
}
576+
predicate forceHighPrecision(Content c) { none() }
486577

487-
final class ContentApprox = Void;
578+
final class ContentApprox = Content; // todo
488579

489-
ContentApprox getContentApprox(Content c) { any() }
580+
ContentApprox getContentApprox(Content c) { result = c }
490581

491582
class ParameterPosition = ParameterPositionAlias;
492583

@@ -519,14 +610,34 @@ module RustDataFlow implements InputSig<Location> {
519610
* `node1` references an object with a content `c.getAReadContent()` whose
520611
* value ends up in `node2`.
521612
*/
522-
predicate readStep(Node node1, ContentSet c, Node node2) { none() }
613+
predicate readStep(Node node1, ContentSet cs, Node node2) {
614+
exists(Content c | c = cs.(SingletonContentSet).getContent() |
615+
node1.asPat() =
616+
any(TupleStructPatCfgNode pat, int i |
617+
variantDestructor(pat.getPat(), c, i) and
618+
node2.asPat() = pat.getField(i)
619+
|
620+
pat
621+
)
622+
)
623+
}
523624

524625
/**
525626
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
526627
* `node2` references an object with a content `c.getAStoreContent()` that
527628
* contains the value of `node1`.
528629
*/
529-
predicate storeStep(Node node1, ContentSet c, Node node2) { none() }
630+
predicate storeStep(Node node1, ContentSet cs, Node node2) {
631+
exists(Content c | c = cs.(SingletonContentSet).getContent() |
632+
node2.asExpr() =
633+
any(CallExprCfgNode call, int i |
634+
variantConstructor(call.getCallExpr(), c, i) and
635+
node1.asExpr() = call.getArgument(i)
636+
|
637+
call
638+
)
639+
)
640+
}
530641

531642
/**
532643
* Holds if values stored inside content `c` are cleared at node `n`. For example,
@@ -593,8 +704,6 @@ module RustDataFlow implements InputSig<Location> {
593704
class DataFlowSecondLevelScope = Void;
594705
}
595706

596-
final class ContentSet = RustDataFlow::ContentSet;
597-
598707
import MakeImpl<Location, RustDataFlow>
599708

600709
/** A collection of cached types and predicates to be evaluated in the same stage. */
@@ -612,14 +721,6 @@ private module Cached {
612721
cached
613722
newtype TDataFlowCall = TCall(CallExprBaseCfgNode c)
614723

615-
cached
616-
newtype TOptionalContentSet =
617-
TAnyElementContent() or
618-
TAnyContent()
619-
620-
cached
621-
class TContentSet = TAnyElementContent or TAnyContent;
622-
623724
cached
624725
newtype TDataFlowCallable = TCfgScope(CfgScope scope)
625726

@@ -635,6 +736,27 @@ private module Cached {
635736
i in [0 .. max([any(ParamList l).getNumberOfParams(), any(ArgList l).getNumberOfArgs()]) - 1]
636737
} or
637738
TSelfParameterPosition()
739+
740+
cached
741+
newtype TContent =
742+
TVariantTupleContent(CrateOriginOption crate, string path, string name, int i) {
743+
exists(Enum e, Variant v |
744+
hasExtendedCanonicalPath(e, crate, path) and
745+
v = e.getVariantList().getAVariant() and
746+
name = v.getName().getText() and
747+
i in [0 .. v.getFieldList().(TupleFieldList).getNumberOfFields() - 1]
748+
)
749+
or
750+
// TODO: Remove once library types are extracted
751+
crate.isNone() and
752+
path = "crate::std::option::Option" and
753+
name = "Some" and
754+
i = 0
755+
}
756+
757+
// todo: add TVariantRecordContent
758+
cached
759+
newtype TContentSet = TSingletonContentSet(Content c)
638760
}
639761

640762
import Cached

0 commit comments

Comments
 (0)