summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--astroid/brain/brain_builtin_inference.py1
-rw-r--r--astroid/helpers.py20
-rw-r--r--tests/unittest_brain.py16
4 files changed, 40 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index 737b682e..c8c6bcfb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -13,6 +13,10 @@ Release Date: TBA
Fixes PyCQA/pylint#3599
+* Prevent recursion error for self referential length calls
+
+ Close #777
+
* Added missing methods to the brain for ``mechanize``, to fix pylint false positives
Close #793
diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py
index 074ec476..3a4f364d 100644
--- a/astroid/brain/brain_builtin_inference.py
+++ b/astroid/brain/brain_builtin_inference.py
@@ -759,6 +759,7 @@ def infer_len(node, context=None):
"({len}) given".format(len=len(call.positional_arguments))
)
[argument_node] = call.positional_arguments
+
try:
return nodes.Const(helpers.object_len(argument_node, context=context))
except (AstroidTypeError, InferenceError) as exc:
diff --git a/astroid/helpers.py b/astroid/helpers.py
index 8ab68799..a7764d7a 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -237,13 +237,31 @@ def object_len(node, context=None):
:raises AstroidTypeError: If an invalid node is returned
from __len__ method or no __len__ method exists
:raises InferenceError: If the given node cannot be inferred
- or if multiple nodes are inferred
+ or if multiple nodes are inferred or if the code executed in python
+ would result in a infinite recursive check for length
:rtype int: Integer length of node
"""
# pylint: disable=import-outside-toplevel; circular import
from astroid.objects import FrozenSet
inferred_node = safe_infer(node, context=context)
+
+ # prevent self referential length calls from causing a recursion error
+ # see https://github.com/PyCQA/astroid/issues/777
+ node_frame = node.frame()
+ if (
+ isinstance(node_frame, scoped_nodes.FunctionDef)
+ and node_frame.name == "__len__"
+ and inferred_node._proxied == node_frame.parent
+ ):
+ message = (
+ "Self referential __len__ function will "
+ "cause a RecursionError on line {} of {}".format(
+ node.lineno, node.root().file
+ )
+ )
+ raise exceptions.InferenceError(message)
+
if inferred_node is None or inferred_node is util.Uninferable:
raise exceptions.InferenceError(node=node)
if isinstance(inferred_node, nodes.Const) and isinstance(
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 25e2bb5b..c308ddd5 100644
--- a/tests/unittest_brain.py
+++ b/tests/unittest_brain.py
@@ -2035,5 +2035,21 @@ def test_str_and_bytes(code, expected_class, expected_value):
assert inferred.value == expected_value
+def test_no_recursionerror_on_self_referential_length_check():
+ """
+ Regression test for https://github.com/PyCQA/astroid/issues/777
+ """
+ with pytest.raises(astroid.InferenceError):
+ node = astroid.extract_node(
+ """
+ class Crash:
+ def __len__(self) -> int:
+ return len(self)
+ len(Crash()) #@
+ """
+ )
+ node.inferred()
+
+
if __name__ == "__main__":
unittest.main()