Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions fickling/fickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,12 @@ def unused_assignments(self) -> dict[str, ast.Assign]:
f"Warning: Duplicate declaration of variable {target.id}\n"
)
assignments[target.id] = statement
else:
# For complex targets like subscripts (e.g. _var[_key] = val),
# names within the target expression are uses, not definitions.
for node in ast.walk(target):
if isinstance(node, ast.Name):
used.add(node.id)
statement = statement.value
if statement is not None:
for node in ast.walk(statement):
Expand Down
23 changes: 23 additions & 0 deletions test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,29 @@ def test_unused_variables(self):
test_unused_variables_results = check_safety(loaded).to_dict()
self.assertEqual(test_unused_variables_results["severity"], "OVERTLY_MALICIOUS")

def test_unused_variables_no_false_positive_for_dict_keys(self):
"""Variables used as dict keys in subscript assignments should not be
reported as unused. Regression test for GH-226."""
# This pickle creates an object via NEWOBJ, then uses REDUCE to create
# a value (long(42)) which becomes _var2, followed by SETITEM that uses
# _var2 as a dict key, then BUILD (__setstate__). The variable assigned
# by REDUCE is genuinely used as a dict key and should not be flagged.
pickle_bytes = (
b"\x80\x02c__main__\nOuter\nq\x00)\x81q\x01}q\x02"
b"c__builtin__\nlong\nq\x03K*\x85q\x04Rq\x05"
b"X\x05\x00\x00\x00valueq\x06sb."
)
loaded = Pickled.load(pickle_bytes)
interpreter = Interpreter(loaded)
unused = interpreter.unused_variables()
# _var2 (the long(42) result) is used as a dict key via SETITEM;
# it must NOT appear in the unused set.
for varname in unused:
self.fail(
f"Variable {varname} incorrectly reported as unused; "
f"expected no false positives for dict-key usage (GH-226)"
)

@stacked_correctness_test([1, 2, 3, 4], [5, 6, 7, 8])
def test_stacked_pickles(self):
pass
Expand Down