Skip to content

Commit 36e8b8b

Browse files
committed
Python: Add call-graph to cached dataflow stage
I didn't do any performance investigation on this, since it just seems so much like the right approach.
1 parent fc05455 commit 36e8b8b

File tree

2 files changed

+88
-76
lines changed

2 files changed

+88
-76
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 83 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ private import DataFlowPublic
3737
private import DataFlowPrivate
3838
private import FlowSummaryImpl as FlowSummaryImpl
3939
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
40+
private import semmle.python.internal.CachedStages
4041

4142
newtype TParameterPosition =
4243
/** Used for `self` in methods, and `cls` in classmethods. */
@@ -1041,20 +1042,23 @@ predicate resolveClassInstanceCall(CallNode call, Function target, Node self) {
10411042
*/
10421043
cached
10431044
predicate resolveCall(ControlFlowNode call, Function target, CallType type) {
1044-
type instanceof CallTypePlainFunction and
1045-
call.(CallNode).getFunction() = functionTracker(target).asCfgNode() and
1046-
not exists(Class cls | cls.getAMethod() = target)
1047-
or
1048-
resolveMethodCall(call, target, type, _)
1049-
or
1050-
type instanceof CallTypeClass and
1051-
exists(Class cls |
1052-
resolveClassCall(call, cls) and
1053-
target = invokedFunctionFromClassConstruction(cls, _)
1045+
Stages::DataFlow::ref() and
1046+
(
1047+
type instanceof CallTypePlainFunction and
1048+
call.(CallNode).getFunction() = functionTracker(target).asCfgNode() and
1049+
not exists(Class cls | cls.getAMethod() = target)
1050+
or
1051+
resolveMethodCall(call, target, type, _)
1052+
or
1053+
type instanceof CallTypeClass and
1054+
exists(Class cls |
1055+
resolveClassCall(call, cls) and
1056+
target = invokedFunctionFromClassConstruction(cls, _)
1057+
)
1058+
or
1059+
type instanceof CallTypeClassInstanceCall and
1060+
resolveClassInstanceCall(call, target, _)
10541061
)
1055-
or
1056-
type instanceof CallTypeClassInstanceCall and
1057-
resolveClassInstanceCall(call, target, _)
10581062
}
10591063

10601064
// =============================================================================
@@ -1119,77 +1123,80 @@ cached
11191123
predicate getCallArg(
11201124
ControlFlowNode call, Function target, CallType type, Node arg, ArgumentPosition apos
11211125
) {
1122-
// normal calls with a real call node
1123-
resolveCall(call, target, type) and
1124-
call instanceof CallNode and
1126+
Stages::DataFlow::ref() and
11251127
(
1126-
type instanceof CallTypePlainFunction and
1127-
normalCallArg(call, arg, apos)
1128-
or
1129-
// self argument for normal method calls
1130-
type instanceof CallTypeNormalMethod and
1131-
apos.isSelf() and
1132-
resolveMethodCall(call, target, type, arg) and
1133-
// dataflow lib has requirement that arguments and calls are in same enclosing callable.
1134-
exists(CfgNode cfgNode | cfgNode.getNode() = call |
1135-
cfgNode.getEnclosingCallable() = arg.getEnclosingCallable()
1136-
)
1137-
or
1138-
// cls argument for classmethod calls
1139-
type instanceof CallTypeClassMethod and
1140-
apos.isSelf() and
1141-
resolveMethodCall(call, target, type, arg) and
1142-
(arg = classTracker(_) or arg = clsTracker(_)) and
1143-
// dataflow lib has requirement that arguments and calls are in same enclosing callable.
1144-
exists(CfgNode cfgNode | cfgNode.getNode() = call |
1145-
cfgNode.getEnclosingCallable() = arg.getEnclosingCallable()
1146-
)
1147-
or
1148-
// normal arguments for method calls
1149-
(
1150-
type instanceof CallTypeNormalMethod or
1151-
type instanceof CallTypeStaticMethod or
1152-
type instanceof CallTypeClassMethod
1153-
) and
1154-
normalCallArg(call, arg, apos)
1155-
or
1156-
// method as plain function call.
1157-
//
1158-
// argument index 0 of call has position self (and MUST be given as positional
1159-
// argument in call). This also means that call-arguments are shifted by 1, such
1160-
// that argument index 1 of call has argument position 0
1161-
type instanceof CallTypeMethodAsPlainFunction and
1128+
// normal calls with a real call node
1129+
resolveCall(call, target, type) and
1130+
call instanceof CallNode and
11621131
(
1163-
apos.isSelf() and arg.asCfgNode() = call.(CallNode).getArg(0)
1164-
or
1165-
not apos.isPositional(_) and normalCallArg(call, arg, apos)
1132+
type instanceof CallTypePlainFunction and
1133+
normalCallArg(call, arg, apos)
11661134
or
1167-
exists(ArgumentPosition normalPos, int index |
1168-
apos.isPositional(index - 1) and
1169-
normalPos.isPositional(index) and
1170-
normalCallArg(call, arg, normalPos)
1171-
)
1172-
)
1173-
or
1174-
// class call
1175-
type instanceof CallTypeClass and
1176-
(
1177-
// only pass synthetic node for created object to __init__, and not __new__ since
1178-
// __new__ is a classmethod.
1179-
target = invokedFunctionFromClassConstruction(_, "__init__") and
1135+
// self argument for normal method calls
1136+
type instanceof CallTypeNormalMethod and
11801137
apos.isSelf() and
1181-
arg = TSyntheticPreUpdateNode(call)
1138+
resolveMethodCall(call, target, type, arg) and
1139+
// dataflow lib has requirement that arguments and calls are in same enclosing callable.
1140+
exists(CfgNode cfgNode | cfgNode.getNode() = call |
1141+
cfgNode.getEnclosingCallable() = arg.getEnclosingCallable()
1142+
)
11821143
or
1183-
normalCallArg(call, arg, apos)
1184-
)
1185-
or
1186-
// call on class instance, which goes to `__call__` method
1187-
type instanceof CallTypeClassInstanceCall and
1188-
(
1144+
// cls argument for classmethod calls
1145+
type instanceof CallTypeClassMethod and
11891146
apos.isSelf() and
1190-
resolveClassInstanceCall(call, target, arg)
1147+
resolveMethodCall(call, target, type, arg) and
1148+
(arg = classTracker(_) or arg = clsTracker(_)) and
1149+
// dataflow lib has requirement that arguments and calls are in same enclosing callable.
1150+
exists(CfgNode cfgNode | cfgNode.getNode() = call |
1151+
cfgNode.getEnclosingCallable() = arg.getEnclosingCallable()
1152+
)
11911153
or
1154+
// normal arguments for method calls
1155+
(
1156+
type instanceof CallTypeNormalMethod or
1157+
type instanceof CallTypeStaticMethod or
1158+
type instanceof CallTypeClassMethod
1159+
) and
11921160
normalCallArg(call, arg, apos)
1161+
or
1162+
// method as plain function call.
1163+
//
1164+
// argument index 0 of call has position self (and MUST be given as positional
1165+
// argument in call). This also means that call-arguments are shifted by 1, such
1166+
// that argument index 1 of call has argument position 0
1167+
type instanceof CallTypeMethodAsPlainFunction and
1168+
(
1169+
apos.isSelf() and arg.asCfgNode() = call.(CallNode).getArg(0)
1170+
or
1171+
not apos.isPositional(_) and normalCallArg(call, arg, apos)
1172+
or
1173+
exists(ArgumentPosition normalPos, int index |
1174+
apos.isPositional(index - 1) and
1175+
normalPos.isPositional(index) and
1176+
normalCallArg(call, arg, normalPos)
1177+
)
1178+
)
1179+
or
1180+
// class call
1181+
type instanceof CallTypeClass and
1182+
(
1183+
// only pass synthetic node for created object to __init__, and not __new__ since
1184+
// __new__ is a classmethod.
1185+
target = invokedFunctionFromClassConstruction(_, "__init__") and
1186+
apos.isSelf() and
1187+
arg = TSyntheticPreUpdateNode(call)
1188+
or
1189+
normalCallArg(call, arg, apos)
1190+
)
1191+
or
1192+
// call on class instance, which goes to `__call__` method
1193+
type instanceof CallTypeClassInstanceCall and
1194+
(
1195+
apos.isSelf() and
1196+
resolveClassInstanceCall(call, target, arg)
1197+
or
1198+
normalCallArg(call, arg, apos)
1199+
)
11931200
)
11941201
)
11951202
}

python/ql/lib/semmle/python/internal/CachedStages.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ module Stages {
180180
predicate ref() { 1 = 1 }
181181

182182
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
183+
private import semmle.python.dataflow.new.internal.DataFlowDispatch as DataFlowDispatch
183184
private import semmle.python.dataflow.new.internal.LocalSources as LocalSources
184185
private import semmle.python.internal.Awaited as Awaited
185186

@@ -195,6 +196,10 @@ module Stages {
195196
or
196197
any(DataFlowPublic::Node node).hasLocationInfo(_, _, _, _, _)
197198
or
199+
DataFlowDispatch::resolveCall(_, _, _)
200+
or
201+
DataFlowDispatch::getCallArg(_, _, _, _, _)
202+
or
198203
any(LocalSources::LocalSourceNode n).flowsTo(_)
199204
or
200205
exists(Awaited::awaited(_))

0 commit comments

Comments
 (0)