[issue15274] Patch for issue 5765: stack overflow evaluating eval("()" * 30000)

Andrea Griffini report at bugs.python.org
Sat Jul 7 15:22:05 CEST 2012


New submission from Andrea Griffini <agriff at tin.it>:

This is a fix for issue #5765: stack overflow evaluating eval("()" * 30000)

The solution was to add two fields (recursion_depth and
recursion_limit) to the symbol table object and just increment and
check the depth in symtable_visit_expr raising a RuntimeError in case
the limit is exceeded.

The test case added also covers other similar cases (a.b.b.b.b.b...
and a[0][0][0][0]....)

There is no depth check in when visiting statement because I cannot
think to a way to nesting statements too much without getting other
errors before. If there is a way to do it, it would be trivial to also
cover that part.

The patch uses the current depth and current recursion limit but
multiplies them for a factor because I suppose that the amount of C
stack used by the compiler per recursion is smaller than the amount
used by the interpreter; the constant in the patch is 4. Using a
constant of 1 (i.e. just using the normal recursion limit) doesn't
allow a previously existing test about big expressions to pass.

----------
files: compiler_recursion_limit_check.patch
keywords: patch
messages: 164839
nosy: ag6502
priority: normal
severity: normal
status: open
title: Patch for issue 5765: stack overflow evaluating eval("()" * 30000)
Added file: http://bugs.python.org/file26292/compiler_recursion_limit_check.patch

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue15274>
_______________________________________
-------------- next part --------------
diff -r d9c98730e2e8 Include/symtable.h
--- a/Include/symtable.h	Sat Jul 07 13:34:50 2012 +1000
+++ b/Include/symtable.h	Sat Jul 07 14:39:38 2012 +0200
@@ -30,6 +30,8 @@
     PyObject *st_private;           /* name of current class or NULL */
     PyFutureFeatures *st_future;    /* module's future features that affect
                                        the symbol table */
+    int recursion_depth;            /* current recursion depth */
+    int recursion_limit;            /* recursion limit */
 };
 
 typedef struct _symtable_entry {
diff -r d9c98730e2e8 Lib/test/test_compile.py
--- a/Lib/test/test_compile.py	Sat Jul 07 13:34:50 2012 +1000
+++ b/Lib/test/test_compile.py	Sat Jul 07 14:39:38 2012 +0200
@@ -474,6 +474,14 @@
         self.assertInvalidSingle('f()\nxy # blah\nblah()')
         self.assertInvalidSingle('x = 5 # comment\nx = 6\n')
 
+    def test_compiler_recursion_limit(self):
+        self.assertRaises(RuntimeError, self.compile_single, "()" * 100000)
+        self.assertRaises(RuntimeError, self.compile_single, "a" + ".b" * 100000)
+        self.assertRaises(RuntimeError, self.compile_single, "a" + "[0]" * 100000)
+        self.compile_single("()" * 2000)
+        self.compile_single("a" + ".b" * 2000)
+        self.compile_single("a" + "[0]" * 2000)
+
 def test_main():
     support.run_unittest(TestSpecifics)
 
diff -r d9c98730e2e8 Python/symtable.c
--- a/Python/symtable.c	Sat Jul 07 13:34:50 2012 +1000
+++ b/Python/symtable.c	Sat Jul 07 14:39:38 2012 +0200
@@ -218,17 +218,37 @@
     return NULL;
 }
 
