diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | astroid/brain/brain_builtin_inference.py | 1 | ||||
-rw-r--r-- | astroid/helpers.py | 20 | ||||
-rw-r--r-- | tests/unittest_brain.py | 16 |
4 files changed, 40 insertions, 1 deletions
@@ -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() |