diff --git a/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll b/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll index c60971656c02..72df1ce120b7 100644 --- a/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll +++ b/python/ql/src/semmle/python/dataflow/SsaDefinitions.qll @@ -300,6 +300,15 @@ class EscapingGlobalVariable extends ModuleVariable { } +class EscapingAssignmentGlobalVariable extends EscapingGlobalVariable { + + EscapingAssignmentGlobalVariable() { + exists(NameNode n | n.defines(this) and not n.getScope() = this.getScope()) + } + +} + + class SpecialSsaSourceVariable extends PythonSsaSourceVariable { SpecialSsaSourceVariable() { diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll index 0ea767330624..d0a0260a96de 100644 --- a/python/ql/src/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -1713,14 +1713,23 @@ module PointsTo { */ pragma [noinline] private predicate callsite_points_to(CallsiteRefinement def, PointsToContext context, Object value, ClassObject cls, ObjectOrCfg origin) { - exists(EssaVariable var, PointsToContext callee | - Flow::callsite_exit_value_transfer(var, callee, def, context) and - ssa_variable_points_to(var, callee, value, cls, origin) + exists(SsaSourceVariable srcvar | + srcvar = def.getSourceVariable() | + if srcvar instanceof EscapingAssignmentGlobalVariable then ( + /* If global variable can be reassigned, we need to track it through calls */ + exists(EssaVariable var, PointsToContext callee | + Flow::callsite_exit_value_transfer(var, callee, def, context) and + ssa_variable_points_to(var, callee, value, cls, origin) + ) + or + callsite_points_to_python(def, context, value, cls, origin) + or + callsite_points_to_builtin(def, context, value, cls, origin) + ) else ( + /* Otherwise we can assume its value (but not those of its attributes or members) has not changed. */ + ssa_variable_points_to(def.getInput(), context, value, cls, origin) + ) ) - or - callsite_points_to_python(def, context, value, cls, origin) - or - callsite_points_to_builtin(def, context, value, cls, origin) } pragma [noinline] diff --git a/python/ql/test/library-tests/PointsTo/new/Dataflow.expected b/python/ql/test/library-tests/PointsTo/new/Dataflow.expected index e0a751c47482..26ca5b914938 100644 --- a/python/ql/test/library-tests/PointsTo/new/Dataflow.expected +++ b/python/ql/test/library-tests/PointsTo/new/Dataflow.expected @@ -625,31 +625,40 @@ | r_regressions.py:0 | __name___0 = ScopeEntryDefinition | | r_regressions.py:0 | __package___0 = ScopeEntryDefinition | | r_regressions.py:0 | _names_0 = ScopeEntryDefinition | -| r_regressions.py:0 | _names_3 = Pi(_names_1) [false] | -| r_regressions.py:0 | _names_4 = phi(_names_2, _names_3) | +| r_regressions.py:0 | gv_0 = ScopeEntryDefinition | +| r_regressions.py:0 | mod_gv_0 = ScopeEntryDefinition | | r_regressions.py:0 | sys_0 = ScopeEntryDefinition | | r_regressions.py:0 | t_0 = ScopeEntryDefinition | -| r_regressions.py:0 | t_2 = phi(t_0, t_1) | | r_regressions.py:5 | Queue_0 = ClassExpr | | r_regressions.py:7 | __init___0 = FunctionExpr | +| r_regressions.py:7 | gv_5 = ScopeEntryDefinition | | r_regressions.py:7 | self_0 = ParameterDefinition | +| r_regressions.py:9 | gv_6 = CallsiteRefinement(gv_5) | | r_regressions.py:9 | self_1 = SelfCallsiteRefinement(self_0) | | r_regressions.py:11 | _after_fork_0 = FunctionExpr | +| r_regressions.py:11 | gv_7 = ScopeEntryDefinition | | r_regressions.py:11 | self_0 = ParameterDefinition | | r_regressions.py:12 | self_1 = AttributeAssignment '_closed'(self_0) | | r_regressions.py:13 | self_2 = AttributeAssignment '_close'(self_1) | | r_regressions.py:15 | close_0 = FunctionExpr | | r_regressions.py:15 | close_4 = Pi(close_0) [false] | | r_regressions.py:15 | close_5 = phi(close_3, close_4) | +| r_regressions.py:15 | gv_8 = ScopeEntryDefinition | +| r_regressions.py:15 | gv_33 = phi(gv_9, gv_11) | | r_regressions.py:15 | self_0 = ParameterDefinition | | r_regressions.py:15 | self_3 = phi(self_1, self_2) | | r_regressions.py:16 | self_1 = AttributeAssignment '_closed'(self_0) | +| r_regressions.py:18 | gv_9 = CallsiteRefinement(gv_8) | | r_regressions.py:20 | close_0 = Attribute | | r_regressions.py:20 | close_1 = Attribute | +| r_regressions.py:20 | gv_10 = phi(gv_8, gv_9) | | r_regressions.py:21 | close_2 = SingleSuccessorGuard(close_1) [true] | | r_regressions.py:22 | close_3 = Pi(close_0) [true] | | r_regressions.py:22 | self_2 = AttributeAssignment '_close'(self_1) | +| r_regressions.py:23 | gv_11 = CallsiteRefinement(gv_9) | | r_regressions.py:27 | f_0 = FunctionExpr | +| r_regressions.py:27 | gv_12 = ScopeEntryDefinition | +| r_regressions.py:27 | gv_33 = phi(gv_12, gv_13) | | r_regressions.py:27 | x_0 = ParameterDefinition | | r_regressions.py:27 | x_5 = phi(x_3, x_4) | | r_regressions.py:27 | y_0 = ParameterDefinition | @@ -664,6 +673,7 @@ | r_regressions.py:33 | y_1 = Pi(y_0) [false] | | r_regressions.py:33 | y_2 = phi(y_0, y_1) | | r_regressions.py:36 | y_3 = Pi(y_2) [true] | +| r_regressions.py:39 | gv_13 = CallsiteRefinement(gv_12) | | r_regressions.py:39 | x_4 = phi(x_1, x_3) | | r_regressions.py:39 | y_4 = Pi(y_0) [true] | | r_regressions.py:39 | y_5 = phi(y_3, y_4) | @@ -671,53 +681,82 @@ | r_regressions.py:39 | z_1 = Pi(z_0) [true] | | r_regressions.py:39 | z_2 = phi(z_0, z_1) | | r_regressions.py:42 | find_library_0 = FunctionExpr | +| r_regressions.py:42 | gv_14 = ScopeEntryDefinition | | r_regressions.py:42 | name_0 = ParameterDefinition | | r_regressions.py:43 | __0 = ... | | r_regressions.py:43 | data_0 = ... | +| r_regressions.py:43 | gv_15 = CallsiteRefinement(gv_14) | | r_regressions.py:46 | fail_0 = FunctionExpr | +| r_regressions.py:46 | gv_16 = ScopeEntryDefinition | | r_regressions.py:46 | msg_0 = ParameterDefinition | | r_regressions.py:49 | C_0 = ClassExpr | | r_regressions.py:51 | fail_0 = FunctionExpr | | r_regressions.py:51 | fail_1 = ScopeEntryDefinition | +| r_regressions.py:51 | gv_17 = ScopeEntryDefinition | | r_regressions.py:51 | msg_0 = ParameterDefinition | | r_regressions.py:51 | self_0 = ParameterDefinition | +| r_regressions.py:52 | gv_18 = CallsiteRefinement(gv_17) | | r_regressions.py:52 | msg_1 = ArgumentRefinement(msg_0) | | r_regressions.py:58 | decorator_0 = ParameterDefinition | +| r_regressions.py:58 | gv_19 = ScopeEntryDefinition | | r_regressions.py:58 | method_decorator_0 = FunctionExpr | | r_regressions.py:58 | name_0 = ParameterDefinition | | r_regressions.py:61 | _dec_0 = FunctionExpr | | r_regressions.py:61 | func_0 = ScopeEntryDefinition | +| r_regressions.py:61 | gv_20 = ScopeEntryDefinition | +| r_regressions.py:61 | gv_33 = phi(gv_23, gv_25) | | r_regressions.py:61 | is_class_6 = phi(is_class_4, is_class_5) | | r_regressions.py:61 | name_1 = ScopeEntryDefinition | | r_regressions.py:61 | obj_0 = ParameterDefinition | | r_regressions.py:61 | obj_3 = phi(obj_1, obj_2) | +| r_regressions.py:62 | gv_21 = CallsiteRefinement(gv_20) | | r_regressions.py:62 | is_class_0 = isinstance() | | r_regressions.py:62 | obj_1 = ArgumentRefinement(obj_0) | +| r_regressions.py:64 | gv_22 = CallsiteRefinement(gv_21) | | r_regressions.py:64 | is_class_1 = Pi(is_class_0) [true] | | r_regressions.py:66 | func_1 = obj | | r_regressions.py:66 | is_class_2 = Pi(is_class_0) [false] | | r_regressions.py:68 | _wrapper_0 = FunctionExpr | | r_regressions.py:68 | args_0 = ParameterDefinition | | r_regressions.py:68 | func_2 = phi(func_0, func_1) | +| r_regressions.py:68 | gv_23 = phi(gv_21, gv_22) | +| r_regressions.py:68 | gv_24 = ScopeEntryDefinition | | r_regressions.py:68 | is_class_3 = phi(is_class_1, is_class_2) | | r_regressions.py:68 | kwargs_0 = ParameterDefinition | | r_regressions.py:68 | self_0 = ParameterDefinition | +| r_regressions.py:73 | gv_25 = CallsiteRefinement(gv_23) | | r_regressions.py:73 | is_class_4 = Pi(is_class_3) [true] | | r_regressions.py:73 | obj_2 = ArgumentRefinement(obj_1) | | r_regressions.py:76 | is_class_5 = Pi(is_class_3) [false] | | r_regressions.py:80 | deco_0 = FunctionExpr | | r_regressions.py:80 | func_0 = ParameterDefinition | +| r_regressions.py:80 | gv_26 = ScopeEntryDefinition | | r_regressions.py:81 | _wrapper_0 = FunctionExpr | | r_regressions.py:81 | args_0 = ParameterDefinition | +| r_regressions.py:81 | gv_27 = ScopeEntryDefinition | | r_regressions.py:81 | kwargs_0 = ParameterDefinition | | r_regressions.py:85 | deco_1 = ArgumentRefinement(deco_0) | +| r_regressions.py:85 | gv_1 = CallsiteRefinement(gv_0) | +| r_regressions.py:85 | gv_2 = CallsiteRefinement(gv_1) | | r_regressions.py:86 | TestFirst_1 = method_decorator()() | +| r_regressions.py:87 | gv_28 = ScopeEntryDefinition | | r_regressions.py:87 | method_0 = FunctionExpr | | r_regressions.py:87 | self_0 = ParameterDefinition | +| r_regressions.py:90 | gv_3 = CallsiteRefinement(gv_2) | +| r_regressions.py:90 | gv_4 = CallsiteRefinement(gv_3) | | r_regressions.py:93 | sys_1 = ImportExpr | | r_regressions.py:95 | _names_1 = Attribute | | r_regressions.py:98 | _names_2 = Pi(_names_1) [true] | | r_regressions.py:98 | t_1 = ImportExpr | +| r_regressions.py:100 | _names_3 = Pi(_names_1) [false] | +| r_regressions.py:100 | _names_4 = phi(_names_2, _names_3) | +| r_regressions.py:100 | gv_29 = C() | +| r_regressions.py:100 | t_2 = phi(t_0, t_1) | +| r_regressions.py:102 | gv_30 = CallsiteRefinement(gv_29) | +| r_regressions.py:106 | gv_31 = ScopeEntryDefinition | +| r_regressions.py:106 | mod_gv_1 = FunctionExpr | +| r_regressions.py:106 | x_0 = ParameterDefinition | +| r_regressions.py:107 | gv_32 = AttributeAssignment 'attr'(gv_31) | | s_scopes.py:0 | __name___0 = ScopeEntryDefinition | | s_scopes.py:0 | __package___0 = ScopeEntryDefinition | | s_scopes.py:0 | float_0 = ScopeEntryDefinition | diff --git a/python/ql/test/library-tests/PointsTo/new/NameSpace.expected b/python/ql/test/library-tests/PointsTo/new/NameSpace.expected index 5f84595de0a8..481b25a82581 100644 --- a/python/ql/test/library-tests/PointsTo/new/NameSpace.expected +++ b/python/ql/test/library-tests/PointsTo/new/NameSpace.expected @@ -164,7 +164,9 @@ | r_regressions.py:0 | Module code.r_regressions | f | Function f | | r_regressions.py:0 | Module code.r_regressions | fail | Function fail | | r_regressions.py:0 | Module code.r_regressions | find_library | Function find_library | +| r_regressions.py:0 | Module code.r_regressions | gv | C() | | r_regressions.py:0 | Module code.r_regressions | method_decorator | Function method_decorator | +| r_regressions.py:0 | Module code.r_regressions | mod_gv | Function mod_gv | | r_regressions.py:0 | Module code.r_regressions | sys | Module sys | | r_regressions.py:0 | Module code.r_regressions | t | Module time | | r_regressions.py:5 | Class Queue | __init__ | Function __init__ | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected index 90c17665977d..dd63fe12bb21 100644 --- a/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected +++ b/python/ql/test/library-tests/PointsTo/new/PointsToUnknown.expected @@ -229,3 +229,7 @@ | r_regressions.py:64 | ControlFlowNode for do_validation() | 64 | | r_regressions.py:90 | ControlFlowNode for Attribute | 90 | | r_regressions.py:90 | ControlFlowNode for Attribute() | 90 | +| r_regressions.py:102 | ControlFlowNode for unrelated_call | 102 | +| r_regressions.py:102 | ControlFlowNode for unrelated_call() | 102 | +| r_regressions.py:107 | ControlFlowNode for Attribute | 106 | +| r_regressions.py:107 | ControlFlowNode for x | 106 | diff --git a/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected index 80ac9fb72c9d..811df60fc982 100755 --- a/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected +++ b/python/ql/test/library-tests/PointsTo/new/PointsToWithContext.expected @@ -998,6 +998,13 @@ | r_regressions.py:97 | ControlFlowNode for _names | tuple object | builtin-class tuple | 95 | import | | r_regressions.py:98 | ControlFlowNode for ImportExpr | Module time | builtin-class module | 98 | import | | r_regressions.py:98 | ControlFlowNode for t | Module time | builtin-class module | 98 | import | +| r_regressions.py:100 | ControlFlowNode for C | class C | builtin-class type | 49 | import | +| r_regressions.py:100 | ControlFlowNode for C() | C() | class C | 100 | import | +| r_regressions.py:100 | ControlFlowNode for gv | C() | class C | 100 | import | +| r_regressions.py:104 | ControlFlowNode for gv | C() | class C | 100 | import | +| r_regressions.py:106 | ControlFlowNode for FunctionExpr | Function mod_gv | builtin-class function | 106 | import | +| r_regressions.py:106 | ControlFlowNode for mod_gv | Function mod_gv | builtin-class function | 106 | import | +| r_regressions.py:107 | ControlFlowNode for gv | C() | class C | 100 | runtime | | s_scopes.py:4 | ControlFlowNode for True | bool True | builtin-class bool | 4 | import | | s_scopes.py:4 | ControlFlowNode for float | bool True | builtin-class bool | 4 | import | | s_scopes.py:7 | ControlFlowNode for C2 | class C2 | builtin-class type | 7 | import | diff --git a/python/ql/test/library-tests/PointsTo/new/SSA.expected b/python/ql/test/library-tests/PointsTo/new/SSA.expected index 18df1e455dc2..a95f19cef718 100644 --- a/python/ql/test/library-tests/PointsTo/new/SSA.expected +++ b/python/ql/test/library-tests/PointsTo/new/SSA.expected @@ -486,10 +486,12 @@ | n_nesting.py:10 | C_5 = ScopeEntryDefinition | int 1 | builtin-class int | | n_nesting.py:10 | compile_ops_3 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | | n_nesting.py:10 | inner_0 = FunctionExpr | Function inner | builtin-class function | +| n_nesting.py:11 | C_6 = CallsiteRefinement(C_5) | int 1 | builtin-class int | | n_nesting.py:13 | C_7 = ScopeEntryDefinition | int 1 | builtin-class int | | n_nesting.py:13 | compile_ops_4 = Pi(compile_ops_1) [false] | bool True | builtin-class bool | | n_nesting.py:13 | compile_ops_5 = ScopeEntryDefinition | *UNDEFINED* | *UNKNOWN TYPE* | | n_nesting.py:13 | inner_1 = FunctionExpr | Function inner | builtin-class function | +| n_nesting.py:14 | C_8 = CallsiteRefinement(C_7) | int 1 | builtin-class int | | n_nesting.py:15 | attrs_0 = Dict | Dict | builtin-class dict | | n_nesting.py:16 | compile_ops_6 = phi(compile_ops_2, compile_ops_4) | bool True | builtin-class bool | | n_nesting.py:16 | inner_2 = phi(inner_0, inner_1) | Function inner | builtin-class function | diff --git a/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py b/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py index 5579de3da8e3..32aebf989c95 100644 --- a/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py +++ b/python/ql/test/library-tests/PointsTo/new/code/r_regressions.py @@ -96,3 +96,14 @@ def method(self): if 'time' in _names: import time as t + +gv = C() + +unrelated_call() + +gv + +def mod_gv(x): + gv.attr = x + +