Skip to content

Commit 9caa9c1

Browse files
authored
Merge pull request #928 from markshannon/python-points-to-through-callsites
Python: Points-to should flow through call-sites if not assigned out of scope.
2 parents c133362 + 220b881 commit 9caa9c1

File tree

8 files changed

+93
-10
lines changed

8 files changed

+93
-10
lines changed

python/ql/src/semmle/python/dataflow/SsaDefinitions.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,15 @@ class EscapingGlobalVariable extends ModuleVariable {
300300

301301
}
302302

303+
class EscapingAssignmentGlobalVariable extends EscapingGlobalVariable {
304+
305+
EscapingAssignmentGlobalVariable() {
306+
exists(NameNode n | n.defines(this) and not n.getScope() = this.getScope())
307+
}
308+
309+
}
310+
311+
303312
class SpecialSsaSourceVariable extends PythonSsaSourceVariable {
304313

305314
SpecialSsaSourceVariable() {

python/ql/src/semmle/python/pointsto/PointsTo.qll

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,14 +1713,23 @@ module PointsTo {
17131713
*/
17141714
pragma [noinline]
17151715
private predicate callsite_points_to(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) {
1716-
exists(EssaVariable var, PointsToContext callee |
1717-
Flow::callsite_exit_value_transfer(var, callee, def, context) and
1718-
ssa_variable_points_to(var, callee, value, cls, origin)
1716+
exists(SsaSourceVariable srcvar |
1717+
srcvar = def.getSourceVariable() |
1718+
if srcvar instanceof EscapingAssignmentGlobalVariable then (
1719+
/* If global variable can be reassigned, we need to track it through calls */
1720+
exists(EssaVariable var, PointsToContext callee |
1721+
Flow::callsite_exit_value_transfer(var, callee, def, context) and
1722+
ssa_variable_points_to(var, callee, value, cls, origin)
1723+
)
1724+
or
1725+
callsite_points_to_python(def, context, value, cls, origin)
1726+
or
1727+
callsite_points_to_builtin(def, context, value, cls, origin)
1728+
) else (
1729+
/* Otherwise we can assume its value (but not those of its attributes or members) has not changed. */
1730+
ssa_variable_points_to(def.getInput(), context, value, cls, origin)
1731+
)
17191732
)
1720-
or
1721-
callsite_points_to_python(def, context, value, cls, origin)
1722-
or
1723-
callsite_points_to_builtin(def, context, value, cls, origin)
17241733
}
17251734

17261735
pragma [noinline]

python/ql/test/library-tests/PointsTo/new/Dataflow.expected

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,31 +625,40 @@
625625
| r_regressions.py:0 | __name___0 = ScopeEntryDefinition |
626626
| r_regressions.py:0 | __package___0 = ScopeEntryDefinition |
627627
| r_regressions.py:0 | _names_0 = ScopeEntryDefinition |
628-
| r_regressions.py:0 | _names_3 = Pi(_names_1) [false] |
629-
| r_regressions.py:0 | _names_4 = phi(_names_2, _names_3) |
628+
| r_regressions.py:0 | gv_0 = ScopeEntryDefinition |
629+
| r_regressions.py:0 | mod_gv_0 = ScopeEntryDefinition |
630630
| r_regressions.py:0 | sys_0 = ScopeEntryDefinition |
631631
| r_regressions.py:0 | t_0 = ScopeEntryDefinition |
632-
| r_regressions.py:0 | t_2 = phi(t_0, t_1) |
633632
| r_regressions.py:5 | Queue_0 = ClassExpr |
634633
| r_regressions.py:7 | __init___0 = FunctionExpr |
634+
| r_regressions.py:7 | gv_5 = ScopeEntryDefinition |
635635
| r_regressions.py:7 | self_0 = ParameterDefinition |
636+
| r_regressions.py:9 | gv_6 = CallsiteRefinement(gv_5) |
636637
| r_regressions.py:9 | self_1 = SelfCallsiteRefinement(self_0) |
637638
| r_regressions.py:11 | _after_fork_0 = FunctionExpr |
639+
| r_regressions.py:11 | gv_7 = ScopeEntryDefinition |
638640
| r_regressions.py:11 | self_0 = ParameterDefinition |
639641
| r_regressions.py:12 | self_1 = AttributeAssignment '_closed'(self_0) |
640642
| r_regressions.py:13 | self_2 = AttributeAssignment '_close'(self_1) |
641643
| r_regressions.py:15 | close_0 = FunctionExpr |
642644
| r_regressions.py:15 | close_4 = Pi(close_0) [false] |
643645
| r_regressions.py:15 | close_5 = phi(close_3, close_4) |
646+
| r_regressions.py:15 | gv_8 = ScopeEntryDefinition |
647+
| r_regressions.py:15 | gv_33 = phi(gv_9, gv_11) |
644648
| r_regressions.py:15 | self_0 = ParameterDefinition |
645649
| r_regressions.py:15 | self_3 = phi(self_1, self_2) |
646650
| r_regressions.py:16 | self_1 = AttributeAssignment '_closed'(self_0) |
651+
| r_regressions.py:18 | gv_9 = CallsiteRefinement(gv_8) |
647652
| r_regressions.py:20 | close_0 = Attribute |
648653
| r_regressions.py:20 | close_1 = Attribute |
654+
| r_regressions.py:20 | gv_10 = phi(gv_8, gv_9) |
649655
| r_regressions.py:21 | close_2 = SingleSuccessorGuard(close_1) [true] |
650656
| r_regressions.py:22 | close_3 = Pi(close_0) [true] |
651657
| r_regressions.py:22 | self_2 = AttributeAssignment '_close'(self_1) |
658+
| r_regressions.py:23 | gv_11 = CallsiteRefinement(gv_9) |
652659
| r_regressions.py:27 | f_0 = FunctionExpr |
660+
| r_regressions.py:27 | gv_12 = ScopeEntryDefinition |
661+
| r_regressions.py:27 | gv_33 = phi(gv_12, gv_13) |
653662
| r_regressions.py:27 | x_0 = ParameterDefinition |
654663
| r_regressions.py:27 | x_5 = phi(x_3, x_4) |
655664
| r_regressions.py:27 | y_0 = ParameterDefinition |
@@ -664,60 +673,90 @@
664673
| r_regressions.py:33 | y_1 = Pi(y_0) [false] |
665674
| r_regressions.py:33 | y_2 = phi(y_0, y_1) |
666675
| r_regressions.py:36 | y_3 = Pi(y_2) [true] |
676+
| r_regressions.py:39 | gv_13 = CallsiteRefinement(gv_12) |
667677
| r_regressions.py:39 | x_4 = phi(x_1, x_3) |
668678
| r_regressions.py:39 | y_4 = Pi(y_0) [true] |
669679
| r_regressions.py:39 | y_5 = phi(y_3, y_4) |
670680
| r_regressions.py:39 | y_6 = ArgumentRefinement(y_5) |
671681
| r_regressions.py:39 | z_1 = Pi(z_0) [true] |
672682
| r_regressions.py:39 | z_2 = phi(z_0, z_1) |
673683
| r_regressions.py:42 | find_library_0 = FunctionExpr |
684+
| r_regressions.py:42 | gv_14 = ScopeEntryDefinition |
674685
| r_regressions.py:42 | name_0 = ParameterDefinition |
675686
| r_regressions.py:43 | __0 = ... |
676687
| r_regressions.py:43 | data_0 = ... |
688+
| r_regressions.py:43 | gv_15 = CallsiteRefinement(gv_14) |
677689
| r_regressions.py:46 | fail_0 = FunctionExpr |
690+
| r_regressions.py:46 | gv_16 = ScopeEntryDefinition |
678691
| r_regressions.py:46 | msg_0 = ParameterDefinition |
679692
| r_regressions.py:49 | C_0 = ClassExpr |
680693
| r_regressions.py:51 | fail_0 = FunctionExpr |
681694
| r_regressions.py:51 | fail_1 = ScopeEntryDefinition |
695+
| r_regressions.py:51 | gv_17 = ScopeEntryDefinition |
682696
| r_regressions.py:51 | msg_0 = ParameterDefinition |
683697
| r_regressions.py:51 | self_0 = ParameterDefinition |
698+
| r_regressions.py:52 | gv_18 = CallsiteRefinement(gv_17) |
684699
| r_regressions.py:52 | msg_1 = ArgumentRefinement(msg_0) |
685700
| r_regressions.py:58 | decorator_0 = ParameterDefinition |
701+
| r_regressions.py:58 | gv_19 = ScopeEntryDefinition |
686702
| r_regressions.py:58 | method_decorator_0 = FunctionExpr |
687703
| r_regressions.py:58 | name_0 = ParameterDefinition |
688704
| r_regressions.py:61 | _dec_0 = FunctionExpr |
689705
| r_regressions.py:61 | func_0 = ScopeEntryDefinition |
706+
| r_regressions.py:61 | gv_20 = ScopeEntryDefinition |
707+
| r_regressions.py:61 | gv_33 = phi(gv_23, gv_25) |
690708
| r_regressions.py:61 | is_class_6 = phi(is_class_4, is_class_5) |
691709
| r_regressions.py:61 | name_1 = ScopeEntryDefinition |
692710
| r_regressions.py:61 | obj_0 = ParameterDefinition |
693711
| r_regressions.py:61 | obj_3 = phi(obj_1, obj_2) |
712+
| r_regressions.py:62 | gv_21 = CallsiteRefinement(gv_20) |
694713
| r_regressions.py:62 | is_class_0 = isinstance() |
695714
| r_regressions.py:62 | obj_1 = ArgumentRefinement(obj_0) |
715+
| r_regressions.py:64 | gv_22 = CallsiteRefinement(gv_21) |
696716
| r_regressions.py:64 | is_class_1 = Pi(is_class_0) [true] |
697717
| r_regressions.py:66 | func_1 = obj |
698718
| r_regressions.py:66 | is_class_2 = Pi(is_class_0) [false] |
699719
| r_regressions.py:68 | _wrapper_0 = FunctionExpr |
700720
| r_regressions.py:68 | args_0 = ParameterDefinition |
701721
| r_regressions.py:68 | func_2 = phi(func_0, func_1) |
722+
| r_regressions.py:68 | gv_23 = phi(gv_21, gv_22) |
723+
| r_regressions.py:68 | gv_24 = ScopeEntryDefinition |
702724
| r_regressions.py:68 | is_class_3 = phi(is_class_1, is_class_2) |
703725
| r_regressions.py:68 | kwargs_0 = ParameterDefinition |
704726
| r_regressions.py:68 | self_0 = ParameterDefinition |
727+
| r_regressions.py:73 | gv_25 = CallsiteRefinement(gv_23) |
705728
| r_regressions.py:73 | is_class_4 = Pi(is_class_3) [true] |
706729
| r_regressions.py:73 | obj_2 = ArgumentRefinement(obj_1) |
707730
| r_regressions.py:76 | is_class_5 = Pi(is_class_3) [false] |
708731
| r_regressions.py:80 | deco_0 = FunctionExpr |
709732
| r_regressions.py:80 | func_0 = ParameterDefinition |
733+
| r_regressions.py:80 | gv_26 = ScopeEntryDefinition |
710734
| r_regressions.py:81 | _wrapper_0 = FunctionExpr |
711735
| r_regressions.py:81 | args_0 = ParameterDefinition |
736+
| r_regressions.py:81 | gv_27 = ScopeEntryDefinition |
712737
| r_regressions.py:81 | kwargs_0 = ParameterDefinition |
713738
| r_regressions.py:85 | deco_1 = ArgumentRefinement(deco_0) |
739+
| r_regressions.py:85 | gv_1 = CallsiteRefinement(gv_0) |
740+
| r_regressions.py:85 | gv_2 = CallsiteRefinement(gv_1) |
714741
| r_regressions.py:86 | TestFirst_1 = method_decorator()() |
742+
| r_regressions.py:87 | gv_28 = ScopeEntryDefinition |
715743
| r_regressions.py:87 | method_0 = FunctionExpr |
716744
| r_regressions.py:87 | self_0 = ParameterDefinition |
745+
| r_regressions.py:90 | gv_3 = CallsiteRefinement(gv_2) |
746+
| r_regressions.py:90 | gv_4 = CallsiteRefinement(gv_3) |
717747
| r_regressions.py:93 | sys_1 = ImportExpr |
718748
| r_regressions.py:95 | _names_1 = Attribute |
719749
| r_regressions.py:98 | _names_2 = Pi(_names_1) [true] |
720750
| r_regressions.py:98 | t_1 = ImportExpr |
751+
| r_regressions.py:100 | _names_3 = Pi(_names_1) [false] |
752+
| r_regressions.py:100 | _names_4 = phi(_names_2, _names_3) |
753+
| r_regressions.py:100 | gv_29 = C() |
754+
| r_regressions.py:100 | t_2 = phi(t_0, t_1) |
755+
| r_regressions.py:102 | gv_30 = CallsiteRefinement(gv_29) |
756+
| r_regressions.py:106 | gv_31 = ScopeEntryDefinition |
757+
| r_regressions.py:106 | mod_gv_1 = FunctionExpr |
758+
| r_regressions.py:106 | x_0 = ParameterDefinition |
759+
| r_regressions.py:107 | gv_32 = AttributeAssignment 'attr'(gv_31) |
721760
| s_scopes.py:0 | __name___0 = ScopeEntryDefinition |
722761
| s_scopes.py:0 | __package___0 = ScopeEntryDefinition |
723762
| s_scopes.py:0 | float_0 = ScopeEntryDefinition |

python/ql/test/library-tests/PointsTo/new/NameSpace.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@
164164
| r_regressions.py:0 | Module code.r_regressions | f | Function f |
165165
| r_regressions.py:0 | Module code.r_regressions | fail | Function fail |
166166
| r_regressions.py:0 | Module code.r_regressions | find_library | Function find_library |
167+
| r_regressions.py:0 | Module code.r_regressions | gv | C() |
167168
| r_regressions.py:0 | Module code.r_regressions | method_decorator | Function method_decorator |
169+
| r_regressions.py:0 | Module code.r_regressions | mod_gv | Function mod_gv |
168170
| r_regressions.py:0 | Module code.r_regressions | sys | Module sys |
169171
| r_regressions.py:0 | Module code.r_regressions | t | Module time |
170172
| r_regressions.py:5 | Class Queue | __init__ | Function __init__ |

python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,7 @@
229229
| r_regressions.py:64 | ControlFlowNode for do_validation() | 64 |
230230
| r_regressions.py:90 | ControlFlowNode for Attribute | 90 |
231231
| r_regressions.py:90 | ControlFlowNode for Attribute() | 90 |
232+
| r_regressions.py:102 | ControlFlowNode for unrelated_call | 102 |
233+
| r_regressions.py:102 | ControlFlowNode for unrelated_call() | 102 |
234+
| r_regressions.py:107 | ControlFlowNode for Attribute | 106 |
235+
| r_regressions.py:107 | ControlFlowNode for x | 106 |

python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,13 @@
998998
| r_regressions.py:97 | ControlFlowNode for _names | tuple object | builtin-class tuple | 95 | import |
999999
| r_regressions.py:98 | ControlFlowNode for ImportExpr | Module time | builtin-class module | 98 | import |
10001000
| r_regressions.py:98 | ControlFlowNode for t | Module time | builtin-class module | 98 | import |
1001+
| r_regressions.py:100 | ControlFlowNode for C | class C | builtin-class type | 49 | import |
1002+
| r_regressions.py:100 | ControlFlowNode for C() | C() | class C | 100 | import |
1003+
| r_regressions.py:100 | ControlFlowNode for gv | C() | class C | 100 | import |
1004+
| r_regressions.py:104 | ControlFlowNode for gv | C() | class C | 100 | import |
1005+
| r_regressions.py:106 | ControlFlowNode for FunctionExpr | Function mod_gv | builtin-class function | 106 | import |
1006+
| r_regressions.py:106 | ControlFlowNode for mod_gv | Function mod_gv | builtin-class function | 106 | import |
1007+
| r_regressions.py:107 | ControlFlowNode for gv | C() | class C | 100 | runtime |
10011008
| s_scopes.py:4 | ControlFlowNode for True | bool True | builtin-class bool | 4 | import |
10021009
| s_scopes.py:4 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import |
10031010
| s_scopes.py:7 | ControlFlowNode for C2 | class C2 | builtin-class type | 7 | import |

python/ql/test/library-tests/PointsTo/new/SSA.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,12 @@
486486
| n_nesting.py:10 | C_5 = ScopeEntryDefinition | int 1 | builtin-class int |
487487
| n_nesting.py:10 | compile_ops_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* |
488488
| n_nesting.py:10 | inner_0 = FunctionExpr | Function inner | builtin-class function |
489+
| n_nesting.py:11 | C_6 = CallsiteRefinement(C_5) | int 1 | builtin-class int |
489490
| n_nesting.py:13 | C_7 = ScopeEntryDefinition | int 1 | builtin-class int |
490491
| n_nesting.py:13 | compile_ops_4 = Pi(compile_ops_1) [false] | bool True | builtin-class bool |
491492
| n_nesting.py:13 | compile_ops_5 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* |
492493
| n_nesting.py:13 | inner_1 = FunctionExpr | Function inner | builtin-class function |
494+
| n_nesting.py:14 | C_8 = CallsiteRefinement(C_7) | int 1 | builtin-class int |
493495
| n_nesting.py:15 | attrs_0 = Dict | Dict | builtin-class dict |
494496
| n_nesting.py:16 | compile_ops_6 = phi(compile_ops_2, compile_ops_4) | bool True | builtin-class bool |
495497
| n_nesting.py:16 | inner_2 = phi(inner_0, inner_1) | Function inner | builtin-class function |

python/ql/test/library-tests/PointsTo/new/code/r_regressions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,14 @@ def method(self):
9696

9797
if 'time' in _names:
9898
import time as t
99+
100+
gv = C()
101+
102+
unrelated_call()
103+
104+
gv
105+
106+
def mod_gv(x):
107+
gv.attr = x
108+
109+

0 commit comments

Comments
 (0)