Skip to content

Commit 231def0

Browse files
authored
Merge pull request #8890 from tausbn/python-add-global-attribute-writes
Python: Add support for global attribute writes
2 parents 29f30a1 + c67b06b commit 231def0

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ private class AttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode {
9292
override string getAttributeName() { result = node.getName() }
9393
}
9494

95+
/**
96+
* An attribute assignment where the object is a global variable: `global_var.attr = value`.
97+
*
98+
* Syntactically, this is identical to the situation that is covered by
99+
* `AttributeAssignmentAsAttrWrite`, however in this case we want to behave as if the object that is
100+
* being written is the underlying `ModuleVariableNode`.
101+
*/
102+
private class GlobalAttributeAssignmentAsAttrWrite extends AttrWrite, CfgNode {
103+
override AttributeAssignmentNode node;
104+
105+
override Node getValue() { result.asCfgNode() = node.getValue() }
106+
107+
override Node getObject() {
108+
result.(ModuleVariableNode).getVariable() = node.getObject().getNode().(Name).getVariable()
109+
}
110+
111+
override ExprNode getAttributeNameExpr() {
112+
// Attribute names don't exist as `Node`s in the control flow graph, as they can only ever be
113+
// identifiers, and are therefore represented directly as strings.
114+
// Use `getAttributeName` to access the name of the attribute.
115+
none()
116+
}
117+
118+
override string getAttributeName() { result = node.getName() }
119+
}
120+
95121
/** Represents `CallNode`s that may refer to calls to built-in functions or classes. */
96122
private class BuiltInCallNode extends CallNode {
97123
string name;

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ def test_create_with_foo():
4949
x = create_with_foo() # $ tracked=foo
5050
print(x.foo) # $ tracked=foo tracked
5151

52+
def test_global_attribute_assignment():
53+
global global_var
54+
global_var.foo = tracked # $ tracked tracked=foo
55+
56+
def test_global_attribute_read():
57+
x = global_var.foo # $ tracked tracked=foo
58+
59+
def test_local_attribute_assignment():
60+
# Same as `test_global_attribute_assignment`, but the assigned variable is not global
61+
# In this case, we don't want flow going to the `ModuleVariableNode` for `local_var`
62+
# (which is referenced in `test_local_attribute_read`).
63+
local_var = object() # $ tracked=foo
64+
local_var.foo = tracked # $ tracked tracked=foo
65+
66+
def test_local_attribute_read():
67+
x = local_var.foo
68+
69+
5270
# ------------------------------------------------------------------------------
5371
# Attributes assigned statically to a class
5472
# ------------------------------------------------------------------------------

0 commit comments

Comments
 (0)