Skip to content

Commit

Permalink
Squash one-off inference utility functions to help reduce recursion e…
Browse files Browse the repository at this point in the history
…rrors (#804)

This also makes debugging a lot simpler reducing the complexity of the
function stack.
  • Loading branch information
brycepg committed Jun 24, 2020
1 parent ec96745 commit 25384d4
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions astroid/brain/brain_builtin_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
20 changes: 19 additions & 1 deletion astroid/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
16 changes: 16 additions & 0 deletions tests/unittest_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit 25384d4

Please sign in to comment.