mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Ruby/Python: Add CallGraphConstruction module for recursive type-tracking based call graph construction
This commit is contained in:
@@ -224,71 +224,47 @@ private module Cached {
|
|||||||
|
|
||||||
private import Cached
|
private import Cached
|
||||||
|
|
||||||
|
private predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
or
|
||||||
|
stepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
private predicate stepNoCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||||
stepNoCall(nodeFrom, _, summary)
|
step(nodeFrom, _, summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
bindingset[nodeFrom, t]
|
||||||
pragma[inline_late]
|
pragma[inline_late]
|
||||||
pragma[noopt]
|
pragma[noopt]
|
||||||
private TypeTracker stepNoCallInlineLate(
|
private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
|
||||||
) {
|
|
||||||
exists(StepSummary summary |
|
exists(StepSummary summary |
|
||||||
stepNoCallProj(nodeFrom, summary) and
|
stepProj(nodeFrom, summary) and
|
||||||
result = t.append(summary) and
|
result = t.append(summary) and
|
||||||
stepNoCall(nodeFrom, nodeTo, summary)
|
step(nodeFrom, nodeTo, summary)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
or
|
||||||
|
smallstepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
private predicate stepCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
|
||||||
stepCall(nodeFrom, _, summary)
|
smallstep(nodeFrom, _, summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
bindingset[nodeFrom, t]
|
||||||
pragma[inline_late]
|
pragma[inline_late]
|
||||||
pragma[noopt]
|
pragma[noopt]
|
||||||
private TypeTracker stepCallInlineLate(
|
private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
||||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
|
||||||
) {
|
|
||||||
exists(StepSummary summary |
|
exists(StepSummary summary |
|
||||||
stepCallProj(nodeFrom, summary) and
|
smallstepProj(nodeFrom, summary) and
|
||||||
result = t.append(summary) and
|
result = t.append(summary) and
|
||||||
stepCall(nodeFrom, nodeTo, summary)
|
smallstep(nodeFrom, nodeTo, summary)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pragma[nomagic]
|
|
||||||
private predicate smallstepNoCallProj(Node nodeFrom, StepSummary summary) {
|
|
||||||
smallstepNoCall(nodeFrom, _, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
|
||||||
pragma[inline_late]
|
|
||||||
pragma[noopt]
|
|
||||||
private TypeTracker smallstepNoCallInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
|
||||||
exists(StepSummary summary |
|
|
||||||
smallstepNoCallProj(nodeFrom, summary) and
|
|
||||||
result = t.append(summary) and
|
|
||||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pragma[nomagic]
|
|
||||||
private predicate smallstepCallProj(Node nodeFrom, StepSummary summary) {
|
|
||||||
smallstepCall(nodeFrom, _, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
|
||||||
pragma[inline_late]
|
|
||||||
pragma[noopt]
|
|
||||||
private TypeTracker smallstepCallInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
|
||||||
exists(StepSummary summary |
|
|
||||||
smallstepCallProj(nodeFrom, summary) and
|
|
||||||
result = t.append(summary) and
|
|
||||||
smallstepCall(nodeFrom, nodeTo, summary)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,14 +361,7 @@ module StepSummary {
|
|||||||
/**
|
/**
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
* Gets the summary that corresponds to having taken a forwards
|
||||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||||
*
|
|
||||||
* This predicate is inlined, which enables better join-orders when
|
|
||||||
* the call graph construction and type tracking are mutually recursive.
|
|
||||||
* In such cases, non-linear recursion involving `step` will be limited
|
|
||||||
* to non-linear recursion for the parts of `step` that involve the
|
|
||||||
* call graph.
|
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
|
||||||
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
stepNoCall(nodeFrom, nodeTo, summary)
|
stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
or
|
or
|
||||||
@@ -424,7 +393,6 @@ module StepSummary {
|
|||||||
* Unlike `StepSummary::step`, this predicate does not compress
|
* Unlike `StepSummary::step`, this predicate does not compress
|
||||||
* type-preserving steps.
|
* type-preserving steps.
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
|
||||||
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||||
or
|
or
|
||||||
@@ -529,66 +497,13 @@ class TypeTracker extends TTypeTracker {
|
|||||||
*/
|
*/
|
||||||
TypeTracker continue() { content = noContent() and result = this }
|
TypeTracker continue() { content = noContent() and result = this }
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker stepNoCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
|
||||||
result = stepNoCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker stepCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
|
||||||
result = stepCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
* Gets the summary that corresponds to having taken a forwards
|
||||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
result = this.stepNoCall(nodeFrom, nodeTo)
|
result = stepInlineLate(this, nodeFrom, nodeTo)
|
||||||
or
|
|
||||||
result = this.stepCall(nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker smallstepNoCall(Node nodeFrom, Node nodeTo) {
|
|
||||||
result = smallstepNoCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
or
|
|
||||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
|
||||||
result = this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker smallstepCall(Node nodeFrom, Node nodeTo) {
|
|
||||||
result = smallstepCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -617,9 +532,10 @@ class TypeTracker extends TTypeTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||||
result = this.smallstepNoCall(nodeFrom, nodeTo)
|
result = smallstepInlineLate(this, nodeFrom, nodeTo)
|
||||||
or
|
or
|
||||||
result = this.smallstepCall(nodeFrom, nodeTo)
|
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||||
|
result = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,6 +547,39 @@ module TypeTracker {
|
|||||||
TypeTracker end() { result.end() }
|
TypeTracker end() { result.end() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
step(_, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeTo, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeBackTracker backStepInlineLate(
|
||||||
|
TypeBackTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||||
|
) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
backStepProj(nodeTo, summary) and
|
||||||
|
result = t.prepend(summary) and
|
||||||
|
step(nodeFrom, nodeTo, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
smallstep(_, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeTo, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeBackTracker backSmallstepInlineLate(TypeBackTracker t, Node nodeFrom, Node nodeTo) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
backSmallstepProj(nodeTo, summary) and
|
||||||
|
result = t.prepend(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.
|
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||||
*
|
*
|
||||||
@@ -714,10 +663,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
exists(StepSummary summary |
|
this = backStepInlineLate(result, nodeFrom, nodeTo)
|
||||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
|
||||||
this = result.prepend(pragma[only_bind_into](summary))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -746,10 +692,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||||
exists(StepSummary summary |
|
this = backSmallstepInlineLate(result, nodeFrom, nodeTo)
|
||||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
|
||||||
this = result.prepend(summary)
|
|
||||||
)
|
|
||||||
or
|
or
|
||||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||||
this = result
|
this = result
|
||||||
@@ -785,3 +728,169 @@ module TypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
TypeBackTracker end() { result.end() }
|
TypeBackTracker end() { result.end() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL: Do not use.
|
||||||
|
*
|
||||||
|
* Provides logic for constructing a call graph in mutual recursion with type tracking.
|
||||||
|
*
|
||||||
|
* When type tracking is used to construct a call graph, we cannot use the join-order
|
||||||
|
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
|
||||||
|
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
|
||||||
|
* and the recursive type tracking call itself. The solution is to split the three-way
|
||||||
|
* non-linear recursion into two non-linear predicates: one that first joins with the
|
||||||
|
* projected `stepCall` relation, followed by a predicate that joins with the full
|
||||||
|
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
|
||||||
|
* same way as in `stepInlineLate`).
|
||||||
|
*/
|
||||||
|
module CallGraphConstruction {
|
||||||
|
/** The input to call graph construction. */
|
||||||
|
signature module InputSig {
|
||||||
|
/** A state to track during type tracking. */
|
||||||
|
class State;
|
||||||
|
|
||||||
|
/** Holds if type tracking should start at `start` in state `state`. */
|
||||||
|
predicate start(Node start, State state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||||
|
* which _does not_ depend on the call graph.
|
||||||
|
*
|
||||||
|
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
|
||||||
|
* standard type tracking.
|
||||||
|
*/
|
||||||
|
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||||
|
* which _does_ depend on the call graph.
|
||||||
|
*
|
||||||
|
* Implementing this predicate using `StepSummary::[small]stepCall` yields
|
||||||
|
* standard type tracking.
|
||||||
|
*/
|
||||||
|
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||||
|
|
||||||
|
/** A projection of an element from the state space. */
|
||||||
|
class StateProj;
|
||||||
|
|
||||||
|
/** Gets the projection of `state`. */
|
||||||
|
StateProj stateProj(State state);
|
||||||
|
|
||||||
|
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
|
||||||
|
predicate filter(Node n, StateProj stateProj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides the `track` predicate for use in call graph construction. */
|
||||||
|
module Make<InputSig Input> {
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
|
||||||
|
Input::stepNoCall(nodeFrom, _, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
|
||||||
|
Input::stepCall(nodeFrom, _, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeFrom, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeTracker stepNoCallInlineLate(
|
||||||
|
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||||
|
) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
stepNoCallProj(nodeFrom, summary) and
|
||||||
|
result = t.append(summary) and
|
||||||
|
Input::stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[state]
|
||||||
|
pragma[inline_late]
|
||||||
|
private Input::StateProj stateProjInlineLate(Input::State state) {
|
||||||
|
result = Input::stateProj(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private Node track(Input::State state, TypeTracker t) {
|
||||||
|
t.start() and Input::start(result, state)
|
||||||
|
or
|
||||||
|
exists(Input::StateProj stateProj |
|
||||||
|
stateProj = stateProjInlineLate(state) and
|
||||||
|
not Input::filter(result, stateProj)
|
||||||
|
|
|
||||||
|
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
|
||||||
|
or
|
||||||
|
exists(StepSummary summary |
|
||||||
|
// non-linear recursion
|
||||||
|
Input::stepCall(trackCall(state, t, summary), result, summary)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[t, summary]
|
||||||
|
pragma[inline_late]
|
||||||
|
private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
|
||||||
|
result = t.append(summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
|
||||||
|
exists(TypeTracker t2 |
|
||||||
|
// non-linear recursion
|
||||||
|
result = track(state, t2) and
|
||||||
|
stepCallProj(result, summary) and
|
||||||
|
t = appendInlineLate(t2, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a node that can be reached from _some_ start node in state `state`. */
|
||||||
|
pragma[nomagic]
|
||||||
|
Node track(Input::State state) { result = track(state, TypeTracker::end()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
|
||||||
|
module Simple {
|
||||||
|
/** The input to call graph construction. */
|
||||||
|
signature module InputSig {
|
||||||
|
/** A state to track during type tracking. */
|
||||||
|
class State;
|
||||||
|
|
||||||
|
/** Holds if type tracking should start at `start` in state `state`. */
|
||||||
|
predicate start(Node start, State state);
|
||||||
|
|
||||||
|
/** Holds if type tracking should stop at `n`. */
|
||||||
|
predicate filter(Node n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides the `track` predicate for use in call graph construction. */
|
||||||
|
module Make<InputSig Input> {
|
||||||
|
private module I implements CallGraphConstruction::InputSig {
|
||||||
|
private import codeql.util.Unit
|
||||||
|
|
||||||
|
class State = Input::State;
|
||||||
|
|
||||||
|
predicate start(Node start, State state) { Input::start(start, state) }
|
||||||
|
|
||||||
|
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||||
|
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||||
|
StepSummary::stepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateProj = Unit;
|
||||||
|
|
||||||
|
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||||
|
|
||||||
|
predicate filter(Node n, Unit u) {
|
||||||
|
Input::filter(n) and
|
||||||
|
exists(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import CallGraphConstruction::Make<I>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -224,71 +224,47 @@ private module Cached {
|
|||||||
|
|
||||||
private import Cached
|
private import Cached
|
||||||
|
|
||||||
|
private predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
or
|
||||||
|
stepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
private predicate stepNoCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||||
stepNoCall(nodeFrom, _, summary)
|
step(nodeFrom, _, summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
bindingset[nodeFrom, t]
|
||||||
pragma[inline_late]
|
pragma[inline_late]
|
||||||
pragma[noopt]
|
pragma[noopt]
|
||||||
private TypeTracker stepNoCallInlineLate(
|
private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
|
||||||
) {
|
|
||||||
exists(StepSummary summary |
|
exists(StepSummary summary |
|
||||||
stepNoCallProj(nodeFrom, summary) and
|
stepProj(nodeFrom, summary) and
|
||||||
result = t.append(summary) and
|
result = t.append(summary) and
|
||||||
stepNoCall(nodeFrom, nodeTo, summary)
|
step(nodeFrom, nodeTo, summary)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
or
|
||||||
|
smallstepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
private predicate stepCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
|
||||||
stepCall(nodeFrom, _, summary)
|
smallstep(nodeFrom, _, summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
bindingset[nodeFrom, t]
|
||||||
pragma[inline_late]
|
pragma[inline_late]
|
||||||
pragma[noopt]
|
pragma[noopt]
|
||||||
private TypeTracker stepCallInlineLate(
|
private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
||||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
|
||||||
) {
|
|
||||||
exists(StepSummary summary |
|
exists(StepSummary summary |
|
||||||
stepCallProj(nodeFrom, summary) and
|
smallstepProj(nodeFrom, summary) and
|
||||||
result = t.append(summary) and
|
result = t.append(summary) and
|
||||||
stepCall(nodeFrom, nodeTo, summary)
|
smallstep(nodeFrom, nodeTo, summary)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pragma[nomagic]
|
|
||||||
private predicate smallstepNoCallProj(Node nodeFrom, StepSummary summary) {
|
|
||||||
smallstepNoCall(nodeFrom, _, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
|
||||||
pragma[inline_late]
|
|
||||||
pragma[noopt]
|
|
||||||
private TypeTracker smallstepNoCallInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
|
||||||
exists(StepSummary summary |
|
|
||||||
smallstepNoCallProj(nodeFrom, summary) and
|
|
||||||
result = t.append(summary) and
|
|
||||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pragma[nomagic]
|
|
||||||
private predicate smallstepCallProj(Node nodeFrom, StepSummary summary) {
|
|
||||||
smallstepCall(nodeFrom, _, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
bindingset[nodeFrom, t]
|
|
||||||
pragma[inline_late]
|
|
||||||
pragma[noopt]
|
|
||||||
private TypeTracker smallstepCallInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
|
||||||
exists(StepSummary summary |
|
|
||||||
smallstepCallProj(nodeFrom, summary) and
|
|
||||||
result = t.append(summary) and
|
|
||||||
smallstepCall(nodeFrom, nodeTo, summary)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,14 +361,7 @@ module StepSummary {
|
|||||||
/**
|
/**
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
* Gets the summary that corresponds to having taken a forwards
|
||||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||||
*
|
|
||||||
* This predicate is inlined, which enables better join-orders when
|
|
||||||
* the call graph construction and type tracking are mutually recursive.
|
|
||||||
* In such cases, non-linear recursion involving `step` will be limited
|
|
||||||
* to non-linear recursion for the parts of `step` that involve the
|
|
||||||
* call graph.
|
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
|
||||||
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
stepNoCall(nodeFrom, nodeTo, summary)
|
stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
or
|
or
|
||||||
@@ -424,7 +393,6 @@ module StepSummary {
|
|||||||
* Unlike `StepSummary::step`, this predicate does not compress
|
* Unlike `StepSummary::step`, this predicate does not compress
|
||||||
* type-preserving steps.
|
* type-preserving steps.
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
|
||||||
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||||
or
|
or
|
||||||
@@ -529,66 +497,13 @@ class TypeTracker extends TTypeTracker {
|
|||||||
*/
|
*/
|
||||||
TypeTracker continue() { content = noContent() and result = this }
|
TypeTracker continue() { content = noContent() and result = this }
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker stepNoCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
|
||||||
result = stepNoCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker stepCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
|
||||||
result = stepCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
* Gets the summary that corresponds to having taken a forwards
|
||||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
result = this.stepNoCall(nodeFrom, nodeTo)
|
result = stepInlineLate(this, nodeFrom, nodeTo)
|
||||||
or
|
|
||||||
result = this.stepCall(nodeFrom, nodeTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker smallstepNoCall(Node nodeFrom, Node nodeTo) {
|
|
||||||
result = smallstepNoCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
or
|
|
||||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
|
||||||
result = this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the summary that corresponds to having taken a forwards
|
|
||||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
|
||||||
*
|
|
||||||
* This predicate should normally not be used; consider using `step`
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
pragma[inline]
|
|
||||||
TypeTracker smallstepCall(Node nodeFrom, Node nodeTo) {
|
|
||||||
result = smallstepCallInlineLate(this, nodeFrom, nodeTo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -617,9 +532,10 @@ class TypeTracker extends TTypeTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||||
result = this.smallstepNoCall(nodeFrom, nodeTo)
|
result = smallstepInlineLate(this, nodeFrom, nodeTo)
|
||||||
or
|
or
|
||||||
result = this.smallstepCall(nodeFrom, nodeTo)
|
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||||
|
result = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,6 +547,39 @@ module TypeTracker {
|
|||||||
TypeTracker end() { result.end() }
|
TypeTracker end() { result.end() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
step(_, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeTo, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeBackTracker backStepInlineLate(
|
||||||
|
TypeBackTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||||
|
) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
backStepProj(nodeTo, summary) and
|
||||||
|
result = t.prepend(summary) and
|
||||||
|
step(nodeFrom, nodeTo, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||||
|
smallstep(_, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeTo, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeBackTracker backSmallstepInlineLate(TypeBackTracker t, Node nodeFrom, Node nodeTo) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
backSmallstepProj(nodeTo, summary) and
|
||||||
|
result = t.prepend(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.
|
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||||
*
|
*
|
||||||
@@ -714,10 +663,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||||
exists(StepSummary summary |
|
this = backStepInlineLate(result, nodeFrom, nodeTo)
|
||||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
|
||||||
this = result.prepend(pragma[only_bind_into](summary))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -746,10 +692,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
pragma[inline]
|
pragma[inline]
|
||||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||||
exists(StepSummary summary |
|
this = backSmallstepInlineLate(result, nodeFrom, nodeTo)
|
||||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
|
||||||
this = result.prepend(summary)
|
|
||||||
)
|
|
||||||
or
|
or
|
||||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||||
this = result
|
this = result
|
||||||
@@ -785,3 +728,169 @@ module TypeBackTracker {
|
|||||||
*/
|
*/
|
||||||
TypeBackTracker end() { result.end() }
|
TypeBackTracker end() { result.end() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL: Do not use.
|
||||||
|
*
|
||||||
|
* Provides logic for constructing a call graph in mutual recursion with type tracking.
|
||||||
|
*
|
||||||
|
* When type tracking is used to construct a call graph, we cannot use the join-order
|
||||||
|
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
|
||||||
|
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
|
||||||
|
* and the recursive type tracking call itself. The solution is to split the three-way
|
||||||
|
* non-linear recursion into two non-linear predicates: one that first joins with the
|
||||||
|
* projected `stepCall` relation, followed by a predicate that joins with the full
|
||||||
|
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
|
||||||
|
* same way as in `stepInlineLate`).
|
||||||
|
*/
|
||||||
|
module CallGraphConstruction {
|
||||||
|
/** The input to call graph construction. */
|
||||||
|
signature module InputSig {
|
||||||
|
/** A state to track during type tracking. */
|
||||||
|
class State;
|
||||||
|
|
||||||
|
/** Holds if type tracking should start at `start` in state `state`. */
|
||||||
|
predicate start(Node start, State state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||||
|
* which _does not_ depend on the call graph.
|
||||||
|
*
|
||||||
|
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
|
||||||
|
* standard type tracking.
|
||||||
|
*/
|
||||||
|
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||||
|
* which _does_ depend on the call graph.
|
||||||
|
*
|
||||||
|
* Implementing this predicate using `StepSummary::[small]stepCall` yields
|
||||||
|
* standard type tracking.
|
||||||
|
*/
|
||||||
|
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||||
|
|
||||||
|
/** A projection of an element from the state space. */
|
||||||
|
class StateProj;
|
||||||
|
|
||||||
|
/** Gets the projection of `state`. */
|
||||||
|
StateProj stateProj(State state);
|
||||||
|
|
||||||
|
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
|
||||||
|
predicate filter(Node n, StateProj stateProj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides the `track` predicate for use in call graph construction. */
|
||||||
|
module Make<InputSig Input> {
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
|
||||||
|
Input::stepNoCall(nodeFrom, _, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
|
||||||
|
Input::stepCall(nodeFrom, _, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[nodeFrom, t]
|
||||||
|
pragma[inline_late]
|
||||||
|
pragma[noopt]
|
||||||
|
private TypeTracker stepNoCallInlineLate(
|
||||||
|
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||||
|
) {
|
||||||
|
exists(StepSummary summary |
|
||||||
|
stepNoCallProj(nodeFrom, summary) and
|
||||||
|
result = t.append(summary) and
|
||||||
|
Input::stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[state]
|
||||||
|
pragma[inline_late]
|
||||||
|
private Input::StateProj stateProjInlineLate(Input::State state) {
|
||||||
|
result = Input::stateProj(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private Node track(Input::State state, TypeTracker t) {
|
||||||
|
t.start() and Input::start(result, state)
|
||||||
|
or
|
||||||
|
exists(Input::StateProj stateProj |
|
||||||
|
stateProj = stateProjInlineLate(state) and
|
||||||
|
not Input::filter(result, stateProj)
|
||||||
|
|
|
||||||
|
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
|
||||||
|
or
|
||||||
|
exists(StepSummary summary |
|
||||||
|
// non-linear recursion
|
||||||
|
Input::stepCall(trackCall(state, t, summary), result, summary)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingset[t, summary]
|
||||||
|
pragma[inline_late]
|
||||||
|
private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
|
||||||
|
result = t.append(summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragma[nomagic]
|
||||||
|
private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
|
||||||
|
exists(TypeTracker t2 |
|
||||||
|
// non-linear recursion
|
||||||
|
result = track(state, t2) and
|
||||||
|
stepCallProj(result, summary) and
|
||||||
|
t = appendInlineLate(t2, summary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a node that can be reached from _some_ start node in state `state`. */
|
||||||
|
pragma[nomagic]
|
||||||
|
Node track(Input::State state) { result = track(state, TypeTracker::end()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
|
||||||
|
module Simple {
|
||||||
|
/** The input to call graph construction. */
|
||||||
|
signature module InputSig {
|
||||||
|
/** A state to track during type tracking. */
|
||||||
|
class State;
|
||||||
|
|
||||||
|
/** Holds if type tracking should start at `start` in state `state`. */
|
||||||
|
predicate start(Node start, State state);
|
||||||
|
|
||||||
|
/** Holds if type tracking should stop at `n`. */
|
||||||
|
predicate filter(Node n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides the `track` predicate for use in call graph construction. */
|
||||||
|
module Make<InputSig Input> {
|
||||||
|
private module I implements CallGraphConstruction::InputSig {
|
||||||
|
private import codeql.util.Unit
|
||||||
|
|
||||||
|
class State = Input::State;
|
||||||
|
|
||||||
|
predicate start(Node start, State state) { Input::start(start, state) }
|
||||||
|
|
||||||
|
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||||
|
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||||
|
StepSummary::stepCall(nodeFrom, nodeTo, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateProj = Unit;
|
||||||
|
|
||||||
|
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||||
|
|
||||||
|
predicate filter(Node n, Unit u) {
|
||||||
|
Input::filter(n) and
|
||||||
|
exists(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import CallGraphConstruction::Make<I>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user