@@ -86,6 +86,8 @@ pub struct VirtualMachine {
8686 pub state : PyRc < PyGlobalState > ,
8787 pub initialized : bool ,
8888 recursion_depth : Cell < usize > ,
89+ //github.com/ C stack soft limit for detecting stack overflow (like c_stack_soft_limit)
90+ c_stack_soft_limit : Cell < usize > ,
8991 //github.com/ Async generator firstiter hook (per-thread, set via sys.set_asyncgen_hooks)
9092 pub async_gen_firstiter : RefCell < Option < PyObjectRef > > ,
9193 //github.com/ Async generator finalizer hook (per-thread, set via sys.set_asyncgen_hooks)
@@ -228,6 +230,7 @@ impl VirtualMachine {
228230 } ) ,
229231 initialized : false ,
230232 recursion_depth : Cell :: new ( 0 ) ,
233+ c_stack_soft_limit : Cell :: new ( Self :: calculate_c_stack_soft_limit ( ) ) ,
231234 async_gen_firstiter : RefCell :: new ( None ) ,
232235 async_gen_finalizer : RefCell :: new ( None ) ,
233236 } ;
@@ -689,11 +692,127 @@ impl VirtualMachine {
689692 self . recursion_depth . get ( )
690693 }
691694
695+ //github.com/ Stack margin bytes (like _PyOS_STACK_MARGIN_BYTES).
696+ //github.com/ 2048 * sizeof(void*) = 16KB for 64-bit.
697+ const STACK_MARGIN_BYTES : usize = 2048 * std:: mem:: size_of :: < usize > ( ) ;
698+
699+ //github.com/ Get the stack boundaries using platform-specific APIs.
700+ //github.com/ Returns (base, top) where base is the lowest address and top is the highest.
701+ #[ cfg( all( not( miri) , windows) ) ]
702+ fn get_stack_bounds ( ) -> ( usize , usize ) {
703+ use windows_sys:: Win32 :: System :: Threading :: {
704+ GetCurrentThreadStackLimits , SetThreadStackGuarantee ,
705+ } ;
706+ let mut low: usize = 0 ;
707+ let mut high: usize = 0 ;
708+ unsafe {
709+ GetCurrentThreadStackLimits ( & mut low as * mut usize , & mut high as * mut usize ) ;
710+ // Add the guaranteed stack space (reserved for exception handling)
711+ let mut guarantee: u32 = 0 ;
712+ SetThreadStackGuarantee ( & mut guarantee) ;
713+ low += guarantee as usize ;
714+ }
715+ ( low, high)
716+ }
717+
718+ //github.com/ Get stack boundaries on non-Windows platforms.
719+ //github.com/ Falls back to estimating based on current stack pointer.
720+ #[ cfg( all( not( miri) , not( windows) ) ) ]
721+ fn get_stack_bounds ( ) -> ( usize , usize ) {
722+ // Use pthread_attr_getstack on platforms that support it
723+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
724+ {
725+ use libc:: {
726+ pthread_attr_destroy, pthread_attr_getstack, pthread_attr_t, pthread_getattr_np,
727+ pthread_self,
728+ } ;
729+ let mut attr: pthread_attr_t = unsafe { std:: mem:: zeroed ( ) } ;
730+ unsafe {
731+ if pthread_getattr_np ( pthread_self ( ) , & mut attr) == 0 {
732+ let mut stack_addr: * mut libc:: c_void = std:: ptr:: null_mut ( ) ;
733+ let mut stack_size: libc:: size_t = 0 ;
734+ if pthread_attr_getstack ( & attr, & mut stack_addr, & mut stack_size) == 0 {
735+ pthread_attr_destroy ( & mut attr) ;
736+ let base = stack_addr as usize ;
737+ let top = base + stack_size;
738+ return ( base, top) ;
739+ }
740+ pthread_attr_destroy ( & mut attr) ;
741+ }
742+ }
743+ }
744+
745+ #[ cfg( target_os = "macos" ) ]
746+ {
747+ use libc:: { pthread_get_stackaddr_np, pthread_get_stacksize_np, pthread_self} ;
748+ unsafe {
749+ let thread = pthread_self ( ) ;
750+ let stack_top = pthread_get_stackaddr_np ( thread) as usize ;
751+ let stack_size = pthread_get_stacksize_np ( thread) ;
752+ let stack_base = stack_top - stack_size;
753+ return ( stack_base, stack_top) ;
754+ }
755+ }
756+
757+ // Fallback: estimate based on current SP and a default stack size
758+ #[ allow( unreachable_code) ]
759+ {
760+ let current_sp = psm:: stack_pointer ( ) as usize ;
761+ // Assume 8MB stack, estimate base
762+ let estimated_size = 8 * 1024 * 1024 ;
763+ let base = current_sp. saturating_sub ( estimated_size) ;
764+ let top = current_sp + 1024 * 1024 ; // Assume we're not at the very top
765+ ( base, top)
766+ }
767+ }
768+
769+ //github.com/ Calculate the C stack soft limit based on actual stack boundaries.
770+ //github.com/ soft_limit = base + 2 * margin (for downward-growing stacks)
771+ #[ cfg( not( miri) ) ]
772+ fn calculate_c_stack_soft_limit ( ) -> usize {
773+ let ( base, _top) = Self :: get_stack_bounds ( ) ;
774+ // Soft limit is 2 margins above the base
775+ base + Self :: STACK_MARGIN_BYTES * 2
776+ }
777+
778+ //github.com/ Miri doesn't support inline assembly, so disable C stack checking.
779+ #[ cfg( miri) ]
780+ fn calculate_c_stack_soft_limit ( ) -> usize {
781+ 0
782+ }
783+
784+ //github.com/ Check if we're near the C stack limit (like _Py_MakeRecCheck).
785+ //github.com/ Returns true only when stack pointer is in the "danger zone" between
786+ //github.com/ soft_limit and hard_limit (soft_limit - 2*margin).
787+ #[ cfg( not( miri) ) ]
788+ #[ inline( always) ]
789+ fn check_c_stack_overflow ( & self ) -> bool {
790+ let current_sp = psm:: stack_pointer ( ) as usize ;
791+ let soft_limit = self . c_stack_soft_limit . get ( ) ;
792+ // Stack grows downward: check if we're below soft limit but above hard limit
793+ // This matches CPython's _Py_MakeRecCheck behavior
794+ current_sp < soft_limit
795+ && current_sp >= soft_limit. saturating_sub ( Self :: STACK_MARGIN_BYTES * 2 )
796+ }
797+
798+ //github.com/ Miri doesn't support inline assembly, so always return false.
799+ #[ cfg( miri) ]
800+ #[ inline( always) ]
801+ fn check_c_stack_overflow ( & self ) -> bool {
802+ false
803+ }
804+
692805 //github.com/ Used to run the body of a (possibly) recursive function. It will raise a
693806 //github.com/ RecursionError if recursive functions are nested far too many times,
694807 //github.com/ preventing a stack overflow.
695808 pub fn with_recursion < R , F : FnOnce ( ) -> PyResult < R > > ( & self , _where : & str , f : F ) -> PyResult < R > {
696809 self . check_recursive_call ( _where) ?;
810+
811+ // Native stack guard: check C stack like _Py_MakeRecCheck
812+ if self . check_c_stack_overflow ( ) {
813+ return Err ( self . new_recursion_error ( _where. to_string ( ) ) ) ;
814+ }
815+
697816 self . recursion_depth . set ( self . recursion_depth . get ( ) + 1 ) ;
698817 let result = f ( ) ;
699818 self . recursion_depth . set ( self . recursion_depth . get ( ) - 1 ) ;
0 commit comments