From b7ff1684f3ac2c1b74d9a95510bc54121eb3e6a0 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Fri, 6 May 2016 22:18:15 +0700 Subject: [PATCH] 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 --- pyflakes/checker.py | 7 +++++++ pyflakes/test/test_other.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 9545cab9..49029b4c 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -734,6 +734,13 @@ 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 sys.getrecursionlimit() <= acceptable_recursion_limit: + sys.setrecursionlimit(acceptable_recursion_limit) for node in iter_child_nodes(tree, omit=omit): self.handleNode(node, tree) diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index ae6cea24..531f063d 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -1,6 +1,7 @@ """ Tests for various Pyflakes behavior. """ +import sys from sys import version_info @@ -1743,3 +1744,18 @@ def test_matmul(self): def foo(a, b): return a @ b ''') + + +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)