Shared: Split up TypeTracking.qll into two files

This commit is contained in:
Tom Hvitved
2023-11-07 14:28:38 +01:00
parent 803ed20962
commit f66f7ce8d7
2 changed files with 851 additions and 813 deletions

View File

@@ -3,9 +3,6 @@
* for tracking types.
*/
private import codeql.util.Boolean
private import codeql.util.Option
/**
* The step relations for type tracking.
*/
@@ -118,832 +115,32 @@ signature module TypeTrackingInput {
predicate hasFeatureBacktrackStoreTarget();
}
private import internal.TypeTrackingImpl as Impl
/**
* Given a set of step relations, this module provides classes and predicates
* for simple data-flow reachability suitable for tracking types.
*/
module TypeTracking<TypeTrackingInput I> {
private import I
private module MkImpl = Impl::TypeTracking<I>;
/** Provides consistency checks for the type-tracker step relations. */
module ConsistencyChecks {
private predicate stepEntry(Node n, string kind) {
simpleLocalSmallStep(n, _) and kind = "simpleLocalSmallStep"
or
exists(StepSummary ss | smallStep(n, _, ss) and kind = ss.toString())
or
hasFeatureBacktrackStoreTarget() and
kind = "storeTarget" and
(storeStep(_, n, _) or loadStoreStep(_, n, _, _))
}
deprecated module ConsistencyChecks = MkImpl::ConsistencyChecks;
/**
* Holds if there is any node in a step relation that is unreachable from a
* `LocalSourceNode`.
*/
query predicate unreachableNode(string msg) {
exists(int k, string kind |
k = strictcount(Node n | stepEntry(n, kind) and not flowsTo(_, n)) and
msg = "There are " + k + " unreachable nodes in steps of kind " + kind + "."
)
}
class TypeTracker = MkImpl::TypeTracker;
/**
* Holds if there is a store target that isn't a `LocalSourceNode` and
* backtracking store target feature isn't enabled.
*/
query predicate nonSourceStoreTarget(string msg) {
not hasFeatureBacktrackStoreTarget() and
exists(int k |
k =
strictcount(Node n |
not n instanceof LocalSourceNode and
(storeStep(_, n, _) or loadStoreStep(_, n, _, _))
) and
msg =
"There are " + k +
" store targets that are not local source nodes and backtracking store targets is not enabled."
)
}
}
module TypeTracker = MkImpl::TypeTracker;
private module ContentOption = Option<Content>;
class TypeBackTracker = MkImpl::TypeBackTracker;
private class ContentOption = ContentOption::Option;
module TypeBackTracker = MkImpl::TypeBackTracker;
private newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(Content content) { storeStep(_, _, content) } or
LoadStep(Content content) { loadStep(_, _, content) } or
LoadStoreStep(Content load, Content store) { loadStoreStep(_, _, load, store) } or
WithContent(ContentFilter filter) { withContentStep(_, _, filter) } or
WithoutContent(ContentFilter filter) { withoutContentStep(_, _, filter) } or
JumpStep()
/**
* A description of a step on an inter-procedural data flow path.
*/
private class StepSummary extends TStepSummary {
/** Gets a textual representation of this step summary. */
string toString() {
this instanceof LevelStep and result = "level"
or
this instanceof CallStep and result = "call"
or
this instanceof ReturnStep and result = "return"
or
exists(Content content | this = StoreStep(content) | result = "store " + content)
or
exists(Content content | this = LoadStep(content) | result = "load " + content)
or
exists(Content load, Content store |
this = LoadStoreStep(load, store) and
result = "load-store " + load + " -> " + store
)
or
this instanceof JumpStep and result = "jump"
}
}
private newtype TTypeTracker =
MkTypeTracker(Boolean hasCall, ContentOption content) {
content.isNone()
or
// Restrict `content` to those that might eventually match a load.
// We can't rely on `basicStoreStep` since `startInContent` might be used with
// a content that has no corresponding store.
exists(Content loadContents |
(
loadStep(_, _, loadContents)
or
loadStoreStep(_, _, loadContents, _)
) and
compatibleContents(content.asSome(), loadContents)
)
}
private newtype TTypeBackTracker =
MkTypeBackTracker(Boolean hasReturn, ContentOption content) {
content.isNone()
or
// As in MkTypeTracker, restrict `content` to those that might eventually match a store.
exists(Content storeContent |
(
storeStep(_, _, storeContent)
or
loadStoreStep(_, _, _, storeContent)
) and
compatibleContents(storeContent, content.asSome())
)
}
pragma[nomagic]
private TypeTracker noContentTypeTracker(boolean hasCall) {
result = MkTypeTracker(hasCall, any(ContentOption::None c))
}
pragma[nomagic]
private TypeTracker contentTypeTracker(boolean hasCall, Content c) {
result = MkTypeTracker(hasCall, ContentOption::some(c))
}
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
pragma[nomagic]
private TypeTracker append(TypeTracker tt, StepSummary step) {
exists(Boolean hasCall, ContentOption currentContents |
tt = MkTypeTracker(hasCall, currentContents)
|
step = LevelStep() and result = tt
or
step = CallStep() and result = MkTypeTracker(true, currentContents)
or
step = ReturnStep() and hasCall = false and result = tt
or
step = JumpStep() and
result = MkTypeTracker(false, currentContents)
or
exists(ContentFilter filter | result = tt |
step = WithContent(filter) and
currentContents.asSome() = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not currentContents.asSome() = filter.getAMatchingContent()
)
)
or
exists(Content storeContents, boolean hasCall |
exists(Content loadContents |
step = LoadStep(pragma[only_bind_into](loadContents)) and
tt = contentTypeTracker(hasCall, storeContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeTracker(hasCall)
)
or
step = StoreStep(pragma[only_bind_into](storeContents)) and
tt = noContentTypeTracker(hasCall) and
result = contentTypeTracker(hasCall, storeContents)
)
or
exists(Content currentContent, Content store, Content load, boolean hasCall |
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(pragma[only_bind_into](currentContent), load) and
tt = contentTypeTracker(pragma[only_bind_into](hasCall), currentContent) and
result = contentTypeTracker(pragma[only_bind_out](hasCall), store)
)
}
pragma[nomagic]
private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) {
result = MkTypeBackTracker(hasReturn, any(ContentOption::None c))
}
pragma[nomagic]
private TypeBackTracker contentTypeBackTracker(boolean hasReturn, Content c) {
result = MkTypeBackTracker(hasReturn, ContentOption::some(c))
}
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
pragma[nomagic]
private TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
exists(Boolean hasReturn, ContentOption content | tbt = MkTypeBackTracker(hasReturn, content) |
step = LevelStep() and result = tbt
or
step = CallStep() and hasReturn = false and result = tbt
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
or
exists(ContentFilter filter | result = tbt |
step = WithContent(filter) and
content.asSome() = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not content.asSome() = filter.getAMatchingContent()
)
)
or
exists(Content loadContents, boolean hasReturn |
exists(Content storeContents |
step = StoreStep(pragma[only_bind_into](storeContents)) and
tbt = contentTypeBackTracker(hasReturn, loadContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeBackTracker(hasReturn)
)
or
step = LoadStep(pragma[only_bind_into](loadContents)) and
tbt = noContentTypeBackTracker(hasReturn) and
result = contentTypeBackTracker(hasReturn, loadContents)
)
or
exists(Content currentContent, Content store, Content load, boolean hasCall |
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(store, pragma[only_bind_into](currentContent)) and
tbt = contentTypeBackTracker(pragma[only_bind_into](hasCall), currentContent) and
result = contentTypeBackTracker(pragma[only_bind_out](hasCall), load)
)
}
pragma[inline]
private predicate isLocalSourceNode(LocalSourceNode n) { any() }
/**
* Holds if there is flow from `localSource` to `dst` using zero or more
* `simpleLocalSmallStep`s.
*/
pragma[nomagic]
predicate flowsTo(LocalSourceNode localSource, Node dst) {
// explicit type check in base case to avoid repeated type tests in recursive case
isLocalSourceNode(localSource) and
dst = localSource
or
exists(Node mid |
flowsTo(localSource, mid) and
simpleLocalSmallStep(mid, dst)
)
}
pragma[nomagic]
private predicate storeStepIntoSource(Node nodeFrom, LocalSourceNode nodeTo, Content c) {
if hasFeatureBacktrackStoreTarget()
then
exists(Node obj |
flowsTo(nodeTo, obj) and
storeStep(nodeFrom, obj, c)
)
else storeStep(nodeFrom, nodeTo, c)
}
pragma[nomagic]
private predicate loadStoreStepIntoSource(
Node nodeFrom, LocalSourceNode nodeTo, Content c1, Content c2
) {
if hasFeatureBacktrackStoreTarget()
then
exists(Node obj |
flowsTo(nodeTo, obj) and
loadStoreStep(nodeFrom, obj, c1, c2)
)
else loadStoreStep(nodeFrom, nodeTo, c1, c2)
}
pragma[nomagic]
private predicate smallStepNoCall(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
levelStepNoCall(nodeFrom, nodeTo) and summary = LevelStep()
or
exists(Content content |
storeStepIntoSource(nodeFrom, nodeTo, content) and
summary = StoreStep(content)
)
or
exists(Content content |
loadStep(nodeFrom, nodeTo, content) and
summary = LoadStep(content)
)
or
exists(Content content1, Content content2 |
loadStoreStepIntoSource(nodeFrom, nodeTo, content1, content2) and
summary = LoadStoreStep(content1, content2)
)
or
exists(ContentFilter filter |
withContentStep(nodeFrom, nodeTo, filter) and
summary = WithContent(filter)
)
or
exists(ContentFilter filter |
withoutContentStep(nodeFrom, nodeTo, filter) and
summary = WithoutContent(filter)
)
or
jumpStep(nodeFrom, nodeTo) and summary = JumpStep()
}
pragma[nomagic]
private predicate smallStepCall(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
levelStepCall(nodeFrom, nodeTo) and summary = LevelStep()
or
callStep(nodeFrom, nodeTo) and summary = CallStep()
or
returnStep(nodeFrom, nodeTo) and summary = ReturnStep()
}
pragma[nomagic]
private predicate stepNoCall(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
exists(Node mid | flowsTo(nodeFrom, mid) and smallStepNoCall(mid, nodeTo, summary))
}
pragma[nomagic]
private predicate stepCall(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
exists(Node mid | flowsTo(nodeFrom, mid) and smallStepCall(mid, nodeTo, summary))
}
pragma[inline]
private predicate smallStepSplit(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
smallStepCall(nodeFrom, nodeTo, summary) or smallStepNoCall(nodeFrom, nodeTo, summary)
}
pragma[inline]
private predicate stepSplit(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
stepNoCall(nodeFrom, nodeTo, summary) or stepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate smallStep(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
smallStepSplit(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
stepSplit(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate stepProj(LocalSourceNode nodeFrom, StepSummary summary) {
step(nodeFrom, _, summary)
}
bindingset[t, nodeFrom]
pragma[inline_late]
pragma[noopt]
private TypeTracker stepInlineLate(TypeTracker t, LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
stepProj(nodeFrom, summary) and
result = append(t, summary) and
step(nodeFrom, nodeTo, summary)
)
}
pragma[nomagic]
private predicate smallStepProj(Node nodeFrom, StepSummary summary) {
smallStep(nodeFrom, _, summary)
}
bindingset[t, nodeFrom]
pragma[inline_late]
pragma[noopt]
private TypeTracker smallStepInlineLate(TypeTracker t, Node nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
smallStepProj(nodeFrom, summary) and
result = append(t, summary) and
smallStep(nodeFrom, nodeTo, summary)
)
}
/**
* A summary of the steps needed to track a value to a given dataflow node.
*
* This can be used to track objects that implement a certain API in order to
* recognize calls to that API. Note that type-tracking does not by itself provide a
* source/sink relation, that is, it may determine that a node has a given type,
* but it won't determine where that type came from.
*
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
* ```ql
* Node myType(TypeTracker tt) {
* tt.start() and
* result = < source of myType >
* or
* exists(TypeTracker tt2 |
* tt = tt2.step(myType(tt2), result)
* )
* }
*
* Node myType() { myType(TypeTracker::end()).flowsTo(result) }
* ```
*
* If you want to track individual intra-procedural steps, use `tt2.smallstep`
* instead of `tt2.step`.
*/
class TypeTracker extends TTypeTracker {
private Boolean hasCall;
private ContentOption content;
TypeTracker() { this = MkTypeTracker(hasCall, content) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withCall, string withContent |
(if hasCall = true then withCall = "with" else withCall = "without") and
(
withContent = " with content " + content.asSome()
or
content instanceof ContentOption::None and
withContent = ""
) and
result = "type tracker " + withCall + " call steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasCall = false and content.isNone() }
/**
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
* The type tracking only ends after the content has been loaded.
*/
predicate startInContent(Content contentName) {
hasCall = false and content = ContentOption::some(contentName)
}
/**
* Holds if this is the starting point of type tracking
* when tracking a parameter into a call, but not out of it.
*/
predicate call() { hasCall = true and content.isNone() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content.isNone() }
/**
* INTERNAL. DO NOT USE.
*
* Gets the content associated with this type tracker.
*/
ContentOption getContent() { result = content }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type is not associated to a piece of content.
*/
TypeTracker continue() { content.isNone() and result = this }
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
pragma[inline]
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
result = stepInlineLate(this, nodeFrom, nodeTo)
}
/**
* Gets the summary that corresponds to having taken a forwards
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*
* Unlike `TypeTracker::step`, this predicate exposes all edges
* in the flow graph, and not just the edges between `Node`s.
* It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* Node myType(TypeTracker tt) {
* tt.start() and
* result = < source of myType >
* or
* exists(TypeTracker tt2 |
* tt = tt2.smallstep(myType(tt2), result)
* )
* }
*
* Node myType() {
* result = myType(TypeTracker::end())
* }
* ```
*/
pragma[inline]
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
result = smallStepInlineLate(this, nodeFrom, nodeTo)
or
simpleLocalSmallStep(nodeFrom, nodeTo) and
result = this
}
}
/** Provides predicates for implementing custom `TypeTracker`s. */
module TypeTracker {
/**
* Gets a valid end point of type tracking.
*/
TypeTracker end() { result.end() }
}
pragma[nomagic]
private predicate backStepProj(LocalSourceNode nodeTo, StepSummary summary) {
step(_, nodeTo, summary)
}
bindingset[t, nodeTo]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backStepInlineLate(
TypeBackTracker t, LocalSourceNode nodeFrom, LocalSourceNode nodeTo
) {
exists(StepSummary summary |
backStepProj(nodeTo, summary) and
result = prepend(t, summary) and
step(nodeFrom, nodeTo, summary)
)
}
pragma[nomagic]
private predicate backSmallStepProj(LocalSourceNode nodeTo, StepSummary summary) {
smallStep(_, nodeTo, summary)
}
bindingset[t, nodeTo]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backSmallStepInlineLate(
TypeBackTracker t, Node nodeFrom, LocalSourceNode nodeTo
) {
exists(StepSummary summary |
backSmallStepProj(nodeTo, summary) and
result = prepend(t, summary) and
smallStep(nodeFrom, nodeTo, summary)
)
}
/**
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
*
* This can for example be used to track callbacks that are passed to a certain API,
* so we can model specific parameters of that callback as having a certain type.
*
* Note that type back-tracking does not provide a source/sink relation, that is,
* it may determine that a node will be used in an API call somewhere, but it won't
* determine exactly where that use was, or the path that led to the use.
*
* It is recommended that all uses of this type are written in the following form,
* for back-tracking some callback type `myCallback`:
*
* ```ql
* Node myCallback(TypeBackTracker t) {
* t.start() and
* result = (< some API call >).getArgument(< n >).getALocalSource()
* or
* exists(TypeBackTracker t2 |
* t = t2.step(result, myCallback(t2))
* )
* }
*
* Node myCallback() { result = myCallback(TypeBackTracker::end()) }
* ```
*
* If you want to track individual intra-procedural steps, use `t2.smallstep`
* instead of `t2.step`.
*/
class TypeBackTracker extends TTypeBackTracker {
private Boolean hasReturn;
private ContentOption content;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withReturn, string withContent |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(
withContent = " with content " + content.asSome()
or
content instanceof ContentOption::None and
withContent = ""
) and
result = "type back-tracker " + withReturn + " return steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasReturn = false and content.isNone() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content.isNone() }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type has not been tracked into a piece of content.
*/
TypeBackTracker continue() { content.isNone() and result = this }
/**
* Gets the summary that corresponds to having taken a backwards
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*/
pragma[inline]
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
result = backStepInlineLate(this, nodeFrom, nodeTo)
}
/**
* Gets the summary that corresponds to having taken a backwards
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
* in the flowgraph, and not just the edges between
* `TypeTrackingNode`s. It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* Node myType(TypeBackTracker t) {
* t.start() and
* result = < some API call >.getArgument(< n >)
* or
* exists (TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2))
* )
* }
*
* Node myType() {
* result = myType(DataFlow::TypeBackTracker::end())
* }
* ```
*/
pragma[inline]
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
result = backSmallStepInlineLate(this, nodeFrom, nodeTo)
or
simpleLocalSmallStep(nodeFrom, nodeTo) and
result = this
}
/**
* Gets a forwards summary that is compatible with this backwards summary.
* That is, if this summary describes the steps needed to back-track a value
* from `sink` to `mid`, and the result is a valid summary of the steps needed
* to track a value from `source` to `mid`, then the value from `source` may
* also flow to `sink`.
*/
TypeTracker getACompatibleTypeTracker() {
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
hasCall = false or hasReturn = false
)
}
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */
module TypeBackTracker {
/**
* Gets a valid end point of type back-tracking.
*/
TypeBackTracker end() { result.end() }
}
signature predicate endpoint(Node node);
signature predicate endpoint(I::Node node);
/**
* Given a source definition, constructs the default forward type tracking from
* those sources.
*/
module TypeTrack<endpoint/1 source> {
pragma[nomagic]
private predicate sourceSimpleLocalSmallSteps(Node src, Node n) {
source(src) and
not src instanceof LocalSourceNode and
src = n
or
exists(Node mid |
sourceSimpleLocalSmallSteps(src, mid) and
simpleLocalSmallStep(mid, n)
)
}
private predicate firstStep(TypeTracker tt, Node src, LocalSourceNode n2) {
exists(Node n1, TypeTracker tt0 |
sourceSimpleLocalSmallSteps(src, n1) and
tt0.start() and
tt = smallStepInlineLate(tt0, n1, n2)
)
}
private Node flow(TypeTracker tt) {
tt.start() and source(result)
or
firstStep(tt, _, result)
or
exists(TypeTracker ttMid | tt = ttMid.step(flow(ttMid), result))
}
/**
* Holds if a source flows to `n`.
*/
predicate flowsTo(Node n) {
flowsTo(flow(TypeTracker::end()), n) or sourceSimpleLocalSmallSteps(_, n)
}
/**
* Given a sink definition, constructs the relation of edges that can be used
* in a source-sink path and calculates the set of source-sink pairs.
*/
module Graph<endpoint/1 sink> {
private newtype TPathNode =
TPathNodeMid(Node node, TypeTracker tt) { node = flow(tt) } or
TPathNodeSink(Node node) { sink(node) and flowsTo(node) }
/**
* A node on a path that is reachable from a source. This is a pair of a
* `Node` and a `TypeTracker` except at sinks for which there is no `TypeTracker`.
*/
class PathNodeFwd extends TPathNode {
/** Gets the node of this `PathNode`. */
Node getNode() { this = TPathNodeMid(result, _) or this = TPathNodeSink(result) }
/** Gets the typetracker of this `PathNode`, if any. */
TypeTracker getTypeTracker() { this = TPathNodeMid(_, result) }
private string ppContent() {
exists(ContentOption c | this.getTypeTracker() = MkTypeTracker(_, c) |
result = " with content " + c.asSome()
or
c instanceof ContentOption::None and
result = ""
)
or
result = "" and this instanceof TPathNodeSink
}
/** Gets a textual representation of this node. */
string toString() { result = this.getNode().toString() + this.ppContent() }
/** Holds if this is a source. */
predicate isSource() {
source(this.getNode()) and
(this.getTypeTracker().start() or this instanceof TPathNodeSink)
}
/** Holds if this is a sink. */
predicate isSink() { this instanceof TPathNodeSink }
}
private predicate edgeCand(Node n1, TypeTracker tt1, Node n2, TypeTracker tt2) {
n1 = flow(tt1) and
(
tt2 = tt1.step(n1, n2)
or
tt1.start() and firstStep(tt2, n1, n2)
)
}
private predicate edgeCand(PathNodeFwd n1, PathNodeFwd n2) {
exists(PathNodeFwd tgt |
edgeCand(n1.getNode(), n1.getTypeTracker(), tgt.getNode(), tgt.getTypeTracker())
|
n2 = tgt
or
n2 = TPathNodeSink(tgt.getNode()) and tgt.getTypeTracker().end()
)
or
n1.getTypeTracker().end() and
flowsTo(n1.getNode(), n2.getNode()) and
n1.getNode() != n2.getNode() and
n2 instanceof TPathNodeSink
or
sourceSimpleLocalSmallSteps(n1.getNode(), n2.getNode()) and
n1.getNode() != n2.getNode() and
n1.isSource() and
n2.isSink()
}
private predicate reachRev(PathNodeFwd n) {
n.isSink()
or
exists(PathNodeFwd mid |
edgeCand(n, mid) and
reachRev(mid)
)
}
/**
* A node on a path that is reachable from a source and can reach a sink.
* This is a pair of a `Node` and a `TypeTracker`.
*/
class PathNode extends PathNodeFwd {
PathNode() { reachRev(this) }
}
/** Holds if `(p1, p2)` is an edge in a path between a source and a sink. */
query predicate edges(PathNode n1, PathNode n2) { edgeCand(n1, n2) }
private predicate stepPlus(PathNode n1, PathNode n2) = fastTC(edges/2)(n1, n2)
/**
* DEPRECATED: Use `flowPath` instead.
*
* Holds if there is a path between `source` and `sink`.
*/
deprecated predicate hasFlow(PathNode source, PathNode sink) { flowPath(source, sink) }
/** Holds if there is a path between `source` and `sink`. */
predicate flowPath(PathNode source, PathNode sink) {
source.isSource() and
sink.isSink() and
(source = sink or stepPlus(source, sink))
}
}
import MkImpl::TypeTrack<source/1>
}
}

