Skip to content

Commit e712edc

Browse files
committed
Squash one-off inference utility functions to help reduce recursion errors (pylint-dev#804)
This also makes debugging a lot simpler reducing the complexity of the function stack.
1 parent ec96745 commit e712edc

File tree

4 files changed

+40
-1
lines changed

4 files changed

+40
-1
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Release Date: TBA
1313

1414
Fixes PyCQA/pylint#3599
1515

16+
* Prevent recursion error for self referential length calls
17+
18+
Close #777
19+
1620
* Added missing methods to the brain for ``mechanize``, to fix pylint false positives
1721

1822
Close #793

astroid/brain/brain_builtin_inference.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ def infer_len(node, context=None):
759759
"({len}) given".format(len=len(call.positional_arguments))
760760
)
761761
[argument_node] = call.positional_arguments
762+
762763
try:
763764
return nodes.Const(helpers.object_len(argument_node, context=context))
764765
except (AstroidTypeError, InferenceError) as exc:

astroid/helpers.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,31 @@ def object_len(node, context=None):
237237
:raises AstroidTypeError: If an invalid node is returned
238238
from __len__ method or no __len__ method exists
239239
:raises InferenceError: If the given node cannot be inferred
240-
or if multiple nodes are inferred
240+
or if multiple nodes are inferred or if the code executed in python
241+
would result in a infinite recursive check for length
241242
:rtype int: Integer length of node
242243
"""
243244
# pylint: disable=import-outside-toplevel; circular import
244245
from astroid.objects import FrozenSet
245246

246247
inferred_node = safe_infer(node, context=context)
248+
249+
# prevent self referential length calls from causing a recursion error
250+
# see https://github.com/PyCQA/astroid/issues/777
251+
node_frame = node.frame()
252+
if (
253+
isinstance(node_frame, scoped_nodes.FunctionDef)
254+
and node_frame.name == "__len__"
255+
and inferred_node._proxied == node_frame.parent
256+
):
257+
message = (
258+
"Self referential __len__ function will "
259+
"cause a RecursionError on line {} of {}".format(
260+
node.lineno, node.root().file
261+
)
262+
)
263+
raise exceptions.InferenceError(message)
264+
247265
if inferred_node is None or inferred_node is util.Uninferable:
248266
raise exceptions.InferenceError(node=node)
249267
if isinstance(inferred_node, nodes.Const) and isinstance(

tests/unittest_brain.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,5 +2035,21 @@ def test_str_and_bytes(code, expected_class, expected_value):
20352035
assert inferred.value == expected_value
20362036

20372037

2038+
def test_no_recursionerror_on_self_referential_length_check():
2039+
"""
2040+
Regression test for https://github.com/PyCQA/astroid/issues/777
2041+
"""
2042+
with pytest.raises(astroid.InferenceError):
2043+
node = astroid.extract_node(
2044+
"""
2045+
class Crash:
2046+
def __len__(self) -> int:
2047+
return len(self)
2048+
len(Crash()) #@
2049+
"""
2050+
)
2051+
node.inferred()
2052+
2053+
20382054
if __name__ == "__main__":
20392055
unittest.main()

0 commit comments

Comments
 (0)