Skip to content

Commit 23f88b5

Browse files
committed
[3.9] bpo-11105: Do not crash when compiling recursive ASTs (pythonGH-20594)
When compiling an AST object with a direct / indirect reference cycles, on the conversion phase because of exceeding amount of calls, a segfault was raised. This patch adds recursion guards to places for preventing user inputs to not to crash AST but instead raise a RecursionError.. (cherry picked from commit f349124) Co-authored-by: Batuhan Taskaya <[email protected]>
1 parent 5a8ddcc commit 23f88b5

File tree

4 files changed

+732
-4
lines changed

4 files changed

+732
-4
lines changed

Lib/test/test_ast.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,20 @@ def test_level_as_none(self):
10271027
exec(code, ns)
10281028
self.assertIn('sleep', ns)
10291029

1030+
def test_recursion_direct(self):
1031+
e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
1032+
e.operand = e
1033+
with self.assertRaises(RecursionError):
1034+
compile(ast.Expression(e), "<test>", "eval")
1035+
1036+
def test_recursion_indirect(self):
1037+
e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
1038+
f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
1039+
e.operand = f
1040+
f.operand = e
1041+
with self.assertRaises(RecursionError):
1042+
compile(ast.Expression(e), "<test>", "eval")
1043+
10301044

10311045
class ASTValidatorTests(unittest.TestCase):
10321046

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
When compiling :class:`ast.AST` objects with recursive references
2+
through :func:`compile`, the interpreter doesn't crash anymore instead
3+
it raises a :exc:`RecursionError`.

Parser/asdl_c.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66

77
from argparse import ArgumentParser
8+
from contextlib import contextmanager
89
from pathlib import Path
910

1011
import asdl
@@ -394,6 +395,14 @@ def visitProduct(self, prod, name):
394395

395396

396397
class Obj2ModVisitor(PickleVisitor):
398+
@contextmanager
399+
def recursive_call(self, node, level):
400+
self.emit('if (Py_EnterRecursiveCall(" while traversing \'%s\' node")) {' % node, level, reflow=False)
401+
self.emit('goto failed;', level + 1)
402+
self.emit('}', level)
403+
yield
404+
self.emit('Py_LeaveRecursiveCall();', level)
405+
397406
def funcHeader(self, name):
398407
ctype = get_c_type(name)
399408
self.emit("int", 0)
@@ -568,8 +577,9 @@ def visitField(self, field, name, sum=None, prod=None, depth=0):
568577
self.emit("%s val;" % ctype, depth+2)
569578
self.emit("PyObject *tmp2 = PyList_GET_ITEM(tmp, i);", depth+2)
570579
self.emit("Py_INCREF(tmp2);", depth+2)
571-
self.emit("res = obj2ast_%s(state, tmp2, &val, arena);" %
572-
field.type, depth+2, reflow=False)
580+
with self.recursive_call(name, depth+2):
581+
self.emit("res = obj2ast_%s(state, tmp2, &val, arena);" %
582+
field.type, depth+2, reflow=False)
573583
self.emit("Py_DECREF(tmp2);", depth+2)
574584
self.emit("if (res != 0) goto failed;", depth+2)
575585
self.emit("if (len != PyList_GET_SIZE(tmp)) {", depth+2)
@@ -582,8 +592,9 @@ def visitField(self, field, name, sum=None, prod=None, depth=0):
582592
self.emit("asdl_seq_SET(%s, i, val);" % field.name, depth+2)
583593
self.emit("}", depth+1)
584594
else:
585-
self.emit("res = obj2ast_%s(state, tmp, &%s, arena);" %
586-
(field.type, field.name), depth+1)
595+
with self.recursive_call(name, depth+1):
596+
self.emit("res = obj2ast_%s(state, tmp, &%s, arena);" %
597+
(field.type, field.name), depth+1)
587598
self.emit("if (res != 0) goto failed;", depth+1)
588599

589600
self.emit("Py_CLEAR(tmp);", depth+1)

0 commit comments

Comments
 (0)