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

Author
141,169
Download Count*
License
Function $F mutates default list $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 list at that time. For example: if $D is None: $D = []
.
Run Locally
Run in CI
Defintion
rules:
- id: default-mutable-list
message: "Function $F mutates default list $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 list 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 = [...]
...
- pattern-not-inside: |
$D = list(...)
...
- pattern-not-inside: |
$D = copy.deepcopy($D)
...
- pattern-not-inside: |
$D = copy.copy($D)
...
- pattern-not-inside: |
$D = list.copy($D)
...
- pattern-not-inside: |
$D = $D[:]
...
- pattern-not-inside: |
$D = [... for ... in ...]
...
- pattern-not-inside: |
$D = $D or []
...
- pattern-either:
- pattern: |
$D.append(...)
- pattern: |
$D.extend(...)
- pattern: |
$D.insert(...)
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-list.py
import copy
def append_func1(default=[]):
# ruleid: default-mutable-list
default.append(5)
def append_func2(default=[]):
for x in range(10):
# ruleid: default-mutable-list
default.append(x)
def append_func3(default=[]):
x = default
# ruleid: default-mutable-list
x.append(5)
def append_func4(x=1, default=[]):
# ruleid: default-mutable-list
default.append(5)
def append_func5(default=[]):
if not default:
# ruleid: default-mutable-list
default.append(1)
def append_func6(default=[], x="string"):
# ruleid: default-mutable-list
default.append(5)
def append_func7(default=[]):
if True:
default = list(default)
else:
# ruleid: default-mutable-list
default.append(1)
def append_func8(default=[]):
while True:
# ruleid: default-mutable-list
default.append(1)
break
def extend_func1(default=[]):
# ruleid: default-mutable-list
default.extend([5])
def extend_func2(default=[]):
for x in range(10):
# ruleid: default-mutable-list
default.extend([x])
def extend_func3(default=[]):
x = default
# ruleid: default-mutable-list
x.extend([5])
def extend_func4(x=1, default=[]):
# ruleid: default-mutable-list
default.extend([5])
def extend_func5(default=[]):
if not default:
# ruleid: default-mutable-list
default.extend([1])
def extend_func6(default=[], x="string"):
# ruleid: default-mutable-list
default.extend([5])
def extend_func7(default=[]):
if True:
default = list(default)
else:
# ruleid: default-mutable-list
default.extend([1])
def extend_func8(default=[]):
while True:
# ruleid: default-mutable-list
default.extend([1])
break
def insert_func1(default=[]):
# ruleid: default-mutable-list
default.insert(0, 5)
def insert_func2(default=[]):
for x in range(10):
# ruleid: default-mutable-list
default.insert(0, x)
def insert_func3(default=[]):
x = default
# ruleid: default-mutable-list
x.insert(0, 5)
def insert_func4(x=1, default=[]):
# ruleid: default-mutable-list
default.insert(0, 5)
def insert_func5(default=[]):
if not default:
# ruleid: default-mutable-list
default.insert(0, 1)
def insert_func6(default=[], x="string"):
# ruleid: default-mutable-list
default.insert(0, 5)
def insert_func7(default=[]):
if True:
default = list(default)
else:
# ruleid: default-mutable-list
default.insert(0, 1)
def insert_func8(default=[]):
while True:
# ruleid: default-mutable-list
default.insert(0, 1)
break
##### Should not fire on anything below this
# OK
def not_append_func0(x=1):
x = []
x.append(2)
# OK
def not_append_func1(default=[]):
# Immediately overwrites default list
default = []
default.append(5)
# OK
def not_append_func2(default=[]):
# list() returns a copy
default = list(default)
default.append(5)
# OK
def not_append_func3(default=[]):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default.append(5)
# OK
def not_append_func3_1(default=[]):
# copy.deepcopy returns a copy
default = copy.copy(default)
default.append(5)
# OK
def not_append_func4(default=[]):
# list.copy returns a copy
default = list.copy(default)
default.append(5)
# OK
def not_append_func5(default=[]):
# [:] returns a copy
default = default[:]
default.append(5)
# OK
def append_wrapper():
x = 1
# OK
def not_append_func6(default=[]):
default.append(5)
not_append_func6()
# OK
def not_append_func7(default=[]):
if default is []:
return 5 + 1
# OK
def not_append_func8(default=[]):
default = default or []
default.append(5)
# OK
def not_append_func9(default=[]):
default = list()
default.append(5)
# OK
def not_append_func10(default=[]):
default = [str(x) for x in default]
default.append(5)
# OK
def not_insert_func0(x=1):
x = []
x.insert(0, 2)
# OK
def not_insert_func1(default=[]):
# Immediately overwrites default list
default = []
default.insert(0, 5)
# OK
def not_insert_func2(default=[]):
# list() returns a copy
default = list(default)
default.insert(0, 5)
# OK
def not_insert_func3(default=[]):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default.insert(0, 5)
# OK
def not_insert_func3_1(default=[]):
# copy.deepcopy returns a copy
default = copy.copy(default)
default.insert(0, 5)
# OK
def not_insert_func4(default=[]):
# list.copy returns a copy
default = list.copy(default)
default.insert(0, 5)
# OK
def not_insert_func5(default=[]):
# [:] returns a copy
default = default[:]
default.insert(0, 5)
# OK
def insert_wrapper():
x = 1
# OK
def not_insert_func6(default=[]):
default.insert(0, 5)
not_insert_func6()
# OK
def not_insert_func7(default=[]):
if default is []:
return 5 + 1
# OK
def not_insert_func8(default=[]):
default = default or []
default.insert(0, 5)
# OK
def not_insert_func9(default=[]):
default = list()
default.insert(0, 5)
# OK
def not_insert_func10(default=[]):
default = [str(x) for x in default]
default.insert(0, 5)
# OK
def not_extend_func0(x=1):
x = []
x.extend([2])
# OK
def not_extend_func1(default=[]):
# Immediately overwrites default list
default = []
default.extend([5])
# OK
def not_extend_func2(default=[]):
# list() returns a copy
default = list(default)
default.extend([5])
# OK
def not_extend_func3(default=[]):
# copy.deepcopy returns a copy
default = copy.deepcopy(default)
default.extend([5])
# OK
def not_extend_func3_1(default=[]):
# copy.deepcopy returns a copy
default = copy.copy(default)
default.extend([5])
# OK
def not_extend_func4(default=[]):
# list.copy returns a copy
default = list.copy(default)
default.extend([5])
# OK
def not_extend_func5(default=[]):
# [:] returns a copy
default = default[:]
default.extend([5])
# OK
def extend_wrapper():
x = 1
# OK
def not_extend_func6(default=[]):
default.extend([5])
not_extend_func6()
# OK
def not_extend_func7(default=[]):
if default is []:
return 5 + 1
# OK
def not_extend_func8(default=[]):
default = default or []
default.extend([5])
# OK
def not_extend_func9(default=[]):
default = list()
default.extend([5])
# OK
def not_extend_func10(default=[]):
default = [str(x) for x in default]
default.extend([5])
Short Link: https://sg.run/BkPW