View File

@@ -0,0 +1,841 @@
/**
* Provides classes and predicates for simple data-flow reachability suitable
* for tracking types.
*/
private import codeql.util.Boolean
private import codeql.util.Option
private import codeql.typetracking.TypeTracking
/**
* Given a set of step relations, this module provides classes and predicates
* for simple data-flow reachability suitable for tracking types.
*
* The constructed module contains both public and internal logic; the public
* interface is exposed via `codeql.typetracking.TypeTracking`.
*/
module TypeTracking<TypeTrackingInput I> {
private import I
/** Provides consistency checks for the type-tracker step relations. */
module ConsistencyChecks {
private predicate stepEntry(Node n, string kind) {
simpleLocalSmallStep(n, _) and kind = "simpleLocalSmallStep"
or
exists(StepSummary ss | smallStep(n, _, ss) and kind = ss.toString())
or
hasFeatureBacktrackStoreTarget() and
kind = "storeTarget" and
(storeStep(_, n, _) or loadStoreStep(_, n, _, _))
}
/**
* Holds if there is any node in a step relation that is unreachable from a
* `LocalSourceNode`.
*/
query predicate unreachableNode(string msg) {
exists(int k, string kind |
k = strictcount(Node n | stepEntry(n, kind) and not flowsTo(_, n)) and
msg = "There are " + k + " unreachable nodes in steps of kind " + kind + "."
)
}
/**
* Holds if there is a store target that isn't a `LocalSourceNode` and
* backtracking store target feature isn't enabled.
*/
query predicate nonSourceStoreTarget(string msg) {
not hasFeatureBacktrackStoreTarget() and
exists(int k |
k =
strictcount(Node n |
not n instanceof LocalSourceNode and
(storeStep(_, n, _) or loadStoreStep(_, n, _, _))
) and
msg =
"There are " + k +
" store targets that are not local source nodes and backtracking store targets is not enabled."
)
}
}
private module ContentOption = Option<Content>;
private class ContentOption = ContentOption::Option;
private newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(Content content) { storeStep(_, _, content) } or
LoadStep(Content content) { loadStep(_, _, content) } or
LoadStoreStep(Content load, Content store) { loadStoreStep(_, _, load, store) } or
WithContent(ContentFilter filter) { withContentStep(_, _, filter) } or
WithoutContent(ContentFilter filter) { withoutContentStep(_, _, filter) } or
JumpStep()
/**
* A description of a step on an inter-procedural data flow path.
*/
private class StepSummary extends TStepSummary {
/** Gets a textual representation of this step summary. */
string toString() {
this instanceof LevelStep and result = "level"
or
this instanceof CallStep and result = "call"
or
this instanceof ReturnStep and result = "return"
or
exists(Content content | this = StoreStep(content) | result = "store " + content)
or
exists(Content content | this = LoadStep(content) | result = "load " + content)
or
exists(Content load, Content store |
this = LoadStoreStep(load, store) and
result = "load-store " + load + " -> " + store
)
or
this instanceof JumpStep and result = "jump"
}
}
private newtype TTypeTracker =
MkTypeTracker(Boolean hasCall, ContentOption content) {
content.isNone()
or
// Restrict `content` to those that might eventually match a load.
// We can't rely on `basicStoreStep` since `startInContent` might be used with
// a content that has no corresponding store.
exists(Content loadContents |
(
loadStep(_, _, loadContents)
or
loadStoreStep(_, _, loadContents, _)
) and
compatibleContents(content.asSome(), loadContents)
)
}
private newtype TTypeBackTracker =
MkTypeBackTracker(Boolean hasReturn, ContentOption content) {
content.isNone()
or
// As in MkTypeTracker, restrict `content` to those that might eventually match a store.
exists(Content storeContent |
(
storeStep(_, _, storeContent)
or
loadStoreStep(_, _, _, storeContent)
) and
compatibleContents(storeContent, content.asSome())
)
}
pragma[nomagic]
private TypeTracker noContentTypeTracker(boolean hasCall) {
result = MkTypeTracker(hasCall, any(ContentOption::None c))
}
pragma[nomagic]
private TypeTracker contentTypeTracker(boolean hasCall, Content c) {
result = MkTypeTracker(hasCall, ContentOption::some(c))
}
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
pragma[nomagic]
private TypeTracker append(TypeTracker tt, StepSummary step) {
exists(Boolean hasCall, ContentOption currentContents |
tt = MkTypeTracker(hasCall, currentContents)
|
step = LevelStep() and result = tt
or
step = CallStep() and result = MkTypeTracker(true, currentContents)
or
step = ReturnStep() and hasCall = false and result = tt
or
step = JumpStep() and
result = MkTypeTracker(false, currentContents)
or
exists(ContentFilter filter | result = tt |
step = WithContent(filter) and
currentContents.asSome() = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not currentContents.asSome() = filter.getAMatchingContent()
)
)
or
exists(Content storeContents, boolean hasCall |
exists(Content loadContents |
step = LoadStep(pragma[only_bind_into](loadContents)) and
tt = contentTypeTracker(hasCall, storeContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeTracker(hasCall)
)
or
step = StoreStep(pragma[only_bind_into](storeContents)) and
tt = noContentTypeTracker(hasCall) and
result = contentTypeTracker(hasCall, storeContents)
)
or
exists(Content currentContent, Content store, Content load, boolean hasCall |
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(pragma[only_bind_into](currentContent), load) and
tt = contentTypeTracker(pragma[only_bind_into](hasCall), currentContent) and
result = contentTypeTracker(pragma[only_bind_out](hasCall), store)
)
}
pragma[nomagic]
private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) {
result = MkTypeBackTracker(hasReturn, any(ContentOption::None c))
}
pragma[nomagic]
private TypeBackTracker contentTypeBackTracker(boolean hasReturn, Content c) {
result = MkTypeBackTracker(hasReturn, ContentOption::some(c))
}
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
pragma[nomagic]
private TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
exists(Boolean hasReturn, ContentOption content | tbt = MkTypeBackTracker(hasReturn, content) |
step = LevelStep() and result = tbt
or
step = CallStep() and hasReturn = false and result = tbt
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
or
exists(ContentFilter filter | result = tbt |
step = WithContent(filter) and
content.asSome() = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not content.asSome() = filter.getAMatchingContent()
)
)
or
exists(Content loadContents, boolean hasReturn |
exists(Content storeContents |
step = StoreStep(pragma[only_bind_into](storeContents)) and
tbt = contentTypeBackTracker(hasReturn, loadContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeBackTracker(hasReturn)
)
or
step = LoadStep(pragma[only_bind_into](loadContents)) and
tbt = noContentTypeBackTracker(hasReturn) and
result = contentTypeBackTracker(hasReturn, loadContents)
)
or
exists(Content currentContent, Content store, Content load, boolean hasCall |
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(store, pragma[only_bind_into](currentContent)) and
tbt = contentTypeBackTracker(pragma[only_bind_into](hasCall), currentContent) and
result = contentTypeBackTracker(pragma[only_bind_out](hasCall), load)
)
}
pragma[inline]
private predicate isLocalSourceNode(LocalSourceNode n) { any() }
/**
* Holds if there is flow from `localSource` to `dst` using zero or more
* `simpleLocalSmallStep`s.
*/
pragma[nomagic]
predicate flowsTo(LocalSourceNode localSource, Node dst) {
// explicit type check in base case to avoid repeated type tests in recursive case
isLocalSourceNode(localSource) and
dst = localSource
or
exists(Node mid |
flowsTo(localSource, mid) and
simpleLocalSmallStep(mid, dst)
)
}
pragma[nomagic]
private predicate storeStepIntoSource(Node nodeFrom, LocalSourceNode nodeTo, Content c) {
if hasFeatureBacktrackStoreTarget()
then
exists(Node obj |
flowsTo(nodeTo, obj) and
storeStep(nodeFrom, obj, c)
)
else storeStep(nodeFrom, nodeTo, c)
}
pragma[nomagic]
private predicate loadStoreStepIntoSource(
Node nodeFrom, LocalSourceNode nodeTo, Content c1, Content c2
) {
if hasFeatureBacktrackStoreTarget()
then
exists(Node obj |
flowsTo(nodeTo, obj) and
loadStoreStep(nodeFrom, obj, c1, c2)
)
else loadStoreStep(nodeFrom, nodeTo, c1, c2)
}
pragma[nomagic]
private predicate smallStepNoCall(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
levelStepNoCall(nodeFrom, nodeTo) and summary = LevelStep()
or
exists(Content content |
storeStepIntoSource(nodeFrom, nodeTo, content) and
summary = StoreStep(content)
)
or
exists(Content content |
loadStep(nodeFrom, nodeTo, content) and
summary = LoadStep(content)
)
or
exists(Content content1, Content content2 |
loadStoreStepIntoSource(nodeFrom, nodeTo, content1, content2) and
summary = LoadStoreStep(content1, content2)
)
or
exists(ContentFilter filter |
withContentStep(nodeFrom, nodeTo, filter) and
summary = WithContent(filter)
)
or
exists(ContentFilter filter |
withoutContentStep(nodeFrom, nodeTo, filter) and
summary = WithoutContent(filter)
)
or
jumpStep(nodeFrom, nodeTo) and summary = JumpStep()
}
pragma[nomagic]
private predicate smallStepCall(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
levelStepCall(nodeFrom, nodeTo) and summary = LevelStep()
or
callStep(nodeFrom, nodeTo) and summary = CallStep()
or
returnStep(nodeFrom, nodeTo) and summary = ReturnStep()
}
pragma[nomagic]
private predicate stepNoCall(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
exists(Node mid | flowsTo(nodeFrom, mid) and smallStepNoCall(mid, nodeTo, summary))
}
pragma[nomagic]
private predicate stepCall(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
exists(Node mid | flowsTo(nodeFrom, mid) and smallStepCall(mid, nodeTo, summary))
}
pragma[inline]
private predicate smallStepSplit(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
smallStepCall(nodeFrom, nodeTo, summary) or smallStepNoCall(nodeFrom, nodeTo, summary)
}
pragma[inline]
private predicate stepSplit(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
stepNoCall(nodeFrom, nodeTo, summary) or stepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate smallStep(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
smallStepSplit(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
stepSplit(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
private predicate stepProj(LocalSourceNode nodeFrom, StepSummary summary) {
step(nodeFrom, _, summary)
}
bindingset[t, nodeFrom]
pragma[inline_late]
pragma[noopt]
private TypeTracker stepInlineLate(TypeTracker t, LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
stepProj(nodeFrom, summary) and
result = append(t, summary) and
step(nodeFrom, nodeTo, summary)
)
}
pragma[nomagic]
private predicate smallStepProj(Node nodeFrom, StepSummary summary) {
smallStep(nodeFrom, _, summary)
}
bindingset[t, nodeFrom]
pragma[inline_late]
pragma[noopt]
private TypeTracker smallStepInlineLate(TypeTracker t, Node nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
smallStepProj(nodeFrom, summary) and
result = append(t, summary) and
smallStep(nodeFrom, nodeTo, summary)
)
}
/**
* A summary of the steps needed to track a value to a given dataflow node.
*
* This can be used to track objects that implement a certain API in order to
* recognize calls to that API. Note that type-tracking does not by itself provide a
* source/sink relation, that is, it may determine that a node has a given type,
* but it won't determine where that type came from.
*
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
* ```ql
* Node myType(TypeTracker tt) {
* tt.start() and
* result = < source of myType >
* or
* exists(TypeTracker tt2 |
* tt = tt2.step(myType(tt2), result)
* )
* }
*
* Node myType() { myType(TypeTracker::end()).flowsTo(result) }
* ```
*
* If you want to track individual intra-procedural steps, use `tt2.smallstep`
* instead of `tt2.step`.
*/
class TypeTracker extends TTypeTracker {
private Boolean hasCall;
private ContentOption content;
TypeTracker() { this = MkTypeTracker(hasCall, content) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withCall, string withContent |
(if hasCall = true then withCall = "with" else withCall = "without") and
(
withContent = " with content " + content.asSome()
or
content instanceof ContentOption::None and
withContent = ""
) and
result = "type tracker " + withCall + " call steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasCall = false and content.isNone() }
/**
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
* The type tracking only ends after the content has been loaded.
*/
predicate startInContent(Content contentName) {
hasCall = false and content = ContentOption::some(contentName)
}
/**
* Holds if this is the starting point of type tracking
* when tracking a parameter into a call, but not out of it.
*/
predicate call() { hasCall = true and content.isNone() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content.isNone() }
/**
* INTERNAL. DO NOT USE.
*
* Gets the content associated with this type tracker.
*/
ContentOption getContent() { result = content }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type is not associated to a piece of content.
*/
TypeTracker continue() { content.isNone() and result = this }
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
pragma[inline]
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
result = stepInlineLate(this, nodeFrom, nodeTo)
}
/**
* Gets the summary that corresponds to having taken a forwards
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*
* Unlike `TypeTracker::step`, this predicate exposes all edges
* in the flow graph, and not just the edges between `Node`s.
* It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* Node myType(TypeTracker tt) {
* tt.start() and
* result = < source of myType >
* or
* exists(TypeTracker tt2 |
* tt = tt2.smallstep(myType(tt2), result)
* )
* }
*
* Node myType() {
* result = myType(TypeTracker::end())
* }
* ```
*/
pragma[inline]
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
result = smallStepInlineLate(this, nodeFrom, nodeTo)
or
simpleLocalSmallStep(nodeFrom, nodeTo) and
result = this
}
}
/** Provides predicates for implementing custom `TypeTracker`s. */
module TypeTracker {
/**
* Gets a valid end point of type tracking.
*/
TypeTracker end() { result.end() }
}
pragma[nomagic]
private predicate backStepProj(LocalSourceNode nodeTo, StepSummary summary) {
step(_, nodeTo, summary)
}
bindingset[t, nodeTo]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backStepInlineLate(
TypeBackTracker t, LocalSourceNode nodeFrom, LocalSourceNode nodeTo
) {
exists(StepSummary summary |
backStepProj(nodeTo, summary) and
result = prepend(t, summary) and
step(nodeFrom, nodeTo, summary)
)
}
pragma[nomagic]
private predicate backSmallStepProj(LocalSourceNode nodeTo, StepSummary summary) {
smallStep(_, nodeTo, summary)
}
bindingset[t, nodeTo]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker backSmallStepInlineLate(
TypeBackTracker t, Node nodeFrom, LocalSourceNode nodeTo
) {
exists(StepSummary summary |
backSmallStepProj(nodeTo, summary) and
result = prepend(t, summary) and
smallStep(nodeFrom, nodeTo, summary)
)
}
/**
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
*
* This can for example be used to track callbacks that are passed to a certain API,
* so we can model specific parameters of that callback as having a certain type.
*
* Note that type back-tracking does not provide a source/sink relation, that is,
* it may determine that a node will be used in an API call somewhere, but it won't
* determine exactly where that use was, or the path that led to the use.
*
* It is recommended that all uses of this type are written in the following form,
* for back-tracking some callback type `myCallback`:
*
* ```ql
* Node myCallback(TypeBackTracker t) {
* t.start() and
* result = (< some API call >).getArgument(< n >).getALocalSource()
* or
* exists(TypeBackTracker t2 |
* t = t2.step(result, myCallback(t2))
* )
* }
*
* Node myCallback() { result = myCallback(TypeBackTracker::end()) }
* ```
*
* If you want to track individual intra-procedural steps, use `t2.smallstep`
* instead of `t2.step`.
*/
class TypeBackTracker extends TTypeBackTracker {
private Boolean hasReturn;
private ContentOption content;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withReturn, string withContent |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(
withContent = " with content " + content.asSome()
or
content instanceof ContentOption::None and
withContent = ""
) and
result = "type back-tracker " + withReturn + " return steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasReturn = false and content.isNone() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content.isNone() }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type has not been tracked into a piece of content.
*/
TypeBackTracker continue() { content.isNone() and result = this }
/**
* Gets the summary that corresponds to having taken a backwards
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*/
pragma[inline]
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
result = backStepInlineLate(this, nodeFrom, nodeTo)
}
/**
* Gets the summary that corresponds to having taken a backwards
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
* in the flowgraph, and not just the edges between
* `TypeTrackingNode`s. It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* Node myType(TypeBackTracker t) {
* t.start() and
* result = < some API call >.getArgument(< n >)
* or
* exists (TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2))
* )
* }
*
* Node myType() {
* result = myType(DataFlow::TypeBackTracker::end())
* }
* ```
*/
pragma[inline]
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
result = backSmallStepInlineLate(this, nodeFrom, nodeTo)
or
simpleLocalSmallStep(nodeFrom, nodeTo) and
result = this
}
/**
* Gets a forwards summary that is compatible with this backwards summary.
* That is, if this summary describes the steps needed to back-track a value
* from `sink` to `mid`, and the result is a valid summary of the steps needed
* to track a value from `source` to `mid`, then the value from `source` may
* also flow to `sink`.
*/
TypeTracker getACompatibleTypeTracker() {
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
hasCall = false or hasReturn = false
)
}
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */
module TypeBackTracker {
/**
* Gets a valid end point of type back-tracking.
*/
TypeBackTracker end() { result.end() }
}
signature predicate endpoint(Node node);
/**
* Given a source definition, constructs the default forward type tracking from
* those sources.
*/
module TypeTrack<endpoint/1 source> {
pragma[nomagic]
private predicate sourceSimpleLocalSmallSteps(Node src, Node n) {
source(src) and
not src instanceof LocalSourceNode and
src = n
or
exists(Node mid |
sourceSimpleLocalSmallSteps(src, mid) and
simpleLocalSmallStep(mid, n)
)
}
private predicate firstStep(TypeTracker tt, Node src, LocalSourceNode n2) {
exists(Node n1, TypeTracker tt0 |
sourceSimpleLocalSmallSteps(src, n1) and
tt0.start() and
tt = smallStepInlineLate(tt0, n1, n2)
)
}
private Node flow(TypeTracker tt) {
tt.start() and source(result)
or
firstStep(tt, _, result)
or
exists(TypeTracker ttMid | tt = ttMid.step(flow(ttMid), result))
}
/**
* Holds if a source flows to `n`.
*/
predicate flowsTo(Node n) {
flowsTo(flow(TypeTracker::end()), n) or sourceSimpleLocalSmallSteps(_, n)
}
/**
* Given a sink definition, constructs the relation of edges that can be used
* in a source-sink path and calculates the set of source-sink pairs.
*/
module Graph<endpoint/1 sink> {
private newtype TPathNode =
TPathNodeMid(Node node, TypeTracker tt) { node = flow(tt) } or
TPathNodeSink(Node node) { sink(node) and flowsTo(node) }
/**
* A node on a path that is reachable from a source. This is a pair of a
* `Node` and a `TypeTracker` except at sinks for which there is no `TypeTracker`.
*/
class PathNodeFwd extends TPathNode {
/** Gets the node of this `PathNode`. */
Node getNode() { this = TPathNodeMid(result, _) or this = TPathNodeSink(result) }
/** Gets the typetracker of this `PathNode`, if any. */
TypeTracker getTypeTracker() { this = TPathNodeMid(_, result) }
private string ppContent() {
exists(ContentOption c | this.getTypeTracker() = MkTypeTracker(_, c) |
result = " with content " + c.asSome()
or
c instanceof ContentOption::None and
result = ""
)
or
result = "" and this instanceof TPathNodeSink
}
/** Gets a textual representation of this node. */
string toString() { result = this.getNode().toString() + this.ppContent() }
/** Holds if this is a source. */
predicate isSource() {
source(this.getNode()) and
(this.getTypeTracker().start() or this instanceof TPathNodeSink)
}
/** Holds if this is a sink. */
predicate isSink() { this instanceof TPathNodeSink }
}
private predicate edgeCand(Node n1, TypeTracker tt1, Node n2, TypeTracker tt2) {
n1 = flow(tt1) and
(
tt2 = tt1.step(n1, n2)
or
tt1.start() and firstStep(tt2, n1, n2)
)
}
private predicate edgeCand(PathNodeFwd n1, PathNodeFwd n2) {
exists(PathNodeFwd tgt |
edgeCand(n1.getNode(), n1.getTypeTracker(), tgt.getNode(), tgt.getTypeTracker())
|
n2 = tgt
or
n2 = TPathNodeSink(tgt.getNode()) and tgt.getTypeTracker().end()
)
or
n1.getTypeTracker().end() and
flowsTo(n1.getNode(), n2.getNode()) and
n1.getNode() != n2.getNode() and
n2 instanceof TPathNodeSink
or
sourceSimpleLocalSmallSteps(n1.getNode(), n2.getNode()) and
n1.getNode() != n2.getNode() and
n1.isSource() and
n2.isSink()
}
private predicate reachRev(PathNodeFwd n) {
n.isSink()
or
exists(PathNodeFwd mid |
edgeCand(n, mid) and
reachRev(mid)
)
}
/**
* A node on a path that is reachable from a source and can reach a sink.
* This is a pair of a `Node` and a `TypeTracker`.
*/
class PathNode extends PathNodeFwd {
PathNode() { reachRev(this) }
}
/** Holds if `(p1, p2)` is an edge in a path between a source and a sink. */
query predicate edges(PathNode n1, PathNode n2) { edgeCand(n1, n2) }
private predicate stepPlus(PathNode n1, PathNode n2) = fastTC(edges/2)(n1, n2)
/**
* DEPRECATED: Use `flowPath` instead.
*
* Holds if there is a path between `source` and `sink`.
*/
deprecated predicate hasFlow(PathNode source, PathNode sink) { flowPath(source, sink) }
/** Holds if there is a path between `source` and `sink`. */
predicate flowPath(PathNode source, PathNode sink) {
source.isSource() and
sink.isSink() and
(source = sink or stepPlus(source, sink))
}
}
}
}