@@ -37,3 +37,54 @@ def foo():
3737assert i .module is None
3838assert i .names [0 ].name == "a"
3939assert i .names [0 ].asname is None
40+
41+
42+ # Regression test for issue #4862:
43+ # A cyclic AST fed to compile() used to overflow the Rust stack and SIGSEGV.
44+ # After the fix, the recursion guard in ast_from_object raises RecursionError,
45+ # matching CPython's behavior. Covers both Box<T> descents (UnaryOp, BinOp,
46+ # Call, Attribute) and Vec<T> descents (List, Tuple).
47+ import warnings
48+
49+
50+ def _cyclic_cases ():
51+ # Box<Expr> descents
52+ u = ast .UnaryOp (op = ast .Not (), lineno = 0 , col_offset = 0 )
53+ u .operand = u
54+ yield "UnaryOp" , u
55+
56+ b = ast .BinOp (
57+ op = ast .Add (),
58+ right = ast .Constant (value = 0 , lineno = 0 , col_offset = 0 ),
59+ lineno = 0 ,
60+ col_offset = 0 ,
61+ )
62+ b .left = b
63+ yield "BinOp" , b
64+
65+ c = ast .Call (args = [], keywords = [], lineno = 0 , col_offset = 0 )
66+ c .func = c
67+ yield "Call" , c
68+
69+ a = ast .Attribute (attr = "x" , ctx = ast .Load (), lineno = 0 , col_offset = 0 )
70+ a .value = a
71+ yield "Attribute" , a
72+
73+ # Vec<Expr> descents
74+ lst = ast .List (ctx = ast .Load (), lineno = 0 , col_offset = 0 )
75+ lst .elts = [lst ]
76+ yield "List" , lst
77+
78+ tup = ast .Tuple (ctx = ast .Load (), lineno = 0 , col_offset = 0 )
79+ tup .elts = [tup ]
80+ yield "Tuple" , tup
81+
82+
83+ with warnings .catch_warnings ():
84+ warnings .simplefilter ("ignore" )
85+ for name , node in _cyclic_cases ():
86+ try :
87+ compile (ast .Expression (node ), "<cyclic>" , "eval" )
88+ raise AssertionError (f"cyclic { name } should raise RecursionError" )
89+ except RecursionError :
90+ pass # expected; matches CPython
0 commit comments