Skip to content

Commit 1068ede

Browse files
authored
Merge pull request #5038 from RasmusWL/import-fix
Python: Fix too many results from DataFlow::importNode
2 parents 2c70106 + 5646af5 commit 1068ede

File tree

5 files changed

+103
-5
lines changed

5 files changed

+103
-5
lines changed

python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,11 @@ Node importNode(string name) {
6868
// Because named imports are modelled as `AttrRead`s, the statement `from foo import bar as baz`
6969
// is interpreted as if it was an assignment `baz = foo.bar`, which means `baz` gets tracked as a
7070
// reference to `foo.bar`, as desired.
71-
result.asCfgNode().getNode() = any(ImportExpr i | i.getName() = name)
71+
exists(ImportExpr imp_expr |
72+
imp_expr.getName() = name and
73+
result.asCfgNode().getNode() = imp_expr and
74+
// in `import foo.bar` we DON'T want to give a result for `importNode("foo.bar")`,
75+
// only for `importNode("foo")`. We exclude those cases with the following clause.
76+
not exists(Import imp | imp.getAName().getValue() = imp_expr)
77+
)
7278
}

python/ql/test/experimental/dataflow/import-helper/ImportHelper.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
| test2.py:1:19:1:21 | ControlFlowNode for ImportMember | mypkg.foo |
55
| test2.py:1:24:1:26 | ControlFlowNode for ImportMember | mypkg.bar |
66
| test3.py:1:8:1:16 | ControlFlowNode for ImportExpr | mypkg |
7-
| test3.py:1:8:1:16 | ControlFlowNode for ImportExpr | mypkg.foo |
87
| test3.py:2:8:2:16 | ControlFlowNode for ImportExpr | mypkg |
9-
| test3.py:2:8:2:16 | ControlFlowNode for ImportExpr | mypkg.bar |
108
| test4.py:1:8:1:16 | ControlFlowNode for ImportExpr | mypkg.foo |
119
| test4.py:2:8:2:16 | ControlFlowNode for ImportExpr | mypkg.bar |
1210
| test5.py:1:8:1:12 | ControlFlowNode for ImportExpr | mypkg |
1311
| test5.py:9:6:9:10 | ControlFlowNode for ImportExpr | mypkg |
1412
| test5.py:9:19:9:29 | ControlFlowNode for ImportMember | mypkg.bar |
1513
| test6.py:1:8:1:12 | ControlFlowNode for ImportExpr | mypkg |
1614
| test6.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg |
17-
| test6.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg.foo |
1815
| test7.py:1:6:1:10 | ControlFlowNode for ImportExpr | mypkg |
1916
| test7.py:1:19:1:21 | ControlFlowNode for ImportMember | mypkg.foo |
2017
| test7.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg |
21-
| test7.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg.foo |
2218
| test7.py:9:6:9:10 | ControlFlowNode for ImportExpr | mypkg |
2319
| test7.py:9:19:9:21 | ControlFlowNode for ImportMember | mypkg.foo |
20+
| test_deep.py:1:6:1:21 | ControlFlowNode for ImportExpr | start.middle.end |
21+
| test_deep.py:1:6:1:21 | ControlFlowNode for ImportExpr | start.middle.end |
22+
| test_deep.py:1:30:1:32 | ControlFlowNode for ImportMember | start.middle.end.foo |
23+
| test_deep.py:1:35:1:37 | ControlFlowNode for ImportMember | start.middle.end.bar |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from start.middle.end import foo, bar
2+
print(foo)
3+
print(bar)

python/ql/test/experimental/dataflow/typetracking/test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,22 @@ def track_self(self): # $ tracked_self
101101
self.meth1() # $ tracked_self
102102
super().meth2()
103103
super(Bar, self).foo3() # $ tracked_self
104+
105+
# ------------------------------------------------------------------------------
106+
# Tracking of attribute lookup after "long" import chain
107+
# ------------------------------------------------------------------------------
108+
109+
def test_long_import_chain():
110+
import foo.bar
111+
foo.baz
112+
x = foo.bar.baz # $ tracked_foo_bar_baz
113+
do_stuff(x) # $ tracked_foo_bar_baz
114+
115+
class Example(foo.bar.baz): # $ tracked_foo_bar_baz
116+
pass
117+
118+
119+
def test_long_import_chain_full_path():
120+
from foo.bar import baz # $ tracked_foo_bar_baz
121+
x = baz # $ tracked_foo_bar_baz
122+
do_stuff(x) # $ tracked_foo_bar_baz

