Repository: pylint-dev/astroid

Issue:
Astroid Infinite loop when `__len__(self)` is mistakenly defined as `return len(self)`
### Steps to reproduce
Lint the following program with pylint
```python
class Crash:
    def __len__(self) -> int:
        return len(self)
```

### Current behavior

The following error is thrown and pylint crashes
```
RecursionError: maximum recursion depth exceeded
```

### Expected behavior

It should not crash.

### Asteroid version
2.4.0



Discussion / hints:

Fix (patch):
diff --git a/ChangeLog b/ChangeLog
index 737b682ec4..c8c6bcfb1d 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 074ec476d7..3a4f364d86 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 8ab687999e..a7764d7acc 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(


Test changes:
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 25e2bb5b73..c308ddd535 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()