Skip to main content

rustpython_vm/vm/
mod.rs

1//! Implement virtual machine to run instructions.
2//!
3//! See also:
4//!   <https://github.com/ProgVal/pythonvm-rust/blob/master/src/processor/mod.rs>
5
6#[cfg(feature = "rustpython-compiler")]
7mod compile;
8mod context;
9mod interpreter;
10mod method;
11#[cfg(feature = "rustpython-compiler")]
12mod python_run;
13mod setting;
14pub mod thread;
15mod vm_new;
16mod vm_object;
17mod vm_ops;
18
19use crate::{
20    AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
21    builtins::{
22        self, PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
23        PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned, PyWeak,
24        code::PyCode,
25        dict::{PyDictItems, PyDictKeys, PyDictValues},
26        pystr::AsPyStr,
27        tuple::PyTuple,
28    },
29    codecs::CodecsRegistry,
30    common::{hash::HashSecret, lock::PyMutex, rc::PyRc},
31    convert::ToPyObject,
32    exceptions::types::PyBaseException,
33    frame::{ExecutionResult, Frame, FrameRef},
34    frozen::FrozenModule,
35    function::{ArgMapping, FuncArgs, PySetterValue},
36    import,
37    protocol::PyIterIter,
38    scope::Scope,
39    signal, stdlib,
40    warn::WarningsState,
41};
42use alloc::{borrow::Cow, collections::BTreeMap};
43#[cfg(all(unix, feature = "threading"))]
44use core::sync::atomic::AtomicI64;
45use core::{
46    cell::{Cell, OnceCell, RefCell},
47    ptr::NonNull,
48    sync::atomic::{AtomicBool, AtomicU64, Ordering},
49};
50use crossbeam_utils::atomic::AtomicCell;
51#[cfg(unix)]
52use nix::{
53    sys::signal::{SaFlags, SigAction, SigSet, Signal::SIGINT, kill, sigaction},
54    unistd::getpid,
55};
56use std::{
57    collections::{HashMap, HashSet},
58    ffi::{OsStr, OsString},
59};
60
61pub use context::Context;
62pub use interpreter::{Interpreter, InterpreterBuilder};
63pub(crate) use method::PyMethod;
64pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings};
65
66pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize;
67
68// Objects are live when they are on stack, or referenced by a name (for now)
69
70/// Top level container of a python virtual machine. In theory you could
71/// create more instances of this struct and have them operate fully isolated.
72///
73/// To construct this, please refer to the [`Interpreter`]
74pub struct VirtualMachine {
75    pub builtins: PyRef<PyModule>,
76    pub sys_module: PyRef<PyModule>,
77    pub ctx: PyRc<Context>,
78    pub frames: RefCell<Vec<FramePtr>>,
79    /// Thread-local data stack for bump-allocating frame-local data
80    /// (localsplus arrays for non-generator frames).
81    datastack: core::cell::UnsafeCell<crate::datastack::DataStack>,
82    pub wasm_id: Option<String>,
83    exceptions: RefCell<ExceptionStack>,
84    pub import_func: PyObjectRef,
85    pub(crate) importlib: PyObjectRef,
86    pub profile_func: RefCell<PyObjectRef>,
87    pub trace_func: RefCell<PyObjectRef>,
88    pub use_tracing: Cell<bool>,
89    pub recursion_limit: Cell<usize>,
90    pub(crate) signal_handlers: OnceCell<Box<RefCell<[Option<PyObjectRef>; signal::NSIG]>>>,
91    pub(crate) signal_rx: Option<signal::UserSignalReceiver>,
92    pub repr_guards: RefCell<HashSet<usize>>,
93    pub state: PyRc<PyGlobalState>,
94    pub initialized: bool,
95    recursion_depth: Cell<usize>,
96    /// C stack soft limit for detecting stack overflow (like c_stack_soft_limit)
97    #[cfg_attr(miri, allow(dead_code))]
98    c_stack_soft_limit: Cell<usize>,
99    /// Async generator firstiter hook (per-thread, set via sys.set_asyncgen_hooks)
100    pub async_gen_firstiter: RefCell<Option<PyObjectRef>>,
101    /// Async generator finalizer hook (per-thread, set via sys.set_asyncgen_hooks)
102    pub async_gen_finalizer: RefCell<Option<PyObjectRef>>,
103    /// Current running asyncio event loop for this thread
104    pub asyncio_running_loop: RefCell<Option<PyObjectRef>>,
105    /// Current running asyncio task for this thread
106    pub asyncio_running_task: RefCell<Option<PyObjectRef>>,
107    pub(crate) callable_cache: CallableCache,
108}
109
110/// Non-owning frame pointer for the frames stack.
111/// The pointed-to frame is kept alive by the caller of with_frame_exc/resume_gen_frame.
112#[derive(Copy, Clone)]
113pub struct FramePtr(NonNull<Py<Frame>>);
114
115impl FramePtr {
116    /// # Safety
117    /// The pointed-to frame must still be alive.
118    pub unsafe fn as_ref(&self) -> &Py<Frame> {
119        unsafe { self.0.as_ref() }
120    }
121}
122
123// SAFETY: FramePtr is only stored in the VM's frames Vec while the corresponding
124// FrameRef is alive on the call stack. The Vec is always empty when the VM moves between threads.
125unsafe impl Send for FramePtr {}
126
127#[derive(Debug, Default)]
128struct ExceptionStack {
129    stack: Vec<Option<PyBaseExceptionRef>>,
130}
131
132/// Stop-the-world state for fork safety. Before `fork()`, the requester
133/// stops all other Python threads so they are not holding internal locks.
134#[cfg(all(unix, feature = "threading"))]
135pub struct StopTheWorldState {
136    /// Fast-path flag checked in the bytecode loop (like `_PY_EVAL_PLEASE_STOP_BIT`)
137    pub(crate) requested: AtomicBool,
138    /// Whether the world is currently stopped (`stw->world_stopped`).
139    world_stopped: AtomicBool,
140    /// Ident of the thread that requested the stop (like `stw->requester`)
141    requester: AtomicU64,
142    /// Signaled by suspending threads when their state transitions to SUSPENDED
143    notify_mutex: std::sync::Mutex<()>,
144    notify_cv: std::sync::Condvar,
145    /// Number of non-requester threads still expected to park for current stop request.
146    thread_countdown: AtomicI64,
147    /// Number of stop-the-world attempts.
148    stats_stop_calls: AtomicU64,
149    /// Most recent stop-the-world wait duration in ns.
150    stats_last_wait_ns: AtomicU64,
151    /// Total accumulated stop-the-world wait duration in ns.
152    stats_total_wait_ns: AtomicU64,
153    /// Max observed stop-the-world wait duration in ns.
154    stats_max_wait_ns: AtomicU64,
155    /// Number of poll-loop iterations spent waiting.
156    stats_poll_loops: AtomicU64,
157    /// Number of ATTACHED threads observed while polling.
158    stats_attached_seen: AtomicU64,
159    /// Number of DETACHED->SUSPENDED parks requested by requester.
160    stats_forced_parks: AtomicU64,
161    /// Number of suspend notifications from worker threads.
162    stats_suspend_notifications: AtomicU64,
163    /// Number of yield loops while attach waited on SUSPENDED->DETACHED.
164    stats_attach_wait_yields: AtomicU64,
165    /// Number of yield loops while suspend waited on SUSPENDED->DETACHED.
166    stats_suspend_wait_yields: AtomicU64,
167}
168
169#[cfg(all(unix, feature = "threading"))]
170#[derive(Debug, Clone, Copy)]
171pub struct StopTheWorldStats {
172    pub stop_calls: u64,
173    pub last_wait_ns: u64,
174    pub total_wait_ns: u64,
175    pub max_wait_ns: u64,
176    pub poll_loops: u64,
177    pub attached_seen: u64,
178    pub forced_parks: u64,
179    pub suspend_notifications: u64,
180    pub attach_wait_yields: u64,
181    pub suspend_wait_yields: u64,
182    pub world_stopped: bool,
183}
184
185#[cfg(all(unix, feature = "threading"))]
186impl Default for StopTheWorldState {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192#[cfg(all(unix, feature = "threading"))]
193impl StopTheWorldState {
194    pub const fn new() -> Self {
195        Self {
196            requested: AtomicBool::new(false),
197            world_stopped: AtomicBool::new(false),
198            requester: AtomicU64::new(0),
199            notify_mutex: std::sync::Mutex::new(()),
200            notify_cv: std::sync::Condvar::new(),
201            thread_countdown: AtomicI64::new(0),
202            stats_stop_calls: AtomicU64::new(0),
203            stats_last_wait_ns: AtomicU64::new(0),
204            stats_total_wait_ns: AtomicU64::new(0),
205            stats_max_wait_ns: AtomicU64::new(0),
206            stats_poll_loops: AtomicU64::new(0),
207            stats_attached_seen: AtomicU64::new(0),
208            stats_forced_parks: AtomicU64::new(0),
209            stats_suspend_notifications: AtomicU64::new(0),
210            stats_attach_wait_yields: AtomicU64::new(0),
211            stats_suspend_wait_yields: AtomicU64::new(0),
212        }
213    }
214
215    /// Wake the stop-the-world requester (called by each thread that suspends).
216    pub(crate) fn notify_suspended(&self) {
217        self.stats_suspend_notifications
218            .fetch_add(1, Ordering::Relaxed);
219        // Synchronize with requester wait loop to avoid lost wakeups.
220        let _guard = self.notify_mutex.lock().unwrap();
221        self.decrement_thread_countdown(1);
222        self.notify_cv.notify_one();
223    }
224
225    #[inline]
226    fn init_thread_countdown(&self, vm: &VirtualMachine) -> i64 {
227        let requester = self.requester.load(Ordering::Relaxed);
228        let registry = vm.state.thread_frames.lock();
229        // Keep requested/count initialization serialized with thread-slot
230        // registration (which also takes this lock), matching the
231        // HEAD_LOCK-guarded stop-the-world bookkeeping.
232        self.requested.store(true, Ordering::Release);
233        let count = registry
234            .keys()
235            .filter(|&&thread_id| thread_id != requester)
236            .count();
237        let count = (count.min(i64::MAX as usize)) as i64;
238        self.thread_countdown.store(count, Ordering::Release);
239        count
240    }
241
242    #[inline]
243    fn decrement_thread_countdown(&self, n: u64) {
244        if n == 0 {
245            return;
246        }
247        let n = (n.min(i64::MAX as u64)) as i64;
248        let prev = self.thread_countdown.fetch_sub(n, Ordering::AcqRel);
249        if prev <= n {
250            // Clamp at 0 for safety in case of duplicate notifications.
251            self.thread_countdown.store(0, Ordering::Release);
252        }
253    }
254
255    /// Try to CAS detached threads directly to SUSPENDED and check whether
256    /// stop countdown reached zero after parking detached threads.
257    fn park_detached_threads(&self, vm: &VirtualMachine) -> bool {
258        use thread::{THREAD_ATTACHED, THREAD_DETACHED, THREAD_SUSPENDED};
259        let requester = self.requester.load(Ordering::Relaxed);
260        let registry = vm.state.thread_frames.lock();
261        let mut attached_seen = 0u64;
262        let mut forced_parks = 0u64;
263        for (&id, slot) in registry.iter() {
264            if id == requester {
265                continue;
266            }
267            let state = slot.state.load(Ordering::Relaxed);
268            if state == THREAD_DETACHED {
269                // CAS DETACHED → SUSPENDED (park without thread cooperation)
270                match slot.state.compare_exchange(
271                    THREAD_DETACHED,
272                    THREAD_SUSPENDED,
273                    Ordering::AcqRel,
274                    Ordering::Relaxed,
275                ) {
276                    Ok(_) => {
277                        slot.stop_requested.store(false, Ordering::Release);
278                        forced_parks = forced_parks.saturating_add(1);
279                    }
280                    Err(THREAD_ATTACHED) => {
281                        // Set per-thread stop bit (_PY_EVAL_PLEASE_STOP_BIT).
282                        slot.stop_requested.store(true, Ordering::Release);
283                        // Raced with a thread re-attaching; it will self-suspend.
284                        attached_seen = attached_seen.saturating_add(1);
285                    }
286                    Err(THREAD_DETACHED) => {
287                        // Extremely unlikely race; next poll will handle it.
288                    }
289                    Err(THREAD_SUSPENDED) => {
290                        slot.stop_requested.store(false, Ordering::Release);
291                        // Another path parked it first.
292                    }
293                    Err(other) => {
294                        debug_assert!(
295                            false,
296                            "unexpected thread state in park_detached_threads: {other}"
297                        );
298                    }
299                }
300            } else if state == THREAD_ATTACHED {
301                // Set per-thread stop bit (_PY_EVAL_PLEASE_STOP_BIT).
302                slot.stop_requested.store(true, Ordering::Release);
303                // Thread is in bytecode — it will see `requested` and self-suspend
304                attached_seen = attached_seen.saturating_add(1);
305            }
306            // THREAD_SUSPENDED → already parked
307        }
308        if attached_seen != 0 {
309            self.stats_attached_seen
310                .fetch_add(attached_seen, Ordering::Relaxed);
311        }
312        if forced_parks != 0 {
313            self.decrement_thread_countdown(forced_parks);
314            self.stats_forced_parks
315                .fetch_add(forced_parks, Ordering::Relaxed);
316        }
317        forced_parks != 0 && self.thread_countdown.load(Ordering::Acquire) == 0
318    }
319
320    /// Stop all non-requester threads (`stop_the_world`).
321    ///
322    /// 1. Sets `requested`, marking the requester thread.
323    /// 2. CAS detached threads to SUSPENDED.
324    /// 3. Waits (polling with 1 ms condvar timeout) for attached threads
325    ///    to self-suspend in `check_signals`.
326    pub fn stop_the_world(&self, vm: &VirtualMachine) {
327        let start = std::time::Instant::now();
328        let requester_ident = crate::stdlib::_thread::get_ident();
329        self.requester.store(requester_ident, Ordering::Relaxed);
330        self.stats_stop_calls.fetch_add(1, Ordering::Relaxed);
331        let initial_countdown = self.init_thread_countdown(vm);
332        stw_trace(format_args!("stop begin requester={requester_ident}"));
333        if initial_countdown == 0 {
334            self.world_stopped.store(true, Ordering::Release);
335            #[cfg(debug_assertions)]
336            self.debug_assert_all_non_requester_suspended(vm);
337            stw_trace(format_args!(
338                "stop end requester={requester_ident} wait_ns=0 polls=0"
339            ));
340            return;
341        }
342
343        let mut polls = 0u64;
344        loop {
345            if self.park_detached_threads(vm) {
346                break;
347            }
348            polls = polls.saturating_add(1);
349            // Wait up to 1 ms for a thread to notify us it suspended.
350            // Re-check under the wait mutex first to avoid a lost-wake race:
351            // a thread may have suspended and notified right before we enter wait.
352            let guard = self.notify_mutex.lock().unwrap();
353            if self.thread_countdown.load(Ordering::Acquire) == 0 || self.park_detached_threads(vm)
354            {
355                drop(guard);
356                break;
357            }
358            let _ = self
359                .notify_cv
360                .wait_timeout(guard, core::time::Duration::from_millis(1));
361        }
362        if polls != 0 {
363            self.stats_poll_loops.fetch_add(polls, Ordering::Relaxed);
364        }
365        let wait_ns = start.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64;
366        self.stats_last_wait_ns.store(wait_ns, Ordering::Relaxed);
367        self.stats_total_wait_ns
368            .fetch_add(wait_ns, Ordering::Relaxed);
369        let mut prev_max = self.stats_max_wait_ns.load(Ordering::Relaxed);
370        while wait_ns > prev_max {
371            match self.stats_max_wait_ns.compare_exchange_weak(
372                prev_max,
373                wait_ns,
374                Ordering::Relaxed,
375                Ordering::Relaxed,
376            ) {
377                Ok(_) => break,
378                Err(observed) => prev_max = observed,
379            }
380        }
381        self.world_stopped.store(true, Ordering::Release);
382        #[cfg(debug_assertions)]
383        self.debug_assert_all_non_requester_suspended(vm);
384        stw_trace(format_args!(
385            "stop end requester={requester_ident} wait_ns={wait_ns} polls={polls}"
386        ));
387    }
388
389    /// Resume all suspended threads (`start_the_world`).
390    pub fn start_the_world(&self, vm: &VirtualMachine) {
391        use thread::{THREAD_DETACHED, THREAD_SUSPENDED};
392        let requester = self.requester.load(Ordering::Relaxed);
393        stw_trace(format_args!("start begin requester={requester}"));
394        let registry = vm.state.thread_frames.lock();
395        // Clear the request flag BEFORE waking threads. Otherwise a thread
396        // returning from allow_threads → attach_thread could observe
397        // `requested == true`, re-suspend itself, and stay parked forever.
398        // Keep this write under the registry lock to serialize with new
399        // thread-slot initialization.
400        self.requested.store(false, Ordering::Release);
401        self.world_stopped.store(false, Ordering::Release);
402        for (&id, slot) in registry.iter() {
403            if id == requester {
404                continue;
405            }
406            slot.stop_requested.store(false, Ordering::Release);
407            let state = slot.state.load(Ordering::Relaxed);
408            debug_assert!(
409                state == THREAD_SUSPENDED,
410                "non-requester thread not suspended at start-the-world: id={id} state={state}"
411            );
412            if state == THREAD_SUSPENDED {
413                slot.state.store(THREAD_DETACHED, Ordering::Release);
414                slot.thread.unpark();
415            }
416        }
417        drop(registry);
418        self.thread_countdown.store(0, Ordering::Release);
419        self.requester.store(0, Ordering::Relaxed);
420        #[cfg(debug_assertions)]
421        self.debug_assert_all_non_requester_detached(vm);
422        stw_trace(format_args!("start end requester={requester}"));
423    }
424
425    /// Reset after fork in the child (only one thread alive).
426    pub fn reset_after_fork(&self) {
427        self.requested.store(false, Ordering::Relaxed);
428        self.world_stopped.store(false, Ordering::Relaxed);
429        self.requester.store(0, Ordering::Relaxed);
430        self.thread_countdown.store(0, Ordering::Relaxed);
431        stw_trace(format_args!("reset-after-fork"));
432    }
433
434    #[inline]
435    pub(crate) fn requester_ident(&self) -> u64 {
436        self.requester.load(Ordering::Relaxed)
437    }
438
439    #[inline]
440    pub(crate) fn notify_thread_gone(&self) {
441        let _guard = self.notify_mutex.lock().unwrap();
442        self.decrement_thread_countdown(1);
443        self.notify_cv.notify_one();
444    }
445
446    pub fn stats_snapshot(&self) -> StopTheWorldStats {
447        StopTheWorldStats {
448            stop_calls: self.stats_stop_calls.load(Ordering::Relaxed),
449            last_wait_ns: self.stats_last_wait_ns.load(Ordering::Relaxed),
450            total_wait_ns: self.stats_total_wait_ns.load(Ordering::Relaxed),
451            max_wait_ns: self.stats_max_wait_ns.load(Ordering::Relaxed),
452            poll_loops: self.stats_poll_loops.load(Ordering::Relaxed),
453            attached_seen: self.stats_attached_seen.load(Ordering::Relaxed),
454            forced_parks: self.stats_forced_parks.load(Ordering::Relaxed),
455            suspend_notifications: self.stats_suspend_notifications.load(Ordering::Relaxed),
456            attach_wait_yields: self.stats_attach_wait_yields.load(Ordering::Relaxed),
457            suspend_wait_yields: self.stats_suspend_wait_yields.load(Ordering::Relaxed),
458            world_stopped: self.world_stopped.load(Ordering::Relaxed),
459        }
460    }
461
462    pub fn reset_stats(&self) {
463        self.stats_stop_calls.store(0, Ordering::Relaxed);
464        self.stats_last_wait_ns.store(0, Ordering::Relaxed);
465        self.stats_total_wait_ns.store(0, Ordering::Relaxed);
466        self.stats_max_wait_ns.store(0, Ordering::Relaxed);
467        self.stats_poll_loops.store(0, Ordering::Relaxed);
468        self.stats_attached_seen.store(0, Ordering::Relaxed);
469        self.stats_forced_parks.store(0, Ordering::Relaxed);
470        self.stats_suspend_notifications.store(0, Ordering::Relaxed);
471        self.stats_attach_wait_yields.store(0, Ordering::Relaxed);
472        self.stats_suspend_wait_yields.store(0, Ordering::Relaxed);
473    }
474
475    #[inline]
476    pub(crate) fn add_attach_wait_yields(&self, n: u64) {
477        if n != 0 {
478            self.stats_attach_wait_yields
479                .fetch_add(n, Ordering::Relaxed);
480        }
481    }
482
483    #[inline]
484    pub(crate) fn add_suspend_wait_yields(&self, n: u64) {
485        if n != 0 {
486            self.stats_suspend_wait_yields
487                .fetch_add(n, Ordering::Relaxed);
488        }
489    }
490
491    #[cfg(debug_assertions)]
492    fn debug_assert_all_non_requester_suspended(&self, vm: &VirtualMachine) {
493        use thread::THREAD_SUSPENDED;
494        let requester = self.requester.load(Ordering::Relaxed);
495        let registry = vm.state.thread_frames.lock();
496        for (&id, slot) in registry.iter() {
497            if id == requester {
498                continue;
499            }
500            let state = slot.state.load(Ordering::Relaxed);
501            debug_assert!(
502                state == THREAD_SUSPENDED,
503                "non-requester thread not suspended during stop-the-world: id={id} state={state}"
504            );
505        }
506    }
507
508    #[cfg(debug_assertions)]
509    fn debug_assert_all_non_requester_detached(&self, vm: &VirtualMachine) {
510        use thread::THREAD_SUSPENDED;
511        let requester = self.requester.load(Ordering::Relaxed);
512        let registry = vm.state.thread_frames.lock();
513        for (&id, slot) in registry.iter() {
514            if id == requester {
515                continue;
516            }
517            let state = slot.state.load(Ordering::Relaxed);
518            debug_assert!(
519                state != THREAD_SUSPENDED,
520                "non-requester thread still suspended after start-the-world: id={id} state={state}"
521            );
522        }
523    }
524}
525
526#[cfg(all(unix, feature = "threading"))]
527pub(super) fn stw_trace_enabled() -> bool {
528    static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
529    *ENABLED.get_or_init(|| std::env::var_os("RUSTPYTHON_STW_TRACE").is_some())
530}
531
532#[cfg(all(unix, feature = "threading"))]
533pub(super) fn stw_trace(msg: core::fmt::Arguments<'_>) {
534    if stw_trace_enabled() {
535        use core::fmt::Write as _;
536
537        // Avoid stdio locking here: this path runs around fork where a child
538        // may inherit a borrowed stderr lock and panic on eprintln!/stderr.
539        struct FixedBuf {
540            buf: [u8; 512],
541            len: usize,
542        }
543
544        impl core::fmt::Write for FixedBuf {
545            fn write_str(&mut self, s: &str) -> core::fmt::Result {
546                if self.len >= self.buf.len() {
547                    return Ok(());
548                }
549                let remain = self.buf.len() - self.len;
550                let src = s.as_bytes();
551                let n = src.len().min(remain);
552                self.buf[self.len..self.len + n].copy_from_slice(&src[..n]);
553                self.len += n;
554                Ok(())
555            }
556        }
557
558        let mut out = FixedBuf {
559            buf: [0u8; 512],
560            len: 0,
561        };
562        let _ = writeln!(
563            &mut out,
564            "[rp-stw tid={}] {}",
565            crate::stdlib::_thread::get_ident(),
566            msg
567        );
568        unsafe {
569            let _ = libc::write(libc::STDERR_FILENO, out.buf.as_ptr().cast(), out.len);
570        }
571    }
572}
573
574#[derive(Clone, Debug, Default)]
575pub(crate) struct CallableCache {
576    pub len: Option<PyObjectRef>,
577    pub isinstance: Option<PyObjectRef>,
578    pub list_append: Option<PyObjectRef>,
579    pub builtin_all: Option<PyObjectRef>,
580    pub builtin_any: Option<PyObjectRef>,
581}
582
583pub struct PyGlobalState {
584    pub config: PyConfig,
585    pub module_defs: BTreeMap<&'static str, &'static builtins::PyModuleDef>,
586    pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>,
587    pub stacksize: AtomicCell<usize>,
588    pub thread_count: AtomicCell<usize>,
589    pub hash_secret: HashSecret,
590    pub atexit_funcs: PyMutex<Vec<Box<(PyObjectRef, FuncArgs)>>>,
591    pub codec_registry: CodecsRegistry,
592    pub finalizing: AtomicBool,
593    pub warnings: WarningsState,
594    pub override_frozen_modules: AtomicCell<isize>,
595    pub before_forkers: PyMutex<Vec<PyObjectRef>>,
596    pub after_forkers_child: PyMutex<Vec<PyObjectRef>>,
597    pub after_forkers_parent: PyMutex<Vec<PyObjectRef>>,
598    pub int_max_str_digits: AtomicCell<usize>,
599    pub switch_interval: AtomicCell<f64>,
600    /// Global trace function for all threads (set by sys._settraceallthreads)
601    pub global_trace_func: PyMutex<Option<PyObjectRef>>,
602    /// Global profile function for all threads (set by sys._setprofileallthreads)
603    pub global_profile_func: PyMutex<Option<PyObjectRef>>,
604    /// Main thread identifier (pthread_self on Unix)
605    #[cfg(feature = "threading")]
606    pub main_thread_ident: AtomicCell<u64>,
607    /// Registry of all threads' slots for sys._current_frames() and sys._current_exceptions()
608    #[cfg(feature = "threading")]
609    pub thread_frames: parking_lot::Mutex<HashMap<u64, stdlib::_thread::CurrentFrameSlot>>,
610    /// Registry of all ThreadHandles for fork cleanup
611    #[cfg(feature = "threading")]
612    pub thread_handles: parking_lot::Mutex<Vec<stdlib::_thread::HandleEntry>>,
613    /// Registry for non-daemon threads that need to be joined at shutdown
614    #[cfg(feature = "threading")]
615    pub shutdown_handles: parking_lot::Mutex<Vec<stdlib::_thread::ShutdownEntry>>,
616    /// sys.monitoring state (tool names, events, callbacks)
617    pub monitoring: PyMutex<stdlib::sys::monitoring::MonitoringState>,
618    /// Fast-path mask: OR of all tools' events. 0 means no monitoring overhead.
619    pub monitoring_events: stdlib::sys::monitoring::MonitoringEventsMask,
620    /// Incremented on every monitoring state change. Code objects compare their
621    /// local version against this to decide whether re-instrumentation is needed.
622    pub instrumentation_version: AtomicU64,
623    /// Stop-the-world state for pre-fork thread suspension
624    #[cfg(all(unix, feature = "threading"))]
625    pub stop_the_world: StopTheWorldState,
626}
627
628pub fn process_hash_secret_seed() -> u32 {
629    use std::sync::OnceLock;
630    static SEED: OnceLock<u32> = OnceLock::new();
631    // os_random is expensive, but this is only ever called once
632    *SEED.get_or_init(|| u32::from_ne_bytes(rustpython_common::rand::os_random()))
633}
634
635impl VirtualMachine {
636    fn init_callable_cache(&mut self) -> PyResult<()> {
637        self.callable_cache.len = Some(self.builtins.get_attr("len", self)?);
638        self.callable_cache.isinstance = Some(self.builtins.get_attr("isinstance", self)?);
639        let list_append = self
640            .ctx
641            .types
642            .list_type
643            .get_attr(self.ctx.intern_str("append"))
644            .ok_or_else(|| self.new_runtime_error("failed to cache list.append".to_owned()))?;
645        self.callable_cache.list_append = Some(list_append);
646        self.callable_cache.builtin_all = Some(self.builtins.get_attr("all", self)?);
647        self.callable_cache.builtin_any = Some(self.builtins.get_attr("any", self)?);
648        Ok(())
649    }
650
651    /// Bump-allocate `size` bytes from the thread data stack.
652    ///
653    /// # Safety
654    /// The returned pointer must be freed by calling `datastack_pop` in LIFO order.
655    #[inline(always)]
656    pub(crate) fn datastack_push(&self, size: usize) -> *mut u8 {
657        unsafe { (*self.datastack.get()).push(size) }
658    }
659
660    /// Check whether the thread data stack currently has room for `size` bytes.
661    #[inline(always)]
662    pub(crate) fn datastack_has_space(&self, size: usize) -> bool {
663        unsafe { (*self.datastack.get()).has_space(size) }
664    }
665
666    /// Pop a previous data stack allocation.
667    ///
668    /// # Safety
669    /// `base` must be a pointer returned by `datastack_push` on this VM,
670    /// and all allocations made after it must already have been popped.
671    #[inline(always)]
672    pub(crate) unsafe fn datastack_pop(&self, base: *mut u8) {
673        unsafe { (*self.datastack.get()).pop(base) }
674    }
675
676    /// Temporarily detach the current thread (ATTACHED → DETACHED) while
677    /// running `f`, then re-attach afterwards.  Allows `stop_the_world` to
678    /// park this thread during blocking syscalls.
679    ///
680    /// Equivalent to CPython's `Py_BEGIN_ALLOW_THREADS` / `Py_END_ALLOW_THREADS`.
681    #[inline]
682    pub fn allow_threads<R>(&self, f: impl FnOnce() -> R) -> R {
683        thread::allow_threads(self, f)
684    }
685
686    /// Check whether the current thread is the main thread.
687    /// Mirrors `_Py_ThreadCanHandleSignals`.
688    #[allow(dead_code)]
689    pub(crate) fn is_main_thread(&self) -> bool {
690        #[cfg(feature = "threading")]
691        {
692            crate::stdlib::_thread::get_ident() == self.state.main_thread_ident.load()
693        }
694        #[cfg(not(feature = "threading"))]
695        {
696            true
697        }
698    }
699
700    /// Create a new `VirtualMachine` structure.
701    pub(crate) fn new(ctx: PyRc<Context>, state: PyRc<PyGlobalState>) -> Self {
702        flame_guard!("new VirtualMachine");
703
704        // make a new module without access to the vm; doesn't
705        // set __spec__, __loader__, etc. attributes
706        let new_module = |def| {
707            PyRef::new_ref(
708                PyModule::from_def(def),
709                ctx.types.module_type.to_owned(),
710                Some(ctx.new_dict()),
711            )
712        };
713
714        // Hard-core modules:
715        let builtins = new_module(stdlib::builtins::module_def(&ctx));
716        let sys_module = new_module(stdlib::sys::module_def(&ctx));
717
718        let import_func = ctx.none();
719        let importlib = ctx.none();
720        let profile_func = RefCell::new(ctx.none());
721        let trace_func = RefCell::new(ctx.none());
722        let signal_handlers = OnceCell::from(signal::new_signal_handlers());
723
724        let vm = Self {
725            builtins,
726            sys_module,
727            ctx,
728            frames: RefCell::new(vec![]),
729            datastack: core::cell::UnsafeCell::new(crate::datastack::DataStack::new()),
730            wasm_id: None,
731            exceptions: RefCell::default(),
732            import_func,
733            importlib,
734            profile_func,
735            trace_func,
736            use_tracing: Cell::new(false),
737            recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }),
738            signal_handlers,
739            signal_rx: None,
740            repr_guards: RefCell::default(),
741            state,
742            initialized: false,
743            recursion_depth: Cell::new(0),
744            c_stack_soft_limit: Cell::new(Self::calculate_c_stack_soft_limit()),
745            async_gen_firstiter: RefCell::new(None),
746            async_gen_finalizer: RefCell::new(None),
747            asyncio_running_loop: RefCell::new(None),
748            asyncio_running_task: RefCell::new(None),
749            callable_cache: CallableCache::default(),
750        };
751
752        if vm.state.hash_secret.hash_str("")
753            != vm
754                .ctx
755                .interned_str("")
756                .expect("empty str must be interned")
757                .hash(&vm)
758        {
759            panic!("Interpreters in same process must share the hash seed");
760        }
761
762        vm.builtins.init_dict(
763            vm.ctx.intern_str("builtins"),
764            Some(vm.ctx.intern_str(stdlib::builtins::DOC.unwrap()).to_owned()),
765            &vm,
766        );
767        vm.sys_module.init_dict(
768            vm.ctx.intern_str("sys"),
769            Some(vm.ctx.intern_str(stdlib::sys::DOC.unwrap()).to_owned()),
770            &vm,
771        );
772        // let name = vm.sys_module.get_attr("__name__", &vm).unwrap();
773        vm
774    }
775
776    /// set up the encodings search function
777    /// init_importlib must be called before this call
778    #[cfg(feature = "encodings")]
779    fn import_encodings(&mut self) -> PyResult<()> {
780        self.import("encodings", 0).map_err(|import_err| {
781            let rustpythonpath_env = std::env::var("RUSTPYTHONPATH").ok();
782            let pythonpath_env = std::env::var("PYTHONPATH").ok();
783            let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some();
784            let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| {
785                Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref()
786            });
787
788            let guide_message = if cfg!(feature = "freeze-stdlib") {
789                "`rustpython_pylib` may not be set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterBuilder::init_stdlib` or manually call `builder.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::builder()`."
790            } else if !env_set {
791                "Neither RUSTPYTHONPATH nor PYTHONPATH is set. Try setting one of them to the stdlib directory."
792            } else if path_contains_env {
793                "RUSTPYTHONPATH or PYTHONPATH is set, but it doesn't contain the encodings library. If you are customizing the RustPython vm/interpreter, try adding the stdlib directory to the path. If you are developing the RustPython interpreter, it might be a bug during development."
794            } else {
795                "RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `PyConfig::paths::module_search_paths`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development."
796            };
797
798            let mut msg = format!(
799                "RustPython could not import the encodings module. It usually means something went wrong. Please carefully read the following messages and follow the steps.\n\
800                \n\
801                {guide_message}");
802            if !cfg!(feature = "freeze-stdlib") {
803                msg += "\n\
804                If you don't have access to a consistent external environment (e.g. targeting wasm, embedding \
805                    rustpython in another application), try enabling the `freeze-stdlib` feature.\n\
806                If this is intended and you want to exclude the encodings module from your interpreter, please remove the `encodings` feature from `rustpython-vm` crate.";
807            }
808
809            let err = self.new_runtime_error(msg);
810            err.set___cause__(Some(import_err));
811            err
812        })?;
813        Ok(())
814    }
815
816    fn import_ascii_utf8_encodings(&mut self) -> PyResult<()> {
817        // Use the Python import machinery (FrozenImporter) so modules get
818        // proper __spec__ and __loader__ attributes.
819        self.import("codecs", 0)?;
820
821        // Use dotted names when freeze-stdlib is enabled (modules come from Lib/encodings/),
822        // otherwise use underscored names (modules come from core_modules/).
823        let (ascii_module_name, utf8_module_name) = if cfg!(feature = "freeze-stdlib") {
824            ("encodings.ascii", "encodings.utf_8")
825        } else {
826            ("encodings_ascii", "encodings_utf_8")
827        };
828
829        // Register ascii encoding
830        // __import__("encodings.ascii") returns top-level "encodings", so
831        // look up the actual submodule in sys.modules.
832        self.import(ascii_module_name, 0)?;
833        let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
834        let ascii_module = sys_modules.get_item(ascii_module_name, self)?;
835        let getregentry = ascii_module.get_attr("getregentry", self)?;
836        let codec_info = getregentry.call((), self)?;
837        self.state
838            .codec_registry
839            .register_manual("ascii", codec_info.try_into_value(self)?)?;
840
841        // Register utf-8 encoding (also as "utf8" alias since normalize_encoding_name
842        // maps "utf-8" → "utf_8" but leaves "utf8" as-is)
843        self.import(utf8_module_name, 0)?;
844        let utf8_module = sys_modules.get_item(utf8_module_name, self)?;
845        let getregentry = utf8_module.get_attr("getregentry", self)?;
846        let codec_info = getregentry.call((), self)?;
847        let utf8_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
848        self.state
849            .codec_registry
850            .register_manual("utf-8", utf8_codec.clone())?;
851        self.state
852            .codec_registry
853            .register_manual("utf8", utf8_codec)?;
854
855        // Register latin-1 / iso8859-1 aliases needed very early for stdio
856        // bootstrap (e.g. PYTHONIOENCODING=latin-1).
857        if cfg!(feature = "freeze-stdlib") {
858            self.import("encodings.latin_1", 0)?;
859            let latin1_module = sys_modules.get_item("encodings.latin_1", self)?;
860            let getregentry = latin1_module.get_attr("getregentry", self)?;
861            let codec_info = getregentry.call((), self)?;
862            let latin1_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
863            for name in ["latin-1", "latin_1", "latin1", "iso8859-1", "iso8859_1"] {
864                self.state
865                    .codec_registry
866                    .register_manual(name, latin1_codec.clone())?;
867            }
868        }
869        Ok(())
870    }
871
872    fn initialize(&mut self) {
873        flame_guard!("init VirtualMachine");
874
875        if self.initialized {
876            panic!("Double Initialize Error");
877        }
878
879        // Initialize main thread ident before any threading operations
880        #[cfg(feature = "threading")]
881        stdlib::_thread::init_main_thread_ident(self);
882
883        stdlib::builtins::init_module(self, &self.builtins);
884        let callable_cache_init = self.init_callable_cache();
885        self.expect_pyresult(callable_cache_init, "failed to initialize callable cache");
886        stdlib::sys::init_module(self, &self.sys_module, &self.builtins);
887        self.expect_pyresult(
888            stdlib::sys::set_bootstrap_stderr(self),
889            "failed to initialize bootstrap stderr",
890        );
891
892        let mut essential_init = || -> PyResult {
893            import::import_builtin(self, "_typing")?;
894            #[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
895            import::import_builtin(self, "_signal")?;
896            #[cfg(any(feature = "parser", feature = "compiler"))]
897            import::import_builtin(self, "_ast")?;
898            #[cfg(not(feature = "threading"))]
899            import::import_frozen(self, "_thread")?;
900            let importlib = import::init_importlib_base(self)?;
901            self.import_ascii_utf8_encodings()?;
902
903            {
904                let io = import::import_builtin(self, "_io")?;
905
906                // Full stdio: FileIO → BufferedWriter → TextIOWrapper
907                #[cfg(all(feature = "host_env", feature = "stdio"))]
908                let make_stdio = |name: &str, fd: i32, write: bool| -> PyResult<PyObjectRef> {
909                    let buffered_stdio = self.state.config.settings.buffered_stdio;
910                    let unbuffered = write && !buffered_stdio;
911                    let buf = crate::stdlib::_io::open(
912                        self.ctx.new_int(fd).into(),
913                        Some(if write { "wb" } else { "rb" }),
914                        crate::stdlib::_io::OpenArgs {
915                            buffering: if unbuffered { 0 } else { -1 },
916                            closefd: false,
917                            ..Default::default()
918                        },
919                        self,
920                    )?;
921                    let raw = if unbuffered {
922                        buf.clone()
923                    } else {
924                        buf.get_attr("raw", self)?
925                    };
926                    raw.set_attr("name", self.ctx.new_str(format!("<{name}>")), self)?;
927                    let isatty = self.call_method(&raw, "isatty", ())?.is_true(self)?;
928                    let write_through = !buffered_stdio;
929                    let line_buffering = buffered_stdio && (isatty || fd == 2);
930
931                    let newline = if cfg!(windows) { None } else { Some("\n") };
932                    let encoding = self.state.config.settings.stdio_encoding.as_deref();
933                    // stderr always uses backslashreplace (ignores stdio_errors)
934                    let errors = if fd == 2 {
935                        Some("backslashreplace")
936                    } else {
937                        self.state.config.settings.stdio_errors.as_deref().or(
938                            if self.state.config.settings.stdio_encoding.is_some() {
939                                Some("strict")
940                            } else {
941                                Some("surrogateescape")
942                            },
943                        )
944                    };
945
946                    let stdio = self.call_method(
947                        &io,
948                        "TextIOWrapper",
949                        (
950                            buf,
951                            encoding,
952                            errors,
953                            newline,
954                            line_buffering,
955                            write_through,
956                        ),
957                    )?;
958                    let mode = if write { "w" } else { "r" };
959                    stdio.set_attr("mode", self.ctx.new_str(mode), self)?;
960                    Ok::<_, self::PyBaseExceptionRef>(stdio)
961                };
962
963                // Sandbox stdio: lightweight wrapper using Rust's std::io directly
964                #[cfg(all(not(feature = "host_env"), feature = "stdio"))]
965                let make_stdio = |name: &str, fd: i32, write: bool| {
966                    let mode = if write { "w" } else { "r" };
967                    let stdio = stdlib::sys::SandboxStdio {
968                        fd,
969                        name: format!("<{name}>"),
970                        mode: mode.to_owned(),
971                    }
972                    .into_ref(&self.ctx);
973                    Ok(stdio.into())
974                };
975
976                // No stdio: set to None (embedding use case)
977                #[cfg(not(feature = "stdio"))]
978                let make_stdio = |_name: &str, _fd: i32, _write: bool| {
979                    Ok(crate::builtins::PyNone.into_pyobject(self))
980                };
981
982                let set_stdio = |name, fd, write| {
983                    let stdio: PyObjectRef = make_stdio(name, fd, write)?;
984                    let dunder_name = self.ctx.intern_str(format!("__{name}__"));
985                    self.sys_module.set_attr(
986                        dunder_name, // e.g. __stdin__
987                        stdio.clone(),
988                        self,
989                    )?;
990                    self.sys_module.set_attr(name, stdio, self)?;
991                    Ok(())
992                };
993                set_stdio("stdin", 0, false)?;
994                set_stdio("stdout", 1, true)?;
995                set_stdio("stderr", 2, true)?;
996
997                let io_open = io.get_attr("open", self)?;
998                self.builtins.set_attr("open", io_open, self)?;
999            }
1000
1001            Ok(importlib)
1002        };
1003
1004        let res = essential_init();
1005        let importlib = self.expect_pyresult(res, "essential initialization failed");
1006
1007        #[cfg(feature = "host_env")]
1008        if self.state.config.settings.allow_external_library
1009            && cfg!(feature = "rustpython-compiler")
1010            && let Err(e) = import::init_importlib_package(self, importlib)
1011        {
1012            eprintln!(
1013                "importlib initialization failed. This is critical for many complicated packages."
1014            );
1015            self.print_exception(e);
1016        }
1017
1018        #[cfg(not(feature = "host_env"))]
1019        let _ = importlib;
1020
1021        let _expect_stdlib = cfg!(feature = "freeze-stdlib")
1022            || !self.state.config.paths.module_search_paths.is_empty();
1023
1024        #[cfg(feature = "encodings")]
1025        if _expect_stdlib {
1026            if let Err(e) = self.import_encodings() {
1027                eprintln!(
1028                    "encodings initialization failed. Only utf-8 encoding will be supported."
1029                );
1030                self.print_exception(e);
1031            }
1032        } else {
1033            // Here may not be the best place to give general `path_list` advice,
1034            // but bare rustpython_vm::VirtualMachine users skipped proper settings must hit here while properly setup vm never enters here.
1035            eprintln!(
1036                "feature `encodings` is enabled but `paths.module_search_paths` is empty. \
1037                Please add the library path to `settings.path_list`. If you intended to disable the entire standard library (including the `encodings` feature), please also make sure to disable the `encodings` feature.\n\
1038                Tip: You may also want to add `\"\"` to `settings.path_list` in order to enable importing from the current working directory."
1039            );
1040        }
1041
1042        self.initialized = true;
1043    }
1044
1045    /// Set the custom signal channel for the interpreter
1046    pub fn set_user_signal_channel(&mut self, signal_rx: signal::UserSignalReceiver) {
1047        self.signal_rx = Some(signal_rx);
1048    }
1049
1050    /// Execute Python bytecode (`.pyc`) from an in-memory buffer.
1051    ///
1052    /// When the RustPython CLI is available, `.pyc` files are normally executed by
1053    /// invoking `rustpython <input>.pyc`. This method provides an alternative for
1054    /// environments where the binary is unavailable or file I/O is restricted
1055    /// (e.g. WASM).
1056    ///
1057    /// ## Preparing a `.pyc` file
1058    ///
1059    /// First, compile a Python source file into bytecode:
1060    ///
1061    /// ```sh
1062    /// # Generate a .pyc file
1063    /// $ rustpython -m py_compile <input>.py
1064    /// ```
1065    ///
1066    /// ## Running the bytecode
1067    ///
1068    /// Load the resulting `.pyc` file into memory and execute it using the VM:
1069    ///
1070    /// ```no_run
1071    /// use rustpython_vm::Interpreter;
1072    /// Interpreter::without_stdlib(Default::default()).enter(|vm| {
1073    ///     let bytes = std::fs::read("__pycache__/<input>.rustpython-314.pyc").unwrap();
1074    ///     let main_scope = vm.new_scope_with_main().unwrap();
1075    ///     vm.run_pyc_bytes(&bytes, main_scope);
1076    /// });
1077    /// ```
1078    pub fn run_pyc_bytes(&self, pyc_bytes: &[u8], scope: Scope) -> PyResult<()> {
1079        let code = PyCode::from_pyc(pyc_bytes, Some("<pyc_bytes>"), None, None, self)?;
1080        self.with_simple_run("<source>", |_module_dict| {
1081            self.run_code_obj(code, scope)?;
1082            Ok(())
1083        })
1084    }
1085
1086    pub fn run_code_obj(&self, code: PyRef<PyCode>, scope: Scope) -> PyResult {
1087        use crate::builtins::{PyFunction, PyModule};
1088
1089        // Create a function object for module code, similar to CPython's PyEval_EvalCode
1090        let func = PyFunction::new(code.clone(), scope.globals.clone(), self)?;
1091        let func_obj = func.into_ref(&self.ctx).into();
1092
1093        // Extract builtins from globals["__builtins__"], like PyEval_EvalCode
1094        let builtins = match scope
1095            .globals
1096            .get_item_opt(identifier!(self, __builtins__), self)?
1097        {
1098            Some(b) => {
1099                if let Some(module) = b.downcast_ref::<PyModule>() {
1100                    module.dict().into()
1101                } else {
1102                    b
1103                }
1104            }
1105            None => self.builtins.dict().into(),
1106        };
1107
1108        let frame =
1109            Frame::new(code, scope, builtins, &[], Some(func_obj), false, self).into_ref(&self.ctx);
1110        self.run_frame(frame)
1111    }
1112
1113    #[cold]
1114    pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
1115        // During interpreter finalization, sys.unraisablehook may not be available,
1116        // but we still need to report exceptions (especially from atexit callbacks).
1117        // Write directly to stderr like PyErr_FormatUnraisable.
1118        if self.state.finalizing.load(Ordering::Acquire) {
1119            self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);
1120            return;
1121        }
1122
1123        let sys_module = self.import("sys", 0).unwrap();
1124        let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
1125
1126        let exc_type = e.class().to_owned();
1127        let exc_traceback = e.__traceback__().to_pyobject(self); // TODO: actual traceback
1128        let exc_value = e.into();
1129        let args = stdlib::sys::UnraisableHookArgsData {
1130            exc_type,
1131            exc_value,
1132            exc_traceback,
1133            err_msg: self.new_pyobj(msg),
1134            object,
1135        };
1136        if let Err(e) = unraisablehook.call((args,), self) {
1137            println!("{}", e.as_object().repr(self).unwrap());
1138        }
1139    }
1140
1141    /// Write unraisable exception to stderr during finalization.
1142    /// Similar to _PyErr_WriteUnraisableDefaultHook in CPython.
1143    fn write_unraisable_to_stderr(
1144        &self,
1145        e: &PyBaseExceptionRef,
1146        msg: Option<&str>,
1147        object: &PyObjectRef,
1148    ) {
1149        // Get stderr once and reuse it
1150        let stderr = crate::stdlib::sys::get_stderr(self).ok();
1151
1152        let write_to_stderr = |s: &str, stderr: &Option<PyObjectRef>, vm: &VirtualMachine| {
1153            if let Some(stderr) = stderr {
1154                let _ = vm.call_method(stderr, "write", (s.to_owned(),));
1155            } else {
1156                eprint!("{}", s);
1157            }
1158        };
1159
1160        let msg_str = if let Some(msg) = msg {
1161            format!("{msg}: ")
1162        } else {
1163            "Exception ignored in: ".to_owned()
1164        };
1165        write_to_stderr(&msg_str, &stderr, self);
1166
1167        let repr_result = object.repr(self);
1168        let repr_wtf8 = repr_result
1169            .as_ref()
1170            .map_or("<object repr failed>".as_ref(), |s| s.as_wtf8());
1171        write_to_stderr(&format!("{repr_wtf8}\n"), &stderr, self);
1172
1173        // Write exception type and message
1174        let exc_type_name = e.class().name();
1175        let msg = match e.as_object().str(self) {
1176            Ok(exc_str) if !exc_str.as_wtf8().is_empty() => {
1177                format!("{}: {}\n", exc_type_name, exc_str.as_wtf8())
1178            }
1179            _ => format!("{}\n", exc_type_name),
1180        };
1181        write_to_stderr(&msg, &stderr, self);
1182
1183        // Flush stderr to ensure output is visible
1184        if let Some(ref stderr) = stderr {
1185            let _ = self.call_method(stderr, "flush", ());
1186        }
1187    }
1188
1189    #[inline(always)]
1190    pub fn run_frame(&self, frame: FrameRef) -> PyResult {
1191        match self.with_frame(frame, |f| f.run(self))? {
1192            ExecutionResult::Return(value) => Ok(value),
1193            _ => panic!("Got unexpected result from function"),
1194        }
1195    }
1196
1197    /// Run `run` with main scope.
1198    fn with_simple_run(
1199        &self,
1200        path: &str,
1201        run: impl FnOnce(&Py<PyDict>) -> PyResult<()>,
1202    ) -> PyResult<()> {
1203        let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
1204        let main_module = sys_modules.get_item(identifier!(self, __main__), self)?;
1205        let module_dict = main_module.dict().expect("main module must have __dict__");
1206
1207        // Track whether we set __file__ (for cleanup)
1208        let set_file_name = !module_dict.contains_key(identifier!(self, __file__), self);
1209        if set_file_name {
1210            module_dict.set_item(
1211                identifier!(self, __file__),
1212                self.ctx.new_str(path).into(),
1213                self,
1214            )?;
1215            module_dict.set_item(identifier!(self, __cached__), self.ctx.none(), self)?;
1216        }
1217
1218        let result = run(&module_dict);
1219
1220        self.flush_io();
1221
1222        // Cleanup __file__ and __cached__ after execution
1223        if set_file_name {
1224            let _ = module_dict.del_item(identifier!(self, __file__), self);
1225            let _ = module_dict.del_item(identifier!(self, __cached__), self);
1226        }
1227
1228        result
1229    }
1230
1231    /// flush_io
1232    ///
1233    /// Flush stdout and stderr. Errors are silently ignored.
1234    fn flush_io(&self) {
1235        if let Ok(stdout) = self.sys_module.get_attr("stdout", self) {
1236            let _ = self.call_method(&stdout, identifier!(self, flush).as_str(), ());
1237        }
1238        if let Ok(stderr) = self.sys_module.get_attr("stderr", self) {
1239            let _ = self.call_method(&stderr, identifier!(self, flush).as_str(), ());
1240        }
1241    }
1242
1243    /// Clear module references during shutdown.
1244    /// Follows the same phased algorithm as pylifecycle.c finalize_modules():
1245    /// no hardcoded module names, reverse import order, only builtins/sys last.
1246    pub fn finalize_modules(&self) {
1247        // Phase 1: Set special sys/builtins attributes to None, restore stdio
1248        self.finalize_modules_delete_special();
1249
1250        // Phase 2: Remove all modules from sys.modules (set values to None),
1251        // and collect weakrefs to modules preserving import order.
1252        // No strong refs are kept — modules freed when their last ref drops.
1253        let module_weakrefs = self.finalize_remove_modules();
1254
1255        // Phase 3: Clear sys.modules dict
1256        self.finalize_clear_modules_dict();
1257
1258        // Phase 4: GC collect — modules removed from sys.modules are freed,
1259        // exposing cycles (e.g., dict ↔ function.__globals__). GC collects
1260        // these and calls __del__ while module dicts are still intact.
1261        crate::gc_state::gc_state().collect_force(2);
1262
1263        // Phase 5: Clear module dicts in reverse import order using 2-pass algorithm.
1264        // Skip builtins and sys — those are cleared last.
1265        self.finalize_clear_module_dicts(&module_weakrefs);
1266
1267        // Phase 6: GC collect — pick up anything freed by dict clearing.
1268        crate::gc_state::gc_state().collect_force(2);
1269
1270        // Phase 7: Clear sys and builtins dicts last
1271        self.finalize_clear_sys_builtins_dict();
1272    }
1273
1274    /// Phase 1: Set special sys attributes to None and restore stdio.
1275    fn finalize_modules_delete_special(&self) {
1276        let none = self.ctx.none();
1277        let sys_dict = self.sys_module.dict();
1278
1279        // Set special sys attributes to None
1280        for attr in &[
1281            "path",
1282            "argv",
1283            "ps1",
1284            "ps2",
1285            "last_exc",
1286            "last_type",
1287            "last_value",
1288            "last_traceback",
1289            "path_importer_cache",
1290            "meta_path",
1291            "path_hooks",
1292        ] {
1293            let _ = sys_dict.set_item(*attr, none.clone(), self);
1294        }
1295
1296        // Restore stdin/stdout/stderr from __stdin__/__stdout__/__stderr__
1297        for (std_name, dunder_name) in &[
1298            ("stdin", "__stdin__"),
1299            ("stdout", "__stdout__"),
1300            ("stderr", "__stderr__"),
1301        ] {
1302            let restored = sys_dict
1303                .get_item_opt(*dunder_name, self)
1304                .ok()
1305                .flatten()
1306                .unwrap_or_else(|| none.clone());
1307            let _ = sys_dict.set_item(*std_name, restored, self);
1308        }
1309
1310        // builtins._ = None
1311        let _ = self.builtins.dict().set_item("_", none, self);
1312    }
1313
1314    /// Phase 2: Set all sys.modules values to None and collect weakrefs.
1315    /// No strong refs are kept — modules are freed when removed from sys.modules
1316    /// (if nothing else references them), allowing GC to collect their cycles.
1317    fn finalize_remove_modules(&self) -> Vec<(String, PyRef<PyWeak>)> {
1318        let mut module_weakrefs = Vec::new();
1319
1320        let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self) else {
1321            return module_weakrefs;
1322        };
1323        let Some(modules_dict) = modules.downcast_ref::<PyDict>() else {
1324            return module_weakrefs;
1325        };
1326
1327        let none = self.ctx.none();
1328        let items: Vec<_> = modules_dict.into_iter().collect();
1329
1330        for (key, value) in items {
1331            let name = key
1332                .downcast_ref::<PyUtf8Str>()
1333                .map(|s| s.as_str().to_owned())
1334                .unwrap_or_default();
1335
1336            // Save weakref to module (for later dict clearing)
1337            if value.downcast_ref::<PyModule>().is_some()
1338                && let Ok(weak) = value.downgrade(None, self)
1339            {
1340                module_weakrefs.push((name, weak));
1341            }
1342
1343            // Set the value to None in sys.modules
1344            let _ = modules_dict.set_item(&*key, none.clone(), self);
1345        }
1346
1347        module_weakrefs
1348    }
1349
1350    /// Phase 3: Clear sys.modules dict.
1351    fn finalize_clear_modules_dict(&self) {
1352        if let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self)
1353            && let Some(modules_dict) = modules.downcast_ref::<PyDict>()
1354        {
1355            modules_dict.clear();
1356        }
1357    }
1358
1359    /// Phase 5: Clear module dicts in reverse import order.
1360    /// Skip builtins and sys — those are cleared last in Phase 7.
1361    fn finalize_clear_module_dicts(&self, module_weakrefs: &[(String, PyRef<PyWeak>)]) {
1362        let builtins_dict = self.builtins.dict();
1363        let sys_dict = self.sys_module.dict();
1364
1365        for (_name, weakref) in module_weakrefs.iter().rev() {
1366            let Some(module_obj) = weakref.upgrade() else {
1367                continue;
1368            };
1369            let Some(module) = module_obj.downcast_ref::<PyModule>() else {
1370                continue;
1371            };
1372
1373            let dict = module.dict();
1374            // Skip builtins and sys — they are cleared last
1375            if dict.is(&builtins_dict) || dict.is(&sys_dict) {
1376                continue;
1377            }
1378
1379            Self::module_clear_dict(&dict, self);
1380        }
1381    }
1382
1383    /// 2-pass module dict clearing (_PyModule_ClearDict algorithm).
1384    /// Pass 1: Set names starting with '_' (except __builtins__) to None.
1385    /// Pass 2: Set all remaining names (except __builtins__) to None.
1386    pub(crate) fn module_clear_dict(dict: &Py<PyDict>, vm: &VirtualMachine) {
1387        let none = vm.ctx.none();
1388
1389        // Pass 1: names starting with '_' (except __builtins__)
1390        for (key, value) in dict.into_iter().collect::<Vec<_>>() {
1391            if vm.is_none(&value) {
1392                continue;
1393            }
1394            if let Some(key_str) = key.downcast_ref::<PyStr>() {
1395                let name = key_str.as_wtf8();
1396                if name.starts_with("_") && name != "__builtins__" {
1397                    let _ = dict.set_item(key_str, none.clone(), vm);
1398                }
1399            }
1400        }
1401
1402        // Pass 2: all remaining (except __builtins__)
1403        for (key, value) in dict.into_iter().collect::<Vec<_>>() {
1404            if vm.is_none(&value) {
1405                continue;
1406            }
1407            if let Some(key_str) = key.downcast_ref::<PyStr>()
1408                && key_str.as_bytes() != b"__builtins__"
1409            {
1410                let _ = dict.set_item(key_str.as_wtf8(), none.clone(), vm);
1411            }
1412        }
1413    }
1414
1415    /// Phase 7: Clear sys and builtins dicts last.
1416    fn finalize_clear_sys_builtins_dict(&self) {
1417        Self::module_clear_dict(&self.sys_module.dict(), self);
1418        Self::module_clear_dict(&self.builtins.dict(), self);
1419    }
1420
1421    pub fn current_recursion_depth(&self) -> usize {
1422        self.recursion_depth.get()
1423    }
1424
1425    /// Stack margin bytes (like _PyOS_STACK_MARGIN_BYTES).
1426    /// 2048 * sizeof(void*) = 16KB for 64-bit.
1427    #[cfg_attr(miri, allow(dead_code))]
1428    const STACK_MARGIN_BYTES: usize = 2048 * core::mem::size_of::<usize>();
1429
1430    /// Get the stack boundaries using platform-specific APIs.
1431    /// Returns (base, top) where base is the lowest address and top is the highest.
1432    #[cfg(all(not(miri), windows))]
1433    fn get_stack_bounds() -> (usize, usize) {
1434        use windows_sys::Win32::System::Threading::{
1435            GetCurrentThreadStackLimits, SetThreadStackGuarantee,
1436        };
1437        let mut low: usize = 0;
1438        let mut high: usize = 0;
1439        unsafe {
1440            GetCurrentThreadStackLimits(&mut low as *mut usize, &mut high as *mut usize);
1441            // Add the guaranteed stack space (reserved for exception handling)
1442            let mut guarantee: u32 = 0;
1443            SetThreadStackGuarantee(&mut guarantee);
1444            low += guarantee as usize;
1445        }
1446        (low, high)
1447    }
1448
1449    /// Get stack boundaries on non-Windows platforms.
1450    /// Falls back to estimating based on current stack pointer.
1451    #[cfg(all(not(miri), not(windows)))]
1452    fn get_stack_bounds() -> (usize, usize) {
1453        // Use pthread_attr_getstack on platforms that support it
1454        #[cfg(any(target_os = "linux", target_os = "android"))]
1455        {
1456            use libc::{
1457                pthread_attr_destroy, pthread_attr_getstack, pthread_attr_t, pthread_getattr_np,
1458                pthread_self,
1459            };
1460            let mut attr: pthread_attr_t = unsafe { core::mem::zeroed() };
1461            unsafe {
1462                if pthread_getattr_np(pthread_self(), &mut attr) == 0 {
1463                    let mut stack_addr: *mut libc::c_void = core::ptr::null_mut();
1464                    let mut stack_size: libc::size_t = 0;
1465                    if pthread_attr_getstack(&attr, &mut stack_addr, &mut stack_size) == 0 {
1466                        pthread_attr_destroy(&mut attr);
1467                        let base = stack_addr as usize;
1468                        let top = base + stack_size;
1469                        return (base, top);
1470                    }
1471                    pthread_attr_destroy(&mut attr);
1472                }
1473            }
1474        }
1475
1476        #[cfg(target_os = "macos")]
1477        {
1478            use libc::{pthread_get_stackaddr_np, pthread_get_stacksize_np, pthread_self};
1479            unsafe {
1480                let thread = pthread_self();
1481                let stack_top = pthread_get_stackaddr_np(thread) as usize;
1482                let stack_size = pthread_get_stacksize_np(thread);
1483                let stack_base = stack_top - stack_size;
1484                return (stack_base, stack_top);
1485            }
1486        }
1487
1488        // Fallback: estimate based on current SP and a default stack size
1489        #[allow(unreachable_code)]
1490        {
1491            let current_sp = psm::stack_pointer() as usize;
1492            // Assume 8MB stack, estimate base
1493            let estimated_size = 8 * 1024 * 1024;
1494            let base = current_sp.saturating_sub(estimated_size);
1495            let top = current_sp + 1024 * 1024; // Assume we're not at the very top
1496            (base, top)
1497        }
1498    }
1499
1500    /// Calculate the C stack soft limit based on actual stack boundaries.
1501    /// soft_limit = base + 2 * margin (for downward-growing stacks)
1502    #[cfg(not(miri))]
1503    fn calculate_c_stack_soft_limit() -> usize {
1504        let (base, _top) = Self::get_stack_bounds();
1505        // Soft limit is 2 margins above the base
1506        base + Self::STACK_MARGIN_BYTES * 2
1507    }
1508
1509    /// Miri doesn't support inline assembly, so disable C stack checking.
1510    #[cfg(miri)]
1511    fn calculate_c_stack_soft_limit() -> usize {
1512        0
1513    }
1514
1515    /// Check if we're near the C stack limit (like _Py_MakeRecCheck).
1516    /// Returns true only when stack pointer is in the "danger zone" between
1517    /// soft_limit and hard_limit (soft_limit - 2*margin).
1518    #[cfg(not(miri))]
1519    #[inline(always)]
1520    fn check_c_stack_overflow(&self) -> bool {
1521        let current_sp = psm::stack_pointer() as usize;
1522        let soft_limit = self.c_stack_soft_limit.get();
1523        // Stack grows downward: check if we're below soft limit but above hard limit
1524        // This matches CPython's _Py_MakeRecCheck behavior
1525        current_sp < soft_limit
1526            && current_sp >= soft_limit.saturating_sub(Self::STACK_MARGIN_BYTES * 2)
1527    }
1528
1529    /// Miri doesn't support inline assembly, so always return false.
1530    #[cfg(miri)]
1531    #[inline(always)]
1532    fn check_c_stack_overflow(&self) -> bool {
1533        false
1534    }
1535
1536    /// Used to run the body of a (possibly) recursive function. It will raise a
1537    /// RecursionError if recursive functions are nested far too many times,
1538    /// preventing a stack overflow.
1539    pub fn with_recursion<R, F: FnOnce() -> PyResult<R>>(&self, _where: &str, f: F) -> PyResult<R> {
1540        self.check_recursive_call(_where)?;
1541
1542        // Native stack guard: check C stack like _Py_MakeRecCheck
1543        if self.check_c_stack_overflow() {
1544            return Err(self.new_recursion_error(_where.to_string()));
1545        }
1546
1547        self.recursion_depth.update(|d| d + 1);
1548        scopeguard::defer! { self.recursion_depth.update(|d| d - 1) }
1549        f()
1550    }
1551
1552    pub fn with_frame<R, F: FnOnce(FrameRef) -> PyResult<R>>(
1553        &self,
1554        frame: FrameRef,
1555        f: F,
1556    ) -> PyResult<R> {
1557        self.with_frame_impl(frame, None, true, f)
1558    }
1559
1560    /// Like `with_frame` but allows specifying the initial exception state.
1561    pub fn with_frame_exc<R, F: FnOnce(FrameRef) -> PyResult<R>>(
1562        &self,
1563        frame: FrameRef,
1564        exc: Option<PyBaseExceptionRef>,
1565        f: F,
1566    ) -> PyResult<R> {
1567        self.with_frame_impl(frame, exc, true, f)
1568    }
1569
1570    pub(crate) fn with_frame_untraced<R, F: FnOnce(FrameRef) -> PyResult<R>>(
1571        &self,
1572        frame: FrameRef,
1573        f: F,
1574    ) -> PyResult<R> {
1575        self.with_frame_impl(frame, None, false, f)
1576    }
1577
1578    fn with_frame_impl<R, F: FnOnce(FrameRef) -> PyResult<R>>(
1579        &self,
1580        frame: FrameRef,
1581        exc: Option<PyBaseExceptionRef>,
1582        traced: bool,
1583        f: F,
1584    ) -> PyResult<R> {
1585        self.with_recursion("", || {
1586            // SAFETY: `frame` (FrameRef) stays alive for the entire closure scope,
1587            // keeping the FramePtr valid. We pass a clone to `f` so that `f`
1588            // consuming its FrameRef doesn't invalidate our pointer.
1589            let fp = FramePtr(NonNull::from(&*frame));
1590            self.frames.borrow_mut().push(fp);
1591            // Update the shared frame stack for sys._current_frames() and faulthandler
1592            #[cfg(feature = "threading")]
1593            crate::vm::thread::push_thread_frame(fp);
1594            // Link frame into the signal-safe frame chain (previous pointer)
1595            let old_frame = crate::vm::thread::set_current_frame((&**frame) as *const Frame);
1596            frame.previous.store(
1597                old_frame as *mut Frame,
1598                core::sync::atomic::Ordering::Relaxed,
1599            );
1600            // Push exception context for frame isolation.
1601            // For normal calls: None (clean slate).
1602            // For generators: the saved exception from last yield.
1603            self.push_exception(exc);
1604            let old_owner = frame.owner.swap(
1605                crate::frame::FrameOwner::Thread as i8,
1606                core::sync::atomic::Ordering::AcqRel,
1607            );
1608
1609            // Ensure cleanup on panic: restore owner, pop exception, frame chain, and frames Vec.
1610            scopeguard::defer! {
1611                frame.owner.store(old_owner, core::sync::atomic::Ordering::Release);
1612                self.pop_exception();
1613                crate::vm::thread::set_current_frame(old_frame);
1614                self.frames.borrow_mut().pop();
1615                #[cfg(feature = "threading")]
1616                crate::vm::thread::pop_thread_frame();
1617            }
1618
1619            if traced {
1620                self.dispatch_traced_frame(&frame, |frame| f(frame.to_owned()))
1621            } else {
1622                f(frame.to_owned())
1623            }
1624        })
1625    }
1626
1627    /// Lightweight frame execution for generator/coroutine resume.
1628    /// Pushes to the thread frame stack and fires trace/profile events,
1629    /// but skips the thread exception update for performance.
1630    pub fn resume_gen_frame<R, F: FnOnce(&Py<Frame>) -> PyResult<R>>(
1631        &self,
1632        frame: &FrameRef,
1633        exc: Option<PyBaseExceptionRef>,
1634        f: F,
1635    ) -> PyResult<R> {
1636        self.check_recursive_call("")?;
1637        if self.check_c_stack_overflow() {
1638            return Err(self.new_recursion_error(String::new()));
1639        }
1640        self.recursion_depth.update(|d| d + 1);
1641
1642        // SAFETY: frame (&FrameRef) stays alive for the duration, so NonNull is valid until pop.
1643        let fp = FramePtr(NonNull::from(&**frame));
1644        self.frames.borrow_mut().push(fp);
1645        #[cfg(feature = "threading")]
1646        crate::vm::thread::push_thread_frame(fp);
1647        let old_frame = crate::vm::thread::set_current_frame((&***frame) as *const Frame);
1648        frame.previous.store(
1649            old_frame as *mut Frame,
1650            core::sync::atomic::Ordering::Relaxed,
1651        );
1652        // Inline exception push without thread exception update
1653        self.exceptions.borrow_mut().stack.push(exc);
1654        let old_owner = frame.owner.swap(
1655            crate::frame::FrameOwner::Thread as i8,
1656            core::sync::atomic::Ordering::AcqRel,
1657        );
1658
1659        // Ensure cleanup on panic: restore owner, pop exception, frame chain, frames Vec,
1660        // and recursion depth.
1661        scopeguard::defer! {
1662            frame.owner.store(old_owner, core::sync::atomic::Ordering::Release);
1663            self.exceptions.borrow_mut().stack
1664                .pop()
1665                .expect("pop_exception() without nested exc stack");
1666            crate::vm::thread::set_current_frame(old_frame);
1667            self.frames.borrow_mut().pop();
1668            #[cfg(feature = "threading")]
1669            crate::vm::thread::pop_thread_frame();
1670
1671            self.recursion_depth.update(|d| d - 1);
1672        }
1673
1674        self.dispatch_traced_frame(frame, |frame| f(frame))
1675    }
1676
1677    /// Fire trace/profile 'call' and 'return' events around a frame body.
1678    ///
1679    /// Matches `call_trace_protected` / `trace_trampoline` protocol:
1680    /// - Fire `TraceEvent::Call`; if the trace function returns non-None,
1681    ///   install it as the per-frame `f_trace`.
1682    /// - Execute the closure (the actual frame body).
1683    /// - Fire `TraceEvent::Return` on both normal return **and** exception
1684    ///   unwind (`PY_UNWIND` → `PyTrace_RETURN` with `arg = None`).
1685    ///   Propagate any trace-function error, replacing the original exception.
1686    fn dispatch_traced_frame<R, F: FnOnce(&Py<Frame>) -> PyResult<R>>(
1687        &self,
1688        frame: &Py<Frame>,
1689        f: F,
1690    ) -> PyResult<R> {
1691        use crate::protocol::TraceEvent;
1692
1693        // Fire 'call' trace event. current_frame() now returns the callee.
1694        let trace_result = self.trace_event(TraceEvent::Call, None)?;
1695        if let Some(local_trace) = trace_result {
1696            *frame.trace.lock() = local_trace;
1697        }
1698
1699        let result = f(frame);
1700
1701        // Fire 'return' event if frame is being traced or profiled.
1702        // PY_UNWIND fires PyTrace_RETURN with arg=None — so we fire for
1703        // both Ok and Err, matching `call_trace_protected` behavior.
1704        if self.use_tracing.get()
1705            && (!self.is_none(&frame.trace.lock()) || !self.is_none(&self.profile_func.borrow()))
1706        {
1707            let ret_result = self.trace_event(TraceEvent::Return, None);
1708            // call_trace_protected: if trace function raises, its error
1709            // replaces the original exception.
1710            ret_result?;
1711        }
1712
1713        result
1714    }
1715
1716    /// Returns a basic CompileOpts instance with options accurate to the vm. Used
1717    /// as the CompileOpts for `vm.compile()`.
1718    #[cfg(feature = "rustpython-codegen")]
1719    pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
1720        crate::compiler::CompileOpts {
1721            optimize: self.state.config.settings.optimize,
1722            debug_ranges: self.state.config.settings.code_debug_ranges,
1723        }
1724    }
1725
1726    // To be called right before raising the recursion depth.
1727    fn check_recursive_call(&self, _where: &str) -> PyResult<()> {
1728        if self.recursion_depth.get() >= self.recursion_limit.get() {
1729            Err(self.new_recursion_error(format!("maximum recursion depth exceeded {_where}")))
1730        } else {
1731            Ok(())
1732        }
1733    }
1734
1735    pub fn current_frame(&self) -> Option<FrameRef> {
1736        self.frames.borrow().last().map(|fp| {
1737            // SAFETY: the caller keeps the FrameRef alive while it's in the Vec
1738            unsafe { fp.as_ref() }.to_owned()
1739        })
1740    }
1741
1742    pub fn current_locals(&self) -> PyResult<ArgMapping> {
1743        self.current_frame()
1744            .expect("called current_locals but no frames on the stack")
1745            .locals(self)
1746    }
1747
1748    pub fn current_globals(&self) -> PyDictRef {
1749        self.current_frame()
1750            .expect("called current_globals but no frames on the stack")
1751            .globals
1752            .clone()
1753    }
1754
1755    pub fn try_class(&self, module: &'static str, class: &'static str) -> PyResult<PyTypeRef> {
1756        let class = self
1757            .import(module, 0)?
1758            .get_attr(class, self)?
1759            .downcast()
1760            .expect("not a class");
1761        Ok(class)
1762    }
1763
1764    pub fn class(&self, module: &'static str, class: &'static str) -> PyTypeRef {
1765        let module = self
1766            .import(module, 0)
1767            .unwrap_or_else(|_| panic!("unable to import {module}"));
1768
1769        let class = module
1770            .get_attr(class, self)
1771            .unwrap_or_else(|_| panic!("module {module:?} has no class {class}"));
1772        class.downcast().expect("not a class")
1773    }
1774
1775    /// Call Python __import__ function without from_list.
1776    /// Roughly equivalent to `import module_name` or `import top.submodule`.
1777    ///
1778    /// See also [`VirtualMachine::import_from`] for more advanced import.
1779    /// See also [`rustpython_vm::import::import_source`] and other primitive import functions.
1780    #[inline]
1781    pub fn import<'a>(&self, module_name: impl AsPyStr<'a>, level: usize) -> PyResult {
1782        let module_name = module_name.as_pystr(&self.ctx);
1783        let from_list = self.ctx.empty_tuple_typed();
1784        self.import_inner(module_name, from_list, level)
1785    }
1786
1787    /// Call Python __import__ function caller with from_list.
1788    /// Roughly equivalent to `from module_name import item1, item2` or `from top.submodule import item1, item2`
1789    #[inline]
1790    pub fn import_from<'a>(
1791        &self,
1792        module_name: impl AsPyStr<'a>,
1793        from_list: &Py<PyTuple<PyStrRef>>,
1794        level: usize,
1795    ) -> PyResult {
1796        let module_name = module_name.as_pystr(&self.ctx);
1797        self.import_inner(module_name, from_list, level)
1798    }
1799
1800    fn import_inner(
1801        &self,
1802        module: &Py<PyStr>,
1803        from_list: &Py<PyTuple<PyStrRef>>,
1804        level: usize,
1805    ) -> PyResult {
1806        let import_func = self
1807            .builtins
1808            .get_attr(identifier!(self, __import__), self)
1809            .map_err(|_| self.new_import_error("__import__ not found", module.to_owned()))?;
1810
1811        let (locals, globals) = if let Some(frame) = self.current_frame() {
1812            (
1813                Some(frame.locals.clone_mapping(self)),
1814                Some(frame.globals.clone()),
1815            )
1816        } else {
1817            (None, None)
1818        };
1819        let from_list: PyObjectRef = from_list.to_owned().into();
1820        import_func
1821            .call((module.to_owned(), globals, locals, from_list, level), self)
1822            .inspect_err(|exc| import::remove_importlib_frames(self, exc))
1823    }
1824
1825    pub fn extract_elements_with<T, F>(&self, value: &PyObject, func: F) -> PyResult<Vec<T>>
1826    where
1827        F: Fn(PyObjectRef) -> PyResult<T>,
1828    {
1829        // Type-specific fast paths corresponding to _list_extend() in CPython
1830        // Objects/listobject.c. Each branch takes an atomic snapshot to avoid
1831        // race conditions from concurrent mutation (no GIL).
1832        let cls = value.class();
1833        let list_borrow;
1834        let slice = if cls.is(self.ctx.types.tuple_type) {
1835            value.downcast_ref::<PyTuple>().unwrap().as_slice()
1836        } else if cls.is(self.ctx.types.list_type) {
1837            list_borrow = value.downcast_ref::<PyList>().unwrap().borrow_vec();
1838            &list_borrow
1839        } else if cls.is(self.ctx.types.dict_type) {
1840            let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
1841            return keys.into_iter().map(func).collect();
1842        } else if cls.is(self.ctx.types.dict_keys_type) {
1843            let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
1844            return keys.into_iter().map(func).collect();
1845        } else if cls.is(self.ctx.types.dict_values_type) {
1846            let values = value
1847                .downcast_ref::<PyDictValues>()
1848                .unwrap()
1849                .dict
1850                .values_vec();
1851            return values.into_iter().map(func).collect();
1852        } else if cls.is(self.ctx.types.dict_items_type) {
1853            let items = value
1854                .downcast_ref::<PyDictItems>()
1855                .unwrap()
1856                .dict
1857                .items_vec();
1858            return items
1859                .into_iter()
1860                .map(|(k, v)| func(self.ctx.new_tuple(vec![k, v]).into()))
1861                .collect();
1862        } else {
1863            return self.map_py_iter(value, func);
1864        };
1865        slice.iter().map(|obj| func(obj.clone())).collect()
1866    }
1867
1868    pub fn map_iterable_object<F, R>(&self, obj: &PyObject, mut f: F) -> PyResult<PyResult<Vec<R>>>
1869    where
1870        F: FnMut(PyObjectRef) -> PyResult<R>,
1871    {
1872        match_class!(match obj {
1873            ref l @ PyList => {
1874                let mut i: usize = 0;
1875                let mut results = Vec::with_capacity(l.borrow_vec().len());
1876                loop {
1877                    let elem = {
1878                        let elements = &*l.borrow_vec();
1879                        if i >= elements.len() {
1880                            results.shrink_to_fit();
1881                            return Ok(Ok(results));
1882                        } else {
1883                            elements[i].clone()
1884                        }
1885                        // free the lock
1886                    };
1887                    match f(elem) {
1888                        Ok(result) => results.push(result),
1889                        Err(err) => return Ok(Err(err)),
1890                    }
1891                    i += 1;
1892                }
1893            }
1894            ref t @ PyTuple => Ok(t.iter().cloned().map(f).collect()),
1895            // TODO: put internal iterable type
1896            obj => {
1897                Ok(self.map_py_iter(obj, f))
1898            }
1899        })
1900    }
1901
1902    fn map_py_iter<F, R>(&self, value: &PyObject, mut f: F) -> PyResult<Vec<R>>
1903    where
1904        F: FnMut(PyObjectRef) -> PyResult<R>,
1905    {
1906        let iter = value.to_owned().get_iter(self)?;
1907        let cap = match self.length_hint_opt(value.to_owned()) {
1908            Err(e) if e.class().is(self.ctx.exceptions.runtime_error) => return Err(e),
1909            Ok(Some(value)) => Some(value),
1910            // Use a power of 2 as a default capacity.
1911            _ => None,
1912        };
1913        // TODO: fix extend to do this check (?), see test_extend in Lib/test/list_tests.py,
1914        // https://github.com/python/cpython/blob/v3.9.0/Objects/listobject.c#L922-L928
1915        if let Some(cap) = cap
1916            && cap >= isize::MAX as usize
1917        {
1918            return Ok(Vec::new());
1919        }
1920
1921        let mut results = PyIterIter::new(self, iter.as_ref(), cap)
1922            .map(|element| f(element?))
1923            .collect::<PyResult<Vec<_>>>()?;
1924        results.shrink_to_fit();
1925        Ok(results)
1926    }
1927
1928    pub fn get_attribute_opt<'a>(
1929        &self,
1930        obj: PyObjectRef,
1931        attr_name: impl AsPyStr<'a>,
1932    ) -> PyResult<Option<PyObjectRef>> {
1933        let attr_name = attr_name.as_pystr(&self.ctx);
1934        match obj.get_attr_inner(attr_name, self) {
1935            Ok(attr) => Ok(Some(attr)),
1936            Err(e) if e.fast_isinstance(self.ctx.exceptions.attribute_error) => Ok(None),
1937            Err(e) => Err(e),
1938        }
1939    }
1940
1941    pub fn set_attribute_error_context(
1942        &self,
1943        exc: &Py<PyBaseException>,
1944        obj: PyObjectRef,
1945        name: PyStrRef,
1946    ) {
1947        if exc.class().is(self.ctx.exceptions.attribute_error) {
1948            let exc = exc.as_object();
1949            // Check if this exception was already augmented
1950            let already_set = exc
1951                .get_attr("name", self)
1952                .ok()
1953                .is_some_and(|v| !self.is_none(&v));
1954            if already_set {
1955                return;
1956            }
1957            exc.set_attr("name", name, self).unwrap();
1958            exc.set_attr("obj", obj, self).unwrap();
1959        }
1960    }
1961
1962    // get_method should be used for internal access to magic methods (by-passing
1963    // the full getattribute look-up.
1964    pub fn get_method_or_type_error<F>(
1965        &self,
1966        obj: PyObjectRef,
1967        method_name: &'static PyStrInterned,
1968        err_msg: F,
1969    ) -> PyResult
1970    where
1971        F: FnOnce() -> String,
1972    {
1973        let method = obj
1974            .class()
1975            .get_attr(method_name)
1976            .ok_or_else(|| self.new_type_error(err_msg()))?;
1977        self.call_if_get_descriptor(&method, obj)
1978    }
1979
1980    // TODO: remove + transfer over to get_special_method
1981    pub(crate) fn get_method(
1982        &self,
1983        obj: PyObjectRef,
1984        method_name: &'static PyStrInterned,
1985    ) -> Option<PyResult> {
1986        let method = obj.get_class_attr(method_name)?;
1987        Some(self.call_if_get_descriptor(&method, obj))
1988    }
1989
1990    pub(crate) fn get_str_method(&self, obj: PyObjectRef, method_name: &str) -> Option<PyResult> {
1991        let method_name = self.ctx.interned_str(method_name)?;
1992        self.get_method(obj, method_name)
1993    }
1994
1995    #[inline]
1996    pub(crate) fn eval_breaker_tripped(&self) -> bool {
1997        #[cfg(feature = "threading")]
1998        if self.state.finalizing.load(Ordering::Relaxed) && !self.is_main_thread() {
1999            return true;
2000        }
2001
2002        #[cfg(all(unix, feature = "threading"))]
2003        if thread::stop_requested_for_current_thread() {
2004            return true;
2005        }
2006
2007        #[cfg(not(target_arch = "wasm32"))]
2008        if crate::signal::is_triggered() {
2009            return true;
2010        }
2011
2012        false
2013    }
2014
2015    #[inline]
2016    /// Checks for triggered signals and calls the appropriate handlers. A no-op on
2017    /// platforms where signals are not supported.
2018    pub fn check_signals(&self) -> PyResult<()> {
2019        #[cfg(feature = "threading")]
2020        if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() {
2021            // once finalization starts,
2022            // non-main Python threads should stop running bytecode.
2023            return Err(self.new_exception(self.ctx.exceptions.system_exit.to_owned(), vec![]));
2024        }
2025
2026        // Suspend this thread if stop-the-world is in progress
2027        #[cfg(all(unix, feature = "threading"))]
2028        thread::suspend_if_needed(&self.state.stop_the_world);
2029
2030        #[cfg(not(target_arch = "wasm32"))]
2031        {
2032            crate::signal::check_signals(self)
2033        }
2034        #[cfg(target_arch = "wasm32")]
2035        {
2036            Ok(())
2037        }
2038    }
2039
2040    pub(crate) fn push_exception(&self, exc: Option<PyBaseExceptionRef>) {
2041        self.exceptions.borrow_mut().stack.push(exc);
2042        #[cfg(feature = "threading")]
2043        thread::update_thread_exception(self.topmost_exception());
2044    }
2045
2046    pub(crate) fn pop_exception(&self) -> Option<PyBaseExceptionRef> {
2047        let exc = self
2048            .exceptions
2049            .borrow_mut()
2050            .stack
2051            .pop()
2052            .expect("pop_exception() without nested exc stack");
2053        #[cfg(feature = "threading")]
2054        thread::update_thread_exception(self.topmost_exception());
2055        exc
2056    }
2057
2058    pub(crate) fn current_exception(&self) -> Option<PyBaseExceptionRef> {
2059        self.exceptions.borrow().stack.last().cloned().flatten()
2060    }
2061
2062    pub(crate) fn set_exception(&self, exc: Option<PyBaseExceptionRef>) {
2063        // don't be holding the RefCell guard while __del__ is called
2064        let mut excs = self.exceptions.borrow_mut();
2065        debug_assert!(
2066            !excs.stack.is_empty(),
2067            "set_exception called with empty exception stack"
2068        );
2069        if let Some(top) = excs.stack.last_mut() {
2070            let prev = core::mem::replace(top, exc);
2071            drop(excs);
2072            drop(prev);
2073        } else {
2074            excs.stack.push(exc);
2075            drop(excs);
2076        }
2077        #[cfg(feature = "threading")]
2078        thread::update_thread_exception(self.topmost_exception());
2079    }
2080
2081    pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) {
2082        if let Some(context_exc) = self.topmost_exception()
2083            && !context_exc.is(exception)
2084        {
2085            // Traverse the context chain to find `exception` and break cycles
2086            // Uses Floyd's cycle detection: o moves every step, slow_o every other step
2087            let mut o = context_exc.clone();
2088            let mut slow_o = context_exc.clone();
2089            let mut slow_update_toggle = false;
2090            while let Some(context) = o.__context__() {
2091                if context.is(exception) {
2092                    o.set___context__(None);
2093                    break;
2094                }
2095                o = context;
2096                if o.is(&slow_o) {
2097                    // Pre-existing cycle detected - all exceptions on the path were visited
2098                    break;
2099                }
2100                if slow_update_toggle && let Some(slow_context) = slow_o.__context__() {
2101                    slow_o = slow_context;
2102                }
2103                slow_update_toggle = !slow_update_toggle;
2104            }
2105            exception.set___context__(Some(context_exc))
2106        }
2107    }
2108
2109    pub(crate) fn topmost_exception(&self) -> Option<PyBaseExceptionRef> {
2110        let excs = self.exceptions.borrow();
2111        excs.stack.iter().rev().find_map(|e| e.clone())
2112    }
2113
2114    pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u32 {
2115        if exc.fast_isinstance(self.ctx.exceptions.system_exit) {
2116            let args = exc.args();
2117            let msg = match args.as_slice() {
2118                [] => return 0,
2119                [arg] => match_class!(match arg {
2120                    ref i @ PyInt => {
2121                        use num_traits::cast::ToPrimitive;
2122                        // Try u32 first, then i32 (for negative values), else -1 for overflow
2123                        let code = i
2124                            .as_bigint()
2125                            .to_u32()
2126                            .or_else(|| i.as_bigint().to_i32().map(|v| v as u32))
2127                            .unwrap_or(-1i32 as u32);
2128                        return code;
2129                    }
2130                    arg => {
2131                        if self.is_none(arg) {
2132                            return 0;
2133                        } else {
2134                            arg.str(self).ok()
2135                        }
2136                    }
2137                }),
2138                _ => args.as_object().repr(self).ok(),
2139            };
2140            if let Some(msg) = msg {
2141                // Write using Python's write() to use stderr's error handler (backslashreplace)
2142                if let Ok(stderr) = stdlib::sys::get_stderr(self) {
2143                    let _ = self.call_method(&stderr, "write", (msg,));
2144                    let _ = self.call_method(&stderr, "write", ("\n",));
2145                }
2146            }
2147            1
2148        } else if exc.fast_isinstance(self.ctx.exceptions.keyboard_interrupt) {
2149            #[allow(clippy::if_same_then_else)]
2150            {
2151                self.print_exception(exc);
2152                #[cfg(unix)]
2153                {
2154                    let action = SigAction::new(
2155                        nix::sys::signal::SigHandler::SigDfl,
2156                        SaFlags::SA_ONSTACK,
2157                        SigSet::empty(),
2158                    );
2159                    let result = unsafe { sigaction(SIGINT, &action) };
2160                    if result.is_ok() {
2161                        self.flush_std();
2162                        kill(getpid(), SIGINT).expect("Expect to be killed.");
2163                    }
2164
2165                    (libc::SIGINT as u32) + 128
2166                }
2167                #[cfg(windows)]
2168                {
2169                    // STATUS_CONTROL_C_EXIT - same as CPython
2170                    0xC000013A
2171                }
2172                #[cfg(not(any(unix, windows)))]
2173                {
2174                    1
2175                }
2176            }
2177        } else {
2178            self.print_exception(exc);
2179            1
2180        }
2181    }
2182
2183    #[doc(hidden)]
2184    pub fn __module_set_attr(
2185        &self,
2186        module: &Py<PyModule>,
2187        attr_name: &'static PyStrInterned,
2188        attr_value: impl Into<PyObjectRef>,
2189    ) -> PyResult<()> {
2190        let val = attr_value.into();
2191        module
2192            .as_object()
2193            .generic_setattr(attr_name, PySetterValue::Assign(val), self)
2194    }
2195
2196    pub fn insert_sys_path(&self, obj: PyObjectRef) -> PyResult<()> {
2197        let sys_path = self.sys_module.get_attr("path", self).unwrap();
2198        self.call_method(&sys_path, "insert", (0, obj))?;
2199        Ok(())
2200    }
2201
2202    pub fn run_module(&self, module: &str) -> PyResult<()> {
2203        let runpy = self.import("runpy", 0)?;
2204        let run_module_as_main = runpy.get_attr("_run_module_as_main", self)?;
2205        run_module_as_main.call((module,), self)?;
2206        Ok(())
2207    }
2208
2209    pub fn fs_encoding(&self) -> &'static PyStrInterned {
2210        identifier!(self, utf_8)
2211    }
2212
2213    pub fn fs_encode_errors(&self) -> &'static PyUtf8StrInterned {
2214        if cfg!(windows) {
2215            identifier_utf8!(self, surrogatepass)
2216        } else {
2217            identifier_utf8!(self, surrogateescape)
2218        }
2219    }
2220
2221    pub fn fsdecode(&self, s: impl Into<OsString>) -> PyStrRef {
2222        match s.into().into_string() {
2223            Ok(s) => self.ctx.new_str(s),
2224            Err(s) => {
2225                let bytes = self.ctx.new_bytes(s.into_encoded_bytes());
2226                let errors = self.fs_encode_errors().to_owned();
2227                let res = self.state.codec_registry.decode_text(
2228                    bytes.into(),
2229                    "utf-8",
2230                    Some(errors),
2231                    self,
2232                );
2233                self.expect_pyresult(res, "fsdecode should be lossless and never fail")
2234            }
2235        }
2236    }
2237
2238    pub fn fsencode<'a>(&self, s: &'a Py<PyStr>) -> PyResult<Cow<'a, OsStr>> {
2239        if cfg!(windows) || s.is_utf8() {
2240            // XXX: this is sketchy on windows; it's not guaranteed that the
2241            //      OsStr encoding will always be compatible with WTF-8.
2242            let s = unsafe { OsStr::from_encoded_bytes_unchecked(s.as_bytes()) };
2243            return Ok(Cow::Borrowed(s));
2244        }
2245        let errors = self.fs_encode_errors().to_owned();
2246        let bytes = self
2247            .state
2248            .codec_registry
2249            .encode_text(s.to_owned(), "utf-8", Some(errors), self)?
2250            .to_vec();
2251        // XXX: this is sketchy on windows; it's not guaranteed that the
2252        //      OsStr encoding will always be compatible with WTF-8.
2253        let s = unsafe { OsString::from_encoded_bytes_unchecked(bytes) };
2254        Ok(Cow::Owned(s))
2255    }
2256}
2257
2258impl AsRef<Context> for VirtualMachine {
2259    fn as_ref(&self) -> &Context {
2260        &self.ctx
2261    }
2262}
2263
2264/// Resolve frozen module alias to its original name.
2265/// Returns the original module name if an alias exists, otherwise returns the input name.
2266pub fn resolve_frozen_alias(name: &str) -> &str {
2267    match name {
2268        "_frozen_importlib" => "importlib._bootstrap",
2269        "_frozen_importlib_external" => "importlib._bootstrap_external",
2270        "encodings_ascii" => "encodings.ascii",
2271        "encodings_utf_8" => "encodings.utf_8",
2272        "__hello_alias__" | "__phello_alias__" | "__phello_alias__.spam" => "__hello__",
2273        "__phello__.__init__" => "<__phello__",
2274        "__phello__.ham.__init__" => "<__phello__.ham",
2275        "__hello_only__" => "",
2276        _ => name,
2277    }
2278}
2279
2280#[test]
2281fn test_nested_frozen() {
2282    use rustpython_vm as vm;
2283
2284    vm::Interpreter::builder(Default::default())
2285        .add_frozen_modules(rustpython_vm::py_freeze!(
2286            dir = "../../../../extra_tests/snippets"
2287        ))
2288        .build()
2289        .enter(|vm| {
2290            let scope = vm.new_scope_with_builtins();
2291
2292            let source = "from dir_module.dir_module_inner import value2";
2293            let code_obj = vm
2294                .compile(source, vm::compiler::Mode::Exec, "<embedded>".to_owned())
2295                .map_err(|err| vm.new_syntax_error(&err, Some(source)))
2296                .unwrap();
2297
2298            if let Err(e) = vm.run_code_obj(code_obj, scope) {
2299                vm.print_exception(e);
2300                panic!();
2301            }
2302        })
2303}
2304
2305#[test]
2306fn frozen_origname_matches() {
2307    use rustpython_vm as vm;
2308
2309    vm::Interpreter::builder(Default::default())
2310        .build()
2311        .enter(|vm| {
2312            let check = |name, expected| {
2313                let module = import::import_frozen(vm, name).unwrap();
2314                let origname: PyStrRef = module
2315                    .get_attr("__origname__", vm)
2316                    .unwrap()
2317                    .try_into_value(vm)
2318                    .unwrap();
2319                assert_eq!(origname.as_wtf8(), expected);
2320            };
2321
2322            check("_frozen_importlib", "importlib._bootstrap");
2323            check(
2324                "_frozen_importlib_external",
2325                "importlib._bootstrap_external",
2326            );
2327        });
2328}