mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Merge pull request #12964 from hvitved/ruby/remove-synth-returns
Ruby: Remove canonical return nodes
This commit is contained in:
@@ -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<TrackFunctionInput>::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<TrackClassInput>::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<TrackClassInstanceInput>::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<TrackSelfInput>::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<TrackClsArgumentInput>::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<TrackSuperCallNoArgumentInput>::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<TrackSuperCallTwoArgumentInput>::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<TrackAttrReadInput>::track(attr)
|
||||
.(LocalSourceNode)
|
||||
.flowsTo(result)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// call and argument resolution
|
||||
|
||||
@@ -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<InputSig Input> {
|
||||
pragma[nomagic]
|
||||
private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepNoCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker stepNoCallInlineLate(
|
||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
stepNoCallProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
Input::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[state]
|
||||
pragma[inline_late]
|
||||
private Input::StateProj stateProjInlineLate(Input::State state) {
|
||||
result = Input::stateProj(state)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private Node track(Input::State state, TypeTracker t) {
|
||||
t.start() and Input::start(result, state)
|
||||
or
|
||||
exists(Input::StateProj stateProj |
|
||||
stateProj = stateProjInlineLate(state) and
|
||||
not Input::filter(result, stateProj)
|
||||
|
|
||||
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
Input::stepCall(trackCall(state, t, summary), result, summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[t, summary]
|
||||
pragma[inline_late]
|
||||
private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
|
||||
result = t.append(summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = track(state, t2) and
|
||||
stepCallProj(result, summary) and
|
||||
t = appendInlineLate(t2, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that can be reached from _some_ start node in state `state`. */
|
||||
pragma[nomagic]
|
||||
Node track(Input::State state) { result = track(state, TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
|
||||
module Simple {
|
||||
/** The input to call graph construction. */
|
||||
signature module InputSig {
|
||||
/** A state to track during type tracking. */
|
||||
class State;
|
||||
|
||||
/** Holds if type tracking should start at `start` in state `state`. */
|
||||
predicate start(Node start, State state);
|
||||
|
||||
/** Holds if type tracking should stop at `n`. */
|
||||
predicate filter(Node n);
|
||||
}
|
||||
|
||||
/** Provides the `track` predicate for use in call graph construction. */
|
||||
module Make<InputSig Input> {
|
||||
private module I implements CallGraphConstruction::InputSig {
|
||||
private import codeql.util.Unit
|
||||
|
||||
class State = Input::State;
|
||||
|
||||
predicate start(Node start, State state) { Input::start(start, state) }
|
||||
|
||||
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj = Unit;
|
||||
|
||||
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||
|
||||
predicate filter(Node n, Unit u) {
|
||||
Input::filter(n) and
|
||||
exists(u)
|
||||
}
|
||||
}
|
||||
|
||||
import CallGraphConstruction::Make<I>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user