Skip to content

Commit 6d4e000

Browse files
authored
Merge pull request github#14590 from RasmusWL/fix-dataflow-class-scope
Python: Fix dataflow consistency error due to missing class scope
2 parents 43d9d2c + 9f43108 commit 6d4e000

File tree

10 files changed

+41
-93
lines changed

10 files changed

+41
-93
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,10 @@ abstract class DataFlowCall extends TDataFlowCall {
13521352
abstract ControlFlowNode getNode();
13531353

13541354
/** Gets the enclosing callable of this call. */
1355-
abstract DataFlowCallable getEnclosingCallable();
1355+
DataFlowCallable getEnclosingCallable() { result = getCallableScope(this.getScope()) }
1356+
1357+
/** Gets the scope of this node, if any. */
1358+
abstract Scope getScope();
13561359

13571360
/** Gets the location of this dataflow call. */
13581361
abstract Location getLocation();
@@ -1400,7 +1403,7 @@ class NormalCall extends ExtractedDataFlowCall, TNormalCall {
14001403

14011404
override ControlFlowNode getNode() { result = call }
14021405

1403-
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() }
1406+
override Scope getScope() { result = call.getScope() }
14041407

14051408
override DataFlowCallable getCallable() { result.(DataFlowFunction).getScope() = target }
14061409

@@ -1450,7 +1453,7 @@ class PotentialLibraryCall extends ExtractedDataFlowCall, TPotentialLibraryCall
14501453

14511454
override ControlFlowNode getNode() { result = call }
14521455

1453-
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() }
1456+
override Scope getScope() { result = call.getScope() }
14541457
}
14551458

14561459
/**
@@ -1474,6 +1477,8 @@ class SummaryCall extends DataFlowCall, TSummaryCall {
14741477

14751478
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
14761479

1480+
override Scope getScope() { none() }
1481+
14771482
override DataFlowCallable getCallable() { none() }
14781483

14791484
override ArgumentNode getArgument(ArgumentPosition apos) { none() }

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,3 +1044,11 @@ class ContentApprox = Unit;
10441044
/** Gets an approximated value for content `c`. */
10451045
pragma[inline]
10461046
ContentApprox getContentApprox(Content c) { any() }
1047+
1048+
/** Helper for `.getEnclosingCallable`. */
1049+
DataFlowCallable getCallableScope(Scope s) {
1050+
result.getScope() = s
1051+
or
1052+
not exists(DataFlowCallable c | c.getScope() = s) and
1053+
result = getCallableScope(s.getEnclosingScope())
1054+
}

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,6 @@ newtype TNode =
117117
exists(ParameterPosition ppos | ppos.isKeyword(_) | exists(callable.getParameter(ppos)))
118118
}
119119

120-
/** Helper for `Node::getEnclosingCallable`. */
121-
private DataFlowCallable getCallableScope(Scope s) {
122-
result.getScope() = s
123-
or
124-
not exists(DataFlowCallable c | c.getScope() = s) and
125-
result = getCallableScope(s.getEnclosingScope())
126-
}
127-
128120
private import semmle.python.internal.CachedStages
129121

