Skip to content

Commit

Permalink
Manage recursion limit preventing RuntimeError
Browse files Browse the repository at this point in the history
pyflakes has traditionally recursed with a handler for every
level of the ast.  The ast depth can become very large, especially
for an expression containing many binary operators.

Python has a maximum recursion limit, defaulting to a low number
like 1000, which resulted in a RuntimeError for the ast of:

    x = 1 + 2 + 3 + ... + 1001

To workaround this problem, pyflakes now increases the recursion
limit at runtime when it knows it will be exceeded.

Fixes lp:1507827
  • Loading branch information
jayvdb committed Jul 11, 2018
1 parent 4b2d720 commit bf41b09
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 0 deletions.
10 changes: 10 additions & 0 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ def __init__(self, tree, filename='(none)', builtins=None,
self.scopeStack = [ModuleScope()]
self.exceptHandlers = [()]
self.root = tree
self._recursion_limit = sys.getrecursionlimit()
self.handleChildren(tree)
self.runDeferred(self._deferredFunctions)
# Set _deferredFunctions to None so that deferFunction will fail
Expand Down Expand Up @@ -820,6 +821,15 @@ def on_conditional_branch():
self.report(messages.UndefinedName, node, name)

def handleChildren(self, tree, omit=None):
# The recursion limit needs to be at least double nodeDepth
# as the recursion cycles between handleChildren and handleNode.
# Set it to triple nodeDepth to account for other items on the stack,
# and to reduce the frequency of changes to the limit.
acceptable_recursion_limit = self.nodeDepth * 3
if self._recursion_limit <= acceptable_recursion_limit:
sys.setrecursionlimit(acceptable_recursion_limit)
self._recursion_limit = acceptable_recursion_limit

for node in iter_child_nodes(tree, omit=omit):
self.handleNode(node, tree)

Expand Down
16 changes: 16 additions & 0 deletions pyflakes/test/test_other.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests for various Pyflakes behavior.
"""
import sys

from sys import version_info

Expand Down Expand Up @@ -1993,3 +1994,18 @@ def test_raise_notimplemented(self):
self.flakes('''
raise NotImplemented
''', m.RaiseNotImplemented)


class TestMaximumRecursion(TestCase):

def setUp(self):
self._recursionlimit = sys.getrecursionlimit()

def test_recursion_limit(self):
# Using self._recursionlimit * 10 tends to cause CPython to core dump.
r = range(self._recursionlimit * 9)
s = 'x = ' + ' + '.join(str(n) for n in r)
self.flakes(s)

def tearDown(self):
sys.setrecursionlimit(self._recursionlimit)

0 comments on commit bf41b09

Please sign in to comment.