diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index cdca96cc4ac..895ef74b41e 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -38,6 +38,7 @@ private import DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl private import FlowSummaryImplSpecific as FlowSummaryImplSpecific private import semmle.python.internal.CachedStages +private import semmle.python.dataflow.new.internal.TypeTracker::CallGraphConstruction as CallGraphConstruction newtype TParameterPosition = /** Used for `self` in methods, and `cls` in classmethods. */ @@ -464,103 +465,105 @@ private predicate ignoreForCallGraph(File f) { f.getAbsolutePath().matches("%/site-packages/sympy/%") } -/** - * Gets a reference to the function `func`. - */ -private TypeTrackingNode functionTracker(TypeTracker t, Function func) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - ( - result.asExpr() = func.getDefinition() +private module TrackFunctionInput implements CallGraphConstruction::Simple::InputSig { + class State = Function; + + predicate start(Node start, Function func) { + start.asExpr() = func.getDefinition() or // when a function is decorated, it's the result of the (last) decorator call that // is used - result.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall() - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = functionTracker(t2, func).track(t2, t)) + start.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall() + } + + predicate filter(Node n) { ignoreForCallGraph(n.getLocation().getFile()) } } /** * Gets a reference to the function `func`. */ -Node functionTracker(Function func) { functionTracker(TypeTracker::end(), func).flowsTo(result) } +Node functionTracker(Function func) { + CallGraphConstruction::Simple::Make::track(func) + .(LocalSourceNode) + .flowsTo(result) +} -/** - * Gets a reference to the class `cls`. - */ -private TypeTrackingNode classTracker(TypeTracker t, Class cls) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - ( - result.asExpr() = cls.getParent() +private module TrackClassInput implements CallGraphConstruction::Simple::InputSig { + class State = Class; + + predicate start(Node start, Class cls) { + start.asExpr() = cls.getParent() or // when a class is decorated, it's the result of the (last) decorator call that // is used - result.asExpr() = cls.getParent().getADecoratorCall() + start.asExpr() = cls.getParent().getADecoratorCall() or // `type(obj)`, where obj is an instance of this class - result = getTypeCall() and - result.(CallCfgNode).getArg(0) = classInstanceTracker(cls) - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = classTracker(t2, cls).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + start = getTypeCall() and + start.(CallCfgNode).getArg(0) = classInstanceTracker(cls) + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** * Gets a reference to the class `cls`. */ -Node classTracker(Class cls) { classTracker(TypeTracker::end(), cls).flowsTo(result) } +Node classTracker(Class cls) { + CallGraphConstruction::Simple::Make::track(cls).(LocalSourceNode).flowsTo(result) +} -/** - * Gets a reference to an instance of the class `cls`. - */ -private TypeTrackingNode classInstanceTracker(TypeTracker t, Class cls) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - resolveClassCall(result.(CallCfgNode).asCfgNode(), cls) - or - // result of `super().__new__` as used in a `__new__` method implementation - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - exists(Class classUsedInSuper | - fromSuperNewCall(result.(CallCfgNode).asCfgNode(), classUsedInSuper, _, _) and - classUsedInSuper = getADirectSuperclass*(cls) - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = classInstanceTracker(t2, cls).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) +private module TrackClassInstanceInput implements CallGraphConstruction::Simple::InputSig { + class State = Class; + + predicate start(Node start, Class cls) { + resolveClassCall(start.(CallCfgNode).asCfgNode(), cls) + or + // result of `super().__new__` as used in a `__new__` method implementation + exists(Class classUsedInSuper | + fromSuperNewCall(start.(CallCfgNode).asCfgNode(), classUsedInSuper, _, _) and + classUsedInSuper = getADirectSuperclass*(cls) + ) + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** * Gets a reference to an instance of the class `cls`. */ Node classInstanceTracker(Class cls) { - classInstanceTracker(TypeTracker::end(), cls).flowsTo(result) + CallGraphConstruction::Simple::Make::track(cls) + .(LocalSourceNode) + .flowsTo(result) } -/** - * Gets a reference to the `self` argument of a method on class `classWithMethod`. - * The method cannot be a `staticmethod` or `classmethod`. - */ -private TypeTrackingNode selfTracker(TypeTracker t, Class classWithMethod) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - exists(Function func | - func = classWithMethod.getAMethod() and - not isStaticmethod(func) and - not isClassmethod(func) - | - result.asExpr() = func.getArg(0) - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = selfTracker(t2, classWithMethod).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) +private module TrackSelfInput implements CallGraphConstruction::Simple::InputSig { + class State = Class; + + predicate start(Node start, Class classWithMethod) { + exists(Function func | + func = classWithMethod.getAMethod() and + not isStaticmethod(func) and + not isClassmethod(func) + | + start.asExpr() = func.getArg(0) + ) + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** @@ -568,33 +571,32 @@ private TypeTrackingNode selfTracker(TypeTracker t, Class classWithMethod) { * The method cannot be a `staticmethod` or `classmethod`. */ Node selfTracker(Class classWithMethod) { - selfTracker(TypeTracker::end(), classWithMethod).flowsTo(result) + CallGraphConstruction::Simple::Make::track(classWithMethod) + .(LocalSourceNode) + .flowsTo(result) } -/** - * Gets a reference to the enclosing class `classWithMethod` from within one of its - * methods, either through the `cls` argument from a `classmethod` or from `type(self)` - * from a normal method. - */ -private TypeTrackingNode clsArgumentTracker(TypeTracker t, Class classWithMethod) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - ( +private module TrackClsArgumentInput implements CallGraphConstruction::Simple::InputSig { + class State = Class; + + predicate start(Node start, Class classWithMethod) { exists(Function func | func = classWithMethod.getAMethod() and isClassmethod(func) | - result.asExpr() = func.getArg(0) + start.asExpr() = func.getArg(0) ) or // type(self) - result = getTypeCall() and - result.(CallCfgNode).getArg(0) = selfTracker(classWithMethod) - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = clsArgumentTracker(t2, classWithMethod).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + start = getTypeCall() and + start.(CallCfgNode).getArg(0) = selfTracker(classWithMethod) + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** @@ -603,26 +605,28 @@ private TypeTrackingNode clsArgumentTracker(TypeTracker t, Class classWithMethod * from a normal method. */ Node clsArgumentTracker(Class classWithMethod) { - clsArgumentTracker(TypeTracker::end(), classWithMethod).flowsTo(result) + CallGraphConstruction::Simple::Make::track(classWithMethod) + .(LocalSourceNode) + .flowsTo(result) } -/** - * Gets a reference to the result of calling `super` without any argument, where the - * call happened in the method `func` (either a method or a classmethod). - */ -private TypeTrackingNode superCallNoArgumentTracker(TypeTracker t, Function func) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - not isStaticmethod(func) and - exists(CallCfgNode call | result = call | - call = getSuperCall() and - not exists(call.getArg(_)) and - call.getScope() = func - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = superCallNoArgumentTracker(t2, func).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) +private module TrackSuperCallNoArgumentInput implements CallGraphConstruction::Simple::InputSig { + class State = Function; + + predicate start(Node start, Function func) { + not isStaticmethod(func) and + exists(CallCfgNode call | start = call | + call = getSuperCall() and + not exists(call.getArg(_)) and + call.getScope() = func + ) + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** @@ -630,25 +634,30 @@ private TypeTrackingNode superCallNoArgumentTracker(TypeTracker t, Function func * call happened in the method `func` (either a method or a classmethod). */ Node superCallNoArgumentTracker(Function func) { - superCallNoArgumentTracker(TypeTracker::end(), func).flowsTo(result) + CallGraphConstruction::Simple::Make::track(func) + .(LocalSourceNode) + .flowsTo(result) } -/** - * Gets a reference to the result of calling `super` with 2 arguments, where the - * first is a reference to the class `cls`, and the second argument is `obj`. - */ -private TypeTrackingNode superCallTwoArgumentTracker(TypeTracker t, Class cls, Node obj) { - not ignoreForCallGraph(result.getLocation().getFile()) and - t.start() and - exists(CallCfgNode call | result = call | +private module TrackSuperCallTwoArgumentInput implements CallGraphConstruction::Simple::InputSig { + additional predicate superCall(CallCfgNode call, Class cls, Node obj) { call = getSuperCall() and call.getArg(0) = classTracker(cls) and call.getArg(1) = obj - ) - or - not ignoreForCallGraph(result.getLocation().getFile()) and - exists(TypeTracker t2 | result = superCallTwoArgumentTracker(t2, cls, obj).track(t2, t)) and - not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } + + class State = CallCfgNode; + + predicate start(Node start, CallCfgNode call) { + superCall(call, _, _) and + start = call + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** @@ -656,7 +665,12 @@ private TypeTrackingNode superCallTwoArgumentTracker(TypeTracker t, Class cls, N * first is a reference to the class `cls`, and the second argument is `obj`. */ Node superCallTwoArgumentTracker(Class cls, Node obj) { - superCallTwoArgumentTracker(TypeTracker::end(), cls, obj).flowsTo(result) + exists(CallCfgNode call | + TrackSuperCallTwoArgumentInput::superCall(call, cls, obj) and + CallGraphConstruction::Simple::Make::track(call) + .(LocalSourceNode) + .flowsTo(result) + ) } // ============================================================================= @@ -800,20 +814,30 @@ Function findFunctionAccordingToMroKnownStartingClass(Class startingClass, strin // ============================================================================= // attribute trackers // ============================================================================= -/** Gets a reference to the attribute read `attr` */ -private TypeTrackingNode attrReadTracker(TypeTracker t, AttrRead attr) { - t.start() and - result = attr and - attr.getObject() in [ - classTracker(_), classInstanceTracker(_), selfTracker(_), clsArgumentTracker(_), - superCallNoArgumentTracker(_), superCallTwoArgumentTracker(_, _) - ] - or - exists(TypeTracker t2 | result = attrReadTracker(t2, attr).track(t2, t)) +private module TrackAttrReadInput implements CallGraphConstruction::Simple::InputSig { + class State = AttrRead; + + predicate start(Node start, AttrRead attr) { + start = attr and + attr.getObject() in [ + classTracker(_), classInstanceTracker(_), selfTracker(_), clsArgumentTracker(_), + superCallNoArgumentTracker(_), superCallTwoArgumentTracker(_, _) + ] + } + + predicate filter(Node n) { + ignoreForCallGraph(n.getLocation().getFile()) + or + n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) + } } /** Gets a reference to the attribute read `attr` */ -Node attrReadTracker(AttrRead attr) { attrReadTracker(TypeTracker::end(), attr).flowsTo(result) } +Node attrReadTracker(AttrRead attr) { + CallGraphConstruction::Simple::Make::track(attr) + .(LocalSourceNode) + .flowsTo(result) +} // ============================================================================= // call and argument resolution 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 d4d9e1f31f5..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,6 +224,50 @@ 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 stepProj(TypeTrackingNode nodeFrom, StepSummary summary) { + step(nodeFrom, _, summary) +} + +bindingset[nodeFrom, t] +pragma[inline_late] +pragma[noopt] +private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { + exists(StepSummary summary | + stepProj(nodeFrom, summary) and + result = t.append(summary) and + 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 smallstepProj(Node nodeFrom, StepSummary summary) { + smallstep(nodeFrom, _, summary) +} + +bindingset[nodeFrom, t] +pragma[inline_late] +pragma[noopt] +private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) { + exists(StepSummary summary | + smallstepProj(nodeFrom, summary) and + result = t.append(summary) and + smallstep(nodeFrom, nodeTo, summary) + ) +} + /** * Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`. * @@ -298,21 +342,50 @@ class StepSummary extends TStepSummary { module StepSummary { /** * Gets the summary that corresponds to having taken a forwards - * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + * 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. + * This predicate should normally not be used; consider using `step` + * instead. + */ + predicate stepCall = Cached::stepCall/3; + + /** + * 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. + */ + predicate stepNoCall = Cached::stepNoCall/3; + + /** + * Gets the summary that corresponds to having taken a forwards + * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. */ - pragma[inline] predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { stepNoCall(nodeFrom, nodeTo, summary) or stepCall(nodeFrom, nodeTo, summary) } + /** + * 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. + */ + predicate smallstepNoCall = Cached::smallstepNoCall/3; + + /** + * 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. + */ + predicate smallstepCall = Cached::smallstepCall/3; + /** * Gets the summary that corresponds to having taken a forwards * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. @@ -320,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 @@ -431,10 +503,7 @@ class TypeTracker extends TTypeTracker { */ pragma[inline] TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { - exists(StepSummary summary | - StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and - result = this.append(pragma[only_bind_into](summary)) - ) + result = stepInlineLate(this, nodeFrom, nodeTo) } /** @@ -463,10 +532,7 @@ class TypeTracker extends TTypeTracker { */ pragma[inline] TypeTracker smallstep(Node nodeFrom, Node nodeTo) { - exists(StepSummary summary | - StepSummary::smallstep(nodeFrom, nodeTo, summary) and - result = this.append(summary) - ) + result = smallstepInlineLate(this, nodeFrom, nodeTo) or simpleLocalFlowStep(nodeFrom, nodeTo) and result = this @@ -481,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. * @@ -564,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) } /** @@ -596,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 @@ -635,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/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index b1320d047cc..1cf4c445781 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -778,7 +778,7 @@ module API { or exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t) and - not result instanceof DataFlowPrivate::SelfParameterNode + not result instanceof DataFlow::SelfParameterNode ) } @@ -800,7 +800,7 @@ module API { or exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid | mid = trackDefNode(rhs, t2) and - not mid instanceof DataFlowPrivate::SelfParameterNode and + not mid instanceof DataFlow::SelfParameterNode and result = mid.backtrack(t2, t) ) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 2b4c030707b..625cc226b96 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -8,18 +8,21 @@ private import FlowSummaryImpl as FlowSummaryImpl private import FlowSummaryImplSpecific as FlowSummaryImplSpecific private import codeql.ruby.dataflow.FlowSummary private import codeql.ruby.dataflow.SSA +private import codeql.util.Boolean +private import codeql.util.Unit /** - * A `LocalSourceNode` for a `self` variable. This is either an implicit `self` - * parameter or an implicit SSA entry definition. + * A `LocalSourceNode` for a `self` variable. This is the implicit `self` + * parameter, when it exists, otherwise the implicit SSA entry definition. */ private class SelfLocalSourceNode extends DataFlow::LocalSourceNode { private SelfVariable self; SelfLocalSourceNode() { - self = this.(SelfParameterNode).getSelfVariable() + self = this.(SelfParameterNodeImpl).getSelfVariable() or - self = this.(SsaSelfDefinitionNode).getVariable() + self = this.(SsaSelfDefinitionNode).getVariable() and + not LocalFlow::localFlowSsaParamInput(_, this) } /** Gets the `self` variable. */ @@ -470,35 +473,28 @@ private module Cached { import Cached pragma[nomagic] -private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) { - t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr()) - or - exists(TypeTracker t2, StepSummary summary | - result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary) - ) +private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl } + +private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig { + class State = Module; + + predicate start(DataFlow::Node start, Module m) { + m = resolveConstantReadAccess(start.asExpr().getExpr()) + } + + // We exclude steps into `self` parameters, and instead rely on the type of the + // enclosing module + predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl } } -/** - * We exclude steps into `self` parameters, and instead rely on the type of the - * enclosing module. - */ -pragma[nomagic] -private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) { - StepSummary::step(trackModuleAccess(m, t), result, summary) and - not result instanceof SelfParameterNode -} - -pragma[nomagic] -private DataFlow::LocalSourceNode trackModuleAccess(Module m) { - result = trackModuleAccess(m, TypeTracker::end()) -} +predicate trackModuleAccess = CallGraphConstruction::Simple::Make::track/1; pragma[nomagic] private predicate hasUserDefinedNew(Module m) { exists(DataFlow::MethodNode method | // not `getAnAncestor` because singleton methods cannot be included singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and - not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode()) + not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode()) ) } @@ -531,141 +527,162 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) { ) } -/** Holds if `n` is an instance of type `tp`. */ -private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) { - n.asExpr().getExpr() instanceof NilLiteral and - tp = TResolved("NilClass") and - exact = true - or - n.asExpr().getExpr().(BooleanLiteral).isFalse() and - tp = TResolved("FalseClass") and - exact = true - or - n.asExpr().getExpr().(BooleanLiteral).isTrue() and - tp = TResolved("TrueClass") and - exact = true - or - n.asExpr().getExpr() instanceof IntegerLiteral and - tp = TResolved("Integer") and - exact = true - or - n.asExpr().getExpr() instanceof FloatLiteral and - tp = TResolved("Float") and - exact = true - or - n.asExpr().getExpr() instanceof RationalLiteral and - tp = TResolved("Rational") and - exact = true - or - n.asExpr().getExpr() instanceof ComplexLiteral and - tp = TResolved("Complex") and - exact = true - or - n.asExpr().getExpr() instanceof StringlikeLiteral and - tp = TResolved("String") and - exact = true - or - n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and - tp = TResolved("Array") and - exact = true - or - n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and - tp = TResolved("Hash") and - exact = true - or - n.asExpr().getExpr() instanceof MethodBase and - tp = TResolved("Symbol") and - exact = true - or - n.asParameter() instanceof BlockParameter and - tp = TResolved("Proc") and - exact = true - or - n.asExpr().getExpr() instanceof Lambda and - tp = TResolved("Proc") and - exact = true - or - isStandardNewCall(n.asExpr(), tp, exact) - or - // `self` reference in method or top-level (but not in module or singleton method, - // where instance methods cannot be called; only singleton methods) - n = - any(SelfLocalSourceNode self | - exists(MethodBase m | - selfInMethod(self.getVariable(), m, tp) and - not m instanceof SingletonMethod and - if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false - ) - or - selfInToplevel(self.getVariable(), tp) and - exact = true - ) - or - // `in C => c then c.foo` - asModulePattern(n, tp) and - exact = false - or - // `case object when C then object.foo` - hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and - exact = false -} - -pragma[nomagic] -private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) { - t.start() and - ( - isInstance(result, tp, exact) - or - exists(Module m | - (if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and - exact = true - | - // needed for e.g. `C.new` - m = resolveConstantReadAccess(result.asExpr().getExpr()) - or - // needed for e.g. `self.include` - selfInModule(result.(SelfLocalSourceNode).getVariable(), m) - or - // needed for e.g. `self.puts` - selfInMethod(result.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m) - ) - ) - or - exists(TypeTracker t2, StepSummary summary | - result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary) - ) -} - private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { localFlowStepTypeTracker(nodeFrom, nodeTo) and summary.toString() = "level" } -pragma[nomagic] -private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) { - hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _) -} - -/** - * We exclude steps into `self` parameters and type checked variables. For those, - * we instead rely on the type of the enclosing module resp. the type being checked - * against, and apply an open-world assumption when determining possible dispatch - * targets. - */ -pragma[nomagic] -private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) { - exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) | - StepSummary::smallstep(mid, result, summary) and - not result instanceof SelfParameterNode +private module TrackInstanceInput implements CallGraphConstruction::InputSig { + pragma[nomagic] + private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) { + n.asExpr().getExpr() instanceof NilLiteral and + tp = TResolved("NilClass") and + exact = true or - localFlowStep(mid, result, summary) and - not hasAdjacentTypeCheckedReads(result) - ) + n.asExpr().getExpr().(BooleanLiteral).isFalse() and + tp = TResolved("FalseClass") and + exact = true + or + n.asExpr().getExpr().(BooleanLiteral).isTrue() and + tp = TResolved("TrueClass") and + exact = true + or + n.asExpr().getExpr() instanceof IntegerLiteral and + tp = TResolved("Integer") and + exact = true + or + n.asExpr().getExpr() instanceof FloatLiteral and + tp = TResolved("Float") and + exact = true + or + n.asExpr().getExpr() instanceof RationalLiteral and + tp = TResolved("Rational") and + exact = true + or + n.asExpr().getExpr() instanceof ComplexLiteral and + tp = TResolved("Complex") and + exact = true + or + n.asExpr().getExpr() instanceof StringlikeLiteral and + tp = TResolved("String") and + exact = true + or + n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and + tp = TResolved("Array") and + exact = true + or + n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and + tp = TResolved("Hash") and + exact = true + or + n.asExpr().getExpr() instanceof MethodBase and + tp = TResolved("Symbol") and + exact = true + or + n.asParameter() instanceof BlockParameter and + tp = TResolved("Proc") and + exact = true + or + n.asExpr().getExpr() instanceof Lambda and + tp = TResolved("Proc") and + exact = true + or + // `self` reference in method or top-level (but not in module or singleton method, + // where instance methods cannot be called; only singleton methods) + n = + any(SelfLocalSourceNode self | + exists(MethodBase m | + selfInMethod(self.getVariable(), m, tp) and + not m instanceof SingletonMethod and + if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false + ) + or + selfInToplevel(self.getVariable(), tp) and + exact = true + ) + or + // `in C => c then c.foo` + asModulePattern(n, tp) and + exact = false + or + // `case object when C then object.foo` + hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and + exact = false + } + + pragma[nomagic] + private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) { + isStandardNewCall(n.asExpr(), tp, exact) + } + + /** Holds if `n` is an instance of type `tp`. */ + pragma[inline] + private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) { + isInstanceNoCall(n, tp, exact) + or + isInstanceCall(n, tp, exact) + } + + pragma[nomagic] + private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) { + hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _) + } + + newtype State = additional MkState(Module m, Boolean exact) + + predicate start(DataFlow::Node start, State state) { + exists(Module tp, boolean exact | state = MkState(tp, exact) | + isInstance(start, tp, exact) + or + exists(Module m | + (if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and + exact = true + | + // needed for e.g. `C.new` + m = resolveConstantReadAccess(start.asExpr().getExpr()) + or + // needed for e.g. `self.include` + selfInModule(start.(SelfLocalSourceNode).getVariable(), m) + or + // needed for e.g. `self.puts` + selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m) + ) + ) + } + + pragma[nomagic] + predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { + // We exclude steps into `self` parameters. For those, we instead rely on the type of + // the enclosing module + StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and + isNotSelf(nodeTo) + or + // We exclude steps into type checked variables. For those, we instead rely on the + // type being checked against + localFlowStep(nodeFrom, nodeTo, summary) and + not hasAdjacentTypeCheckedReads(nodeTo) + } + + predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { + StepSummary::smallstepCall(nodeFrom, nodeTo, summary) + } + + class StateProj = Unit; + + Unit stateProj(State state) { exists(state) and exists(result) } + + // We exclude steps into `self` parameters, and instead rely on the type of the + // enclosing module + predicate filter(DataFlow::Node n, Unit u) { + n instanceof SelfParameterNodeImpl and + exists(u) + } } pragma[nomagic] private DataFlow::Node trackInstance(Module tp, boolean exact) { - result = trackInstance(tp, exact, TypeTracker::end()) + result = + CallGraphConstruction::Make::track(TrackInstanceInput::MkState(tp, exact)) } pragma[nomagic] @@ -706,30 +723,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) { ) } -pragma[nomagic] -private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) { - t.start() and result.asExpr().getExpr() = block - or - exists(TypeTracker t2, StepSummary summary | - result = trackBlockRec(block, t2, summary) and - t = t2.append(summary) - ) +private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig { + class State = Block; + + predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block } + + // We exclude steps into `self` parameters, and instead rely on the type of the + // enclosing module + predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl } } -/** - * We exclude steps into `self` parameters, which may happen when the code - * base contains implementations of `call`. - */ -pragma[nomagic] -private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) { - StepSummary::step(trackBlock(block, t), result, summary) and - not result instanceof SelfParameterNode -} - -pragma[nomagic] -private DataFlow::LocalSourceNode trackBlock(Block block) { - result = trackBlock(block, TypeTracker::end()) -} +private predicate trackBlock = CallGraphConstruction::Simple::Make::track/1; /** Holds if `m` is a singleton method named `name`, defined on `object. */ private predicate singletonMethod(MethodBase m, string name, Expr object) { @@ -896,92 +900,98 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object) ) } -/** - * Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter. - * - * This is only used for tracking singleton methods, where we want to be able - * to handle cases like - * - * ```rb - * def add_singleton x - * def x.foo; end - * end - * - * y = add_singleton C.new - * y.foo - * ``` - * - * and - * - * ```rb - * class C - * def add_singleton_to_self - * def self.foo; end - * end - * end - * - * y = C.new - * y.add_singleton_to_self - * y.foo - * ``` - */ -pragma[nomagic] -private predicate paramReturnFlow( - DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary -) { - exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr | - TypeTrackerSpecific::callStep(call, arg, p) and - nodeTo.getPreUpdateNode() = arg and - summary.toString() = "return" and - ( - nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr() +private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig { + /** + * Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter. + * + * This is only used for tracking singleton methods, where we want to be able + * to handle cases like + * + * ```rb + * def add_singleton x + * def x.foo; end + * end + * + * y = add_singleton C.new + * y.foo + * ``` + * + * and + * + * ```rb + * class C + * def add_singleton_to_self + * def self.foo; end + * end + * end + * + * y = C.new + * y.add_singleton_to_self + * y.foo + * ``` + */ + pragma[nomagic] + private predicate paramReturnFlow( + DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary + ) { + exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr | + TypeTrackerSpecific::callStep(call, arg, p) and + nodeTo.getPreUpdateNode() = arg and + summary.toString() = "return" and + ( + nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr() + or + nodeFromPreExpr = nodeFrom.asExpr().getExpr() and + singletonMethodOnInstance(_, _, nodeFromPreExpr) + ) + | + nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess() or - nodeFromPreExpr = nodeFrom.asExpr().getExpr() and - singletonMethodOnInstance(_, _, nodeFromPreExpr) + nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess() ) - | - nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess() - or - nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess() - ) -} + } -pragma[nomagic] -private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) { - t.start() and - singletonMethodOnInstance(method, name, result.asExpr().getExpr()) - or - exists(TypeTracker t2, StepSummary summary | - result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and - t = t2.append(summary) and - // Stop flow at redefinitions. - // - // Example: - // ```rb - // def x.foo; end - // def x.foo; end - // x.foo # <- we want to resolve this call to the second definition only - // ``` - not singletonMethodOnInstance(_, name, result.asExpr().getExpr()) - ) -} + class State = MethodBase; -pragma[nomagic] -private DataFlow::Node trackSingletonMethodOnInstanceRec( - MethodBase method, string name, TypeTracker t, StepSummary summary -) { - exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) | - StepSummary::smallstep(mid, result, summary) + predicate start(DataFlow::Node start, MethodBase method) { + singletonMethodOnInstance(method, _, start.asExpr().getExpr()) + } + + predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { + StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) or - paramReturnFlow(mid, result, summary) + localFlowStep(nodeFrom, nodeTo, summary) + } + + predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { + StepSummary::smallstepCall(nodeFrom, nodeTo, summary) or - localFlowStep(mid, result, summary) - ) + paramReturnFlow(nodeFrom, nodeTo, summary) + } + + class StateProj extends string { + StateProj() { singletonMethodOnInstance(_, this, _) } + } + + StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) } + + // Stop flow at redefinitions. + // + // Example: + // ```rb + // def x.foo; end + // def x.foo; end + // x.foo # <- we want to resolve this call to the second definition only + // ``` + predicate filter(DataFlow::Node n, StateProj name) { + singletonMethodOnInstance(_, name, n.asExpr().getExpr()) + } } pragma[nomagic] private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) { - result = trackSingletonMethodOnInstance(method, name, TypeTracker::end()) + result = CallGraphConstruction::Make::track(method) and + singletonMethodOnInstance(method, name, _) } /** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */ diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index d6908827ba9..6a7d87e9bd5 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -131,10 +131,10 @@ module LocalFlow { /** * Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node. */ - predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) { + predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) { nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter()) or - nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod()) + nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod()) } /** @@ -143,14 +143,13 @@ module LocalFlow { * * This is intended to recover from flow not currently recognised by ordinary capture flow. */ - predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) { - exists(Ssa::CapturedEntryDefinition def, ParameterNodeImpl p | - (nodeFrom = p or LocalFlow::localFlowSsaParamInput(p, nodeFrom)) and + predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) { + exists(Ssa::CapturedEntryDefinition def | nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def | - p.getParameter().(NamedParameter).getVariable() = def.getSourceVariable() + nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable() or - p.(SelfParameterNode).getSelfVariable() = def.getSourceVariable() + nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable() ) } @@ -164,7 +163,7 @@ module LocalFlow { /** * Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving - * SSA definition `def`. + * some SSA definition. */ private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) { exists(SsaImpl::DefinitionExt def | @@ -182,6 +181,8 @@ module LocalFlow { // Flow into phi (read) SSA definition node from def localFlowSsaInputFromDef(nodeFrom, def, nodeTo) ) + or + localFlowSsaParamInput(nodeFrom, nodeTo) // TODO // or // // Flow into uncertain SSA definition @@ -223,6 +224,13 @@ module LocalFlow { op.getExpr() instanceof BinaryLogicalOperation and nodeFrom.asExpr() = op.getAnOperand() ) + or + nodeTo.(ParameterNodeImpl).getParameter() = + any(NamedParameter p | + p.(OptionalParameter).getDefaultValue() = nodeFrom.asExpr().getExpr() + or + p.(KeywordParameter).getDefaultValue() = nodeFrom.asExpr().getExpr() + ) } } @@ -279,12 +287,6 @@ private module Cached { newtype TNode = TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or TReturningNode(CfgNodes::ReturningCfgNode n) or - TSynthReturnNode(CfgScope scope, ReturnKind kind) { - exists(ReturningNode ret | - ret.(NodeImpl).getCfgScope() = scope and - ret.getKind() = kind - ) - } or TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or TNormalParameterNode(Parameter p) { p instanceof SimpleParameter or @@ -326,12 +328,6 @@ private module Cached { TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSynthHashSplatParameterNode or TSummaryParameterNode; - private predicate defaultValueFlow(NamedParameter p, ExprNode e) { - p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr() - or - p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr() - } - cached Location getLocation(NodeImpl n) { result = n.getLocationImpl() } @@ -346,12 +342,6 @@ private module Cached { predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) or - defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom) - or - LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo) - or - nodeTo.(SynthReturnNode).getAnInput() = nodeFrom - or LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _) or @@ -373,10 +363,6 @@ private module Cached { predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) { LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) or - defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom) - or - LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo) - or LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) or // Simple flow through library code is included in the exposed local @@ -386,19 +372,11 @@ private module Cached { /** * This is the local flow predicate that is used in type tracking. - * - * This needs to exclude `localFlowSsaParamInput` due to a performance trick - * in type tracking, where such steps are treated as call steps. */ cached predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) { LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) or - exists(NamedParameter p | - defaultValueFlow(p, nodeFrom) and - nodeTo = LocalFlow::getParameterDefNode(p) - ) - or LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) or // Flow into phi node from read @@ -440,12 +418,10 @@ private module Cached { n instanceof ExprNode and not reachedFromExprOrEntrySsaDef(n) or - // Ensure all entry SSA definitions are local sources -- for parameters, this - // is needed by type tracking - entrySsaDefinition(n) - or - // Needed for flow out in type tracking - n instanceof SynthReturnNode + // Ensure all entry SSA definitions are local sources, except those that correspond + // to parameters (which are themselves local sources) + entrySsaDefinition(n) and + not LocalFlow::localFlowSsaParamInput(_, n) or // Needed for stores in type tracking TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _) @@ -507,7 +483,7 @@ private module Cached { */ cached predicate exprNodeReturnedFromCached(ExprNode e, Callable c) { - exists(ReturningNode r | + exists(ReturnNode r | nodeGetEnclosingCallable(r).asCallable() = c and ( r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or @@ -542,8 +518,6 @@ predicate nodeIsHidden(Node n) { or n instanceof SummaryParameterNode or - n instanceof SynthReturnNode - or n instanceof SynthHashSplatParameterNode or n instanceof SynthHashSplatArgumentNode @@ -658,10 +632,10 @@ private module ParameterNodes { * The value of the `self` parameter at function entry, viewed as a node in a data * flow graph. */ - class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode { + class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode { private MethodBase method; - SelfParameterNode() { this = TSelfParameterNode(method) } + SelfParameterNodeImpl() { this = TSelfParameterNode(method) } final MethodBase getMethod() { result = method } @@ -937,24 +911,26 @@ private class NewCall extends DataFlowCall { NewCall() { this.asCall().getExpr().(MethodCall).getMethodName() = "new" } } -/** A data-flow node that represents a value syntactically returned by a callable. */ -abstract class ReturningNode extends Node { - /** Gets the kind of this return node. */ - abstract ReturnKind getKind(); - - pragma[nomagic] - predicate hasKind(ReturnKind kind, CfgScope scope) { - kind = this.getKind() and - scope = this.(NodeImpl).getCfgScope() - } -} - /** A data-flow node that represents a value returned by a callable. */ abstract class ReturnNode extends Node { /** Gets the kind of this return node. */ abstract ReturnKind getKind(); } +/** A data-flow node that represents a value returned by a callable. */ +abstract class SourceReturnNode extends ReturnNode { + /** Gets the kind of this return node. */ + abstract ReturnKind getKindSource(); // only exists to avoid spurious negative recursion + + final override ReturnKind getKind() { result = this.getKindSource() } + + pragma[nomagic] + predicate hasKind(ReturnKind kind, CfgScope scope) { + kind = this.getKindSource() and + scope = this.(NodeImpl).getCfgScope() + } +} + private module ReturnNodes { private predicate isValid(CfgNodes::ReturningCfgNode node) { exists(ReturningStmt stmt, Callable scope | @@ -976,14 +952,14 @@ private module ReturnNodes { * A data-flow node that represents an expression explicitly returned by * a callable. */ - class ExplicitReturnNode extends ReturningNode, ReturningStatementNode { + class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode { ExplicitReturnNode() { isValid(n) and n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and n.getScope() instanceof Callable } - override ReturnKind getKind() { + override ReturnKind getKindSource() { if n.getNode() instanceof BreakStmt then result instanceof BreakReturnKind else @@ -1012,10 +988,10 @@ private module ReturnNodes { * a callable. An implicit return happens when an expression can be the * last thing that is evaluated in the body of the callable. */ - class ExprReturnNode extends ReturningNode, ExprNode { + class ExprReturnNode extends SourceReturnNode, ExprNode { ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) } - override ReturnKind getKind() { + override ReturnKind getKindSource() { exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() | if isUserDefinedNew(scope) then result instanceof NewReturnKind @@ -1040,7 +1016,7 @@ private module ReturnNodes { * the implicit `self` reference in `@x` will return data stored in the field * `x` out to the call `C.new`. */ - class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode { + class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode { InitializeReturnNode() { exists(Method initialize | this.getCfgScope() = initialize and @@ -1053,32 +1029,6 @@ private module ReturnNodes { override ReturnKind getKind() { result instanceof NewReturnKind } } - /** - * A synthetic data-flow node for joining flow from different syntactic - * returns into a single node. - * - * This node only exists to avoid computing the product of a large fan-in - * with a large fan-out. - */ - class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode { - private CfgScope scope; - private ReturnKind kind; - - SynthReturnNode() { this = TSynthReturnNode(scope, kind) } - - /** Gets a syntactic return node that flows into this synthetic node. */ - pragma[nomagic] - ReturningNode getAnInput() { result.hasKind(kind, scope) } - - override ReturnKind getKind() { result = kind } - - override CfgScope getCfgScope() { result = scope } - - override Location getLocationImpl() { result = scope.getLocation() } - - override string toStringImpl() { result = "return " + kind + " in " + scope } - } - private class SummaryReturnNode extends SummaryNode, ReturnNode { private ReturnKind rk; @@ -1339,9 +1289,6 @@ private import PostUpdateNodes /** A node that performs a type cast. */ class CastNode extends Node { CastNode() { - // ensure that actual return nodes are included in the path graph - this instanceof ReturningNode - or // ensure that all variable assignments are included in the path graph this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index 9d668e0b300..501cf70593e 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -195,10 +195,22 @@ class ParameterNode extends LocalSourceNode, TParameterNode instanceof Parameter /** Gets the parameter corresponding to this node, if any. */ final Parameter getParameter() { result = super.getParameter() } + /** Gets the callable that this parameter belongs to. */ + final Callable getCallable() { result = super.getCfgScope() } + /** Gets the name of the parameter, if any. */ final string getName() { result = this.getParameter().(NamedParameter).getName() } } +/** + * The value of an implicit `self` parameter at function entry, viewed as a node in a data + * flow graph. + */ +class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl { + /** Gets the underlying `self` variable. */ + final SelfVariable getSelfVariable() { result = super.getSelfVariable() } +} + /** * A data-flow node that is a source of local flow. */ @@ -328,9 +340,6 @@ private module Cached { exists(Node mid | hasLocalSource(mid, source) | localFlowStepTypeTracker(mid, sink) or - // Explicitly include the SSA param input step as type-tracking omits this step. - LocalFlow::localFlowSsaParamInput(mid, sink) - or LocalFlow::localFlowSsaParamCaptureInput(mid, sink) ) } @@ -1176,19 +1185,15 @@ class CallableNode extends StmtSequenceNode { result = this.getBlockParameter().getAMethodCall("call") } - /** - * Gets the canonical return node from this callable. - * - * Each callable has exactly one such node, and its location may not correspond - * to any particular return site - consider using `getAReturningNode` to get nodes - * whose locations correspond to return sites. - */ - Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable } - /** * Gets a data flow node whose value is about to be returned by this callable. */ - Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() } + Node getAReturnNode() { result.(ReturnNode).(NodeImpl).getCfgScope() = callable } + + /** + * DEPRECATED. Use `getAReturnNode` instead. + */ + deprecated Node getAReturningNode() { result = this.getAReturnNode() } } /** diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll index 5869486ac91..fcca078f933 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveRecord.qll @@ -7,7 +7,6 @@ private import codeql.ruby.Concepts private import codeql.ruby.controlflow.CfgNodes private import codeql.ruby.DataFlow private import codeql.ruby.dataflow.internal.DataFlowDispatch -private import codeql.ruby.dataflow.internal.DataFlowPrivate private import codeql.ruby.ApiGraphs private import codeql.ruby.frameworks.Stdlib private import codeql.ruby.frameworks.Core @@ -319,19 +318,19 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation // A `self` reference that may resolve to an active record model object private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation, - SsaSelfDefinitionNode + DataFlow::SelfParameterNode { private ActiveRecordModelClass cls; ActiveRecordModelClassSelfReference() { exists(MethodBase m | - m = this.getCfgScope() and + m = this.getCallable() and m.getEnclosingModule() = cls and m = cls.getAMethod() ) and // In a singleton method, `self` refers to the class itself rather than an // instance of that class - not this.getSelfScope() instanceof SingletonMethod + not this.getSelfVariable().getDeclaringScope() instanceof SingletonMethod } final override ActiveRecordModelClass getClass() { result = cls } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll b/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll index 693cd4c197c..49281c609bd 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Rack.qll @@ -21,7 +21,7 @@ module Rack { AppCandidate() { call = this.getInstanceMethod("call") and call.getNumberOfParameters() = 1 and - call.getReturn() = trackRackResponse() + call.getAReturnNode() = trackRackResponse() } /** diff --git a/ruby/ql/lib/codeql/ruby/frameworks/actioncontroller/Filters.qll b/ruby/ql/lib/codeql/ruby/frameworks/actioncontroller/Filters.qll index 97ea8de1cd6..bc1766580e9 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/actioncontroller/Filters.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/actioncontroller/Filters.qll @@ -412,9 +412,7 @@ module Filters { /** * Holds if `n` is the self parameter of method `m`. */ - private predicate selfParameter(DataFlowPrivate::SelfParameterNode n, Method m) { - m = n.getMethod() - } + private predicate selfParameter(DataFlow::SelfParameterNode n, Method m) { m = n.getCallable() } /** * A class defining additional jump steps arising from filters. diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index d4d9e1f31f5..25521f5f1a5 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -224,6 +224,50 @@ 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 stepProj(TypeTrackingNode nodeFrom, StepSummary summary) { + step(nodeFrom, _, summary) +} + +bindingset[nodeFrom, t] +pragma[inline_late] +pragma[noopt] +private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { + exists(StepSummary summary | + stepProj(nodeFrom, summary) and + result = t.append(summary) and + 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 smallstepProj(Node nodeFrom, StepSummary summary) { + smallstep(nodeFrom, _, summary) +} + +bindingset[nodeFrom, t] +pragma[inline_late] +pragma[noopt] +private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) { + exists(StepSummary summary | + smallstepProj(nodeFrom, summary) and + result = t.append(summary) and + smallstep(nodeFrom, nodeTo, summary) + ) +} + /** * Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`. * @@ -298,21 +342,50 @@ class StepSummary extends TStepSummary { module StepSummary { /** * Gets the summary that corresponds to having taken a forwards - * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. + * 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. + * This predicate should normally not be used; consider using `step` + * instead. + */ + predicate stepCall = Cached::stepCall/3; + + /** + * 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. + */ + predicate stepNoCall = Cached::stepNoCall/3; + + /** + * Gets the summary that corresponds to having taken a forwards + * heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. */ - pragma[inline] predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { stepNoCall(nodeFrom, nodeTo, summary) or stepCall(nodeFrom, nodeTo, summary) } + /** + * 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. + */ + predicate smallstepNoCall = Cached::smallstepNoCall/3; + + /** + * 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. + */ + predicate smallstepCall = Cached::smallstepCall/3; + /** * Gets the summary that corresponds to having taken a forwards * local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`. @@ -320,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 @@ -431,10 +503,7 @@ class TypeTracker extends TTypeTracker { */ pragma[inline] TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) { - exists(StepSummary summary | - StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and - result = this.append(pragma[only_bind_into](summary)) - ) + result = stepInlineLate(this, nodeFrom, nodeTo) } /** @@ -463,10 +532,7 @@ class TypeTracker extends TTypeTracker { */ pragma[inline] TypeTracker smallstep(Node nodeFrom, Node nodeTo) { - exists(StepSummary summary | - StepSummary::smallstep(nodeFrom, nodeTo, summary) and - result = this.append(summary) - ) + result = smallstepInlineLate(this, nodeFrom, nodeTo) or simpleLocalFlowStep(nodeFrom, nodeTo) and result = this @@ -481,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. * @@ -564,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) } /** @@ -596,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 @@ -635,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/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 55ec26258d6..7725d201fba 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -79,7 +79,7 @@ predicate jumpStep = DataFlowPrivate::jumpStep/2; /** Holds if there is direct flow from `param` to a return. */ pragma[nomagic] private predicate flowThrough(DataFlowPublic::ParameterNode param) { - exists(DataFlowPrivate::ReturningNode returnNode, DataFlowDispatch::ReturnKind rk | + exists(DataFlowPrivate::SourceReturnNode returnNode, DataFlowDispatch::ReturnKind rk | param.flowsTo(returnNode) and returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope()) | @@ -221,15 +221,7 @@ predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::Param * recursion (or, at best, terrible performance), since identifying calls to library * methods is done using API graphs (which uses type tracking). */ -predicate callStep(Node nodeFrom, Node nodeTo) { - callStep(_, nodeFrom, nodeTo) - or - // In normal data-flow, this will be a local flow step. But for type tracking - // we model it as a call step, in order to avoid computing a potential - // self-cross product of all calls to a function that returns one of its parameters - // (only to later filter that flow out using `TypeTracker::append`). - DataFlowPrivate::LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo) -} +predicate callStep(Node nodeFrom, Node nodeTo) { callStep(_, nodeFrom, nodeTo) } /** * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. @@ -241,19 +233,13 @@ predicate callStep(Node nodeFrom, Node nodeTo) { predicate returnStep(Node nodeFrom, Node nodeTo) { exists(ExprNodes::CallCfgNode call | nodeFrom instanceof DataFlowPrivate::ReturnNode and + not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode and nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and // deliberately do not include `getInitializeTarget`, since calls to `new` should not // get the return value from `initialize`. Any fields being set in the initializer // will reach all reads via `callStep` and `localFieldStep`. nodeTo.asExpr().getNode() = call.getNode() ) - or - // In normal data-flow, this will be a local flow step. But for type tracking - // we model it as a returning flow step, in order to avoid computing a potential - // self-cross product of all calls to a function that returns one of its parameters - // (only to later filter that flow out using `TypeTracker::append`). - nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom and - not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode } /** @@ -609,26 +595,15 @@ private DataFlow::Node evaluateSummaryComponentStackLocal( pragma[only_bind_out](tail)) and stack = SCS::push(pragma[only_bind_out](head), pragma[only_bind_out](tail)) | - exists( - DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos, - DataFlowPrivate::ParameterNodeImpl p - | + exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos | head = SummaryComponent::parameter(apos) and DataFlowDispatch::parameterMatch(ppos, apos) and - p.isSourceParameterOf(prev.asExpr().getExpr(), ppos) and - // We need to include both `p` and the SSA definition for `p`, since in type-tracking - // the step from `p` to the SSA definition is considered a call step. - result = - [p.(DataFlow::Node), DataFlowPrivate::LocalFlow::getParameterDefNode(p.getParameter())] + result.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(prev.asExpr().getExpr(), ppos) ) or - exists(DataFlowPrivate::SynthReturnNode ret | - head = SummaryComponent::return() and - ret.getCfgScope() = prev.asExpr().getExpr() and - // We need to include both `ret` and `ret.getAnInput()`, since in type-tracking - // the step from `ret.getAnInput()` to `ret` is considered a return step. - result = [ret.(DataFlow::Node), ret.getAnInput()] - ) + head = SummaryComponent::return() and + result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() = + prev.asExpr().getExpr() or exists(DataFlow::ContentSet content | head = SummaryComponent::withoutContent(content) and diff --git a/ruby/ql/test/library-tests/dataflow/local/Nodes.ql b/ruby/ql/test/library-tests/dataflow/local/Nodes.ql index 2f89f667625..88ee1c5b208 100644 --- a/ruby/ql/test/library-tests/dataflow/local/Nodes.ql +++ b/ruby/ql/test/library-tests/dataflow/local/Nodes.ql @@ -2,7 +2,7 @@ import codeql.ruby.AST import codeql.ruby.dataflow.internal.DataFlowPrivate import codeql.ruby.dataflow.internal.DataFlowDispatch -query predicate ret(ReturningNode node) { any() } +query predicate ret(SourceReturnNode node) { any() } query predicate arg(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) { n.argumentOf(call, pos) and diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected index bc7b35eb941..9a4277f4c0e 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -1,129 +1,72 @@ track | type_tracker.rb:1:1:10:3 | self (Container) | type tracker without call steps | type_tracker.rb:1:1:10:3 | self (Container) | -| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) | | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self in positional | -| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) | | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self in keyword | | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker without call steps | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | | type_tracker.rb:2:5:5:7 | &block | type tracker without call steps | type_tracker.rb:2:5:5:7 | &block | | type_tracker.rb:2:5:5:7 | field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | field= | -| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) | -| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:2:5:5:7 | self (field=) | type tracker without call steps | type_tracker.rb:2:5:5:7 | self (field=) | -| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) | -| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= | -| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:2:16:2:18 | val | -| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:3:9:3:23 | call to puts | type tracker without call steps | type_tracker.rb:3:9:3:23 | call to puts | | type_tracker.rb:3:14:3:23 | call to field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | | type_tracker.rb:4:9:4:14 | @field | type tracker without call steps | type_tracker.rb:4:9:4:14 | @field | | type_tracker.rb:7:5:9:7 | &block | type tracker without call steps | type_tracker.rb:7:5:9:7 | &block | | type_tracker.rb:7:5:9:7 | field | type tracker without call steps | type_tracker.rb:7:5:9:7 | field | -| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | -| type_tracker.rb:7:5:9:7 | self (field) | type tracker without call steps | type_tracker.rb:7:5:9:7 | self (field) | -| type_tracker.rb:7:5:9:7 | self in field | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:7:5:9:7 | self in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field | | type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:12:1:16:3 | &block | type tracker without call steps | type_tracker.rb:12:1:16:3 | &block | | type_tracker.rb:12:1:16:3 | m | type tracker without call steps | type_tracker.rb:12:1:16:3 | m | -| type_tracker.rb:12:1:16:3 | return return in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m | -| type_tracker.rb:12:1:16:3 | self (m) | type tracker without call steps | type_tracker.rb:12:1:16:3 | self (m) | -| type_tracker.rb:12:1:16:3 | self in m | type tracker with call steps | type_tracker.rb:12:1:16:3 | self (m) | | type_tracker.rb:12:1:16:3 | self in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | self in m | | type_tracker.rb:13:5:13:7 | var | type tracker without call steps | type_tracker.rb:13:5:13:7 | var | | type_tracker.rb:13:11:13:19 | Container | type tracker without call steps | type_tracker.rb:13:11:13:19 | Container | -| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) | | type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self in field= | -| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:13:11:13:23 | call to new | type tracker without call steps | type_tracker.rb:13:11:13:23 | call to new | -| type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:14:5:14:7 | [post] var | type tracker without call steps | type_tracker.rb:14:5:14:7 | [post] var | | type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= | | type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val | -| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field | -| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= | | type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:17:14:23 | "hello" | | type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content attribute field | type_tracker.rb:14:5:14:7 | [post] var | | type_tracker.rb:14:17:14:23 | __synth__0 | type tracker without call steps | type_tracker.rb:14:17:14:23 | __synth__0 | -| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m | | type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:15:5:15:18 | call to puts | | type_tracker.rb:15:10:15:18 | call to field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:18:1:21:3 | &block | type tracker without call steps | type_tracker.rb:18:1:21:3 | &block | | type_tracker.rb:18:1:21:3 | positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | positional | -| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional | -| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional | -| type_tracker.rb:18:1:21:3 | self (positional) | type tracker without call steps | type_tracker.rb:18:1:21:3 | self (positional) | -| type_tracker.rb:18:1:21:3 | self in positional | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) | | type_tracker.rb:18:1:21:3 | self in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | self in positional | -| type_tracker.rb:18:16:18:17 | p1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:18:20:18:21 | p2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:19:5:19:11 | call to puts | type tracker without call steps | type_tracker.rb:19:5:19:11 | call to puts | -| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional | | type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:20:5:20:11 | call to puts | | type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional | | type_tracker.rb:23:1:23:16 | call to positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional | | type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:23:12:23:12 | 1 | type tracker without call steps | type_tracker.rb:23:12:23:12 | 1 | | type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:23:15:23:15 | 2 | type tracker without call steps | type_tracker.rb:23:15:23:15 | 2 | | type_tracker.rb:25:1:28:3 | &block | type tracker without call steps | type_tracker.rb:25:1:28:3 | &block | | type_tracker.rb:25:1:28:3 | **kwargs | type tracker without call steps | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:25:1:28:3 | keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:32:1:32:27 | call to keyword | -| type_tracker.rb:25:1:28:3 | self (keyword) | type tracker without call steps | type_tracker.rb:25:1:28:3 | self (keyword) | -| type_tracker.rb:25:1:28:3 | self in keyword | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) | | type_tracker.rb:25:1:28:3 | self in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | self in keyword | -| type_tracker.rb:25:13:25:14 | p1 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:25:18:25:19 | p2 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:26:5:26:11 | call to puts | type tracker without call steps | type_tracker.rb:26:5:26:11 | call to puts | -| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword | | type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:27:5:27:11 | call to puts | | type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword | | type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword | @@ -134,14 +77,12 @@ track | type_tracker.rb:30:9:30:10 | :p1 | type tracker without call steps | type_tracker.rb:30:9:30:10 | :p1 | | type_tracker.rb:30:9:30:13 | Pair | type tracker without call steps | type_tracker.rb:30:9:30:13 | Pair | | type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps | type_tracker.rb:30:13:30:13 | 3 | | type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps with content element :p1 | type_tracker.rb:30:1:30:21 | ** | | type_tracker.rb:30:16:30:17 | :p2 | type tracker without call steps | type_tracker.rb:30:16:30:17 | :p2 | | type_tracker.rb:30:16:30:20 | Pair | type tracker without call steps | type_tracker.rb:30:16:30:20 | Pair | | type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps | type_tracker.rb:30:20:30:20 | 4 | | type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps with content element :p2 | type_tracker.rb:30:1:30:21 | ** | @@ -151,14 +92,12 @@ track | type_tracker.rb:31:9:31:10 | :p2 | type tracker without call steps | type_tracker.rb:31:9:31:10 | :p2 | | type_tracker.rb:31:9:31:13 | Pair | type tracker without call steps | type_tracker.rb:31:9:31:13 | Pair | | type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps | type_tracker.rb:31:13:31:13 | 5 | | type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps with content element :p2 | type_tracker.rb:31:1:31:21 | ** | | type_tracker.rb:31:16:31:17 | :p1 | type tracker without call steps | type_tracker.rb:31:16:31:17 | :p1 | | type_tracker.rb:31:16:31:20 | Pair | type tracker without call steps | type_tracker.rb:31:16:31:20 | Pair | | type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps | type_tracker.rb:31:20:31:20 | 6 | | type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps with content element :p1 | type_tracker.rb:31:1:31:21 | ** | @@ -168,68 +107,33 @@ track | type_tracker.rb:32:9:32:11 | :p2 | type tracker without call steps | type_tracker.rb:32:9:32:11 | :p2 | | type_tracker.rb:32:9:32:16 | Pair | type tracker without call steps | type_tracker.rb:32:9:32:16 | Pair | | type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps | type_tracker.rb:32:16:32:16 | 7 | | type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps with content element :p2 | type_tracker.rb:32:1:32:27 | ** | | type_tracker.rb:32:19:32:21 | :p1 | type tracker without call steps | type_tracker.rb:32:19:32:21 | :p1 | | type_tracker.rb:32:19:32:26 | Pair | type tracker without call steps | type_tracker.rb:32:19:32:26 | Pair | | type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps | type_tracker.rb:32:26:32:26 | 8 | | type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps with content element :p1 | type_tracker.rb:32:1:32:27 | ** | | type_tracker.rb:34:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block | -| type_tracker.rb:34:1:53:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray | | type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | -| type_tracker.rb:34:23:34:23 | y | type tracker with call steps | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | -| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | -| type_tracker.rb:34:26:34:26 | z | type tracker with call steps | type_tracker.rb:34:26:34:26 | z | -| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp | @@ -315,46 +219,33 @@ track | type_tracker.rb:50:5:50:10 | array4 | type tracker without call steps | type_tracker.rb:50:5:50:10 | array4 | | type_tracker.rb:50:14:50:26 | Array | type tracker without call steps | type_tracker.rb:50:14:50:26 | Array | | type_tracker.rb:50:14:50:26 | call to [] | type tracker without call steps | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:50:15:50:15 | 1 | | type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:50:17:50:17 | 2 | | type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:50:19:50:19 | 3 | | type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:50:21:50:21 | 4 | | type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:50:23:50:23 | 5 | | type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:50:14:50:26 | call to [] | -| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:50:25:50:25 | 6 | | type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:51:5:51:10 | [post] array4 | type tracker without call steps | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:51:5:51:13 | call to []= | type tracker without call steps | type_tracker.rb:51:5:51:13 | call to []= | | type_tracker.rb:51:17:51:19 | __synth__0 | type tracker without call steps | type_tracker.rb:51:17:51:19 | __synth__0 | -| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | trackEnd | type_tracker.rb:1:1:10:3 | self (Container) | type_tracker.rb:1:1:10:3 | self (Container) | @@ -373,15 +264,6 @@ trackEnd | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type_tracker.rb:32:1:32:27 | self | | type_tracker.rb:2:5:5:7 | &block | type_tracker.rb:2:5:5:7 | &block | | type_tracker.rb:2:5:5:7 | field= | type_tracker.rb:2:5:5:7 | field= | -| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:2:5:5:7 | self (field=) | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:9:3:23 | self | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:14:3:17 | self | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:4:9:4:14 | self | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self (field) | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self (field=) | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self in field= | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:3:9:3:23 | self | @@ -390,25 +272,14 @@ trackEnd | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:8:9:8:14 | self | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts | | type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field | @@ -416,23 +287,14 @@ trackEnd | type_tracker.rb:7:5:9:7 | &block | type_tracker.rb:7:5:9:7 | &block | | type_tracker.rb:7:5:9:7 | field | type_tracker.rb:1:1:10:3 | Container | | type_tracker.rb:7:5:9:7 | field | type_tracker.rb:7:5:9:7 | field | -| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:7:5:9:7 | return return in field | -| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:15:10:15:18 | call to field | -| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:7:5:9:7 | self (field) | -| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self in field | | type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:3:14:3:23 | call to field | -| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:7:5:9:7 | return return in field | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:8:9:8:14 | @field | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:12:1:16:3 | &block | type_tracker.rb:12:1:16:3 | &block | | type_tracker.rb:12:1:16:3 | m | type_tracker.rb:12:1:16:3 | m | -| type_tracker.rb:12:1:16:3 | return return in m | type_tracker.rb:12:1:16:3 | return return in m | -| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:12:1:16:3 | self (m) | -| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:15:5:15:18 | self | | type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self (m) | | type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self in m | | type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:15:5:15:18 | self | @@ -470,16 +332,10 @@ trackEnd | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | __synth__0 | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:14:17:14:23 | __synth__0 | type_tracker.rb:14:17:14:23 | __synth__0 | -| type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:12:1:16:3 | return return in m | | type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:15:5:15:18 | call to puts | | type_tracker.rb:15:10:15:18 | call to field | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:18:1:21:3 | &block | type_tracker.rb:18:1:21:3 | &block | | type_tracker.rb:18:1:21:3 | positional | type_tracker.rb:18:1:21:3 | positional | -| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:18:1:21:3 | return return in positional | -| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:23:1:23:16 | call to positional | -| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:18:1:21:3 | self (positional) | -| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:19:5:19:11 | self | -| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:20:5:20:11 | self | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self (positional) | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self in positional | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:19:5:19:11 | self | @@ -487,17 +343,12 @@ trackEnd | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 | | type_tracker.rb:19:5:19:11 | call to puts | type_tracker.rb:19:5:19:11 | call to puts | -| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:18:1:21:3 | return return in positional | | type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:20:5:20:11 | call to puts | | type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:23:1:23:16 | call to positional | | type_tracker.rb:23:1:23:16 | call to positional | type_tracker.rb:23:1:23:16 | call to positional | @@ -512,13 +363,6 @@ trackEnd | type_tracker.rb:25:1:28:3 | &block | type_tracker.rb:25:1:28:3 | &block | | type_tracker.rb:25:1:28:3 | **kwargs | type_tracker.rb:25:1:28:3 | **kwargs | | type_tracker.rb:25:1:28:3 | keyword | type_tracker.rb:25:1:28:3 | keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:25:1:28:3 | return return in keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:30:1:30:21 | call to keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:31:1:31:21 | call to keyword | -| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:32:1:32:27 | call to keyword | -| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:25:1:28:3 | self (keyword) | -| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:26:5:26:11 | self | -| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:27:5:27:11 | self | | type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self (keyword) | | type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self in keyword | | type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:26:5:26:11 | self | @@ -526,17 +370,12 @@ trackEnd | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:26:5:26:11 | call to puts | type_tracker.rb:26:5:26:11 | call to puts | -| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:25:1:28:3 | return return in keyword | | type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:27:5:27:11 | call to puts | | type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:30:1:30:21 | call to keyword | | type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:31:1:31:21 | call to keyword | @@ -587,80 +426,45 @@ trackEnd | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 | | type_tracker.rb:34:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block | -| type_tracker.rb:34:1:53:3 | return return in throughArray | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray | | type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y | -| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y | -| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y | -| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y | | type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | -| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | -| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z | | type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z | | type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp | | type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array | @@ -743,29 +547,22 @@ trackEnd | type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:51:5:51:10 | array4 | | type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:52:5:52:10 | array4 | -| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:50:15:50:15 | 1 | | type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:50:17:50:17 | 2 | | type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:50:19:50:19 | 3 | | type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:50:21:50:21 | 4 | | type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:50:23:50:23 | 5 | | type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:50:25:50:25 | 6 | | type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:52:5:52:10 | array4 | | type_tracker.rb:51:5:51:13 | call to []= | type_tracker.rb:51:5:51:13 | call to []= | | type_tracker.rb:51:17:51:19 | __synth__0 | type_tracker.rb:51:17:51:19 | __synth__0 | -| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:52:5:52:13 | ...[...] | forwardButNoBackwardFlow backwardButNoForwardFlow