#[cfg(feature = "rustpython-compiler")]
mod compile;
mod context;
mod interpreter;
mod method;
#[cfg(feature = "rustpython-compiler")]
mod python_run;
mod setting;
pub mod thread;
mod vm_new;
mod vm_object;
mod vm_ops;
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
self, PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
PyStrRef, PyTypeRef, PyUtf8Str, PyUtf8StrInterned, PyWeak,
code::PyCode,
dict::{PyDictItems, PyDictKeys, PyDictValues},
pystr::AsPyStr,
tuple::PyTuple,
},
codecs::CodecsRegistry,
common::{hash::HashSecret, lock::PyMutex, rc::PyRc},
convert::ToPyObject,
exceptions::types::PyBaseException,
frame::{ExecutionResult, Frame, FrameRef},
frozen::FrozenModule,
function::{ArgMapping, FuncArgs, PySetterValue},
import,
protocol::PyIterIter,
scope::Scope,
signal, stdlib,
warn::WarningsState,
};
use alloc::{borrow::Cow, collections::BTreeMap};
#[cfg(all(unix, feature = "threading"))]
use core::sync::atomic::AtomicI64;
use core::{
cell::{Cell, OnceCell, RefCell},
ptr::NonNull,
sync::atomic::{AtomicBool, AtomicU64, Ordering},
};
use crossbeam_utils::atomic::AtomicCell;
#[cfg(unix)]
use nix::{
sys::signal::{SaFlags, SigAction, SigSet, Signal::SIGINT, kill, sigaction},
unistd::getpid,
};
use std::{
collections::{HashMap, HashSet},
ffi::{OsStr, OsString},
};
pub use context::Context;
pub use interpreter::{Interpreter, InterpreterBuilder};
pub(crate) use method::PyMethod;
pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings};
pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize;
pub struct VirtualMachine {
pub builtins: PyRef<PyModule>,
pub sys_module: PyRef<PyModule>,
pub ctx: PyRc<Context>,
pub frames: RefCell<Vec<FramePtr>>,
datastack: core::cell::UnsafeCell<crate::datastack::DataStack>,
pub wasm_id: Option<String>,
exceptions: RefCell<ExceptionStack>,
pub import_func: PyObjectRef,
pub(crate) importlib: PyObjectRef,
pub profile_func: RefCell<PyObjectRef>,
pub trace_func: RefCell<PyObjectRef>,
pub use_tracing: Cell<bool>,
pub recursion_limit: Cell<usize>,
pub(crate) signal_handlers: OnceCell<Box<RefCell<[Option<PyObjectRef>; signal::NSIG]>>>,
pub(crate) signal_rx: Option<signal::UserSignalReceiver>,
pub repr_guards: RefCell<HashSet<usize>>,
pub state: PyRc<PyGlobalState>,
pub initialized: bool,
recursion_depth: Cell<usize>,
#[cfg_attr(miri, allow(dead_code))]
c_stack_soft_limit: Cell<usize>,
pub async_gen_firstiter: RefCell<Option<PyObjectRef>>,
pub async_gen_finalizer: RefCell<Option<PyObjectRef>>,
pub asyncio_running_loop: RefCell<Option<PyObjectRef>>,
pub asyncio_running_task: RefCell<Option<PyObjectRef>>,
pub(crate) callable_cache: CallableCache,
}
#[derive(Copy, Clone)]
pub struct FramePtr(NonNull<Py<Frame>>);
impl FramePtr {
pub unsafe fn as_ref(&self) -> &Py<Frame> {
unsafe { self.0.as_ref() }
}
}
unsafe impl Send for FramePtr {}
#[derive(Debug, Default)]
struct ExceptionStack {
stack: Vec<Option<PyBaseExceptionRef>>,
}
#[cfg(all(unix, feature = "threading"))]
pub struct StopTheWorldState {
pub(crate) requested: AtomicBool,
world_stopped: AtomicBool,
requester: AtomicU64,
notify_mutex: std::sync::Mutex<()>,
notify_cv: std::sync::Condvar,
thread_countdown: AtomicI64,
stats_stop_calls: AtomicU64,
stats_last_wait_ns: AtomicU64,
stats_total_wait_ns: AtomicU64,
stats_max_wait_ns: AtomicU64,
stats_poll_loops: AtomicU64,
stats_attached_seen: AtomicU64,
stats_forced_parks: AtomicU64,
stats_suspend_notifications: AtomicU64,
stats_attach_wait_yields: AtomicU64,
stats_suspend_wait_yields: AtomicU64,
}
#[cfg(all(unix, feature = "threading"))]
#[derive(Debug, Clone, Copy)]
pub struct StopTheWorldStats {
pub stop_calls: u64,
pub last_wait_ns: u64,
pub total_wait_ns: u64,
pub max_wait_ns: u64,
pub poll_loops: u64,
pub attached_seen: u64,
pub forced_parks: u64,
pub suspend_notifications: u64,
pub attach_wait_yields: u64,
pub suspend_wait_yields: u64,
pub world_stopped: bool,
}
#[cfg(all(unix, feature = "threading"))]
impl Default for StopTheWorldState {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(unix, feature = "threading"))]
impl StopTheWorldState {
pub const fn new() -> Self {
Self {
requested: AtomicBool::new(false),
world_stopped: AtomicBool::new(false),
requester: AtomicU64::new(0),
notify_mutex: std::sync::Mutex::new(()),
notify_cv: std::sync::Condvar::new(),
thread_countdown: AtomicI64::new(0),
stats_stop_calls: AtomicU64::new(0),
stats_last_wait_ns: AtomicU64::new(0),
stats_total_wait_ns: AtomicU64::new(0),
stats_max_wait_ns: AtomicU64::new(0),
stats_poll_loops: AtomicU64::new(0),
stats_attached_seen: AtomicU64::new(0),
stats_forced_parks: AtomicU64::new(0),
stats_suspend_notifications: AtomicU64::new(0),
stats_attach_wait_yields: AtomicU64::new(0),
stats_suspend_wait_yields: AtomicU64::new(0),
}
}
pub(crate) fn notify_suspended(&self) {
self.stats_suspend_notifications
.fetch_add(1, Ordering::Relaxed);
let _guard = self.notify_mutex.lock().unwrap();
self.decrement_thread_countdown(1);
self.notify_cv.notify_one();
}
#[inline]
fn init_thread_countdown(&self, vm: &VirtualMachine) -> i64 {
let requester = self.requester.load(Ordering::Relaxed);
let registry = vm.state.thread_frames.lock();
self.requested.store(true, Ordering::Release);
let count = registry
.keys()
.filter(|&&thread_id| thread_id != requester)
.count();
let count = (count.min(i64::MAX as usize)) as i64;
self.thread_countdown.store(count, Ordering::Release);
count
}
#[inline]
fn decrement_thread_countdown(&self, n: u64) {
if n == 0 {
return;
}
let n = (n.min(i64::MAX as u64)) as i64;
let prev = self.thread_countdown.fetch_sub(n, Ordering::AcqRel);
if prev <= n {
self.thread_countdown.store(0, Ordering::Release);
}
}
fn park_detached_threads(&self, vm: &VirtualMachine) -> bool {
use thread::{THREAD_ATTACHED, THREAD_DETACHED, THREAD_SUSPENDED};
let requester = self.requester.load(Ordering::Relaxed);
let registry = vm.state.thread_frames.lock();
let mut attached_seen = 0u64;
let mut forced_parks = 0u64;
for (&id, slot) in registry.iter() {
if id == requester {
continue;
}
let state = slot.state.load(Ordering::Relaxed);
if state == THREAD_DETACHED {
match slot.state.compare_exchange(
THREAD_DETACHED,
THREAD_SUSPENDED,
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => {
slot.stop_requested.store(false, Ordering::Release);
forced_parks = forced_parks.saturating_add(1);
}
Err(THREAD_ATTACHED) => {
slot.stop_requested.store(true, Ordering::Release);
attached_seen = attached_seen.saturating_add(1);
}
Err(THREAD_DETACHED) => {
}
Err(THREAD_SUSPENDED) => {
slot.stop_requested.store(false, Ordering::Release);
}
Err(other) => {
debug_assert!(
false,
"unexpected thread state in park_detached_threads: {other}"
);
}
}
} else if state == THREAD_ATTACHED {
slot.stop_requested.store(true, Ordering::Release);
attached_seen = attached_seen.saturating_add(1);
}
}
if attached_seen != 0 {
self.stats_attached_seen
.fetch_add(attached_seen, Ordering::Relaxed);
}
if forced_parks != 0 {
self.decrement_thread_countdown(forced_parks);
self.stats_forced_parks
.fetch_add(forced_parks, Ordering::Relaxed);
}
forced_parks != 0 && self.thread_countdown.load(Ordering::Acquire) == 0
}
pub fn stop_the_world(&self, vm: &VirtualMachine) {
let start = std::time::Instant::now();
let requester_ident = crate::stdlib::_thread::get_ident();
self.requester.store(requester_ident, Ordering::Relaxed);
self.stats_stop_calls.fetch_add(1, Ordering::Relaxed);
let initial_countdown = self.init_thread_countdown(vm);
stw_trace(format_args!("stop begin requester={requester_ident}"));
if initial_countdown == 0 {
self.world_stopped.store(true, Ordering::Release);
#[cfg(debug_assertions)]
self.debug_assert_all_non_requester_suspended(vm);
stw_trace(format_args!(
"stop end requester={requester_ident} wait_ns=0 polls=0"
));
return;
}
let mut polls = 0u64;
loop {
if self.park_detached_threads(vm) {
break;
}
polls = polls.saturating_add(1);
let guard = self.notify_mutex.lock().unwrap();
if self.thread_countdown.load(Ordering::Acquire) == 0 || self.park_detached_threads(vm)
{
drop(guard);
break;
}
let _ = self
.notify_cv
.wait_timeout(guard, core::time::Duration::from_millis(1));
}
if polls != 0 {
self.stats_poll_loops.fetch_add(polls, Ordering::Relaxed);
}
let wait_ns = start.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64;
self.stats_last_wait_ns.store(wait_ns, Ordering::Relaxed);
self.stats_total_wait_ns
.fetch_add(wait_ns, Ordering::Relaxed);
let mut prev_max = self.stats_max_wait_ns.load(Ordering::Relaxed);
while wait_ns > prev_max {
match self.stats_max_wait_ns.compare_exchange_weak(
prev_max,
wait_ns,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(observed) => prev_max = observed,
}
}
self.world_stopped.store(true, Ordering::Release);
#[cfg(debug_assertions)]
self.debug_assert_all_non_requester_suspended(vm);
stw_trace(format_args!(
"stop end requester={requester_ident} wait_ns={wait_ns} polls={polls}"
));
}
pub fn start_the_world(&self, vm: &VirtualMachine) {
use thread::{THREAD_DETACHED, THREAD_SUSPENDED};
let requester = self.requester.load(Ordering::Relaxed);
stw_trace(format_args!("start begin requester={requester}"));
let registry = vm.state.thread_frames.lock();
self.requested.store(false, Ordering::Release);
self.world_stopped.store(false, Ordering::Release);
for (&id, slot) in registry.iter() {
if id == requester {
continue;
}
slot.stop_requested.store(false, Ordering::Release);
let state = slot.state.load(Ordering::Relaxed);
debug_assert!(
state == THREAD_SUSPENDED,
"non-requester thread not suspended at start-the-world: id={id} state={state}"
);
if state == THREAD_SUSPENDED {
slot.state.store(THREAD_DETACHED, Ordering::Release);
slot.thread.unpark();
}
}
drop(registry);
self.thread_countdown.store(0, Ordering::Release);
self.requester.store(0, Ordering::Relaxed);
#[cfg(debug_assertions)]
self.debug_assert_all_non_requester_detached(vm);
stw_trace(format_args!("start end requester={requester}"));
}
pub fn reset_after_fork(&self) {
self.requested.store(false, Ordering::Relaxed);
self.world_stopped.store(false, Ordering::Relaxed);
self.requester.store(0, Ordering::Relaxed);
self.thread_countdown.store(0, Ordering::Relaxed);
stw_trace(format_args!("reset-after-fork"));
}
#[inline]
pub(crate) fn requester_ident(&self) -> u64 {
self.requester.load(Ordering::Relaxed)
}
#[inline]
pub(crate) fn notify_thread_gone(&self) {
let _guard = self.notify_mutex.lock().unwrap();
self.decrement_thread_countdown(1);
self.notify_cv.notify_one();
}
pub fn stats_snapshot(&self) -> StopTheWorldStats {
StopTheWorldStats {
stop_calls: self.stats_stop_calls.load(Ordering::Relaxed),
last_wait_ns: self.stats_last_wait_ns.load(Ordering::Relaxed),
total_wait_ns: self.stats_total_wait_ns.load(Ordering::Relaxed),
max_wait_ns: self.stats_max_wait_ns.load(Ordering::Relaxed),
poll_loops: self.stats_poll_loops.load(Ordering::Relaxed),
attached_seen: self.stats_attached_seen.load(Ordering::Relaxed),
forced_parks: self.stats_forced_parks.load(Ordering::Relaxed),
suspend_notifications: self.stats_suspend_notifications.load(Ordering::Relaxed),
attach_wait_yields: self.stats_attach_wait_yields.load(Ordering::Relaxed),
suspend_wait_yields: self.stats_suspend_wait_yields.load(Ordering::Relaxed),
world_stopped: self.world_stopped.load(Ordering::Relaxed),
}
}
pub fn reset_stats(&self) {
self.stats_stop_calls.store(0, Ordering::Relaxed);
self.stats_last_wait_ns.store(0, Ordering::Relaxed);
self.stats_total_wait_ns.store(0, Ordering::Relaxed);
self.stats_max_wait_ns.store(0, Ordering::Relaxed);
self.stats_poll_loops.store(0, Ordering::Relaxed);
self.stats_attached_seen.store(0, Ordering::Relaxed);
self.stats_forced_parks.store(0, Ordering::Relaxed);
self.stats_suspend_notifications.store(0, Ordering::Relaxed);
self.stats_attach_wait_yields.store(0, Ordering::Relaxed);
self.stats_suspend_wait_yields.store(0, Ordering::Relaxed);
}
#[inline]
pub(crate) fn add_attach_wait_yields(&self, n: u64) {
if n != 0 {
self.stats_attach_wait_yields
.fetch_add(n, Ordering::Relaxed);
}
}
#[inline]
pub(crate) fn add_suspend_wait_yields(&self, n: u64) {
if n != 0 {
self.stats_suspend_wait_yields
.fetch_add(n, Ordering::Relaxed);
}
}
#[cfg(debug_assertions)]
fn debug_assert_all_non_requester_suspended(&self, vm: &VirtualMachine) {
use thread::THREAD_SUSPENDED;
let requester = self.requester.load(Ordering::Relaxed);
let registry = vm.state.thread_frames.lock();
for (&id, slot) in registry.iter() {
if id == requester {
continue;
}
let state = slot.state.load(Ordering::Relaxed);
debug_assert!(
state == THREAD_SUSPENDED,
"non-requester thread not suspended during stop-the-world: id={id} state={state}"
);
}
}
#[cfg(debug_assertions)]
fn debug_assert_all_non_requester_detached(&self, vm: &VirtualMachine) {
use thread::THREAD_SUSPENDED;
let requester = self.requester.load(Ordering::Relaxed);
let registry = vm.state.thread_frames.lock();
for (&id, slot) in registry.iter() {
if id == requester {
continue;
}
let state = slot.state.load(Ordering::Relaxed);
debug_assert!(
state != THREAD_SUSPENDED,
"non-requester thread still suspended after start-the-world: id={id} state={state}"
);
}
}
}
#[cfg(all(unix, feature = "threading"))]
pub(super) fn stw_trace_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| std::env::var_os("RUSTPYTHON_STW_TRACE").is_some())
}
#[cfg(all(unix, feature = "threading"))]
pub(super) fn stw_trace(msg: core::fmt::Arguments<'_>) {
if stw_trace_enabled() {
use core::fmt::Write as _;
struct FixedBuf {
buf: [u8; 512],
len: usize,
}
impl core::fmt::Write for FixedBuf {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.len >= self.buf.len() {
return Ok(());
}
let remain = self.buf.len() - self.len;
let src = s.as_bytes();
let n = src.len().min(remain);
self.buf[self.len..self.len + n].copy_from_slice(&src[..n]);
self.len += n;
Ok(())
}
}
let mut out = FixedBuf {
buf: [0u8; 512],
len: 0,
};
let _ = writeln!(
&mut out,
"[rp-stw tid={}] {}",
crate::stdlib::_thread::get_ident(),
msg
);
unsafe {
let _ = libc::write(libc::STDERR_FILENO, out.buf.as_ptr().cast(), out.len);
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct CallableCache {
pub len: Option<PyObjectRef>,
pub isinstance: Option<PyObjectRef>,
pub list_append: Option<PyObjectRef>,
pub builtin_all: Option<PyObjectRef>,
pub builtin_any: Option<PyObjectRef>,
}
pub struct PyGlobalState {
pub config: PyConfig,
pub module_defs: BTreeMap<&'static str, &'static builtins::PyModuleDef>,
pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>,
pub stacksize: AtomicCell<usize>,
pub thread_count: AtomicCell<usize>,
pub hash_secret: HashSecret,
pub atexit_funcs: PyMutex<Vec<Box<(PyObjectRef, FuncArgs)>>>,
pub codec_registry: CodecsRegistry,
pub finalizing: AtomicBool,
pub warnings: WarningsState,
pub override_frozen_modules: AtomicCell<isize>,
pub before_forkers: PyMutex<Vec<PyObjectRef>>,
pub after_forkers_child: PyMutex<Vec<PyObjectRef>>,
pub after_forkers_parent: PyMutex<Vec<PyObjectRef>>,
pub int_max_str_digits: AtomicCell<usize>,
pub switch_interval: AtomicCell<f64>,
pub global_trace_func: PyMutex<Option<PyObjectRef>>,
pub global_profile_func: PyMutex<Option<PyObjectRef>>,
#[cfg(feature = "threading")]
pub main_thread_ident: AtomicCell<u64>,
#[cfg(feature = "threading")]
pub thread_frames: parking_lot::Mutex<HashMap<u64, stdlib::_thread::CurrentFrameSlot>>,
#[cfg(feature = "threading")]
pub thread_handles: parking_lot::Mutex<Vec<stdlib::_thread::HandleEntry>>,
#[cfg(feature = "threading")]
pub shutdown_handles: parking_lot::Mutex<Vec<stdlib::_thread::ShutdownEntry>>,
pub monitoring: PyMutex<stdlib::sys::monitoring::MonitoringState>,
pub monitoring_events: stdlib::sys::monitoring::MonitoringEventsMask,
pub instrumentation_version: AtomicU64,
#[cfg(all(unix, feature = "threading"))]
pub stop_the_world: StopTheWorldState,
}
pub fn process_hash_secret_seed() -> u32 {
use std::sync::OnceLock;
static SEED: OnceLock<u32> = OnceLock::new();
*SEED.get_or_init(|| u32::from_ne_bytes(rustpython_common::rand::os_random()))
}
impl VirtualMachine {
fn init_callable_cache(&mut self) -> PyResult<()> {
self.callable_cache.len = Some(self.builtins.get_attr("len", self)?);
self.callable_cache.isinstance = Some(self.builtins.get_attr("isinstance", self)?);
let list_append = self
.ctx
.types
.list_type
.get_attr(self.ctx.intern_str("append"))
.ok_or_else(|| self.new_runtime_error("failed to cache list.append".to_owned()))?;
self.callable_cache.list_append = Some(list_append);
self.callable_cache.builtin_all = Some(self.builtins.get_attr("all", self)?);
self.callable_cache.builtin_any = Some(self.builtins.get_attr("any", self)?);
Ok(())
}
#[inline(always)]
pub(crate) fn datastack_push(&self, size: usize) -> *mut u8 {
unsafe { (*self.datastack.get()).push(size) }
}
#[inline(always)]
pub(crate) fn datastack_has_space(&self, size: usize) -> bool {
unsafe { (*self.datastack.get()).has_space(size) }
}
#[inline(always)]
pub(crate) unsafe fn datastack_pop(&self, base: *mut u8) {
unsafe { (*self.datastack.get()).pop(base) }
}
#[inline]
pub fn allow_threads<R>(&self, f: impl FnOnce() -> R) -> R {
thread::allow_threads(self, f)
}
#[allow(dead_code)]
pub(crate) fn is_main_thread(&self) -> bool {
#[cfg(feature = "threading")]
{
crate::stdlib::_thread::get_ident() == self.state.main_thread_ident.load()
}
#[cfg(not(feature = "threading"))]
{
true
}
}
pub(crate) fn new(ctx: PyRc<Context>, state: PyRc<PyGlobalState>) -> Self {
flame_guard!("new VirtualMachine");
let new_module = |def| {
PyRef::new_ref(
PyModule::from_def(def),
ctx.types.module_type.to_owned(),
Some(ctx.new_dict()),
)
};
let builtins = new_module(stdlib::builtins::module_def(&ctx));
let sys_module = new_module(stdlib::sys::module_def(&ctx));
let import_func = ctx.none();
let importlib = ctx.none();
let profile_func = RefCell::new(ctx.none());
let trace_func = RefCell::new(ctx.none());
let signal_handlers = OnceCell::from(signal::new_signal_handlers());
let vm = Self {
builtins,
sys_module,
ctx,
frames: RefCell::new(vec![]),
datastack: core::cell::UnsafeCell::new(crate::datastack::DataStack::new()),
wasm_id: None,
exceptions: RefCell::default(),
import_func,
importlib,
profile_func,
trace_func,
use_tracing: Cell::new(false),
recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }),
signal_handlers,
signal_rx: None,
repr_guards: RefCell::default(),
state,
initialized: false,
recursion_depth: Cell::new(0),
c_stack_soft_limit: Cell::new(Self::calculate_c_stack_soft_limit()),
async_gen_firstiter: RefCell::new(None),
async_gen_finalizer: RefCell::new(None),
asyncio_running_loop: RefCell::new(None),
asyncio_running_task: RefCell::new(None),
callable_cache: CallableCache::default(),
};
if vm.state.hash_secret.hash_str("")
!= vm
.ctx
.interned_str("")
.expect("empty str must be interned")
.hash(&vm)
{
panic!("Interpreters in same process must share the hash seed");
}
vm.builtins.init_dict(
vm.ctx.intern_str("builtins"),
Some(vm.ctx.intern_str(stdlib::builtins::DOC.unwrap()).to_owned()),
&vm,
);
vm.sys_module.init_dict(
vm.ctx.intern_str("sys"),
Some(vm.ctx.intern_str(stdlib::sys::DOC.unwrap()).to_owned()),
&vm,
);
vm
}
#[cfg(feature = "encodings")]
fn import_encodings(&mut self) -> PyResult<()> {
self.import("encodings", 0).map_err(|import_err| {
let rustpythonpath_env = std::env::var("RUSTPYTHONPATH").ok();
let pythonpath_env = std::env::var("PYTHONPATH").ok();
let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some();
let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| {
Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref()
});
let guide_message = if cfg!(feature = "freeze-stdlib") {
"`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()`."
} else if !env_set {
"Neither RUSTPYTHONPATH nor PYTHONPATH is set. Try setting one of them to the stdlib directory."
} else if path_contains_env {
"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."
} else {
"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."
};
let mut msg = format!(
"RustPython could not import the encodings module. It usually means something went wrong. Please carefully read the following messages and follow the steps.\n\
\n\
{guide_message}");
if !cfg!(feature = "freeze-stdlib") {
msg += "\n\
If you don't have access to a consistent external environment (e.g. targeting wasm, embedding \
rustpython in another application), try enabling the `freeze-stdlib` feature.\n\
If this is intended and you want to exclude the encodings module from your interpreter, please remove the `encodings` feature from `rustpython-vm` crate.";
}
let err = self.new_runtime_error(msg);
err.set___cause__(Some(import_err));
err
})?;
Ok(())
}
fn import_ascii_utf8_encodings(&mut self) -> PyResult<()> {
self.import("codecs", 0)?;
let (ascii_module_name, utf8_module_name) = if cfg!(feature = "freeze-stdlib") {
("encodings.ascii", "encodings.utf_8")
} else {
("encodings_ascii", "encodings_utf_8")
};
self.import(ascii_module_name, 0)?;
let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
let ascii_module = sys_modules.get_item(ascii_module_name, self)?;
let getregentry = ascii_module.get_attr("getregentry", self)?;
let codec_info = getregentry.call((), self)?;
self.state
.codec_registry
.register_manual("ascii", codec_info.try_into_value(self)?)?;
self.import(utf8_module_name, 0)?;
let utf8_module = sys_modules.get_item(utf8_module_name, self)?;
let getregentry = utf8_module.get_attr("getregentry", self)?;
let codec_info = getregentry.call((), self)?;
let utf8_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
self.state
.codec_registry
.register_manual("utf-8", utf8_codec.clone())?;
self.state
.codec_registry
.register_manual("utf8", utf8_codec)?;
if cfg!(feature = "freeze-stdlib") {
self.import("encodings.latin_1", 0)?;
let latin1_module = sys_modules.get_item("encodings.latin_1", self)?;
let getregentry = latin1_module.get_attr("getregentry", self)?;
let codec_info = getregentry.call((), self)?;
let latin1_codec: crate::codecs::PyCodec = codec_info.try_into_value(self)?;
for name in ["latin-1", "latin_1", "latin1", "iso8859-1", "iso8859_1"] {
self.state
.codec_registry
.register_manual(name, latin1_codec.clone())?;
}
}
Ok(())
}
fn initialize(&mut self) {
flame_guard!("init VirtualMachine");
if self.initialized {
panic!("Double Initialize Error");
}
#[cfg(feature = "threading")]
stdlib::_thread::init_main_thread_ident(self);
stdlib::builtins::init_module(self, &self.builtins);
let callable_cache_init = self.init_callable_cache();
self.expect_pyresult(callable_cache_init, "failed to initialize callable cache");
stdlib::sys::init_module(self, &self.sys_module, &self.builtins);
self.expect_pyresult(
stdlib::sys::set_bootstrap_stderr(self),
"failed to initialize bootstrap stderr",
);
let mut essential_init = || -> PyResult {
import::import_builtin(self, "_typing")?;
#[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
import::import_builtin(self, "_signal")?;
#[cfg(any(feature = "parser", feature = "compiler"))]
import::import_builtin(self, "_ast")?;
#[cfg(not(feature = "threading"))]
import::import_frozen(self, "_thread")?;
let importlib = import::init_importlib_base(self)?;
self.import_ascii_utf8_encodings()?;
{
let io = import::import_builtin(self, "_io")?;
#[cfg(all(feature = "host_env", feature = "stdio"))]
let make_stdio = |name: &str, fd: i32, write: bool| -> PyResult<PyObjectRef> {
let buffered_stdio = self.state.config.settings.buffered_stdio;
let unbuffered = write && !buffered_stdio;
let buf = crate::stdlib::_io::open(
self.ctx.new_int(fd).into(),
Some(if write { "wb" } else { "rb" }),
crate::stdlib::_io::OpenArgs {
buffering: if unbuffered { 0 } else { -1 },
closefd: false,
..Default::default()
},
self,
)?;
let raw = if unbuffered {
buf.clone()
} else {
buf.get_attr("raw", self)?
};
raw.set_attr("name", self.ctx.new_str(format!("<{name}>")), self)?;
let isatty = self.call_method(&raw, "isatty", ())?.is_true(self)?;
let write_through = !buffered_stdio;
let line_buffering = buffered_stdio && (isatty || fd == 2);
let newline = if cfg!(windows) { None } else { Some("\n") };
let encoding = self.state.config.settings.stdio_encoding.as_deref();
let errors = if fd == 2 {
Some("backslashreplace")
} else {
self.state.config.settings.stdio_errors.as_deref().or(
if self.state.config.settings.stdio_encoding.is_some() {
Some("strict")
} else {
Some("surrogateescape")
},
)
};
let stdio = self.call_method(
&io,
"TextIOWrapper",
(
buf,
encoding,
errors,
newline,
line_buffering,
write_through,
),
)?;
let mode = if write { "w" } else { "r" };
stdio.set_attr("mode", self.ctx.new_str(mode), self)?;
Ok::<_, self::PyBaseExceptionRef>(stdio)
};
#[cfg(all(not(feature = "host_env"), feature = "stdio"))]
let make_stdio = |name: &str, fd: i32, write: bool| {
let mode = if write { "w" } else { "r" };
let stdio = stdlib::sys::SandboxStdio {
fd,
name: format!("<{name}>"),
mode: mode.to_owned(),
}
.into_ref(&self.ctx);
Ok(stdio.into())
};
#[cfg(not(feature = "stdio"))]
let make_stdio = |_name: &str, _fd: i32, _write: bool| {
Ok(crate::builtins::PyNone.into_pyobject(self))
};
let set_stdio = |name, fd, write| {
let stdio: PyObjectRef = make_stdio(name, fd, write)?;
let dunder_name = self.ctx.intern_str(format!("__{name}__"));
self.sys_module.set_attr(
dunder_name, stdio.clone(),
self,
)?;
self.sys_module.set_attr(name, stdio, self)?;
Ok(())
};
set_stdio("stdin", 0, false)?;
set_stdio("stdout", 1, true)?;
set_stdio("stderr", 2, true)?;
let io_open = io.get_attr("open", self)?;
self.builtins.set_attr("open", io_open, self)?;
}
Ok(importlib)
};
let res = essential_init();
let importlib = self.expect_pyresult(res, "essential initialization failed");
#[cfg(feature = "host_env")]
if self.state.config.settings.allow_external_library
&& cfg!(feature = "rustpython-compiler")
&& let Err(e) = import::init_importlib_package(self, importlib)
{
eprintln!(
"importlib initialization failed. This is critical for many complicated packages."
);
self.print_exception(e);
}
#[cfg(not(feature = "host_env"))]
let _ = importlib;
let _expect_stdlib = cfg!(feature = "freeze-stdlib")
|| !self.state.config.paths.module_search_paths.is_empty();
#[cfg(feature = "encodings")]
if _expect_stdlib {
if let Err(e) = self.import_encodings() {
eprintln!(
"encodings initialization failed. Only utf-8 encoding will be supported."
);
self.print_exception(e);
}
} else {
eprintln!(
"feature `encodings` is enabled but `paths.module_search_paths` is empty. \
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\
Tip: You may also want to add `\"\"` to `settings.path_list` in order to enable importing from the current working directory."
);
}
self.initialized = true;
}
pub fn set_user_signal_channel(&mut self, signal_rx: signal::UserSignalReceiver) {
self.signal_rx = Some(signal_rx);
}
pub fn run_pyc_bytes(&self, pyc_bytes: &[u8], scope: Scope) -> PyResult<()> {
let code = PyCode::from_pyc(pyc_bytes, Some("<pyc_bytes>"), None, None, self)?;
self.with_simple_run("<source>", |_module_dict| {
self.run_code_obj(code, scope)?;
Ok(())
})
}
pub fn run_code_obj(&self, code: PyRef<PyCode>, scope: Scope) -> PyResult {
use crate::builtins::{PyFunction, PyModule};
let func = PyFunction::new(code.clone(), scope.globals.clone(), self)?;
let func_obj = func.into_ref(&self.ctx).into();
let builtins = match scope
.globals
.get_item_opt(identifier!(self, __builtins__), self)?
{
Some(b) => {
if let Some(module) = b.downcast_ref::<PyModule>() {
module.dict().into()
} else {
b
}
}
None => self.builtins.dict().into(),
};
let frame =
Frame::new(code, scope, builtins, &[], Some(func_obj), false, self).into_ref(&self.ctx);
self.run_frame(frame)
}
#[cold]
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
if self.state.finalizing.load(Ordering::Acquire) {
self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);
return;
}
let sys_module = self.import("sys", 0).unwrap();
let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
let exc_type = e.class().to_owned();
let exc_traceback = e.__traceback__().to_pyobject(self); let exc_value = e.into();
let args = stdlib::sys::UnraisableHookArgsData {
exc_type,
exc_value,
exc_traceback,
err_msg: self.new_pyobj(msg),
object,
};
if let Err(e) = unraisablehook.call((args,), self) {
println!("{}", e.as_object().repr(self).unwrap());
}
}
fn write_unraisable_to_stderr(
&self,
e: &PyBaseExceptionRef,
msg: Option<&str>,
object: &PyObjectRef,
) {
let stderr = crate::stdlib::sys::get_stderr(self).ok();
let write_to_stderr = |s: &str, stderr: &Option<PyObjectRef>, vm: &VirtualMachine| {
if let Some(stderr) = stderr {
let _ = vm.call_method(stderr, "write", (s.to_owned(),));
} else {
eprint!("{}", s);
}
};
let msg_str = if let Some(msg) = msg {
format!("{msg}: ")
} else {
"Exception ignored in: ".to_owned()
};
write_to_stderr(&msg_str, &stderr, self);
let repr_result = object.repr(self);
let repr_wtf8 = repr_result
.as_ref()
.map_or("<object repr failed>".as_ref(), |s| s.as_wtf8());
write_to_stderr(&format!("{repr_wtf8}\n"), &stderr, self);
let exc_type_name = e.class().name();
let msg = match e.as_object().str(self) {
Ok(exc_str) if !exc_str.as_wtf8().is_empty() => {
format!("{}: {}\n", exc_type_name, exc_str.as_wtf8())
}
_ => format!("{}\n", exc_type_name),
};
write_to_stderr(&msg, &stderr, self);
if let Some(ref stderr) = stderr {
let _ = self.call_method(stderr, "flush", ());
}
}
#[inline(always)]
pub fn run_frame(&self, frame: FrameRef) -> PyResult {
match self.with_frame(frame, |f| f.run(self))? {
ExecutionResult::Return(value) => Ok(value),
_ => panic!("Got unexpected result from function"),
}
}
fn with_simple_run(
&self,
path: &str,
run: impl FnOnce(&Py<PyDict>) -> PyResult<()>,
) -> PyResult<()> {
let sys_modules = self.sys_module.get_attr(identifier!(self, modules), self)?;
let main_module = sys_modules.get_item(identifier!(self, __main__), self)?;
let module_dict = main_module.dict().expect("main module must have __dict__");
let set_file_name = !module_dict.contains_key(identifier!(self, __file__), self);
if set_file_name {
module_dict.set_item(
identifier!(self, __file__),
self.ctx.new_str(path).into(),
self,
)?;
module_dict.set_item(identifier!(self, __cached__), self.ctx.none(), self)?;
}
let result = run(&module_dict);
self.flush_io();
if set_file_name {
let _ = module_dict.del_item(identifier!(self, __file__), self);
let _ = module_dict.del_item(identifier!(self, __cached__), self);
}
result
}
fn flush_io(&self) {
if let Ok(stdout) = self.sys_module.get_attr("stdout", self) {
let _ = self.call_method(&stdout, identifier!(self, flush).as_str(), ());
}
if let Ok(stderr) = self.sys_module.get_attr("stderr", self) {
let _ = self.call_method(&stderr, identifier!(self, flush).as_str(), ());
}
}
pub fn finalize_modules(&self) {
self.finalize_modules_delete_special();
let module_weakrefs = self.finalize_remove_modules();
self.finalize_clear_modules_dict();
crate::gc_state::gc_state().collect_force(2);
self.finalize_clear_module_dicts(&module_weakrefs);
crate::gc_state::gc_state().collect_force(2);
self.finalize_clear_sys_builtins_dict();
}
fn finalize_modules_delete_special(&self) {
let none = self.ctx.none();
let sys_dict = self.sys_module.dict();
for attr in &[
"path",
"argv",
"ps1",
"ps2",
"last_exc",
"last_type",
"last_value",
"last_traceback",
"path_importer_cache",
"meta_path",
"path_hooks",
] {
let _ = sys_dict.set_item(*attr, none.clone(), self);
}
for (std_name, dunder_name) in &[
("stdin", "__stdin__"),
("stdout", "__stdout__"),
("stderr", "__stderr__"),
] {
let restored = sys_dict
.get_item_opt(*dunder_name, self)
.ok()
.flatten()
.unwrap_or_else(|| none.clone());
let _ = sys_dict.set_item(*std_name, restored, self);
}
let _ = self.builtins.dict().set_item("_", none, self);
}
fn finalize_remove_modules(&self) -> Vec<(String, PyRef<PyWeak>)> {
let mut module_weakrefs = Vec::new();
let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self) else {
return module_weakrefs;
};
let Some(modules_dict) = modules.downcast_ref::<PyDict>() else {
return module_weakrefs;
};
let none = self.ctx.none();
let items: Vec<_> = modules_dict.into_iter().collect();
for (key, value) in items {
let name = key
.downcast_ref::<PyUtf8Str>()
.map(|s| s.as_str().to_owned())
.unwrap_or_default();
if value.downcast_ref::<PyModule>().is_some()
&& let Ok(weak) = value.downgrade(None, self)
{
module_weakrefs.push((name, weak));
}
let _ = modules_dict.set_item(&*key, none.clone(), self);
}
module_weakrefs
}
fn finalize_clear_modules_dict(&self) {
if let Ok(modules) = self.sys_module.get_attr(identifier!(self, modules), self)
&& let Some(modules_dict) = modules.downcast_ref::<PyDict>()
{
modules_dict.clear();
}
}
fn finalize_clear_module_dicts(&self, module_weakrefs: &[(String, PyRef<PyWeak>)]) {
let builtins_dict = self.builtins.dict();
let sys_dict = self.sys_module.dict();
for (_name, weakref) in module_weakrefs.iter().rev() {
let Some(module_obj) = weakref.upgrade() else {
continue;
};
let Some(module) = module_obj.downcast_ref::<PyModule>() else {
continue;
};
let dict = module.dict();
if dict.is(&builtins_dict) || dict.is(&sys_dict) {
continue;
}
Self::module_clear_dict(&dict, self);
}
}
pub(crate) fn module_clear_dict(dict: &Py<PyDict>, vm: &VirtualMachine) {
let none = vm.ctx.none();
for (key, value) in dict.into_iter().collect::<Vec<_>>() {
if vm.is_none(&value) {
continue;
}
if let Some(key_str) = key.downcast_ref::<PyStr>() {
let name = key_str.as_wtf8();
if name.starts_with("_") && name != "__builtins__" {
let _ = dict.set_item(key_str, none.clone(), vm);
}
}
}
for (key, value) in dict.into_iter().collect::<Vec<_>>() {
if vm.is_none(&value) {
continue;
}
if let Some(key_str) = key.downcast_ref::<PyStr>()
&& key_str.as_bytes() != b"__builtins__"
{
let _ = dict.set_item(key_str.as_wtf8(), none.clone(), vm);
}
}
}
fn finalize_clear_sys_builtins_dict(&self) {
Self::module_clear_dict(&self.sys_module.dict(), self);
Self::module_clear_dict(&self.builtins.dict(), self);
}
pub fn current_recursion_depth(&self) -> usize {
self.recursion_depth.get()
}
#[cfg_attr(miri, allow(dead_code))]
const STACK_MARGIN_BYTES: usize = 2048 * core::mem::size_of::<usize>();
#[cfg(all(not(miri), windows))]
fn get_stack_bounds() -> (usize, usize) {
use windows_sys::Win32::System::Threading::{
GetCurrentThreadStackLimits, SetThreadStackGuarantee,
};
let mut low: usize = 0;
let mut high: usize = 0;
unsafe {
GetCurrentThreadStackLimits(&mut low as *mut usize, &mut high as *mut usize);
let mut guarantee: u32 = 0;
SetThreadStackGuarantee(&mut guarantee);
low += guarantee as usize;
}
(low, high)
}
#[cfg(all(not(miri), not(windows)))]
fn get_stack_bounds() -> (usize, usize) {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use libc::{
pthread_attr_destroy, pthread_attr_getstack, pthread_attr_t, pthread_getattr_np,
pthread_self,
};
let mut attr: pthread_attr_t = unsafe { core::mem::zeroed() };
unsafe {
if pthread_getattr_np(pthread_self(), &mut attr) == 0 {
let mut stack_addr: *mut libc::c_void = core::ptr::null_mut();
let mut stack_size: libc::size_t = 0;
if pthread_attr_getstack(&attr, &mut stack_addr, &mut stack_size) == 0 {
pthread_attr_destroy(&mut attr);
let base = stack_addr as usize;
let top = base + stack_size;
return (base, top);
}
pthread_attr_destroy(&mut attr);
}
}
}
#[cfg(target_os = "macos")]
{
use libc::{pthread_get_stackaddr_np, pthread_get_stacksize_np, pthread_self};
unsafe {
let thread = pthread_self();
let stack_top = pthread_get_stackaddr_np(thread) as usize;
let stack_size = pthread_get_stacksize_np(thread);
let stack_base = stack_top - stack_size;
return (stack_base, stack_top);
}
}
#[allow(unreachable_code)]
{
let current_sp = psm::stack_pointer() as usize;
let estimated_size = 8 * 1024 * 1024;
let base = current_sp.saturating_sub(estimated_size);
let top = current_sp + 1024 * 1024; (base, top)
}
}
#[cfg(not(miri))]
fn calculate_c_stack_soft_limit() -> usize {
let (base, _top) = Self::get_stack_bounds();
base + Self::STACK_MARGIN_BYTES * 2
}
#[cfg(miri)]
fn calculate_c_stack_soft_limit() -> usize {
0
}
#[cfg(not(miri))]
#[inline(always)]
fn check_c_stack_overflow(&self) -> bool {
let current_sp = psm::stack_pointer() as usize;
let soft_limit = self.c_stack_soft_limit.get();
current_sp < soft_limit
&& current_sp >= soft_limit.saturating_sub(Self::STACK_MARGIN_BYTES * 2)
}
#[cfg(miri)]
#[inline(always)]
fn check_c_stack_overflow(&self) -> bool {
false
}
pub fn with_recursion<R, F: FnOnce() -> PyResult<R>>(&self, _where: &str, f: F) -> PyResult<R> {
self.check_recursive_call(_where)?;
if self.check_c_stack_overflow() {
return Err(self.new_recursion_error(_where.to_string()));
}
self.recursion_depth.update(|d| d + 1);
scopeguard::defer! { self.recursion_depth.update(|d| d - 1) }
f()
}
pub fn with_frame<R, F: FnOnce(FrameRef) -> PyResult<R>>(
&self,
frame: FrameRef,
f: F,
) -> PyResult<R> {
self.with_frame_impl(frame, None, true, f)
}
pub fn with_frame_exc<R, F: FnOnce(FrameRef) -> PyResult<R>>(
&self,
frame: FrameRef,
exc: Option<PyBaseExceptionRef>,
f: F,
) -> PyResult<R> {
self.with_frame_impl(frame, exc, true, f)
}
pub(crate) fn with_frame_untraced<R, F: FnOnce(FrameRef) -> PyResult<R>>(
&self,
frame: FrameRef,
f: F,
) -> PyResult<R> {
self.with_frame_impl(frame, None, false, f)
}
fn with_frame_impl<R, F: FnOnce(FrameRef) -> PyResult<R>>(
&self,
frame: FrameRef,
exc: Option<PyBaseExceptionRef>,
traced: bool,
f: F,
) -> PyResult<R> {
self.with_recursion("", || {
let fp = FramePtr(NonNull::from(&*frame));
self.frames.borrow_mut().push(fp);
#[cfg(feature = "threading")]
crate::vm::thread::push_thread_frame(fp);
let old_frame = crate::vm::thread::set_current_frame((&**frame) as *const Frame);
frame.previous.store(
old_frame as *mut Frame,
core::sync::atomic::Ordering::Relaxed,
);
self.push_exception(exc);
let old_owner = frame.owner.swap(
crate::frame::FrameOwner::Thread as i8,
core::sync::atomic::Ordering::AcqRel,
);
scopeguard::defer! {
frame.owner.store(old_owner, core::sync::atomic::Ordering::Release);
self.pop_exception();
crate::vm::thread::set_current_frame(old_frame);
self.frames.borrow_mut().pop();
#[cfg(feature = "threading")]
crate::vm::thread::pop_thread_frame();
}
if traced {
self.dispatch_traced_frame(&frame, |frame| f(frame.to_owned()))
} else {
f(frame.to_owned())
}
})
}
pub fn resume_gen_frame<R, F: FnOnce(&Py<Frame>) -> PyResult<R>>(
&self,
frame: &FrameRef,
exc: Option<PyBaseExceptionRef>,
f: F,
) -> PyResult<R> {
self.check_recursive_call("")?;
if self.check_c_stack_overflow() {
return Err(self.new_recursion_error(String::new()));
}
self.recursion_depth.update(|d| d + 1);
let fp = FramePtr(NonNull::from(&**frame));
self.frames.borrow_mut().push(fp);
#[cfg(feature = "threading")]
crate::vm::thread::push_thread_frame(fp);
let old_frame = crate::vm::thread::set_current_frame((&***frame) as *const Frame);
frame.previous.store(
old_frame as *mut Frame,
core::sync::atomic::Ordering::Relaxed,
);
self.exceptions.borrow_mut().stack.push(exc);
let old_owner = frame.owner.swap(
crate::frame::FrameOwner::Thread as i8,
core::sync::atomic::Ordering::AcqRel,
);
scopeguard::defer! {
frame.owner.store(old_owner, core::sync::atomic::Ordering::Release);
self.exceptions.borrow_mut().stack
.pop()
.expect("pop_exception() without nested exc stack");
crate::vm::thread::set_current_frame(old_frame);
self.frames.borrow_mut().pop();
#[cfg(feature = "threading")]
crate::vm::thread::pop_thread_frame();
self.recursion_depth.update(|d| d - 1);
}
self.dispatch_traced_frame(frame, |frame| f(frame))
}
fn dispatch_traced_frame<R, F: FnOnce(&Py<Frame>) -> PyResult<R>>(
&self,
frame: &Py<Frame>,
f: F,
) -> PyResult<R> {
use crate::protocol::TraceEvent;
let trace_result = self.trace_event(TraceEvent::Call, None)?;
if let Some(local_trace) = trace_result {
*frame.trace.lock() = local_trace;
}
let result = f(frame);
if self.use_tracing.get()
&& (!self.is_none(&frame.trace.lock()) || !self.is_none(&self.profile_func.borrow()))
{
let ret_result = self.trace_event(TraceEvent::Return, None);
ret_result?;
}
result
}
#[cfg(feature = "rustpython-codegen")]
pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
crate::compiler::CompileOpts {
optimize: self.state.config.settings.optimize,
debug_ranges: self.state.config.settings.code_debug_ranges,
}
}
fn check_recursive_call(&self, _where: &str) -> PyResult<()> {
if self.recursion_depth.get() >= self.recursion_limit.get() {
Err(self.new_recursion_error(format!("maximum recursion depth exceeded {_where}")))
} else {
Ok(())
}
}
pub fn current_frame(&self) -> Option<FrameRef> {
self.frames.borrow().last().map(|fp| {
unsafe { fp.as_ref() }.to_owned()
})
}
pub fn current_locals(&self) -> PyResult<ArgMapping> {
self.current_frame()
.expect("called current_locals but no frames on the stack")
.locals(self)
}
pub fn current_globals(&self) -> PyDictRef {
self.current_frame()
.expect("called current_globals but no frames on the stack")
.globals
.clone()
}
pub fn try_class(&self, module: &'static str, class: &'static str) -> PyResult<PyTypeRef> {
let class = self
.import(module, 0)?
.get_attr(class, self)?
.downcast()
.expect("not a class");
Ok(class)
}
pub fn class(&self, module: &'static str, class: &'static str) -> PyTypeRef {
let module = self
.import(module, 0)
.unwrap_or_else(|_| panic!("unable to import {module}"));
let class = module
.get_attr(class, self)
.unwrap_or_else(|_| panic!("module {module:?} has no class {class}"));
class.downcast().expect("not a class")
}
#[inline]
pub fn import<'a>(&self, module_name: impl AsPyStr<'a>, level: usize) -> PyResult {
let module_name = module_name.as_pystr(&self.ctx);
let from_list = self.ctx.empty_tuple_typed();
self.import_inner(module_name, from_list, level)
}
#[inline]
pub fn import_from<'a>(
&self,
module_name: impl AsPyStr<'a>,
from_list: &Py<PyTuple<PyStrRef>>,
level: usize,
) -> PyResult {
let module_name = module_name.as_pystr(&self.ctx);
self.import_inner(module_name, from_list, level)
}
fn import_inner(
&self,
module: &Py<PyStr>,
from_list: &Py<PyTuple<PyStrRef>>,
level: usize,
) -> PyResult {
let import_func = self
.builtins
.get_attr(identifier!(self, __import__), self)
.map_err(|_| self.new_import_error("__import__ not found", module.to_owned()))?;
let (locals, globals) = if let Some(frame) = self.current_frame() {
(
Some(frame.locals.clone_mapping(self)),
Some(frame.globals.clone()),
)
} else {
(None, None)
};
let from_list: PyObjectRef = from_list.to_owned().into();
import_func
.call((module.to_owned(), globals, locals, from_list, level), self)
.inspect_err(|exc| import::remove_importlib_frames(self, exc))
}
pub fn extract_elements_with<T, F>(&self, value: &PyObject, func: F) -> PyResult<Vec<T>>
where
F: Fn(PyObjectRef) -> PyResult<T>,
{
let cls = value.class();
let list_borrow;
let slice = if cls.is(self.ctx.types.tuple_type) {
value.downcast_ref::<PyTuple>().unwrap().as_slice()
} else if cls.is(self.ctx.types.list_type) {
list_borrow = value.downcast_ref::<PyList>().unwrap().borrow_vec();
&list_borrow
} else if cls.is(self.ctx.types.dict_type) {
let keys = value.downcast_ref::<PyDict>().unwrap().keys_vec();
return keys.into_iter().map(func).collect();
} else if cls.is(self.ctx.types.dict_keys_type) {
let keys = value.downcast_ref::<PyDictKeys>().unwrap().dict.keys_vec();
return keys.into_iter().map(func).collect();
} else if cls.is(self.ctx.types.dict_values_type) {
let values = value
.downcast_ref::<PyDictValues>()
.unwrap()
.dict
.values_vec();
return values.into_iter().map(func).collect();
} else if cls.is(self.ctx.types.dict_items_type) {
let items = value
.downcast_ref::<PyDictItems>()
.unwrap()
.dict
.items_vec();
return items
.into_iter()
.map(|(k, v)| func(self.ctx.new_tuple(vec![k, v]).into()))
.collect();
} else {
return self.map_py_iter(value, func);
};
slice.iter().map(|obj| func(obj.clone())).collect()
}
pub fn map_iterable_object<F, R>(&self, obj: &PyObject, mut f: F) -> PyResult<PyResult<Vec<R>>>
where
F: FnMut(PyObjectRef) -> PyResult<R>,
{
match_class!(match obj {
ref l @ PyList => {
let mut i: usize = 0;
let mut results = Vec::with_capacity(l.borrow_vec().len());
loop {
let elem = {
let elements = &*l.borrow_vec();
if i >= elements.len() {
results.shrink_to_fit();
return Ok(Ok(results));
} else {
elements[i].clone()
}
};
match f(elem) {
Ok(result) => results.push(result),
Err(err) => return Ok(Err(err)),
}
i += 1;
}
}
ref t @ PyTuple => Ok(t.iter().cloned().map(f).collect()),
obj => {
Ok(self.map_py_iter(obj, f))
}
})
}
fn map_py_iter<F, R>(&self, value: &PyObject, mut f: F) -> PyResult<Vec<R>>
where
F: FnMut(PyObjectRef) -> PyResult<R>,
{
let iter = value.to_owned().get_iter(self)?;
let cap = match self.length_hint_opt(value.to_owned()) {
Err(e) if e.class().is(self.ctx.exceptions.runtime_error) => return Err(e),
Ok(Some(value)) => Some(value),
_ => None,
};
if let Some(cap) = cap
&& cap >= isize::MAX as usize
{
return Ok(Vec::new());
}
let mut results = PyIterIter::new(self, iter.as_ref(), cap)
.map(|element| f(element?))
.collect::<PyResult<Vec<_>>>()?;
results.shrink_to_fit();
Ok(results)
}
pub fn get_attribute_opt<'a>(
&self,
obj: PyObjectRef,
attr_name: impl AsPyStr<'a>,
) -> PyResult<Option<PyObjectRef>> {
let attr_name = attr_name.as_pystr(&self.ctx);
match obj.get_attr_inner(attr_name, self) {
Ok(attr) => Ok(Some(attr)),
Err(e) if e.fast_isinstance(self.ctx.exceptions.attribute_error) => Ok(None),
Err(e) => Err(e),
}
}
pub fn set_attribute_error_context(
&self,
exc: &Py<PyBaseException>,
obj: PyObjectRef,
name: PyStrRef,
) {
if exc.class().is(self.ctx.exceptions.attribute_error) {
let exc = exc.as_object();
let already_set = exc
.get_attr("name", self)
.ok()
.is_some_and(|v| !self.is_none(&v));
if already_set {
return;
}
exc.set_attr("name", name, self).unwrap();
exc.set_attr("obj", obj, self).unwrap();
}
}
pub fn get_method_or_type_error<F>(
&self,
obj: PyObjectRef,
method_name: &'static PyStrInterned,
err_msg: F,
) -> PyResult
where
F: FnOnce() -> String,
{
let method = obj
.class()
.get_attr(method_name)
.ok_or_else(|| self.new_type_error(err_msg()))?;
self.call_if_get_descriptor(&method, obj)
}
pub(crate) fn get_method(
&self,
obj: PyObjectRef,
method_name: &'static PyStrInterned,
) -> Option<PyResult> {
let method = obj.get_class_attr(method_name)?;
Some(self.call_if_get_descriptor(&method, obj))
}
pub(crate) fn get_str_method(&self, obj: PyObjectRef, method_name: &str) -> Option<PyResult> {
let method_name = self.ctx.interned_str(method_name)?;
self.get_method(obj, method_name)
}
#[inline]
pub(crate) fn eval_breaker_tripped(&self) -> bool {
#[cfg(feature = "threading")]
if self.state.finalizing.load(Ordering::Relaxed) && !self.is_main_thread() {
return true;
}
#[cfg(all(unix, feature = "threading"))]
if thread::stop_requested_for_current_thread() {
return true;
}
#[cfg(not(target_arch = "wasm32"))]
if crate::signal::is_triggered() {
return true;
}
false
}
#[inline]
pub fn check_signals(&self) -> PyResult<()> {
#[cfg(feature = "threading")]
if self.state.finalizing.load(Ordering::Acquire) && !self.is_main_thread() {
return Err(self.new_exception(self.ctx.exceptions.system_exit.to_owned(), vec![]));
}
#[cfg(all(unix, feature = "threading"))]
thread::suspend_if_needed(&self.state.stop_the_world);
#[cfg(not(target_arch = "wasm32"))]
{
crate::signal::check_signals(self)
}
#[cfg(target_arch = "wasm32")]
{
Ok(())
}
}
pub(crate) fn push_exception(&self, exc: Option<PyBaseExceptionRef>) {
self.exceptions.borrow_mut().stack.push(exc);
#[cfg(feature = "threading")]
thread::update_thread_exception(self.topmost_exception());
}
pub(crate) fn pop_exception(&self) -> Option<PyBaseExceptionRef> {
let exc = self
.exceptions
.borrow_mut()
.stack
.pop()
.expect("pop_exception() without nested exc stack");
#[cfg(feature = "threading")]
thread::update_thread_exception(self.topmost_exception());
exc
}
pub(crate) fn current_exception(&self) -> Option<PyBaseExceptionRef> {
self.exceptions.borrow().stack.last().cloned().flatten()
}
pub(crate) fn set_exception(&self, exc: Option<PyBaseExceptionRef>) {
let mut excs = self.exceptions.borrow_mut();
debug_assert!(
!excs.stack.is_empty(),
"set_exception called with empty exception stack"
);
if let Some(top) = excs.stack.last_mut() {
let prev = core::mem::replace(top, exc);
drop(excs);
drop(prev);
} else {
excs.stack.push(exc);
drop(excs);
}
#[cfg(feature = "threading")]
thread::update_thread_exception(self.topmost_exception());
}
pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) {
if let Some(context_exc) = self.topmost_exception()
&& !context_exc.is(exception)
{
let mut o = context_exc.clone();
let mut slow_o = context_exc.clone();
let mut slow_update_toggle = false;
while let Some(context) = o.__context__() {
if context.is(exception) {
o.set___context__(None);
break;
}
o = context;
if o.is(&slow_o) {
break;
}
if slow_update_toggle && let Some(slow_context) = slow_o.__context__() {
slow_o = slow_context;
}
slow_update_toggle = !slow_update_toggle;
}
exception.set___context__(Some(context_exc))
}
}
pub(crate) fn topmost_exception(&self) -> Option<PyBaseExceptionRef> {
let excs = self.exceptions.borrow();
excs.stack.iter().rev().find_map(|e| e.clone())
}
pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u32 {
if exc.fast_isinstance(self.ctx.exceptions.system_exit) {
let args = exc.args();
let msg = match args.as_slice() {
[] => return 0,
[arg] => match_class!(match arg {
ref i @ PyInt => {
use num_traits::cast::ToPrimitive;
let code = i
.as_bigint()
.to_u32()
.or_else(|| i.as_bigint().to_i32().map(|v| v as u32))
.unwrap_or(-1i32 as u32);
return code;
}
arg => {
if self.is_none(arg) {
return 0;
} else {
arg.str(self).ok()
}
}
}),
_ => args.as_object().repr(self).ok(),
};
if let Some(msg) = msg {
if let Ok(stderr) = stdlib::sys::get_stderr(self) {
let _ = self.call_method(&stderr, "write", (msg,));
let _ = self.call_method(&stderr, "write", ("\n",));
}
}
1
} else if exc.fast_isinstance(self.ctx.exceptions.keyboard_interrupt) {
#[allow(clippy::if_same_then_else)]
{
self.print_exception(exc);
#[cfg(unix)]
{
let action = SigAction::new(
nix::sys::signal::SigHandler::SigDfl,
SaFlags::SA_ONSTACK,
SigSet::empty(),
);
let result = unsafe { sigaction(SIGINT, &action) };
if result.is_ok() {
self.flush_std();
kill(getpid(), SIGINT).expect("Expect to be killed.");
}
(libc::SIGINT as u32) + 128
}
#[cfg(windows)]
{
0xC000013A
}
#[cfg(not(any(unix, windows)))]
{
1
}
}
} else {
self.print_exception(exc);
1
}
}
#[doc(hidden)]
pub fn __module_set_attr(
&self,
module: &Py<PyModule>,
attr_name: &'static PyStrInterned,
attr_value: impl Into<PyObjectRef>,
) -> PyResult<()> {
let val = attr_value.into();
module
.as_object()
.generic_setattr(attr_name, PySetterValue::Assign(val), self)
}
pub fn insert_sys_path(&self, obj: PyObjectRef) -> PyResult<()> {
let sys_path = self.sys_module.get_attr("path", self).unwrap();
self.call_method(&sys_path, "insert", (0, obj))?;
Ok(())
}
pub fn run_module(&self, module: &str) -> PyResult<()> {
let runpy = self.import("runpy", 0)?;
let run_module_as_main = runpy.get_attr("_run_module_as_main", self)?;
run_module_as_main.call((module,), self)?;
Ok(())
}
pub fn fs_encoding(&self) -> &'static PyStrInterned {
identifier!(self, utf_8)
}
pub fn fs_encode_errors(&self) -> &'static PyUtf8StrInterned {
if cfg!(windows) {
identifier_utf8!(self, surrogatepass)
} else {
identifier_utf8!(self, surrogateescape)
}
}
pub fn fsdecode(&self, s: impl Into<OsString>) -> PyStrRef {
match s.into().into_string() {
Ok(s) => self.ctx.new_str(s),
Err(s) => {
let bytes = self.ctx.new_bytes(s.into_encoded_bytes());
let errors = self.fs_encode_errors().to_owned();
let res = self.state.codec_registry.decode_text(
bytes.into(),
"utf-8",
Some(errors),
self,
);
self.expect_pyresult(res, "fsdecode should be lossless and never fail")
}
}
}
pub fn fsencode<'a>(&self, s: &'a Py<PyStr>) -> PyResult<Cow<'a, OsStr>> {
if cfg!(windows) || s.is_utf8() {
let s = unsafe { OsStr::from_encoded_bytes_unchecked(s.as_bytes()) };
return Ok(Cow::Borrowed(s));
}
let errors = self.fs_encode_errors().to_owned();
let bytes = self
.state
.codec_registry
.encode_text(s.to_owned(), "utf-8", Some(errors), self)?
.to_vec();
let s = unsafe { OsString::from_encoded_bytes_unchecked(bytes) };
Ok(Cow::Owned(s))
}
}
impl AsRef<Context> for VirtualMachine {
fn as_ref(&self) -> &Context {
&self.ctx
}
}
pub fn resolve_frozen_alias(name: &str) -> &str {
match name {
"_frozen_importlib" => "importlib._bootstrap",
"_frozen_importlib_external" => "importlib._bootstrap_external",
"encodings_ascii" => "encodings.ascii",
"encodings_utf_8" => "encodings.utf_8",
"__hello_alias__" | "__phello_alias__" | "__phello_alias__.spam" => "__hello__",
"__phello__.__init__" => "<__phello__",
"__phello__.ham.__init__" => "<__phello__.ham",
"__hello_only__" => "",
_ => name,
}
}
#[test]
fn test_nested_frozen() {
use rustpython_vm as vm;
vm::Interpreter::builder(Default::default())
.add_frozen_modules(rustpython_vm::py_freeze!(
dir = "../../../../extra_tests/snippets"
))
.build()
.enter(|vm| {
let scope = vm.new_scope_with_builtins();
let source = "from dir_module.dir_module_inner import value2";
let code_obj = vm
.compile(source, vm::compiler::Mode::Exec, "<embedded>".to_owned())
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
.unwrap();
if let Err(e) = vm.run_code_obj(code_obj, scope) {
vm.print_exception(e);
panic!();
}
})
}
#[test]
fn frozen_origname_matches() {
use rustpython_vm as vm;
vm::Interpreter::builder(Default::default())
.build()
.enter(|vm| {
let check = |name, expected| {
let module = import::import_frozen(vm, name).unwrap();
let origname: PyStrRef = module
.get_attr("__origname__", vm)
.unwrap()
.try_into_value(vm)
.unwrap();
assert_eq!(origname.as_wtf8(), expected);
};
check("_frozen_importlib", "importlib._bootstrap");
check(
"_frozen_importlib_external",
"importlib._bootstrap_external",
);
});
}