mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +01:00
Python: Use CallGraphConstruction in call graph construction
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,189 +465,105 @@ private predicate ignoreForCallGraph(File f) {
|
||||
f.getAbsolutePath().matches("%/site-packages/sympy/%")
|
||||
}
|
||||
|
||||
bindingset[n]
|
||||
pragma[inline_late]
|
||||
private predicate includeInCallGraph(TypeTrackingNode n) {
|
||||
not ignoreForCallGraph(n.getLocation().getFile())
|
||||
}
|
||||
private module TrackFunctionInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Function;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate stepCallProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
StepSummary::stepCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[t, summary]
|
||||
pragma[inline_late]
|
||||
private TypeTracker append(TypeTracker t, StepSummary summary) { result = t.append(summary) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the function `func`.
|
||||
*/
|
||||
private TypeTrackingNode functionTracker(TypeTracker t, Function func) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
(
|
||||
result.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()
|
||||
)
|
||||
predicate start(Node start, Function func) {
|
||||
start.asExpr() = func.getDefinition()
|
||||
or
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(functionTracker(t2, func), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(functionTrackerCall(t, func, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
// when a function is decorated, it's the result of the (last) decorator call that
|
||||
// is used
|
||||
start.asExpr() = func.getDefinition().(FunctionExpr).getADecoratorCall()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode functionTrackerCall(TypeTracker t, Function func, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = functionTracker(t2, func) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
(
|
||||
result.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()
|
||||
or
|
||||
// `type(obj)`, where obj is an instance of this class
|
||||
result = getTypeCall() and
|
||||
result.(CallCfgNode).getArg(0) = classInstanceTracker(cls)
|
||||
)
|
||||
private module TrackClassInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Class;
|
||||
|
||||
predicate start(Node start, Class cls) {
|
||||
start.asExpr() = cls.getParent()
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(classTracker(t2, cls), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(classTrackerCall(t, cls, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
// when a class is decorated, it's the result of the (last) decorator call that
|
||||
// is used
|
||||
start.asExpr() = cls.getParent().getADecoratorCall()
|
||||
or
|
||||
// `type(obj)`, where obj is an instance of this class
|
||||
start = getTypeCall() and
|
||||
start.(CallCfgNode).getArg(0) = classInstanceTracker(cls)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode classTrackerCall(TypeTracker t, Class cls, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = classTracker(t2, cls) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
resolveClassCall(result.(CallCfgNode).asCfgNode(), cls)
|
||||
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
|
||||
t.start() and
|
||||
exists(Class classUsedInSuper |
|
||||
fromSuperNewCall(result.(CallCfgNode).asCfgNode(), classUsedInSuper, _, _) and
|
||||
fromSuperNewCall(start.(CallCfgNode).asCfgNode(), classUsedInSuper, _, _) and
|
||||
classUsedInSuper = getADirectSuperclass*(cls)
|
||||
)
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(classInstanceTracker(t2, cls), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(classInstanceTrackerCall(t, cls, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode classInstanceTrackerCall(TypeTracker t, Class cls, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = classInstanceTracker(t2, cls) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
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)
|
||||
|
|
||||
result.asExpr() = func.getArg(0)
|
||||
start.asExpr() = func.getArg(0)
|
||||
)
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(selfTracker(t2, classWithMethod), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(selfTrackerCall(t, classWithMethod, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode selfTrackerCall(TypeTracker t, Class classWithMethod, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = selfTracker(t2, classWithMethod) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
predicate filter(Node n) {
|
||||
ignoreForCallGraph(n.getLocation().getFile())
|
||||
or
|
||||
n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -654,53 +571,32 @@ private TypeTrackingNode selfTrackerCall(TypeTracker t, Class classWithMethod, S
|
||||
* 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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
(
|
||||
exists(Function func |
|
||||
func = classWithMethod.getAMethod() and
|
||||
isClassmethod(func)
|
||||
|
|
||||
result.asExpr() = func.getArg(0)
|
||||
)
|
||||
or
|
||||
// type(self)
|
||||
result = getTypeCall() and
|
||||
result.(CallCfgNode).getArg(0) = selfTracker(classWithMethod)
|
||||
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)
|
||||
|
|
||||
start.asExpr() = func.getArg(0)
|
||||
)
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(clsArgumentTracker(t2, classWithMethod), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(clsArgumentTrackerCall(t, classWithMethod, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
// type(self)
|
||||
start = getTypeCall() and
|
||||
start.(CallCfgNode).getArg(0) = selfTracker(classWithMethod)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode clsArgumentTrackerCall(
|
||||
TypeTracker t, Class classWithMethod, StepSummary summary
|
||||
) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = clsArgumentTracker(t2, classWithMethod) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
predicate filter(Node n) {
|
||||
ignoreForCallGraph(n.getLocation().getFile())
|
||||
or
|
||||
n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -709,46 +605,28 @@ private TypeTrackingNode clsArgumentTrackerCall(
|
||||
* 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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
private module TrackSuperCallNoArgumentInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Function;
|
||||
|
||||
predicate start(Node start, Function func) {
|
||||
not isStaticmethod(func) and
|
||||
exists(CallCfgNode call | result = call |
|
||||
exists(CallCfgNode call | start = call |
|
||||
call = getSuperCall() and
|
||||
not exists(call.getArg(_)) and
|
||||
call.getScope() = func
|
||||
)
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(superCallNoArgumentTracker(t2, func), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(superCallNoArgumentTrackerCall(t, func, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode superCallNoArgumentTrackerCall(
|
||||
TypeTracker t, Function func, StepSummary summary
|
||||
) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = functionTracker(t2, func) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
predicate filter(Node n) {
|
||||
ignoreForCallGraph(n.getLocation().getFile())
|
||||
or
|
||||
n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -756,45 +634,30 @@ private TypeTrackingNode superCallNoArgumentTrackerCall(
|
||||
* 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) {
|
||||
includeInCallGraph(result) and
|
||||
(
|
||||
t.start() and
|
||||
exists(CallCfgNode call | result = call |
|
||||
call = getSuperCall() and
|
||||
call.getArg(0) = classTracker(cls) and
|
||||
call.getArg(1) = obj
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
class State = CallCfgNode;
|
||||
|
||||
predicate start(Node start, CallCfgNode call) {
|
||||
superCall(call, _, _) and
|
||||
start = call
|
||||
}
|
||||
|
||||
predicate filter(Node n) {
|
||||
ignoreForCallGraph(n.getLocation().getFile())
|
||||
or
|
||||
not result.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf())) and
|
||||
(
|
||||
exists(TypeTracker t2 | t = t2.stepNoCall(superCallTwoArgumentTracker(t2, cls, obj), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(superCallTwoArgumentTrackerCall(t, cls, obj, summary), result, summary)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode superCallTwoArgumentTrackerCall(
|
||||
TypeTracker t, Class cls, Node obj, StepSummary summary
|
||||
) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = superCallTwoArgumentTracker(t2, cls, obj) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
n.(ParameterNodeImpl).isParameterOf(_, any(ParameterPosition pp | pp.isSelf()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -802,7 +665,12 @@ private TypeTrackingNode superCallTwoArgumentTrackerCall(
|
||||
* 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)
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -946,35 +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 | t = t2.stepNoCall(attrReadTracker(t2, attr), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
StepSummary::stepCall(attrReadTrackerCall(t, attr, summary), result, summary)
|
||||
)
|
||||
}
|
||||
private module TrackAttrReadInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = AttrRead;
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTrackingNode attrReadTrackerCall(TypeTracker t, AttrRead attr, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = attrReadTracker(t2, attr) and
|
||||
stepCallProj(result, summary) and
|
||||
t = append(t2, summary)
|
||||
)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user