+/* When compiling the use of C stack is probably going to be a lot
+   lighter than when executing Python code but still can overflow
+   and causing a Python crash if not checked (e.g. eval("()"*300000)).
+   Using the current recursion limit for the compiler too seems
+   overconstraining so a factor is used to allow deeper recursion
+   when compiling an expression.
+*/
+#define COMPILER_STACK_FRAME_SCALE 4
+
 struct symtable *
 PySymtable_Build(mod_ty mod, const char *filename, PyFutureFeatures *future)
 {
     struct symtable *st = symtable_new();
     asdl_seq *seq;
     int i;
+    PyThreadState *tstate;
 
     if (st == NULL)
         return st;
     st->st_filename = filename;
     st->st_future = future;
+
+    /* Setup recursion depth check counters */
+    tstate = PyThreadState_GET();
+    if (!tstate) {
+        PySymtable_Free(st);
+        return NULL;
+    }
+    st->recursion_depth = tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE;
+    st->recursion_limit = Py_GetRecursionLimit() * COMPILER_STACK_FRAME_SCALE;
+
     /* Make the initial symbol information gathering pass */
     if (!GET_IDENTIFIER(top) ||
         !symtable_enter_block(st, top, ModuleBlock, (void *)mod, 0, 0)) {
@@ -1268,6 +1288,12 @@
 static int
 symtable_visit_expr(struct symtable *st, expr_ty e)
 {
+    if (++st->recursion_depth > st->recursion_limit) {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "maximum recursion depth exceeded while compiling an expression");
+        --st->recursion_depth;
+        return 0;
+    }
     switch (e->kind) {
     case BoolOp_kind:
         VISIT_SEQ(st, expr, e->v.BoolOp.values);
@@ -1280,8 +1306,10 @@
         VISIT(st, expr, e->v.UnaryOp.operand);
         break;
     case Lambda_kind: {
-        if (!GET_IDENTIFIER(lambda))
+        if (!GET_IDENTIFIER(lambda)) {
+            --st->recursion_depth;
             return 0;
+        }
         if (e->v.Lambda.args->defaults)
             VISIT_SEQ(st, expr, e->v.Lambda.args->defaults);
         if (e->v.Lambda.args->kw_defaults)
@@ -1289,12 +1317,16 @@
                                  e->v.Lambda.args->kw_defaults);
         if (!symtable_enter_block(st, lambda,
                                   FunctionBlock, (void *)e, e->lineno,
-                                  e->col_offset))
+                                  e->col_offset)) {
+            --st->recursion_depth;
             return 0;
+        }
         VISIT(st, arguments, e->v.Lambda.args);
         VISIT(st, expr, e->v.Lambda.body);
-        if (!symtable_exit_block(st, (void *)e))
+        if (!symtable_exit_block(st, (void *)e)) {
+            --st->recursion_depth;
             return 0;
+        }
         break;
     }
     case IfExp_kind:
@@ -1310,20 +1342,28 @@
         VISIT_SEQ(st, expr, e->v.Set.elts);
         break;
     case GeneratorExp_kind:
-        if (!symtable_visit_genexp(st, e))
+        if (!symtable_visit_genexp(st, e)) {
+            --st->recursion_depth;
             return 0;
+        }
         break;
     case ListComp_kind:
-        if (!symtable_visit_listcomp(st, e))
+        if (!symtable_visit_listcomp(st, e)) {
+            --st->recursion_depth;
             return 0;
+        }
         break;
     case SetComp_kind:
-        if (!symtable_visit_setcomp(st, e))
+        if (!symtable_visit_setcomp(st, e)) {
+            --st->recursion_depth;
             return 0;
+        }
         break;
     case DictComp_kind:
-        if (!symtable_visit_dictcomp(st, e))
+        if (!symtable_visit_dictcomp(st, e)) {
+            --st->recursion_depth;
             return 0;
+        }
         break;
     case Yield_kind:
     case YieldFrom_kind: {
@@ -1366,15 +1406,19 @@
         break;
     case Name_kind:
         if (!symtable_add_def(st, e->v.Name.id,
-                              e->v.Name.ctx == Load ? USE : DEF_LOCAL))
+                              e->v.Name.ctx == Load ? USE : DEF_LOCAL)) {
+            --st->recursion_depth;
             return 0;
+        }
         /* Special-case super: it counts as a use of __class__ */
         if (e->v.Name.ctx == Load &&
             st->st_cur->ste_type == FunctionBlock &&
             !PyUnicode_CompareWithASCIIString(e->v.Name.id, "super")) {
             if (!GET_IDENTIFIER(__class__) ||
-                !symtable_add_def(st, __class__, USE))
+                !symtable_add_def(st, __class__, USE)) {
+                --st->recursion_depth;
                 return 0;
+            }
         }
         break;
     /* child nodes of List and Tuple will have expr_context set */
@@ -1385,6 +1429,7 @@
         VISIT_SEQ(st, expr, e->v.Tuple.elts);
         break;
     }
+    --st->recursion_depth;
     return 1;
 }
 


More information about the Python-bugs-list mailing list