python/ql/test/experimental/dataflow/typetracking/tracked.ql

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import semmle.python.dataflow.new.DataFlow
33
import semmle.python.dataflow.new.TypeTracker
44
import TestUtilities.InlineExpectationsTest
55

6+
// -----------------------------------------------------------------------------
7+
// tracked
8+
// -----------------------------------------------------------------------------
69
DataFlow::Node tracked(TypeTracker t) {
710
t.start() and
811
result.asCfgNode() = any(NameNode n | n.getId() = "tracked")
@@ -28,6 +31,9 @@ class TrackedTest extends InlineExpectationsTest {
2831
}
2932
}
3033

34+
// -----------------------------------------------------------------------------
35+
// int + str
36+
// -----------------------------------------------------------------------------
3137
DataFlow::Node int_type(TypeTracker t) {
3238
t.start() and
3339
result.asCfgNode() = any(CallNode c | c.getFunction().(NameNode).getId() = "int")
@@ -74,6 +80,9 @@ class TrackedStringTest extends InlineExpectationsTest {
7480
}
7581
}
7682

83+
// -----------------------------------------------------------------------------
84+
// tracked_self
85+
// -----------------------------------------------------------------------------
7786
DataFlow::Node tracked_self(TypeTracker t) {
7887
t.start() and
7988
exists(Function f |
@@ -102,3 +111,64 @@ class TrackedSelfTest extends InlineExpectationsTest {
102111
)
103112
}
104113
}
114+
115+
// -----------------------------------------------------------------------------
116+
// tracked_foo_bar_baz
117+
// -----------------------------------------------------------------------------
118+
// This modeling follows the same pattern that we currently use in our real library modeling.
119+
/** Gets a reference to `foo` (fictive module). */
120+
private DataFlow::Node foo(DataFlow::TypeTracker t) {
121+
t.start() and
122+
result = DataFlow::importNode("foo")
123+
or
124+
exists(DataFlow::TypeTracker t2 | result = foo(t2).track(t2, t))
125+
}
126+
127+
/** Gets a reference to `foo` (fictive module). */
128+
DataFlow::Node foo() { result = foo(DataFlow::TypeTracker::end()) }
129+
130+
/** Gets a reference to `foo.bar` (fictive module). */
131+
private DataFlow::Node foo_bar(DataFlow::TypeTracker t) {
132+
t.start() and
133+
result = DataFlow::importNode("foo.bar")
134+
or
135+
t.startInAttr("bar") and
136+
result = foo()
137+
or
138+
exists(DataFlow::TypeTracker t2 | result = foo_bar(t2).track(t2, t))
139+
}
140+
141+
/** Gets a reference to `foo.bar` (fictive module). */
142+
DataFlow::Node foo_bar() { result = foo_bar(DataFlow::TypeTracker::end()) }
143+
144+
/** Gets a reference to `foo.bar.baz` (fictive attribute on `foo.bar` module). */
145+
private DataFlow::Node foo_bar_baz(DataFlow::TypeTracker t) {
146+
t.start() and
147+
result = DataFlow::importNode("foo.bar.baz")
148+
or
149+
t.startInAttr("baz") and
150+
result = foo_bar()
151+
or
152+
exists(DataFlow::TypeTracker t2 | result = foo_bar_baz(t2).track(t2, t))
153+
}
154+
155+
/** Gets a reference to `foo.bar.baz` (fictive attribute on `foo.bar` module). */
156+
DataFlow::Node foo_bar_baz() { result = foo_bar_baz(DataFlow::TypeTracker::end()) }
157+
158+
class TrackedFooBarBaz extends InlineExpectationsTest {
159+
TrackedFooBarBaz() { this = "TrackedFooBarBaz" }
160+
161+
override string getARelevantTag() { result = "tracked_foo_bar_baz" }
162+
163+
override predicate hasActualResult(Location location, string element, string tag, string value) {
164+
exists(DataFlow::Node e |
165+
e = foo_bar_baz() and
166+
// Module variables have no sensible location, and hence can't be annotated.
167+
not e instanceof DataFlow::ModuleVariableNode and
168+
tag = "tracked_foo_bar_baz" and
169+
location = e.getLocation() and
170+
value = "" and
171+
element = e.toString()
172+
)
173+
}
174+
}

0 commit comments

Comments
 (0)