Skip to content

Commit e2488ff

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 4b2d720 commit e2488ff

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

pyflakes/checker.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ def __init__(self, tree, filename='(none)', builtins=None,
494494
self.scopeStack = [ModuleScope()]
495495
self.exceptHandlers = [()]
496496
self.root = tree
497+
self._recursion_limit = sys.getrecursionlimit()
497498
self.handleChildren(tree)
498499
self.runDeferred(self._deferredFunctions)
499500
# Set _deferredFunctions to None so that deferFunction will fail
@@ -820,6 +821,15 @@ def on_conditional_branch():
820821
self.report(messages.UndefinedName, node, name)
821822

822823
def handleChildren(self, tree, omit=None):
824+
# The recursion limit needs to be at least double nodeDepth
825+
# as the recursion cycles between handleChildren and handleNode.
826+
# Set it to triple nodeDepth to account for other items on the stack,
827+
# and to reduce the frequency of changes to the limit.
828+
acceptable_recursion_limit = self.nodeDepth * 3
829+
if self._recursion_limit <= acceptable_recursion_limit:
830+
sys.setrecursionlimit(acceptable_recursion_limit)
831+
self._recursion_limit = acceptable_recursion_limit
832+
823833
for node in iter_child_nodes(tree, omit=omit):
824834
self.handleNode(node, tree)
825835

pyflakes/test/test_other.py

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

56
from sys import version_info
67

78
from pyflakes import messages as m
89
from pyflakes.test.harness import TestCase, skip, skipIf
910

11+
try:
12+
sys.pypy_version_info
13+
PYPY = True
14+
except AttributeError:
15+
PYPY = False
16+
1017

1118
class Test(TestCase):
1219

@@ -1993,3 +2000,24 @@ def test_raise_notimplemented(self):
19932000
self.flakes('''
19942001
raise NotImplemented
19952002
''', m.RaiseNotImplemented)
2003+
2004+
2005+
class TestMaximumRecursion(TestCase):
2006+
2007+
def setUp(self):
2008+
self._recursionlimit = sys.getrecursionlimit()
2009+
2010+
def test_recursion_limit(self):
2011+
# Using self._recursionlimit * 10 tends to cause CPython to core dump.
2012+
# Older PyPy tend to break with lower recusion limits.
2013+
if PYPY and version_info < (3, 5):
2014+
new_recursion_limit = self._recursionlimit * 3
2015+
else:
2016+
new_recursion_limit = self._recursionlimit * 6
2017+
2018+
r = range(new_recursion_limit)
2019+
s = 'x = ' + ' + '.join(str(n) for n in r)
2020+
self.flakes(s)
2021+
2022+
def tearDown(self):
2023+
sys.setrecursionlimit(self._recursionlimit)

0 commit comments

Comments
 (0)