130122
/**

python/ql/test/experimental/dataflow/calls/dataflow-consistency.expected

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| new_cls_param.py:14:6:14:16 | classmethod() | Call should have one enclosing callable but has 0. |
4-
| test.py:21:6:21:17 | staticmethod() | Call should have one enclosing callable but has 0. |
5-
| test.py:25:6:25:16 | classmethod() | Call should have one enclosing callable but has 0. |
6-
| test.py:29:6:29:16 | classmethod() | Call should have one enclosing callable but has 0. |
73
uniqueType
84
uniqueNodeLocation
95
missingLocation
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Originally we had module and functions as `DataFlowCallable``, and any call inside a
2+
# class scope would not have a result for getEnclosingCallable. Since this was only a
3+
# consistency error for calls, originally we added a new `DataFlowClassScope` only for
4+
# those classes that had a call in their scope. That's why all the class definitions in
5+
# this test do a call to the dummy function `func`.
6+
#
7+
# Note: this was shortsighted, since most DataFlow::Node use `getCallableScope` helper
8+
# to define their .getEnclosingCallable(), which picks the first DataFlowCallable to
9+
# contain the node. (so for some classes that would be DataFlowClassScope, and for some
10+
# it would be the module/function containing the class definition)
11+
12+
def func(*args, **kwargs):
13+
print("func()")
14+
15+
class Cls:
16+
func()
17+
class Inner:
18+
func()
19+
20+
def other_func():
21+
class Cls2:
22+
func()
23+
return Cls2
24+
25+
x = other_func()

python/ql/test/experimental/dataflow/coverage/dataflow-consistency.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| datamodel.py:71:6:71:16 | classmethod() | Call should have one enclosing callable but has 0. |
4-
| datamodel.py:76:6:76:17 | staticmethod() | Call should have one enclosing callable but has 0. |
53
uniqueType
64
uniqueNodeLocation
75
missingLocation

python/ql/test/experimental/dataflow/variable-capture/dataflow-consistency.expected

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| test_collections.py:39:17:39:38 | Lambda() | Call should have one enclosing callable but has 0. |
4-
| test_collections.py:39:17:39:38 | Lambda() | Call should have one enclosing callable but has 0. |
5-
| test_collections.py:45:19:45:24 | mod() | Call should have one enclosing callable but has 0. |
6-
| test_collections.py:45:19:45:24 | mod() | Call should have one enclosing callable but has 0. |
7-
| test_collections.py:52:13:52:24 | mod_local() | Call should have one enclosing callable but has 0. |
8-
| test_collections.py:52:13:52:24 | mod_local() | Call should have one enclosing callable but has 0. |
93
uniqueType
104
uniqueNodeLocation
115
missingLocation

python/ql/test/experimental/library-tests/CallGraph/dataflow-consistency.expected

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| code/bound_method_arg.py:5:6:5:16 | classmethod() | Call should have one enclosing callable but has 0. |
4-
| code/callable_as_argument.py:37:6:37:17 | staticmethod() | Call should have one enclosing callable but has 0. |
5-
| code/callable_as_argument.py:49:10:49:21 | staticmethod() | Call should have one enclosing callable but has 0. |
6-
| code/class_construction.py:13:6:13:16 | classmethod() | Call should have one enclosing callable but has 0. |
7-
| code/class_properties.py:9:6:9:13 | property() | Call should have one enclosing callable but has 0. |
8-
| code/class_properties.py:11:9:11:32 | print() | Call should have one enclosing callable but has 0. |
9-
| code/class_properties.py:14:6:14:15 | Attribute() | Call should have one enclosing callable but has 0. |
10-
| code/class_properties.py:19:6:19:16 | Attribute() | Call should have one enclosing callable but has 0. |
11-
| code/class_properties.py:36:12:36:62 | property() | Call should have one enclosing callable but has 0. |
12-
| code/class_properties.py:38:6:38:13 | property() | Call should have one enclosing callable but has 0. |
13-
| code/class_properties.py:40:9:40:38 | print() | Call should have one enclosing callable but has 0. |
14-
| code/class_subclass.py:10:6:10:17 | staticmethod() | Call should have one enclosing callable but has 0. |
15-
| code/class_subclass.py:14:6:14:16 | classmethod() | Call should have one enclosing callable but has 0. |
16-
| code/class_subclass.py:104:6:104:17 | staticmethod() | Call should have one enclosing callable but has 0. |
17-
| code/class_subclass.py:108:6:108:16 | classmethod() | Call should have one enclosing callable but has 0. |
18-
| code/class_subclass.py:112:6:112:17 | staticmethod() | Call should have one enclosing callable but has 0. |
19-
| code/class_subclass.py:116:6:116:16 | classmethod() | Call should have one enclosing callable but has 0. |
20-
| code/class_subclass.py:120:6:120:16 | classmethod() | Call should have one enclosing callable but has 0. |
21-
| code/class_subclass.py:149:6:149:17 | staticmethod() | Call should have one enclosing callable but has 0. |
22-
| code/class_subclass.py:153:6:153:16 | classmethod() | Call should have one enclosing callable but has 0. |
23-
| code/class_super.py:13:6:13:16 | classmethod() | Call should have one enclosing callable but has 0. |
24-
| code/class_super.py:28:6:28:17 | staticmethod() | Call should have one enclosing callable but has 0. |
25-
| code/class_super.py:36:6:36:16 | classmethod() | Call should have one enclosing callable but has 0. |
26-
| code/class_super.py:40:6:40:16 | classmethod() | Call should have one enclosing callable but has 0. |
27-
| code/func_defined_outside_class.py:17:18:17:41 | staticmethod() | Call should have one enclosing callable but has 0. |
28-
| code/func_defined_outside_class.py:18:18:18:40 | classmethod() | Call should have one enclosing callable but has 0. |
29-
| code/func_defined_outside_class.py:38:11:38:21 | _gen() | Call should have one enclosing callable but has 0. |
30-
| code/func_defined_outside_class.py:39:11:39:21 | _gen() | Call should have one enclosing callable but has 0. |
31-
| code/nested_class.py:3:10:3:21 | staticmethod() | Call should have one enclosing callable but has 0. |
32-
| code/nested_class.py:7:10:7:21 | staticmethod() | Call should have one enclosing callable but has 0. |
33-
| code/self_passing.py:60:6:60:16 | classmethod() | Call should have one enclosing callable but has 0. |
343
uniqueType
354
uniqueNodeLocation
365
missingLocation

python/ql/test/library-tests/ApiGraphs/py3/dataflow-consistency.expected

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
4-
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
5-
| test_captured.py:14:26:14:30 | pp() | Call should have one enclosing callable but has 0. |
6-
| test_captured.py:14:26:14:30 | pp() | Call should have one enclosing callable but has 0. |
73
uniqueType
84
uniqueNodeLocation
95
missingLocation

python/ql/test/library-tests/frameworks/django-orm/dataflow-consistency.expected

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,5 @@
11
uniqueEnclosingCallable
22
uniqueCallEnclosingCallable
3-
| testapp/orm_form_test.py:7:12:7:43 | Attribute() | Call should have one enclosing callable but has 0. |
4-
| testapp/orm_inheritance.py:30:13:30:44 | Attribute() | Call should have one enclosing callable but has 0. |
5-
| testapp/orm_inheritance.py:34:25:34:56 | Attribute() | Call should have one enclosing callable but has 0. |
6-
| testapp/orm_inheritance.py:35:33:35:64 | Attribute() | Call should have one enclosing callable but has 0. |
7-
| testapp/orm_inheritance.py:39:21:39:52 | Attribute() | Call should have one enclosing callable but has 0. |
8-
| testapp/orm_inheritance.py:40:33:40:64 | Attribute() | Call should have one enclosing callable but has 0. |
9-
| testapp/orm_inheritance.py:118:13:118:44 | Attribute() | Call should have one enclosing callable but has 0. |
10-
| testapp/orm_inheritance.py:122:25:122:56 | Attribute() | Call should have one enclosing callable but has 0. |
11-
| testapp/orm_inheritance.py:123:33:123:64 | Attribute() | Call should have one enclosing callable but has 0. |
12-
| testapp/orm_inheritance.py:127:21:127:52 | Attribute() | Call should have one enclosing callable but has 0. |
13-
| testapp/orm_inheritance.py:128:33:128:64 | Attribute() | Call should have one enclosing callable but has 0. |
14-
| testapp/orm_security_tests.py:16:12:16:43 | Attribute() | Call should have one enclosing callable but has 0. |
15-
| testapp/orm_security_tests.py:17:11:17:31 | Attribute() | Call should have one enclosing callable but has 0. |
16-
| testapp/orm_security_tests.py:93:12:93:65 | Attribute() | Call should have one enclosing callable but has 0. |
17-
| testapp/orm_security_tests.py:112:12:112:65 | Attribute() | Call should have one enclosing callable but has 0. |
18-
| testapp/orm_tests.py:29:12:29:43 | Attribute() | Call should have one enclosing callable but has 0. |
19-
| testapp/orm_tests.py:43:12:43:43 | Attribute() | Call should have one enclosing callable but has 0. |
20-
| testapp/orm_tests.py:59:12:59:61 | Attribute() | Call should have one enclosing callable but has 0. |
21-
| testapp/orm_tests.py:74:12:74:43 | Attribute() | Call should have one enclosing callable but has 0. |
22-
| testapp/orm_tests.py:90:12:90:43 | Attribute() | Call should have one enclosing callable but has 0. |
23-
| testapp/orm_tests.py:111:12:111:43 | Attribute() | Call should have one enclosing callable but has 0. |
24-
| testapp/orm_tests.py:127:12:127:43 | Attribute() | Call should have one enclosing callable but has 0. |
25-
| testapp/orm_tests.py:128:13:128:44 | Attribute() | Call should have one enclosing callable but has 0. |
26-
| testapp/orm_tests.py:145:12:145:43 | Attribute() | Call should have one enclosing callable but has 0. |
27-
| testapp/orm_tests.py:146:13:146:44 | Attribute() | Call should have one enclosing callable but has 0. |
28-
| testapp/orm_tests.py:162:12:162:43 | Attribute() | Call should have one enclosing callable but has 0. |
29-
| testapp/orm_tests.py:178:12:178:43 | Attribute() | Call should have one enclosing callable but has 0. |
30-
| testapp/orm_tests.py:181:12:181:64 | Attribute() | Call should have one enclosing callable but has 0. |
31-
| testapp/orm_tests.py:207:12:207:43 | Attribute() | Call should have one enclosing callable but has 0. |
32-
| testapp/orm_tests.py:208:12:208:70 | Attribute() | Call should have one enclosing callable but has 0. |
33-
| testapp/orm_tests.py:234:12:234:43 | Attribute() | Call should have one enclosing callable but has 0. |
34-
| testapp/orm_tests.py:235:12:235:95 | Attribute() | Call should have one enclosing callable but has 0. |
35-
| testapp/orm_tests.py:256:12:256:43 | Attribute() | Call should have one enclosing callable but has 0. |
36-
| testapp/orm_tests.py:274:12:274:43 | Attribute() | Call should have one enclosing callable but has 0. |
37-
| testapp/orm_tests.py:295:12:295:43 | Attribute() | Call should have one enclosing callable but has 0. |
383
uniqueType
394
uniqueNodeLocation
405
missingLocation

0 commit comments

Comments
 (0)