Commit 082777de authored by Nick Coghlan's avatar Nick Coghlan

Issue #5765: Apply a hard recursion limit in the compiler

Previously, excessive nesting in expressions would blow the
stack and segfault the interpreter. Now, a hard limit based
on the configured recursion limit and a hardcoded scaling
factor is applied.
parent ebd0c4e5
...@@ -30,6 +30,8 @@ struct symtable { ...@@ -30,6 +30,8 @@ struct symtable {
PyObject *st_private; /* name of current class or NULL */ PyObject *st_private; /* name of current class or NULL */
PyFutureFeatures *st_future; /* module's future features that affect PyFutureFeatures *st_future; /* module's future features that affect
the symbol table */ the symbol table */
int recursion_depth; /* current recursion depth */
int recursion_limit; /* recursion limit */
}; };
typedef struct _symtable_entry { typedef struct _symtable_entry {
......
"""
The compiler (>= 2.5) recurses happily until it blows the stack.
Recorded on the tracker as http://bugs.python.org/issue11383
"""
# The variant below blows up in compiler_call, but there are assorted
# other variations that blow up in other functions
# e.g. '1*'*10**5+'1' will die in compiler_visit_expr
# The exact limit to destroy the stack will vary by platform
# but 10M should do the trick even with huge stack allocations
compile('()'*10**7, '?', 'exec')
...@@ -474,6 +474,33 @@ if 1: ...@@ -474,6 +474,33 @@ if 1:
self.assertInvalidSingle('f()\nxy # blah\nblah()') self.assertInvalidSingle('f()\nxy # blah\nblah()')
self.assertInvalidSingle('x = 5 # comment\nx = 6\n') self.assertInvalidSingle('x = 5 # comment\nx = 6\n')
@support.cpython_only
def test_compiler_recursion_limit(self):
# Expected limit is sys.getrecursionlimit() * the scaling factor
# in symtable.c (currently 3)
# We expect to fail *at* that limit, because we use up some of
# the stack depth limit in the test suite code
# So we check the expected limit and 75% of that
# XXX (ncoghlan): duplicating the scaling factor here is a little
# ugly. Perhaps it should be exposed somewhere...
fail_depth = sys.getrecursionlimit() * 3
success_depth = int(fail_depth * 0.75)
def check_limit(prefix, repeated):
expect_ok = prefix + repeated * success_depth
self.compile_single(expect_ok)
broken = prefix + repeated * fail_depth
details = "Compiling ({!r} + {!r} * {})".format(
prefix, repeated, fail_depth)
with self.assertRaises(RuntimeError, msg=details):
self.compile_single(broken)
check_limit("a", "()")
check_limit("a", ".b")
check_limit("a", "[0]")
check_limit("a", "*a")
def test_main(): def test_main():
support.run_unittest(TestSpecifics) support.run_unittest(TestSpecifics)
......
...@@ -431,6 +431,7 @@ Hans de Graaff ...@@ -431,6 +431,7 @@ Hans de Graaff
Nathaniel Gray Nathaniel Gray
Eddy De Greef Eddy De Greef
Grant Griffin Grant Griffin
Andrea Griffini
Duncan Grisby Duncan Grisby
Fabian Groffen Fabian Groffen
Eric Groo Eric Groo
......
...@@ -12,6 +12,9 @@ What's New in Python 3.3.1? ...@@ -12,6 +12,9 @@ What's New in Python 3.3.1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #5765: Apply a hard recursion limit in the compiler instead of
blowing the stack and segfaulting.
- Issue #16402: When slicing a range, fix shadowing of exceptions from - Issue #16402: When slicing a range, fix shadowing of exceptions from
__index__. __index__.
......
...@@ -141,6 +141,11 @@ struct compiler_unit { ...@@ -141,6 +141,11 @@ struct compiler_unit {
The u pointer points to the current compilation unit, while units The u pointer points to the current compilation unit, while units
for enclosing blocks are stored in c_stack. The u and c_stack are for enclosing blocks are stored in c_stack. The u and c_stack are
managed by compiler_enter_scope() and compiler_exit_scope(). managed by compiler_enter_scope() and compiler_exit_scope().
Note that we don't track recursion levels during compilation - the
task of detecting and rejecting excessive levels of nesting is
handled by the symbol analysis pass.
*/ */
struct compiler { struct compiler {
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment