diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll index 4be26f6cea9..25521f5f1a5 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -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 { + 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 { + 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 + } + } +} diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 4be26f6cea9..25521f5f1a5 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -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 { + 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 { + 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 + } + } +}