python.lang.correctness.common-mistakes.default-mutable-dict.default-mutable-dict
Verifed by r2c
Community Favorite

Author
141,169
Download Count*
License
Function $F mutates default dict $D. Python only instantiates default function arguments once and shares the instance across the function calls. If the default function argument is mutated, that will modify the instance used by all future function calls. This can cause unexpected results, or lead to security vulnerabilities whereby one function consumer can view or modify the data of another function consumer. Instead, use a default argument (like None) to indicate that no argument was provided and instantiate a new dictionary at that time. For example: if $D is None: $D = {}
.
Run Locally
Run in CI
Defintion
rules:
- id: default-mutable-dict
message: "Function $F mutates default dict $D. Python only instantiates default
function arguments once and shares the instance across the function calls.
If the default function argument is mutated, that will modify the instance
used by all future function calls. This can cause unexpected results, or
lead to security vulnerabilities whereby one function consumer can view or
modify the data of another function consumer. Instead, use a default
argument (like None) to indicate that no argument was provided and
instantiate a new dictionary at that time. For example: `if $D is None: $D
= {}`."
languages:
- python
severity: ERROR
options:
symbolic_propagation: true
patterns:
- pattern-not-inside: |
def $A(...):
...
def $F(..., $D={}, ...):
...
- pattern-inside: |
def $F(..., $D={}, ...):
...
- pattern-not-inside: |
$D = {}
...
- pattern-not-inside: |
$D = dict(...)
...
- pattern-not-inside: |
$D = $D.copy()
...
- pattern-not-inside: |
$D = copy.deepcopy($D)
...
- pattern-not-inside: |
$D = copy.copy($D)
...
- pattern-not-inside: |
$D = dict.copy($D)
...
- pattern-not-inside: |
$D = {... for ... in ...}
...
- pattern-not-inside: |
$D = $D or {}
...
- pattern-either:
- pattern: |
$D[...] = ...
- pattern: |
$D.update(...)
- pattern: |
$D.setdefault(...)
metadata:
category: correctness
technology:
- python
references:
- https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
Examples
default-mutable-dict.py
import copy
def assign_func1(default={}):
# ruleid: default-mutable-dict
default["potato"] = 5
def assign_func2(default={}):
for x in range(10):
# ruleid: default-mutable-dict
default[x] = 1
def assign_func3(default={}):
x = default
# ruleid: default-mutable-dict
x[3] = 2
def assign_func4(x=1, default={}):
# ruleid: default-mutable-dict
default["1"] = 1
def assign_func5(default={}):
if not default:
# ruleid: default-mutable-dict
default["1"] = "test"
def assign_func6(default={}, x="string"):
# ruleid: default-mutable-dict
default[1] = 0
def assign_func7(default={}):
if True:
default = dict(default)
else:
# ruleid: default-mutable-dict
default[1] = 21
def assign_func8(default={}):
while True:
# ruleid: default-mutable-dict
default[1] = 4
break
def update_func1(default={}):
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func2(default={}):
for x in range(10):
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func3(default={}):
x = default
# ruleid: default-mutable-dict
x.update({1: 2})
def update_func4(x=1, default={}):
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func5(default={}):
if not default:
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func6(default={}, x="string"):
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func7(default={}):
if True:
default = dict(default)
else:
# ruleid: default-mutable-dict
default.update({1: 2})
def update_func8(default={}):
while True:
# ruleid: default-mutable-dict
default.update({1: 2})
break
def setdefault_func1(default={}):
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func2(default={}):
for x in range(10):
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func3(default={}):
x = default
# ruleid: default-mutable-dict
x.setdefault(1, 2)
def setdefault_func4(x=1, default={}):
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func5(default={}):
if not default:
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func6(default={}, x="string"):
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func7(default={}):
if True:
default = dict(default)
else:
# ruleid: default-mutable-dict
default.setdefault(1, 2)
def setdefault_func8(default={}):
while True:
# ruleid: default-mutable-dict
default.setdefault(1, 2)
break
##### Should not fire on anything below this
# OK
def not_assign_func0(x=1):
x = {}
x[123] = 456
# OK
def not_assign_func1(default={}):
# Immediately overwrites default dict
default = {}
default[123] = 456
# OK
def not_assign_func2(default={}):
# dict() returns a copy
default = dict(default)
default[123] = 456
# OK
def not_assign_func2_1(default={}):
default = dict(m=1, n=2)
default[123] = 456
# OK
def not_assign_func3(default={}):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default[123] = 456
# OK
def not_assign_func3_1(default={}):
# copy.deepcopy returns a copy
default = copy.copy(default)
default[123] = 456
# OK
def not_assign_func4(default={}):
# dict.copy returns a copy
default = dict.copy(default)
default[123] = 456
# OK
def not_assign_func5(default={}):
# copy returns a copy
default = default.copy()
default[123] = 456
# OK
def assign_wrapper():
x = 1
# OK
def not_assign_func6(default={}):
default[123] = 456
not_assign_func6()
# OK
def not_assign_func7(default={}):
if default is {}:
return 5 + 1
# OK
def not_assign_func8(default={}):
default = default or {}
default[123] = 456
# OK
def not_assign_func9(default={}):
default = {str(x) for x in default}
default[123] = 456
# OK
def not_update_func0(x=1):
x = {}
x.update({1: 2})
# OK
def not_update_func1(default={}):
# Immediately overwrites default dict
default = {}
default.update({1: 2})
# OK
def not_update_func2(default={}):
# dict() returns a copy
default = dict(default)
default.update({1: 2})
# OK
def not_update_func2_1(default={}):
default = dict(m=1, n=2)
default.update({1: 2})
# OK
def not_update_func3(default={}):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default.update({1: 2})
# OK
def not_update_func3_1(default={}):
# copy.deepcopy returns a copy
default = copy.copy(default)
default.update({1: 2})
# OK
def not_update_func4(default={}):
# dict.copy returns a copy
default = dict.copy(default)
default.update({1: 2})
# OK
def not_update_func5(default={}):
# copy returns a copy
default = default.copy()
default.update({1: 2})
# OK
def update_wrapper():
x = 1
# OK
def not_update_func6(default={}):
default.update({1: 2})
not_update_func6()
# OK
def not_update_func7(default={}):
if default is {}:
return 5 + 1
# OK
def not_update_func8(default={}):
default = default or {}
default.update({1: 2})
# OK
def not_update_func9(default={}):
default = {str(x) for x in default}
default.update({1: 2})
# OK
def not_setdefault_func0(x=1):
x = {}
x.setdefault(1, 2)
# OK
def not_setdefault_func1(default={}):
# Immediately overwrites default dict
default = {}
default.setdefault(1, 2)
# OK
def not_setdefault_func2(default={}):
# dict() returns a copy
default = dict(default)
default.setdefault(1, 2)
# OK
def not_setdefault_func2_1(default={}):
# dict() returns a copy
default = dict(m=1, n=2)
default.setdefault(1, 2)
# OK
def not_setdefault_func3(default={}):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default.setdefault(1, 2)
# OK
def not_setdefault_func3_1(default={}):
# copy.deepcopy returns a copy
default = copy.copy(default)
default.setdefault(1, 2)
# OK
def not_setdefault_func4(default={}):
# dict.copy returns a copy
default = dict.copy(default)
default.setdefault(1, 2)
# OK
def not_setdefault_func5(default={}):
# copy returns a copy
default = default.copy()
default.setdefault(1, 2)
# OK
def setdefault_wrapper():
x = 1
# OK
def not_setdefault_func6(default={}):
default.setdefault(1, 2)
not_setdefault_func6()
# OK
def not_setdefault_func7(default={}):
if default is {}:
return 5 + 1
# OK
def not_setdefault_func8(default={}):
default = default or {}
default.setdefault(1, 2)
# OK
def not_setdefault_func9(default={}):
default = {str(x) for x in default}
default.setdefault(1, 2)
Short Link: https://sg.run/Av4p