Skip to content

Commit 4fa83f5

Browse files
committed
Rust: Flow through enum constructors
1 parent aeb7b46 commit 4fa83f5

File tree

5 files changed

+204
-78
lines changed

5 files changed

+204
-78
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: 160 additions & 76 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,18 +337,126 @@ 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+
)
345353
}
346354
}
347355

356+
abstract class Content extends TContent {
357+
abstract string toString();
358+
}
359+
360+
private class VariantContent extends Content, TVariantContent {
361+
private CrateOriginOption crate;
362+
private string path;
363+
private string name;
364+
365+
VariantContent() { this = TVariantContent(crate, path, name) }
366+
367+
override string toString() { result = name }
368+
}
369+
370+
abstract class ContentSet extends TContentSet {
371+
/** Gets a textual representation of this element. */
372+
abstract string toString();
373+
374+
/** Gets a content that may be stored into when storing into this set. */
375+
abstract Content getAStoreContent();
376+
377+
/** Gets a content that may be read from when reading from this set. */
378+
abstract Content getAReadContent();
379+
}
380+
381+
private class SingletonContentSet extends ContentSet, TSingletonContentSet {
382+
private Content c;
383+
384+
SingletonContentSet() { this = TSingletonContentSet(c) }
385+
386+
Content getContent() { result = c }
387+
388+
override string toString() { result = c.toString() }
389+
390+
override Content getAStoreContent() { result = c }
391+
392+
override Content getAReadContent() { result = c }
393+
}
394+
395+
private import codeql.util.Option
396+
397+
private class CrateOrigin extends string {
398+
CrateOrigin() {
399+
this = [any(Item i).getCrateOrigin(), any(Resolvable r).getResolvedCrateOrigin()]
400+
}
401+
}
402+
403+
private class CrateOriginOption = Option<CrateOrigin>::Option;
404+
405+
pragma[nomagic]
406+
private predicate hasExtendedCanonicalPath(Item i, CrateOriginOption crate, string path) {
407+
path = i.getExtendedCanonicalPath() and
408+
(
409+
crate.asSome() = i.getCrateOrigin()
410+
or
411+
crate.isNone() and
412+
not i.hasCrateOrigin()
413+
)
414+
}
415+
416+
pragma[nomagic]
417+
private predicate resolvesExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
418+
path = r.getResolvedPath() and
419+
(
420+
crate.asSome() = r.getResolvedCrateOrigin()
421+
or
422+
crate.isNone() and
423+
not r.hasResolvedCrateOrigin()
424+
)
425+
}
426+
427+
pragma[nomagic]
428+
private predicate callResolvesExtendedCanonicalPath(
429+
CallExprBase call, CrateOriginOption crate, string path
430+
) {
431+
exists(Resolvable r | resolvesExtendedCanonicalPath(r, crate, path) |
432+
r = call.(MethodCallExpr)
433+
or
434+
r = call.(CallExpr).getExpr().(PathExpr).getPath()
435+
)
436+
}
437+
438+
/** Holds if qualified path `p` resolves to variant `c`. */
439+
private predicate pathResolvesToVariant(Path p, VariantContent c) {
440+
exists(CrateOriginOption crate, string path |
441+
resolvesExtendedCanonicalPath(p.getQualifier(), crate, path) and
442+
c = TVariantContent(crate, path, p.getPart().getNameRef().getText())
443+
)
444+
or
445+
// TODO: Remove once library types are extracted
446+
not p.hasQualifier() and
447+
c = TVariantContent(_, "crate::std::option::Option", p.getPart().getNameRef().getText())
448+
}
449+
450+
/** Holds if `ce` constructs an enum value of type `c`. */
451+
private predicate variantConstructor(CallExpr ce, VariantContent c) {
452+
pathResolvesToVariant(ce.getExpr().(PathExpr).getPath(), c)
453+
}
454+
455+
/** Holds if `p` destructs an enum value of type `c`. */
456+
private predicate variantDestructor(TupleStructPat p, VariantContent c) {
457+
pathResolvesToVariant(p.getPath(), c)
458+
}
459+
348460
private class DataFlowCallableAlias = DataFlowCallable;
349461

350462
private class ReturnKindAlias = ReturnKind;
@@ -353,6 +465,10 @@ private class DataFlowCallAlias = DataFlowCall;
353465

