mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Fix segfault on cyclic or deeply-nested AST in compile() (#7630)
This commit is contained in:
1 parent
59deb17828
commit
7d2b37ddd6
2 files changed
+66
-2
No files matched your search
@@ -31,7 +31,14 @@ impl<T: Node> Node for Vec<T> {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
vm.extract_elements_with(&object, |obj| Node::ast_from_object(vm, source_file, obj))
|
||||
// Recursion guard for each element: prevents stack overflow when a
|
||||
// sequence element transitively references the sequence itself
|
||||
// (e.g. `l = ast.List(...); l.elts = [l]`). See issue #4862.
|
||||
vm.extract_elements_with(&object, |obj| {
|
||||
vm.with_recursion("while traversing AST node", || {
|
||||
Node::ast_from_object(vm, source_file, obj)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +52,13 @@ impl<T: Node> Node for Box<T> {
|
||||
source_file: &SourceFile,
|
||||
object: PyObjectRef,
|
||||
) -> PyResult<Self> {
|
||||
T::ast_from_object(vm, source_file, object).map(Self::new)
|
||||
// Recursion guard: every descent through a Box<AstNode> increments the
|
||||
// VM's recursion depth so cyclic or pathologically deep ASTs raise
|
||||
// RecursionError instead of overflowing the native stack.
|
||||
// See issue #4862.
|
||||
vm.with_recursion("while traversing AST node", || {
|
||||
T::ast_from_object(vm, source_file, object).map(Self::new)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_none(&self) -> bool {
|
||||
|
||||
@@ -37,3 +37,54 @@ assert i.level == 3
|
||||
assert i.module is None
|
||||
assert i.names[0].name == "a"
|
||||
assert i.names[0].asname is None
|
||||
|
||||
|
||||
# Regression test for issue #4862:
|
||||
# A cyclic AST fed to compile() used to overflow the Rust stack and SIGSEGV.
|
||||
# After the fix, the recursion guard in ast_from_object raises RecursionError,
|
||||
# matching CPython's behavior. Covers both Box<T> descents (UnaryOp, BinOp,
|
||||
# Call, Attribute) and Vec<T> descents (List, Tuple).
|
||||
import warnings
|
||||
|
||||
|
||||
def _cyclic_cases():
|
||||
# Box<Expr> descents
|
||||
u = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0)
|
||||
u.operand = u
|
||||
yield "UnaryOp", u
|
||||
|
||||
b = ast.BinOp(
|
||||
op=ast.Add(),
|
||||
right=ast.Constant(value=0, lineno=0, col_offset=0),
|
||||
lineno=0,
|
||||
col_offset=0,
|
||||
)
|
||||
b.left = b
|
||||
yield "BinOp", b
|
||||
|
||||
c = ast.Call(args=[], keywords=[], lineno=0, col_offset=0)
|
||||
c.func = c
|
||||
yield "Call", c
|
||||
|
||||
a = ast.Attribute(attr="x", ctx=ast.Load(), lineno=0, col_offset=0)
|
||||
a.value = a
|
||||
yield "Attribute", a
|
||||
|
||||
# Vec<Expr> descents
|
||||
lst = ast.List(ctx=ast.Load(), lineno=0, col_offset=0)
|
||||
lst.elts = [lst]
|
||||
yield "List", lst
|
||||
|
||||
tup = ast.Tuple(ctx=ast.Load(), lineno=0, col_offset=0)
|
||||
tup.elts = [tup]
|
||||
yield "Tuple", tup
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
for name, node in _cyclic_cases():
|
||||
try:
|
||||
compile(ast.Expression(node), "<cyclic>", "eval")
|
||||
raise AssertionError(f"cyclic {name} should raise RecursionError")
|
||||
except RecursionError:
|
||||
pass # expected; matches CPython
|
||||
Reference in new issue
Block a user