Skip to content

Commit a1eba06

Browse files
committed
Manage recursion limit preventing RuntimeError
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
1 parent 885a8e5 commit a1eba06

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

pyflakes/checker.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ def __init__(self, tree, filename='(none)', builtins=None,
413413
self.scopeStack = [ModuleScope()]
414414
self.exceptHandlers = [()]
415415
self.root = tree
416+
self._recursion_limit = sys.getrecursionlimit()
416417
self.handleChildren(tree)
417418
self.runDeferred(self._deferredFunctions)
418419
# Set _deferredFunctions to None so that deferFunction will fail
@@ -734,6 +735,15 @@ def on_conditional_branch():
734735
self.report(messages.UndefinedName, node, name)
735736

736737
def handleChildren(self, tree, omit=None):
738+
# The recursion limit needs to be at least double nodeDepth
739+
# as the recursion cycles between handleChildren and handleNode.
740+
# Set it to triple nodeDepth to account for other items on the stack,
741+
# and to reduce the frequency of changes to the limit.
742+
acceptable_recursion_limit = self.nodeDepth * 3
743+
if self._recursion_limit <= acceptable_recursion_limit:
744+
sys.setrecursionlimit(acceptable_recursion_limit)
745+
self._recursion_limit = acceptable_recursion_limit
746+
737747
for node in iter_child_nodes(tree, omit=omit):
738748
self.handleNode(node, tree)
739749

pyflakes/test/test_other.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Tests for various Pyflakes behavior.
33
"""
4+
import sys
45

56
from sys import version_info
67

@@ -1743,3 +1744,18 @@ def test_matmul(self):
17431744
def foo(a, b):
17441745
return a @ b
17451746
''')
1747+
1748+
1749+
class TestMaximumRecursion(TestCase):
1750+
1751+
def setUp(self):
1752+
self._recursionlimit = sys.getrecursionlimit()
1753+
1754+
def test_recursion_limit(self):
1755+
# Using self._recursionlimit * 10 tends to cause CPython to core dump.
1756+
r = range(self._recursionlimit * 9)
1757+
s = 'x = ' + ' + '.join(str(n) for n in r)
1758+
self.flakes(s)
1759+
1760+
def tearDown(self):
1761+
sys.setrecursionlimit(self._recursionlimit)

0 commit comments

Comments
 (0)