mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +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 predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate stepNoCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
stepNoCall(nodeFrom, _, summary)
|
||||
private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
step(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker stepNoCallInlineLate(
|
||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
stepNoCallProj(nodeFrom, summary) and
|
||||
stepProj(nodeFrom, 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]
|
||||
private predicate stepCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
stepCall(nodeFrom, _, summary)
|
||||
private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
|
||||
smallstep(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker stepCallInlineLate(
|
||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
stepCallProj(nodeFrom, summary) and
|
||||
smallstepProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
stepCall(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)
|
||||
smallstep(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -385,14 +361,7 @@ module StepSummary {
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* 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) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
@@ -424,7 +393,6 @@ module StepSummary {
|
||||
* Unlike `StepSummary::step`, this predicate does not compress
|
||||
* type-preserving steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
@@ -529,66 +497,13 @@ class TypeTracker extends TTypeTracker {
|
||||
*/
|
||||
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
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
result = this.stepNoCall(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)
|
||||
result = stepInlineLate(this, nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -617,9 +532,10 @@ class TypeTracker extends TTypeTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
result = this.smallstepNoCall(nodeFrom, nodeTo)
|
||||
result = smallstepInlineLate(this, nodeFrom, nodeTo)
|
||||
or
|
||||
result = this.smallstepCall(nodeFrom, nodeTo)
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,6 +547,39 @@ module TypeTracker {
|
||||
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.
|
||||
*
|
||||
@@ -714,10 +663,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
||||
this = result.prepend(pragma[only_bind_into](summary))
|
||||
)
|
||||
this = backStepInlineLate(result, nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -746,10 +692,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
this = result.prepend(summary)
|
||||
)
|
||||
this = backSmallstepInlineLate(result, nodeFrom, nodeTo)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
@@ -785,3 +728,169 @@ module TypeBackTracker {
|
||||
*/
|
||||
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