Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance checker "Dangerous default value" detecting callables #8553

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/4659.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Dangerous-default-value warns also about callables: functions, methods, lambdas, ...

Closes #4659
33 changes: 33 additions & 0 deletions pylint/checkers/base/basic_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ class _BasicChecker(BaseChecker):
)
},
)
DEFAULT_ARGUMENT_CALLABLE_IMMUTABLE_BUILTINS = [
"frozenset",
"bytes",
"tuple",
"str",
"int",
"float",
"complex",
"range",
]


def report_by_type_stats(
Expand Down Expand Up @@ -605,6 +615,23 @@ def _check_dangerous_default(self, node: nodes.FunctionDef) -> None:
def is_iterable(internal_node: nodes.NodeNG) -> bool:
return isinstance(internal_node, (nodes.List, nodes.Set, nodes.Dict))

def is_callable_immutable_builtins_func(
default: nodes.NodeNG, value: nodes.NodeNG
) -> bool:
"""Indirect recognize immutable builtins function exceptions:

1 - If the name is the same
2 - And type of value provided is the right one
"""
if not hasattr(default, "func"):
return False
default_as_string = default.func.as_string()
return (
default_as_string in DEFAULT_ARGUMENT_CALLABLE_IMMUTABLE_BUILTINS
and isinstance(value, astroid.Instance)
and value.qname() == f"builtins.{default_as_string}"
)

defaults = (node.args.defaults or []) + (node.args.kw_defaults or [])
for default in defaults:
if not default:
Expand Down Expand Up @@ -639,6 +666,12 @@ def is_iterable(internal_node: nodes.NodeNG) -> bool:
msg = f"{default.as_string()} ({DEFAULT_ARGUMENT_SYMBOLS[value.qname()]})"
self.add_message("dangerous-default-value", node=node, args=(msg,))

elif isinstance(
default, astroid.nodes.node_classes.Call
) and not is_callable_immutable_builtins_func(default, value):
msg = f"{default.as_string()} Callable"
self.add_message("dangerous-default-value", node=node, args=(msg,))

@utils.only_required_for_messages("unreachable", "lost-exception")
def visit_return(self, node: nodes.Return) -> None:
"""Return node visitor.
Expand Down
41 changes: 40 additions & 1 deletion tests/functional/d/dangerous_default_value.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pylint: disable=missing-docstring, use-list-literal, use-dict-literal
# pylint: disable=missing-docstring, use-list-literal, use-dict-literal, unnecessary-direct-lambda-call, too-few-public-methods
import collections
from datetime import datetime

HEHE = {}

Expand Down Expand Up @@ -110,6 +111,44 @@ def function24(*, value=[]): # [dangerous-default-value]
"""dangerous default value in kwarg."""
return value

def function25(value=datetime.date.today()): # [dangerous-default-value]
return value

def function26(*, value=datetime.date.today()): # [dangerous-default-value]
return value

def function27(value=str()):
return value

def function28(value=tuple()):
return value

def function29(value=bytes()):
return value

def function30(value=int()):
return value

def function31(value=float()):
return value

def function32(value=complex()):
return value

def function33(value=range()):
return value

class MyClass:
def try_with_a_method(self, arg):
return arg

def function34(value=MyClass().try_with_a_method("beta")): # [dangerous-default-value]
"""docstring"""
return value

def function35(value=(lambda x: x+1)(2)): # [dangerous-default-value]
"""docstring"""
return value

class Clazz:
# pylint: disable=too-few-public-methods
Expand Down
50 changes: 27 additions & 23 deletions tests/functional/d/dangerous_default_value.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
dangerous-default-value:6:0:6:13:function1:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:10:0:10:13:function2:Dangerous default value HEHE (builtins.dict) as argument:UNDEFINED
dangerous-default-value:18:0:18:13:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED
dangerous-default-value:28:0:28:13:function6:Dangerous default value GLOBAL_SET (builtins.set) as argument:UNDEFINED
dangerous-default-value:32:0:32:13:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:36:0:36:13:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED
dangerous-default-value:40:0:40:13:function9:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:44:0:44:14:function10:Dangerous default value {} as argument:UNDEFINED
dangerous-default-value:48:0:48:14:function11:Dangerous default value list() (builtins.list) as argument:UNDEFINED
dangerous-default-value:52:0:52:14:function12:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:61:0:61:14:function13:Dangerous default value OINK (builtins.dict) as argument:UNDEFINED
dangerous-default-value:65:0:65:14:function14:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:73:0:73:14:function15:Dangerous default value INVALID_DICT (builtins.dict) as argument:UNDEFINED
dangerous-default-value:77:0:77:14:function16:Dangerous default value set() as argument:UNDEFINED
dangerous-default-value:81:0:81:14:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED
dangerous-default-value:85:0:85:14:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED
dangerous-default-value:89:0:89:14:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED
dangerous-default-value:93:0:93:14:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED
dangerous-default-value:97:0:97:14:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED
dangerous-default-value:101:0:101:14:function22:Dangerous default value UserDict() (collections.UserDict) as argument:UNDEFINED
dangerous-default-value:105:0:105:14:function23:Dangerous default value UserList() (collections.UserList) as argument:UNDEFINED
dangerous-default-value:109:0:109:14:function24:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:116:4:116:16:Clazz.__init__:Dangerous default value {} as argument:UNDEFINED
dangerous-default-value:7:0:7:13:function1:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:11:0:11:13:function2:Dangerous default value HEHE (builtins.dict) as argument:UNDEFINED
dangerous-default-value:19:0:19:13:function4:Dangerous default value set() (builtins.set) as argument:UNDEFINED
dangerous-default-value:29:0:29:13:function6:Dangerous default value GLOBAL_SET (builtins.set) as argument:UNDEFINED
dangerous-default-value:33:0:33:13:function7:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:37:0:37:13:function8:Dangerous default value list() (builtins.list) as argument:UNDEFINED
dangerous-default-value:41:0:41:13:function9:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:45:0:45:14:function10:Dangerous default value {} as argument:UNDEFINED
dangerous-default-value:49:0:49:14:function11:Dangerous default value list() (builtins.list) as argument:UNDEFINED
dangerous-default-value:53:0:53:14:function12:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:62:0:62:14:function13:Dangerous default value OINK (builtins.dict) as argument:UNDEFINED
dangerous-default-value:66:0:66:14:function14:Dangerous default value dict() (builtins.dict) as argument:UNDEFINED
dangerous-default-value:74:0:74:14:function15:Dangerous default value INVALID_DICT (builtins.dict) as argument:UNDEFINED
dangerous-default-value:78:0:78:14:function16:Dangerous default value set() as argument:UNDEFINED
dangerous-default-value:82:0:82:14:function17:Dangerous default value deque() (collections.deque) as argument:UNDEFINED
dangerous-default-value:86:0:86:14:function18:Dangerous default value ChainMap() (collections.ChainMap) as argument:UNDEFINED
dangerous-default-value:90:0:90:14:function19:Dangerous default value Counter() (collections.Counter) as argument:UNDEFINED
dangerous-default-value:94:0:94:14:function20:Dangerous default value OrderedDict() (collections.OrderedDict) as argument:UNDEFINED
dangerous-default-value:98:0:98:14:function21:Dangerous default value defaultdict() (collections.defaultdict) as argument:UNDEFINED
dangerous-default-value:102:0:102:14:function22:Dangerous default value UserDict() (collections.UserDict) as argument:UNDEFINED
dangerous-default-value:106:0:106:14:function23:Dangerous default value UserList() (collections.UserList) as argument:UNDEFINED
dangerous-default-value:110:0:110:14:function24:Dangerous default value [] as argument:UNDEFINED
dangerous-default-value:114:0:114:14:function25:Dangerous default value datetime.date.today() Callable as argument:UNDEFINED
dangerous-default-value:117:0:117:14:function26:Dangerous default value datetime.date.today() Callable as argument:UNDEFINED
dangerous-default-value:145:0:145:14:function34:Dangerous default value MyClass().try_with_a_method('beta') Callable as argument:UNDEFINED
dangerous-default-value:149:0:149:14:function35:"Dangerous default value (lambda x: x + 1)(2) Callable as argument":UNDEFINED
dangerous-default-value:155:4:155:16:Clazz.__init__:Dangerous default value {} as argument:UNDEFINED