Python: Use CallGraphConstruction in call graph construction

This commit is contained in:
Tom Hvitved
2023-06-06 16:09:10 +02:00
parent 88c5700c24
commit 48ac3e58ee

View File

@@ -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