354466
private class ParameterPositionAlias = ParameterPosition;
355467

468+
private class ContentAlias = Content;
469+
470+
private class ContentSetAlias = ContentSet;
471+
356472
module RustDataFlow implements InputSig<Location> {
357473
/**
358474
* An element, viewed as a node in a data flow graph. Either an expression
@@ -399,55 +515,11 @@ module RustDataFlow implements InputSig<Location> {
399515

400516
final class ReturnKind = ReturnKindAlias;
401517

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-
446518
/** Gets a viable implementation of the target of the given `Call`. */
447519
DataFlowCallable viableCallable(DataFlowCall call) {
448520
exists(string path, CrateOriginOption crate |
449-
hasExtendedCanonicalPath(result, crate, path) and
450-
resolvesExtendedCanonicalPath(call, crate, path)
521+
hasExtendedCanonicalPath(result.asCfgScope(), crate, path) and
522+
callResolvesExtendedCanonicalPath(call.asCallBaseExprCfgNode().getExpr(), crate, path)
451523
)
452524
}
453525

@@ -469,24 +541,15 @@ module RustDataFlow implements InputSig<Location> {
469541

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

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" }
544+
class Content = ContentAlias;
479545

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

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

487-
final class ContentApprox = Void;
550+
final class ContentApprox = Content; // todo
488551

489-
ContentApprox getContentApprox(Content c) { any() }
552+
ContentApprox getContentApprox(Content c) { result = c }
490553

491554
class ParameterPosition = ParameterPositionAlias;
492555

@@ -519,14 +582,27 @@ module RustDataFlow implements InputSig<Location> {
519582
* `node1` references an object with a content `c.getAReadContent()` whose
520583
* value ends up in `node2`.
521584
*/
522-
predicate readStep(Node node1, ContentSet c, Node node2) { none() }
585+
predicate readStep(Node node1, ContentSet c, Node node2) {
586+
node1.asPat() =
587+
any(TupleStructPatCfgNode pat |
588+
variantDestructor(pat.getPat(), c.(SingletonContentSet).getContent()) and
589+
node2.asPat() = pat.getField(0)
590+
)
591+
}
523592

524593
/**
525594
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
526595
* `node2` references an object with a content `c.getAStoreContent()` that
527596
* contains the value of `node1`.
528597
*/
529-
predicate storeStep(Node node1, ContentSet c, Node node2) { none() }
598+
predicate storeStep(Node node1, ContentSet c, Node node2) {
599+
// todo: use post-update
600+
node2.asExpr() =
601+
any(CallExprCfgNode call |
602+
variantConstructor(call.getCallExpr(), c.(SingletonContentSet).getContent()) and
603+
node1.asExpr() = call.getArgument(0)
604+
)
605+
}
530606

531607
/**
532608
* Holds if values stored inside content `c` are cleared at node `n`. For example,
@@ -593,8 +669,6 @@ module RustDataFlow implements InputSig<Location> {
593669
class DataFlowSecondLevelScope = Void;
594670
}
595671

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

600674
/** A collection of cached types and predicates to be evaluated in the same stage. */
@@ -612,14 +686,6 @@ private module Cached {
612686
cached
613687
newtype TDataFlowCall = TCall(CallExprBaseCfgNode c)
614688

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

@@ -635,6 +701,24 @@ private module Cached {
635701
i in [0 .. max([any(ParamList l).getNumberOfParams(), any(ArgList l).getNumberOfArgs()]) - 1]
636702
} or
637703
TSelfParameterPosition()
704+
705+
cached
706+
newtype TContent =
707+
TVariantContent(CrateOriginOption crate, string path, string name) {
708+
exists(Enum e |
709+
hasExtendedCanonicalPath(e, crate, path) and
710+
// s = e.getExtendedCanonicalPath() and
711+
name = e.getVariantList().getAVariant().getName().getText()
712+
)
713+
or
714+
// TODO: Remove once library types are extracted
715+
crate.isNone() and
716+
path = "crate::std::option::Option" and
717+
name = ["None", "Some"]
718+
}
719+
720+
cached
721+
newtype TContentSet = TSingletonContentSet(Content c)
638722
}
639723

640724
import Cached

0 commit comments

Comments
 (0)