diff --git a/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected index 547cec0c3b8..5575d3930c1 100644 --- a/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected +++ b/python/ql/test/query-tests/Statements/general/ModificationOfLocals.expected @@ -3,3 +3,10 @@ | test.py:101:5:101:14 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | | test.py:102:9:102:14 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | | test.py:103:5:103:13 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:206:5:206:11 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:207:5:207:23 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:208:5:208:15 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:209:9:209:15 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:210:5:210:14 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:228:9:228:24 | Subscript | Modification of the locals() dictionary will have no effect on the local variables. | +| test.py:229:9:229:35 | Attribute() | Modification of the locals() dictionary will have no effect on the local variables. | diff --git a/python/ql/test/query-tests/Statements/general/test.py b/python/ql/test/query-tests/Statements/general/test.py index 326c3f2a112..eb38acd89b1 100644 --- a/python/ql/test/query-tests/Statements/general/test.py +++ b/python/ql/test/query-tests/Statements/general/test.py @@ -196,3 +196,41 @@ class false_positive: class MyClass: locals()['x'] = 43 # OK y = x + + +# Once a `locals()` dictionary is passed out of the scope that created it, it is +# just an ordinary mapping. Modifying it in a different scope is meaningful and +# effective, so these modifications must NOT be flagged: the "no effect on local +# variables" gotcha only applies within the scope that called `locals()`. +def modify_passed_dict(ns): + ns['k'] = 1 # OK: `ns` is a parameter here, not this scope's locals() + ns.update({'j': 2}) # OK + ns.pop('k') # OK + del ns['j'] # OK + ns.clear() # OK + + +def pass_locals_to_function(): + y = 1 + modify_passed_dict(locals()) + return y + + +# The same situation, but where the `locals()` dictionary is laundered through an +# instance attribute (as instance-attribute type tracking now models). These must +# also not be flagged. +class NamespaceHolder(object): + + def __init__(self, ns): + self.ns = ns + + def populate(self): + self.ns['extra'] = 1 # OK: different scope from the `locals()` call + self.ns.update({'more': 2}) # OK + + +def launder_locals_through_instance(): + x = 1 + holder = NamespaceHolder(locals()) + holder.populate() + return x