Skip to main content

rustpython_vm/stdlib/
sys.rs

1pub(crate) mod monitoring;
2
3use crate::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject};
4
5#[cfg(all(not(feature = "host_env"), feature = "stdio"))]
6pub(crate) use sys::SandboxStdio;
7pub use sys::{COPYRIGHT, PLATFORM};
8pub(crate) use sys::{DOC, MAXSIZE, RUST_MULTIARCH, UnraisableHookArgsData, module_def, multiarch};
9
10#[pymodule(name = "_jit")]
11mod sys_jit {
12    // Return True if the current Python executable supports JIT compilation,
13    // and False otherwise.
14    #[pyfunction]
15    const fn is_available() -> bool {
16        false // RustPython has no JIT
17    }
18
19    // Return True if JIT compilation is enabled for the current Python process,
20    // and False otherwise.
21    #[pyfunction]
22    const fn is_enabled() -> bool {
23        false // RustPython has no JIT
24    }
25
26    // Return True if the topmost Python frame is currently executing JIT code,
27    // and False otherwise.
28    #[pyfunction]
29    const fn is_active() -> bool {
30        false // RustPython has no JIT
31    }
32}
33
34#[pymodule]
35pub mod sys {
36    use crate::{
37        AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
38        builtins::{
39            PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTuple,
40            PyTupleRef, PyTypeRef, PyUtf8StrRef,
41        },
42        common::{
43            ascii,
44            hash::{PyHash, PyUHash},
45        },
46        convert::ToPyObject,
47        frame::FrameObjectRef,
48        function::{FuncArgs, KwArgs, OptionalArg, PosArgs},
49        stdlib::{_warnings::warn, builtins},
50        types::PyStructSequence,
51        version,
52        vm::{Settings, VirtualMachine},
53    };
54    use core::ffi::CStr;
55    use core::sync::atomic::Ordering;
56    use num_traits::ToPrimitive;
57    use std::{
58        env,
59        io::{IsTerminal, Read, Write},
60    };
61
62    // Rust target triple (e.g., "x86_64-unknown-linux-gnu")
63    pub(crate) const RUST_MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");
64
65    /// Convert Rust target triple to CPython-style multiarch
66    /// e.g., "x86_64-unknown-linux-gnu" -> "x86_64-linux-gnu"
67    pub(crate) fn multiarch() -> String {
68        RUST_MULTIARCH.replace("-unknown", "")
69    }
70
71    #[pymodule(name = "monitoring", with(super::monitoring::sys_monitoring))]
72    pub(super) mod monitoring {}
73
74    #[pyclass(no_attr, name = "_BootstrapStderr")]
75    #[derive(Debug, PyPayload)]
76    pub(super) struct BootstrapStderr;
77
78    #[pyclass(with(Py))]
79    impl BootstrapStderr {}
80
81    #[pyclass]
82    impl Py<BootstrapStderr> {
83        #[pymethod]
84        fn write(&self, s: PyStrRef) -> usize {
85            let bytes = s.as_bytes();
86            let _ = std::io::stderr().write_all(bytes);
87            bytes.len()
88        }
89
90        #[pymethod]
91        fn flush(&self) {
92            let _ = std::io::stderr().flush();
93        }
94    }
95
96    // Lightweight stdio wrapper for sandbox mode (no host_env).
97    // Directly uses Rust's std::io for stdin/stdout/stderr without FileIO.
98    #[pyclass(no_attr, name = "_SandboxStdio")]
99    #[derive(Debug, PyPayload)]
100    pub struct SandboxStdio {
101        pub fd: i32,
102        pub name: String,
103        pub mode: String,
104    }
105
106    #[pyclass(with(Py))]
107    impl SandboxStdio {}
108
109    #[pyclass]
110    impl Py<SandboxStdio> {
111        #[pymethod]
112        fn write(&self, s: PyStrRef, vm: &VirtualMachine) -> PyResult<usize> {
113            if self.fd == 0 {
114                return Err(vm.new_os_error("not writable"));
115            }
116            let bytes = s.as_bytes();
117            if self.fd == 2 {
118                std::io::stderr()
119                    .write_all(bytes)
120                    .map_err(|e| vm.new_os_error(e.to_string()))?;
121            } else {
122                std::io::stdout()
123                    .write_all(bytes)
124                    .map_err(|e| vm.new_os_error(e.to_string()))?;
125            }
126            Ok(bytes.len())
127        }
128
129        #[pymethod]
130        fn readline(&self, size: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult<String> {
131            if self.fd != 0 {
132                return Err(vm.new_os_error("not readable"));
133            }
134            let size = size.unwrap_or(-1);
135            if size == 0 {
136                return Ok(String::new());
137            }
138            let mut line = String::new();
139            std::io::stdin()
140                .read_line(&mut line)
141                .map_err(|e| vm.new_os_error(e.to_string()))?;
142            if size > 0 {
143                line.truncate(size as usize);
144            }
145            Ok(line)
146        }
147
148        #[pymethod]
149        fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
150            match self.fd {
151                1 => {
152                    std::io::stdout()
153                        .flush()
154                        .map_err(|e| vm.new_os_error(e.to_string()))?;
155                }
156                2 => {
157                    std::io::stderr()
158                        .flush()
159                        .map_err(|e| vm.new_os_error(e.to_string()))?;
160                }
161                _ => {}
162            }
163            Ok(())
164        }
165
166        #[pymethod]
167        fn fileno(&self) -> i32 {
168            self.fd
169        }
170
171        #[pymethod]
172        fn isatty(&self) -> bool {
173            match self.fd {
174                0 => std::io::stdin().is_terminal(),
175                1 => std::io::stdout().is_terminal(),
176                2 => std::io::stderr().is_terminal(),
177                _ => false,
178            }
179        }
180
181        #[pymethod]
182        fn readable(&self) -> bool {
183            self.fd == 0
184        }
185
186        #[pymethod]
187        fn writable(&self) -> bool {
188            self.fd == 1 || self.fd == 2
189        }
190
191        #[pygetset]
192        fn closed(&self) -> bool {
193            false
194        }
195
196        #[pygetset]
197        fn encoding(&self) -> String {
198            "utf-8".to_owned()
199        }
200
201        #[pygetset]
202        fn errors(&self) -> String {
203            if self.fd == 2 {
204                "backslashreplace"
205            } else {
206                "strict"
207            }
208            .to_owned()
209        }
210
211        #[pygetset(name = "name")]
212        fn name_prop(&self) -> String {
213            self.name.clone()
214        }
215
216        #[pygetset(name = "mode")]
217        fn mode_prop(&self) -> String {
218            self.mode.clone()
219        }
220    }
221
222    #[pyattr(name = "_rustpython_debugbuild")]
223    const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions);
224
225    #[cfg(not(windows))]
226    #[pyattr(name = "abiflags")]
227    const ABIFLAGS_ATTR: &str = "t"; // 't' for free-threaded (no GIL)
228    // Internal constant used for sysconfigdata_name
229    pub const ABIFLAGS: &str = "t";
230    #[pyattr(name = "api_version")]
231    const API_VERSION: u32 = 0x0; // what C api?
232    #[pyattr(name = "copyright")]
233    pub const COPYRIGHT: &CStr = c"Copyright (c) 2019 RustPython Team";
234    #[pyattr(name = "float_repr_style")]
235    const FLOAT_REPR_STYLE: &str = "short";
236    #[pyattr(name = "_framework")]
237    const FRAMEWORK: &str = "";
238    #[pyattr(name = "hexversion")]
239    const HEXVERSION: usize = version::VERSION_HEX;
240    #[pyattr(name = "maxsize")]
241    pub(crate) const MAXSIZE: isize = isize::MAX;
242    #[pyattr(name = "maxunicode")]
243    const MAXUNICODE: u32 = core::char::MAX as u32;
244
245    #[pyattr(name = "platform")]
246    pub const PLATFORM: &CStr = cfg_select! {
247        target_os = "linux" => c"linux",
248        target_os = "android" => c"android",
249        target_os = "macos" => c"darwin",
250        target_os = "ios" => c"ios",
251        windows => c"win32",
252        target_os = "wasi" => c"wasi",
253        _ => c"unknown"
254    };
255
256    #[pyattr(name = "ps1")]
257    const PS1: &str = ">>> ";
258    #[pyattr(name = "ps2")]
259    const PS2: &str = "... ";
260
261    #[cfg(windows)]
262    #[pyattr(name = "_vpath")]
263    const VPATH: Option<&'static str> = None; // TODO: actual VPATH value
264
265    #[cfg(windows)]
266    #[pyattr(name = "dllhandle")]
267    const DLLHANDLE: usize = 0;
268
269    #[pyattr]
270    fn prefix(vm: &VirtualMachine) -> String {
271        vm.state.config.paths.prefix.clone()
272    }
273    #[pyattr]
274    fn base_prefix(vm: &VirtualMachine) -> String {
275        vm.state.config.paths.base_prefix.clone()
276    }
277    #[pyattr]
278    fn exec_prefix(vm: &VirtualMachine) -> String {
279        vm.state.config.paths.exec_prefix.clone()
280    }
281    #[pyattr]
282    fn base_exec_prefix(vm: &VirtualMachine) -> String {
283        vm.state.config.paths.base_exec_prefix.clone()
284    }
285    #[pyattr]
286    fn platlibdir(_vm: &VirtualMachine) -> &'static str {
287        option_env!("RUSTPYTHON_PLATLIBDIR").unwrap_or("lib")
288    }
289    #[pyattr]
290    fn _stdlib_dir(vm: &VirtualMachine) -> PyObjectRef {
291        vm.state.config.paths.stdlib_dir.clone().to_pyobject(vm)
292    }
293
294    // alphabetical order with segments of pyattr and others
295
296    #[pyattr]
297    fn argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
298        vm.state
299            .config
300            .settings
301            .argv
302            .iter()
303            .map(|arg| vm.ctx.new_str(arg.clone()).into())
304            .collect()
305    }
306
307    #[pyattr]
308    fn builtin_module_names(vm: &VirtualMachine) -> PyTupleRef {
309        let mut module_names: Vec<String> =
310            vm.state.module_defs.keys().map(|&s| s.to_owned()).collect();
311        module_names.push("sys".to_owned());
312        module_names.push("builtins".to_owned());
313
314        module_names.sort();
315        vm.ctx.new_tuple(
316            module_names
317                .into_iter()
318                .map(|n| vm.ctx.new_utf8_str(n).into())
319                .collect(),
320        )
321    }
322
323    // List from cpython/Python/stdlib_module_names.h
324    const STDLIB_MODULE_NAMES: &[&str] = &[
325        "__future__",
326        "_abc",
327        "_aix_support",
328        "_android_support",
329        "_apple_support",
330        "_ast",
331        "_asyncio",
332        "_bisect",
333        "_blake2",
334        "_bz2",
335        "_codecs",
336        "_codecs_cn",
337        "_codecs_hk",
338        "_codecs_iso2022",
339        "_codecs_jp",
340        "_codecs_kr",
341        "_codecs_tw",
342        "_collections",
343        "_collections_abc",
344        "_colorize",
345        "_compat_pickle",
346        "_compression",
347        "_contextvars",
348        "_csv",
349        "_ctypes",
350        "_curses",
351        "_curses_panel",
352        "_datetime",
353        "_dbm",
354        "_decimal",
355        "_elementtree",
356        "_frozen_importlib",
357        "_frozen_importlib_external",
358        "_functools",
359        "_gdbm",
360        "_hashlib",
361        "_heapq",
362        "_imp",
363        "_interpchannels",
364        "_interpqueues",
365        "_interpreters",
366        "_io",
367        "_ios_support",
368        "_json",
369        "_locale",
370        "_lsprof",
371        "_lzma",
372        "_markupbase",
373        "_md5",
374        "_multibytecodec",
375        "_multiprocessing",
376        "_opcode",
377        "_opcode_metadata",
378        "_operator",
379        "_osx_support",
380        "_overlapped",
381        "_pickle",
382        "_posixshmem",
383        "_posixsubprocess",
384        "_py_abc",
385        "_pydatetime",
386        "_pydecimal",
387        "_pyio",
388        "_pylong",
389        "_pyrepl",
390        "_queue",
391        "_random",
392        "_scproxy",
393        "_sha1",
394        "_sha2",
395        "_sha3",
396        "_signal",
397        "_sitebuiltins",
398        "_socket",
399        "_sqlite3",
400        "_sre",
401        "_ssl",
402        "_stat",
403        "_statistics",
404        "_string",
405        "_strptime",
406        "_struct",
407        "_suggestions",
408        "_symtable",
409        "_sysconfig",
410        "_testinternalcapi",
411        "_thread",
412        "_threading_local",
413        "_tkinter",
414        "_tokenize",
415        "_tracemalloc",
416        "_typing",
417        "_uuid",
418        "_warnings",
419        "_weakref",
420        "_weakrefset",
421        "_winapi",
422        "_wmi",
423        "_zoneinfo",
424        "abc",
425        "antigravity",
426        "argparse",
427        "array",
428        "ast",
429        "asyncio",
430        "atexit",
431        "base64",
432        "bdb",
433        "binascii",
434        "bisect",
435        "builtins",
436        "bz2",
437        "cProfile",
438        "calendar",
439        "cmath",
440        "cmd",
441        "code",
442        "codecs",
443        "codeop",
444        "collections",
445        "colorsys",
446        "compileall",
447        "concurrent",
448        "configparser",
449        "contextlib",
450        "contextvars",
451        "copy",
452        "copyreg",
453        "csv",
454        "ctypes",
455        "curses",
456        "dataclasses",
457        "datetime",
458        "dbm",
459        "decimal",
460        "difflib",
461        "dis",
462        "doctest",
463        "email",
464        "encodings",
465        "ensurepip",
466        "enum",
467        "errno",
468        "faulthandler",
469        "fcntl",
470        "filecmp",
471        "fileinput",
472        "fnmatch",
473        "fractions",
474        "ftplib",
475        "functools",
476        "gc",
477        "genericpath",
478        "getopt",
479        "getpass",
480        "gettext",
481        "glob",
482        "graphlib",
483        "grp",
484        "gzip",
485        "hashlib",
486        "heapq",
487        "hmac",
488        "html",
489        "http",
490        "idlelib",
491        "imaplib",
492        "importlib",
493        "inspect",
494        "io",
495        "ipaddress",
496        "itertools",
497        "json",
498        "keyword",
499        "linecache",
500        "locale",
501        "logging",
502        "lzma",
503        "mailbox",
504        "marshal",
505        "math",
506        "mimetypes",
507        "mmap",
508        "modulefinder",
509        "msvcrt",
510        "multiprocessing",
511        "netrc",
512        "nt",
513        "ntpath",
514        "nturl2path",
515        "numbers",
516        "opcode",
517        "operator",
518        "optparse",
519        "os",
520        "pathlib",
521        "pdb",
522        "pickle",
523        "pickletools",
524        "pkgutil",
525        "platform",
526        "plistlib",
527        "poplib",
528        "posix",
529        "posixpath",
530        "pprint",
531        "profile",
532        "pstats",
533        "pty",
534        "pwd",
535        "py_compile",
536        "pyclbr",
537        "pydoc",
538        "pydoc_data",
539        "pyexpat",
540        "queue",
541        "quopri",
542        "random",
543        "re",
544        "readline",
545        "reprlib",
546        "resource",
547        "rlcompleter",
548        "runpy",
549        "sched",
550        "secrets",
551        "select",
552        "selectors",
553        "shelve",
554        "shlex",
555        "shutil",
556        "signal",
557        "site",
558        "smtplib",
559        "socket",
560        "socketserver",
561        "sqlite3",
562        "sre_compile",
563        "sre_constants",
564        "sre_parse",
565        "ssl",
566        "stat",
567        "statistics",
568        "string",
569        "stringprep",
570        "struct",
571        "subprocess",
572        "symtable",
573        "sys",
574        "sysconfig",
575        "syslog",
576        "tabnanny",
577        "tarfile",
578        "tempfile",
579        "termios",
580        "textwrap",
581        "this",
582        "threading",
583        "time",
584        "timeit",
585        "tkinter",
586        "token",
587        "tokenize",
588        "tomllib",
589        "trace",
590        "traceback",
591        "tracemalloc",
592        "tty",
593        "turtle",
594        "turtledemo",
595        "types",
596        "typing",
597        "unicodedata",
598        "unittest",
599        "urllib",
600        "uuid",
601        "venv",
602        "warnings",
603        "wave",
604        "weakref",
605        "webbrowser",
606        "winreg",
607        "winsound",
608        "wsgiref",
609        "xml",
610        "xmlrpc",
611        "zipapp",
612        "zipfile",
613        "zipimport",
614        "zlib",
615        "zoneinfo",
616    ];
617
618    #[pyattr(once)]
619    fn stdlib_module_names(vm: &VirtualMachine) -> PyObjectRef {
620        let names = STDLIB_MODULE_NAMES
621            .iter()
622            .map(|&n| vm.ctx.new_str(n).into());
623        PyFrozenSet::from_iter(vm, names)
624            .expect("Creating stdlib_module_names frozen set must succeed")
625            .to_pyobject(vm)
626    }
627
628    #[pyattr]
629    fn byteorder(vm: &VirtualMachine) -> PyStrRef {
630        // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian
631        vm.ctx
632            .intern_str(if cfg!(target_endian = "little") {
633                "little"
634            } else if cfg!(target_endian = "big") {
635                "big"
636            } else {
637                "unknown"
638            })
639            .to_owned()
640    }
641
642    #[pyattr]
643    fn _base_executable(vm: &VirtualMachine) -> String {
644        vm.state.config.paths.base_executable.clone()
645    }
646
647    #[pyattr]
648    fn dont_write_bytecode(vm: &VirtualMachine) -> bool {
649        !vm.state.config.settings.write_bytecode
650    }
651
652    #[pyattr]
653    fn executable(vm: &VirtualMachine) -> String {
654        vm.state.config.paths.executable.clone()
655    }
656
657    #[pyattr]
658    fn _git(vm: &VirtualMachine) -> PyTupleRef {
659        vm.new_tuple((
660            ascii!("RustPython"),
661            version::GIT_IDENTIFIER,
662            version::GIT_REVISION,
663        ))
664    }
665
666    #[pyattr]
667    fn implementation(vm: &VirtualMachine) -> PyRef<PyNamespace> {
668        const NAME: &str = "rustpython";
669
670        // cache tag uses 'cpython' because our compiler is cpython compatible
671        let cache_tag = format!("cpython-{}{}", version::MAJOR, version::MINOR);
672        let ctx = &vm.ctx;
673        py_namespace!(vm, {
674            "name" => ctx.new_str(NAME),
675            "cache_tag" => ctx.new_str(cache_tag),
676            "_multiarch" => ctx.new_str(multiarch()),
677            "version" => PyVersionInfo::from_data(VersionInfoData::IMPLEMENTATION, vm),
678            "hexversion" => ctx.new_int(version::VERSION_HEX_IMPL),
679            "supports_isolated_interpreters" =>
680                ctx.new_bool(crate::vm::runtime::SUPPORTS_ISOLATED_INTERPRETERS),
681        })
682    }
683
684    #[pyattr]
685    const fn meta_path(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
686        Vec::new()
687    }
688
689    #[pyattr]
690    fn orig_argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
691        env::args().map(|arg| vm.ctx.new_str(arg).into()).collect()
692    }
693
694    #[pyattr]
695    fn path(vm: &VirtualMachine) -> Vec<PyObjectRef> {
696        vm.state
697            .config
698            .paths
699            .module_search_paths
700            .iter()
701            .map(|path| vm.ctx.new_str(path.clone()).into())
702            .collect()
703    }
704
705    #[pyattr]
706    const fn path_hooks(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
707        Vec::new()
708    }
709
710    #[pyattr]
711    fn path_importer_cache(vm: &VirtualMachine) -> PyDictRef {
712        vm.ctx.new_dict()
713    }
714
715    #[pyattr]
716    fn pycache_prefix(vm: &VirtualMachine) -> PyObjectRef {
717        vm.ctx.none()
718    }
719
720    #[pyattr(name = "version")]
721    const VERSION: &str = version::RUSTPYTHON_VERSION;
722
723    // Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility
724    #[cfg(windows)]
725    #[pyattr(name = "winver")]
726    const WINVER: &str = version::WINVER;
727
728    #[pyattr]
729    fn _xoptions(vm: &VirtualMachine) -> PyDictRef {
730        let ctx = &vm.ctx;
731        let xopts = ctx.new_dict();
732        for (key, value) in &vm.state.config.settings.xoptions {
733            let value = value.as_ref().map_or_else(
734                || ctx.new_bool(true).into(),
735                |s| ctx.new_str(s.clone()).into(),
736            );
737            xopts.set_item(&**key, value, vm).unwrap();
738        }
739        xopts
740    }
741
742    #[pyattr]
743    fn warnoptions(vm: &VirtualMachine) -> Vec<PyObjectRef> {
744        vm.state
745            .config
746            .settings
747            .warnoptions
748            .iter()
749            .map(|s| vm.ctx.new_str(s.clone()).into())
750            .collect()
751    }
752
753    #[cfg(feature = "rustpython-compiler")]
754    #[pyfunction]
755    fn _baserepl(vm: &VirtualMachine) -> PyResult<()> {
756        // read stdin to end
757        let stdin = std::io::stdin();
758        let mut handle = stdin.lock();
759        let mut source = String::new();
760        handle
761            .read_to_string(&mut source)
762            .map_err(|e| vm.new_os_error(format!("Error reading from stdin: {e}")))?;
763        vm.compile(&source, crate::compiler::Mode::Single, "<stdin>")
764            .map_err(|e| e.into_pyexception(vm, Some(&source)))?;
765        Ok(())
766    }
767
768    #[pyfunction]
769    const fn _is_gil_enabled() -> bool {
770        false // RustPython has no GIL (like free-threaded Python)
771    }
772
773    #[pyfunction]
774    const fn is_remote_debug_enabled() -> bool {
775        false // RustPython does not support remote debugging
776    }
777
778    #[derive(FromArgs)]
779    struct ExitArgs {
780        #[pyarg(positional, optional)]
781        status: Option<PyObjectRef>,
782    }
783
784    #[derive(FromArgs)]
785    struct GetFrameArgs {
786        #[pyarg(positional, default)]
787        depth: usize,
788    }
789
790    #[derive(FromArgs)]
791    struct GetFrameModuleNameArgs {
792        #[pyarg(any, default)]
793        depth: usize,
794    }
795
796    #[derive(FromArgs)]
797    struct SetMaxDigitsArgs {
798        #[pyarg(any)]
799        maxdigits: usize,
800    }
801
802    #[derive(FromArgs)]
803    struct SetDepthArgs {
804        #[pyarg(any)]
805        depth: i32,
806    }
807
808    #[derive(FromArgs)]
809    struct AuditHookArgs {
810        #[pyarg(any)]
811        hook: PyObjectRef,
812    }
813
814    #[pyfunction]
815    fn exit(args: ExitArgs, vm: &VirtualMachine) -> PyResult {
816        let status = args.status.unwrap_or_else(|| vm.ctx.none());
817        let args = if let Some(status_tuple) = status.downcast_ref::<PyTuple>() {
818            status_tuple.as_slice().to_vec()
819        } else {
820            vec![status]
821        };
822        Err(vm.new_system_exit(args.into()))
823    }
824
825    #[pyfunction]
826    fn call_tracing(func: PyObjectRef, args: PyTupleRef, vm: &VirtualMachine) -> PyResult {
827        // CPython temporarily enables tracing state around this call.
828        // RustPython does not currently model the full C-level tracing toggles,
829        // but call semantics (func(*args)) are matched.
830        func.call(PosArgs::new(args.as_slice().to_vec()), vm)
831    }
832
833    #[pyfunction]
834    fn exception(vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
835        vm.topmost_exception()
836    }
837
838    #[pyfunction(name = "__displayhook__")]
839    #[pyfunction]
840    fn displayhook(object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
841        // Save non-None values as "_"
842        if vm.is_none(&object) {
843            return Ok(());
844        }
845        // set to none to avoid recursion while printing
846        vm.builtins.set_attr("_", vm.ctx.none(), vm)?;
847        // TODO: catch encoding errors
848        let repr = object.repr(vm)?.into();
849        builtins::print(PosArgs::new(vec![repr]), Default::default(), vm)?;
850        vm.builtins.set_attr("_", object, vm)?;
851        Ok(())
852    }
853
854    #[pyfunction(name = "__excepthook__")]
855    #[pyfunction]
856    fn excepthook(
857        exctype: PyObjectRef,
858        value: PyObjectRef,
859        traceback: PyObjectRef,
860        vm: &VirtualMachine,
861    ) -> PyResult<()> {
862        let stderr = super::get_stderr(vm)?;
863        // Keep runtime SyntaxErrors on the normal traceback path.
864        let has_traceback = !vm.is_none(&traceback);
865        match vm.normalize_exception(exctype, value.clone(), traceback) {
866            Ok(exc) => {
867                let native_syntax_error_display = !has_traceback
868                    && exc.fast_isinstance(vm.ctx.exceptions.syntax_error)
869                    && exc
870                        .as_object()
871                        .get_attr("msg", vm)
872                        .ok()
873                        .and_then(|msg| msg.downcast::<PyStr>().ok())
874                        .is_some_and(|msg| msg.to_string_lossy() == "unexpected EOF while parsing")
875                    && exc
876                        .as_object()
877                        .get_attr("text", vm)
878                        .ok()
879                        .and_then(|text| text.downcast::<PyStr>().ok())
880                        .is_some_and(|text| text.to_string_lossy().trim_end() == "\\");
881                if native_syntax_error_display {
882                    return vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc);
883                }
884                // PyErr_Display: try traceback._print_exception_bltin first
885                if let Ok(tb_mod) = vm.import("traceback", 0)
886                    && let Ok(print_exc_builtin) = tb_mod.get_attr("_print_exception_bltin", vm)
887                    && print_exc_builtin
888                        .call((exc.as_object().to_owned(),), vm)
889                        .is_ok()
890                {
891                    return Ok(());
892                }
893                // Fallback to Rust-level exception printing
894                vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc)
895            }
896            Err(_) => {
897                let type_name = value.class().name();
898                let msg = format!(
899                    "TypeError: print_exception(): Exception expected for value, {type_name} found\n"
900                );
901                use crate::py_io::Write;
902                write!(&mut crate::py_io::PyWriter(stderr, vm), "{msg}")?;
903                Ok(())
904            }
905        }
906    }
907
908    #[pyfunction(name = "__breakpointhook__")]
909    #[pyfunction]
910    pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
911        #[cfg(feature = "host_env")]
912        let env_var = crate::host_env::os::var("PYTHONBREAKPOINT")
913            .and_then(|env_var| {
914                if env_var.is_empty() {
915                    Err(std::env::VarError::NotPresent)
916                } else {
917                    Ok(env_var)
918                }
919            })
920            .unwrap_or_else(|_| "pdb.set_trace".to_owned());
921        #[cfg(not(feature = "host_env"))]
922        let env_var = "pdb.set_trace".to_owned();
923
924        if env_var.eq("0") {
925            return Ok(vm.ctx.none());
926        };
927
928        let print_unimportable_module_warn = || {
929            warn(
930                vm.ctx.exceptions.runtime_warning,
931                format!("Ignoring unimportable $PYTHONBREAKPOINT: \"{env_var}\"",),
932                0,
933                vm,
934            )?;
935            Ok(vm.ctx.none())
936        };
937
938        let last = match env_var.rsplit_once('.') {
939            Some((_, last)) => last,
940            None if !env_var.is_empty() => env_var.as_str(),
941            _ => return print_unimportable_module_warn(),
942        };
943
944        let (module_path, attr_name) = if last == env_var {
945            ("builtins", env_var.as_str())
946        } else {
947            (&env_var[..(env_var.len() - last.len() - 1)], last)
948        };
949
950        let module = match vm.import(&vm.ctx.new_str(module_path), 0) {
951            Ok(module) => module,
952            Err(_) => {
953                return print_unimportable_module_warn();
954            }
955        };
956
957        match vm.get_attribute_opt(&module, &vm.ctx.new_str(attr_name)) {
958            Ok(Some(hook)) => hook.as_ref().call(args, vm),
959            _ => print_unimportable_module_warn(),
960        }
961    }
962
963    #[pyfunction]
964    fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
965        match vm.topmost_exception() {
966            Some(exception) => vm.split_exception(exception),
967            None => (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()),
968        }
969    }
970
971    #[pyattr]
972    fn flags(vm: &VirtualMachine) -> PyTupleRef {
973        PyFlags::from_data(FlagsData::from_settings(&vm.state.config.settings), vm)
974    }
975
976    #[pyattr]
977    fn float_info(vm: &VirtualMachine) -> PyTupleRef {
978        PyFloatInfo::from_data(FloatInfoData::INFO, vm)
979    }
980
981    #[pyfunction]
982    const fn getdefaultencoding() -> &'static str {
983        crate::codecs::DEFAULT_ENCODING
984    }
985
986    #[pyfunction]
987    fn getrefcount(object: PyObjectRef) -> usize {
988        object.strong_count()
989    }
990
991    #[pyfunction]
992    fn getrecursionlimit(vm: &VirtualMachine) -> usize {
993        vm.recursion_limit.get()
994    }
995
996    #[derive(FromArgs)]
997    struct GetsizeofArgs {
998        object: PyObjectRef,
999        #[pyarg(any, optional)]
1000        default: Option<PyObjectRef>,
1001    }
1002
1003    #[pyfunction]
1004    fn getsizeof(args: GetsizeofArgs, vm: &VirtualMachine) -> PyResult {
1005        let sizeof = || -> PyResult<usize> {
1006            let res = vm.call_special_method(&args.object, identifier!(vm, __sizeof__), ())?;
1007            let res = res.try_index(vm)?.try_to_primitive::<usize>(vm)?;
1008            Ok(res + core::mem::size_of::<PyObject>())
1009        };
1010        sizeof()
1011            .map(|x| vm.ctx.new_int(x).into())
1012            .or_else(|err| args.default.ok_or(err))
1013    }
1014
1015    #[pyfunction]
1016    fn getfilesystemencoding(vm: &VirtualMachine) -> PyStrRef {
1017        vm.fs_encoding().to_owned()
1018    }
1019
1020    #[pyfunction]
1021    fn getfilesystemencodeerrors(vm: &VirtualMachine) -> PyUtf8StrRef {
1022        vm.fs_encode_errors().to_owned()
1023    }
1024
1025    #[pyfunction]
1026    fn getprofile(vm: &VirtualMachine) -> PyObjectRef {
1027        #[cfg(feature = "threading")]
1028        if let Some(slot) = crate::vm::thread::current_thread_slot() {
1029            return slot.profile_func.lock().clone();
1030        }
1031        vm.profile_func.borrow().clone()
1032    }
1033
1034    #[pyfunction]
1035    fn _getframe(args: GetFrameArgs, vm: &VirtualMachine) -> PyResult<FrameObjectRef> {
1036        let depth = args.depth;
1037        let frame_ref = crate::frame::frame_at_offset(depth, vm)
1038            .ok_or_else(|| vm.new_value_error("call stack is not deep enough"))?;
1039        vm.audit("sys._getframe", || (frame_ref.to_owned(),))?;
1040
1041        Ok(frame_ref)
1042    }
1043
1044    #[pyfunction]
1045    fn _getframemodulename(
1046        args: GetFrameModuleNameArgs,
1047        vm: &VirtualMachine,
1048    ) -> PyResult<PyObjectRef> {
1049        let depth = args.depth;
1050        vm.audit("sys._getframemodulename", || (depth,))?;
1051
1052        // Get the frame at the specified depth
1053        let func_obj = match crate::frame::frame_at_offset(depth, vm) {
1054            Some(frame) => frame.iframe().func_obj().map(|o| o.to_owned()),
1055            None => return Ok(vm.ctx.none()),
1056        };
1057
1058        // If the frame has a function object, return its __module__ attribute
1059        Ok(if let Some(func_obj) = func_obj {
1060            func_obj
1061                .get_attr(identifier!(vm, __module__), vm)
1062                .unwrap_or_else(
1063                    // CPython clears the error and returns None
1064                    |_| vm.ctx.none(),
1065                )
1066        } else {
1067            vm.ctx.none()
1068        })
1069    }
1070
1071    #[cfg(feature = "threading")]
1072    #[pyfunction]
1073    fn _current_frames(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1074        use crate::AsObject;
1075        use crate::stdlib::_thread::get_all_current_frames;
1076
1077        let frames = get_all_current_frames(vm);
1078        let dict = vm.ctx.new_dict();
1079
1080        for (thread_id, frame) in frames {
1081            let key = vm.ctx.new_int(thread_id);
1082            dict.set_item(key.as_object(), frame.into(), vm)?;
1083        }
1084
1085        Ok(dict)
1086    }
1087
1088    #[cfg(feature = "threading")]
1089    #[pyfunction]
1090    fn _current_exceptions(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1091        use crate::AsObject;
1092        use crate::vm::thread::get_all_current_exceptions;
1093
1094        let dict = vm.ctx.new_dict();
1095        for (thread_id, exc) in get_all_current_exceptions(vm) {
1096            let key = vm.ctx.new_int(thread_id);
1097            let value = exc.map_or_else(|| vm.ctx.none(), |e| e.into());
1098            dict.set_item(key.as_object(), value, vm)?;
1099        }
1100
1101        Ok(dict)
1102    }
1103
1104    #[cfg(not(feature = "threading"))]
1105    #[pyfunction]
1106    fn _current_exceptions(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1107        let dict = vm.ctx.new_dict();
1108        let key = vm.ctx.new_int(0);
1109        dict.set_item(key.as_object(), vm.topmost_exception().to_pyobject(vm), vm)?;
1110        Ok(dict)
1111    }
1112
1113    // Stub for non-threading builds - returns empty dict
1114    #[cfg(not(feature = "threading"))]
1115    #[pyfunction]
1116    fn _current_frames(vm: &VirtualMachine) -> PyDictRef {
1117        vm.ctx.new_dict()
1118    }
1119
1120    #[pyfunction]
1121    fn gettrace(vm: &VirtualMachine) -> PyObjectRef {
1122        #[cfg(feature = "threading")]
1123        if let Some(slot) = crate::vm::thread::current_thread_slot() {
1124            return slot.trace_func.lock().clone();
1125        }
1126        vm.trace_func.borrow().clone()
1127    }
1128
1129    #[cfg(windows)]
1130    #[pyfunction]
1131    fn getwindowsversion(vm: &VirtualMachine) -> PyResult<crate::builtins::tuple::PyTupleRef> {
1132        let version = crate::host_env::windows::get_windows_version()
1133            .map_err(|e| vm.new_os_error(e.to_string()))?;
1134        let winver = WindowsVersionData {
1135            major: version.major,
1136            minor: version.minor,
1137            build: version.build,
1138            platform: version.platform,
1139            service_pack: version.service_pack,
1140            service_pack_major: version.service_pack_major,
1141            service_pack_minor: version.service_pack_minor,
1142            suite_mask: version.suite_mask,
1143            product_type: version.product_type,
1144            platform_version: (version.major, version.minor, version.build), // TODO Provide accurate version, like CPython impl
1145        };
1146        Ok(PyWindowsVersion::from_data(winver, vm))
1147    }
1148
1149    fn _unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) -> PyResult<()> {
1150        use super::PyStderr;
1151
1152        let stderr = PyStderr(vm);
1153        if !vm.is_none(&unraisable.object) {
1154            if !vm.is_none(&unraisable.err_msg) {
1155                write!(stderr, "{}: ", unraisable.err_msg.str(vm)?);
1156            } else {
1157                write!(stderr, "Exception ignored in: ");
1158            }
1159            // exception in del will be ignored but printed
1160            let repr = &unraisable.object.repr(vm);
1161            let str = match repr {
1162                Ok(v) => v.to_string(),
1163                Err(_) => format!(
1164                    "<object {} repr() failed>",
1165                    unraisable.object.class().name()
1166                ),
1167            };
1168            writeln!(stderr, "{str}");
1169        } else if !vm.is_none(&unraisable.err_msg) {
1170            writeln!(stderr, "{}:", unraisable.err_msg.str(vm)?);
1171        }
1172
1173        // Print traceback (using actual exc_traceback, not current stack)
1174        if !vm.is_none(&unraisable.exc_traceback) {
1175            let tb_module = vm.import("traceback", 0)?;
1176            let print_tb = tb_module.get_attr("print_tb", vm)?;
1177            let stderr_obj = super::get_stderr(vm)?;
1178            let kwargs: KwArgs = core::iter::once(("file".to_string(), stderr_obj)).collect();
1179            let _ = print_tb.call(
1180                FuncArgs::new(vec![unraisable.exc_traceback.clone()], kwargs),
1181                vm,
1182            );
1183        }
1184
1185        // Check exc_type
1186        if vm.is_none(unraisable.exc_type.as_object()) {
1187            return Ok(());
1188        }
1189        assert!(
1190            unraisable
1191                .exc_type
1192                .fast_issubclass(vm.ctx.exceptions.base_exception_type)
1193        );
1194
1195        // Print module name (if not builtins or __main__).
1196        // A failed lookup or a non-str `__module__` is discarded: print
1197        // `<unknown>` with no trailing `.`, then the qualname.
1198        let module_name = unraisable.exc_type.__module__(vm).ok();
1199        if let Some(module_str) = module_name
1200            .as_ref()
1201            .and_then(|name| name.downcast_ref::<PyStr>())
1202        {
1203            let module = module_str.as_wtf8();
1204            if module != "builtins" && module != "__main__" {
1205                write!(stderr, "{module}.");
1206            }
1207        } else {
1208            write!(stderr, "<unknown>");
1209        }
1210
1211        // Print qualname
1212        let qualname = unraisable.exc_type.__qualname__(vm);
1213        if let Ok(qualname_str) = qualname.downcast::<PyStr>() {
1214            write!(stderr, "{}", qualname_str.as_wtf8());
1215        } else {
1216            write!(stderr, "{}", unraisable.exc_type.name());
1217        }
1218
1219        // Print exception value
1220        if !vm.is_none(&unraisable.exc_value) {
1221            write!(stderr, ": ");
1222            if let Ok(str) = unraisable.exc_value.str(vm) {
1223                write!(stderr, "{}", str.as_wtf8());
1224            } else {
1225                write!(stderr, "<exception str() failed>");
1226            }
1227        }
1228        writeln!(stderr);
1229
1230        // Flush stderr
1231        if let Ok(stderr_obj) = super::get_stderr(vm)
1232            && let Ok(flush) = stderr_obj.get_attr("flush", vm)
1233        {
1234            let _ = flush.call((), vm);
1235        }
1236
1237        Ok(())
1238    }
1239
1240    #[pyattr]
1241    #[pyfunction(name = "__unraisablehook__")]
1242    fn unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) {
1243        if let Err(e) = _unraisablehook(unraisable, vm) {
1244            let stderr = super::PyStderr(vm);
1245            writeln!(
1246                stderr,
1247                "{}",
1248                e.as_object()
1249                    .repr(vm)
1250                    .unwrap_or_else(|_| vm.ctx.empty_str.to_owned())
1251            );
1252        }
1253    }
1254
1255    #[pyattr]
1256    fn hash_info(vm: &VirtualMachine) -> PyTupleRef {
1257        PyHashInfo::from_data(HashInfoData::INFO, vm)
1258    }
1259
1260    #[pyfunction]
1261    fn intern(string: PyRefExact<PyStr>, vm: &VirtualMachine) -> PyRef<PyStr> {
1262        vm.ctx.intern_str(string).to_owned()
1263    }
1264
1265    #[pyattr]
1266    fn int_info(vm: &VirtualMachine) -> PyTupleRef {
1267        PyIntInfo::from_data(IntInfoData::INFO, vm)
1268    }
1269
1270    // Private function for getting PyConfig.cpu_count
1271    #[pyfunction]
1272    fn _get_cpu_count_config(vm: &VirtualMachine) -> i32 {
1273        vm.state.config.settings.cpu_count.map_or(-1, |n| n.get())
1274    }
1275
1276    #[pyfunction]
1277    fn get_int_max_str_digits(vm: &VirtualMachine) -> usize {
1278        vm.state.int_max_str_digits.load()
1279    }
1280
1281    #[pyfunction]
1282    fn set_int_max_str_digits(
1283        SetMaxDigitsArgs { maxdigits }: SetMaxDigitsArgs,
1284        vm: &VirtualMachine,
1285    ) -> PyResult<()> {
1286        let threshold = IntInfoData::INFO.str_digits_check_threshold;
1287        if maxdigits == 0 || maxdigits >= threshold {
1288            vm.state.int_max_str_digits.store(maxdigits);
1289            Ok(())
1290        } else {
1291            let error = format!("maxdigits must be 0 or larger than {threshold}");
1292            Err(vm.new_value_error(error))
1293        }
1294    }
1295
1296    #[pyfunction]
1297    fn is_finalizing(vm: &VirtualMachine) -> bool {
1298        vm.state.finalizing.load(Ordering::Acquire)
1299    }
1300
1301    #[pyfunction]
1302    fn setprofile(function: PyObjectRef, vm: &VirtualMachine) {
1303        #[cfg(feature = "threading")]
1304        if let Some(slot) = crate::vm::thread::current_thread_slot() {
1305            *slot.profile_func.lock() = function.clone();
1306        }
1307        vm.profile_func.replace(function);
1308        update_use_tracing(vm);
1309    }
1310
1311    #[pyfunction]
1312    fn setrecursionlimit(limit: i32, vm: &VirtualMachine) -> PyResult<()> {
1313        let limit = limit.to_usize().filter(|&u| u >= 1).ok_or_else(|| {
1314            vm.new_value_error("recursion limit must be greater than or equal to one")
1315        })?;
1316        let recursion_depth = vm.current_recursion_depth();
1317
1318        if limit > recursion_depth {
1319            vm.recursion_limit.set(limit);
1320            Ok(())
1321        } else {
1322            Err(vm.new_recursion_error(format!(
1323                "cannot set the recursion limit to {limit} at the recursion depth {recursion_depth}: the limit is too low"
1324            )))
1325        }
1326    }
1327
1328    #[pyfunction]
1329    fn settrace(function: PyObjectRef, vm: &VirtualMachine) {
1330        #[cfg(feature = "threading")]
1331        if let Some(slot) = crate::vm::thread::current_thread_slot() {
1332            *slot.trace_func.lock() = function.clone();
1333        }
1334        vm.trace_func.replace(function);
1335        update_use_tracing(vm);
1336        // The rest of the current line already started before tracing was
1337        // enabled; sync prev_line so leftover opcodes on this line do not
1338        // emit a spurious 'line' event.
1339        if let Some(frame) = vm.current_frame() {
1340            frame.iframe().sync_prev_line_from_lasti();
1341        }
1342    }
1343
1344    #[pyfunction]
1345    fn _settraceallthreads(function: PyObjectRef, vm: &VirtualMachine) {
1346        let func = (!vm.is_none(&function)).then(|| function.clone());
1347        *vm.state.global_trace_func.lock() = func;
1348        #[cfg(feature = "threading")]
1349        {
1350            let registry = vm.state.thread_frames.lock();
1351            #[expect(
1352                clippy::iter_over_hash_type,
1353                reason = "every thread slot, order is irrelevant"
1354            )]
1355            for slot in registry.values() {
1356                *slot.trace_func.lock() = function.clone();
1357            }
1358        }
1359        vm.trace_func.replace(function);
1360        update_use_tracing(vm);
1361    }
1362
1363    #[pyfunction]
1364    fn _setprofileallthreads(function: PyObjectRef, vm: &VirtualMachine) {
1365        let func = (!vm.is_none(&function)).then(|| function.clone());
1366        *vm.state.global_profile_func.lock() = func;
1367        #[cfg(feature = "threading")]
1368        {
1369            let registry = vm.state.thread_frames.lock();
1370            #[expect(
1371                clippy::iter_over_hash_type,
1372                reason = "every thread slot, order is irrelevant"
1373            )]
1374            for slot in registry.values() {
1375                *slot.profile_func.lock() = function.clone();
1376            }
1377        }
1378        vm.profile_func.replace(function);
1379        update_use_tracing(vm);
1380    }
1381
1382    #[cfg(feature = "threading")]
1383    #[pyattr]
1384    fn thread_info(vm: &VirtualMachine) -> PyTupleRef {
1385        PyThreadInfo::from_data(ThreadInfoData::INFO, vm)
1386    }
1387
1388    #[pyattr]
1389    fn version_info(vm: &VirtualMachine) -> PyTupleRef {
1390        PyVersionInfo::from_data(VersionInfoData::VERSION, vm)
1391    }
1392
1393    fn update_use_tracing(vm: &VirtualMachine) {
1394        let trace_is_none = vm.is_none(&vm.trace_func.borrow());
1395        let profile_is_none = vm.is_none(&vm.profile_func.borrow());
1396        let tracing = !(trace_is_none && profile_is_none);
1397        vm.use_tracing.set(tracing);
1398    }
1399
1400    #[pyfunction]
1401    fn set_coroutine_origin_tracking_depth(
1402        SetDepthArgs { depth }: SetDepthArgs,
1403        vm: &VirtualMachine,
1404    ) -> PyResult<()> {
1405        if depth < 0 {
1406            return Err(vm.new_value_error("depth must be >= 0"));
1407        }
1408        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.set(depth as u32);
1409        Ok(())
1410    }
1411
1412    #[pyfunction]
1413    fn get_coroutine_origin_tracking_depth() -> i32 {
1414        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.get() as i32
1415    }
1416
1417    #[pyfunction]
1418    fn _clear_type_descriptors(r#type: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> {
1419        use crate::types::PyTypeFlags;
1420
1421        // Check if type is immutable
1422        if r#type.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1423            return Err(vm.new_type_error("argument is immutable"));
1424        }
1425
1426        // Remove __dict__ descriptor if present
1427        r#type.attributes.remove(identifier!(vm, __dict__));
1428
1429        // Remove __weakref__ descriptor if present
1430        r#type.attributes.remove(identifier!(vm, __weakref__));
1431
1432        // Update slots to notify subclasses and recalculate cached values
1433        r#type.update_slot::<true>(identifier!(vm, __dict__), &vm.ctx);
1434        r#type.update_slot::<true>(identifier!(vm, __weakref__), &vm.ctx);
1435
1436        Ok(())
1437    }
1438
1439    #[pyfunction]
1440    fn getswitchinterval(vm: &VirtualMachine) -> f64 {
1441        // Return the stored switch interval
1442        vm.state.switch_interval.load()
1443    }
1444
1445    // TODO: vm.state.switch_interval is currently not used anywhere in the VM
1446    #[pyfunction]
1447    fn setswitchinterval(interval: f64, vm: &VirtualMachine) -> PyResult<()> {
1448        // Validate the interval parameter like CPython does
1449        if interval <= 0.0 {
1450            return Err(vm.new_value_error("switch interval must be strictly positive"));
1451        }
1452
1453        // Store the switch interval value
1454        vm.state.switch_interval.store(interval);
1455        Ok(())
1456    }
1457
1458    #[derive(FromArgs)]
1459    struct SetAsyncgenHooksArgs {
1460        #[pyarg(any, optional)]
1461        firstiter: OptionalArg<Option<PyObjectRef>>,
1462        #[pyarg(any, optional)]
1463        finalizer: OptionalArg<Option<PyObjectRef>>,
1464    }
1465
1466    #[pyfunction]
1467    fn set_asyncgen_hooks(args: SetAsyncgenHooksArgs, vm: &VirtualMachine) -> PyResult<()> {
1468        if let Some(Some(finalizer)) = args.finalizer.as_option()
1469            && !finalizer.is_callable()
1470        {
1471            return Err(vm.new_type_error(format!(
1472                "callable finalizer expected, got {:.50}",
1473                finalizer.class().name()
1474            )));
1475        }
1476
1477        if let Some(Some(firstiter)) = args.firstiter.as_option()
1478            && !firstiter.is_callable()
1479        {
1480            return Err(vm.new_type_error(format!(
1481                "callable firstiter expected, got {:.50}",
1482                firstiter.class().name()
1483            )));
1484        }
1485
1486        if let Some(finalizer) = args.finalizer.into_option() {
1487            *vm.async_gen_finalizer.borrow_mut() = finalizer;
1488        }
1489        if let Some(firstiter) = args.firstiter.into_option() {
1490            *vm.async_gen_firstiter.borrow_mut() = firstiter;
1491        }
1492
1493        Ok(())
1494    }
1495
1496    #[pystruct_sequence_data]
1497    pub(super) struct AsyncgenHooksData {
1498        firstiter: PyObjectRef,
1499        finalizer: PyObjectRef,
1500    }
1501
1502    #[pyattr]
1503    #[pystruct_sequence(name = "asyncgen_hooks", data = "AsyncgenHooksData")]
1504    pub(super) struct PyAsyncgenHooks;
1505
1506    #[pyclass(with(PyStructSequence))]
1507    impl PyAsyncgenHooks {}
1508
1509    #[pyfunction]
1510    fn get_asyncgen_hooks(vm: &VirtualMachine) -> AsyncgenHooksData {
1511        AsyncgenHooksData {
1512            firstiter: vm.async_gen_firstiter.borrow().clone().to_pyobject(vm),
1513            finalizer: vm.async_gen_finalizer.borrow().clone().to_pyobject(vm),
1514        }
1515    }
1516
1517    /// sys.flags
1518    ///
1519    /// Flags provided through command line arguments or environment vars.
1520    #[derive(Debug)]
1521    #[pystruct_sequence_data]
1522    pub(super) struct FlagsData {
1523        /// -d
1524        debug: u8,
1525        /// -i
1526        inspect: u8,
1527        /// -i
1528        interactive: u8,
1529        /// -O or -OO
1530        optimize: u8,
1531        /// -B
1532        dont_write_bytecode: u8,
1533        /// -s
1534        no_user_site: u8,
1535        /// -S
1536        no_site: u8,
1537        /// -E
1538        ignore_environment: u8,
1539        /// -v
1540        verbose: u8,
1541        /// -b
1542        bytes_warning: u64,
1543        /// -q
1544        quiet: u8,
1545        /// -R
1546        hash_randomization: u8,
1547        /// -I
1548        isolated: u8,
1549        /// -X dev
1550        dev_mode: bool,
1551        /// -X utf8
1552        utf8_mode: u8,
1553        /// -X int_max_str_digits=number
1554        int_max_str_digits: i64,
1555        /// -P, `PYTHONSAFEPATH`
1556        safe_path: bool,
1557        /// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
1558        warn_default_encoding: u8,
1559    }
1560
1561    impl FlagsData {
1562        const fn from_settings(settings: &Settings) -> Self {
1563            Self {
1564                debug: settings.debug,
1565                inspect: settings.inspect as u8,
1566                interactive: settings.interactive as u8,
1567                optimize: settings.optimize,
1568                dont_write_bytecode: (!settings.write_bytecode) as u8,
1569                no_user_site: (!settings.user_site_directory) as u8,
1570                no_site: (!settings.import_site) as u8,
1571                ignore_environment: settings.ignore_environment as u8,
1572                verbose: settings.verbose,
1573                bytes_warning: settings.bytes_warning,
1574                quiet: settings.quiet as u8,
1575                hash_randomization: settings.hash_seed.is_none() as u8,
1576                isolated: settings.isolated as u8,
1577                dev_mode: settings.dev_mode,
1578                utf8_mode: if settings.utf8_mode < 0 {
1579                    1
1580                } else {
1581                    settings.utf8_mode as u8
1582                },
1583                int_max_str_digits: settings.int_max_str_digits,
1584                safe_path: settings.safe_path,
1585                warn_default_encoding: settings.warn_default_encoding as u8,
1586            }
1587        }
1588    }
1589
1590    #[pystruct_sequence(name = "flags", data = "FlagsData", no_attr)]
1591    pub(super) struct PyFlags;
1592
1593    #[pyclass(with(PyStructSequence))]
1594    impl Py<PyFlags> {
1595        #[pyslot]
1596        fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1597            Err(vm.new_type_error("cannot create 'sys.flags' instances"))
1598        }
1599
1600        #[pygetset]
1601        fn context_aware_warnings(&self, vm: &VirtualMachine) -> bool {
1602            vm.state.config.settings.context_aware_warnings
1603        }
1604
1605        #[pygetset]
1606        fn thread_inherit_context(&self, vm: &VirtualMachine) -> bool {
1607            vm.state.config.settings.thread_inherit_context
1608        }
1609    }
1610
1611    #[cfg(feature = "threading")]
1612    #[pystruct_sequence_data]
1613    pub(super) struct ThreadInfoData {
1614        name: Option<&'static str>,
1615        lock: Option<&'static str>,
1616        version: Option<&'static str>,
1617    }
1618
1619    #[cfg(feature = "threading")]
1620    impl ThreadInfoData {
1621        const INFO: Self = Self {
1622            name: crate::stdlib::_thread::_thread::PYTHREAD_NAME,
1623            // As I know, there's only way to use lock as "Mutex" in Rust
1624            // with satisfying python document spec.
1625            lock: Some("mutex+cond"),
1626            version: None,
1627        };
1628    }
1629
1630    #[cfg(feature = "threading")]
1631    #[pystruct_sequence(name = "thread_info", data = "ThreadInfoData", no_attr)]
1632    pub(super) struct PyThreadInfo;
1633
1634    #[cfg(feature = "threading")]
1635    #[pyclass(with(PyStructSequence))]
1636    impl PyThreadInfo {}
1637
1638    #[pystruct_sequence_data]
1639    pub(super) struct FloatInfoData {
1640        max: f64,
1641        max_exp: i32,
1642        max_10_exp: i32,
1643        min: f64,
1644        min_exp: i32,
1645        min_10_exp: i32,
1646        dig: u32,
1647        mant_dig: u32,
1648        epsilon: f64,
1649        radix: u32,
1650        rounds: i32,
1651    }
1652
1653    impl FloatInfoData {
1654        const INFO: Self = Self {
1655            max: f64::MAX,
1656            max_exp: f64::MAX_EXP,
1657            max_10_exp: f64::MAX_10_EXP,
1658            min: f64::MIN_POSITIVE,
1659            min_exp: f64::MIN_EXP,
1660            min_10_exp: f64::MIN_10_EXP,
1661            dig: f64::DIGITS,
1662            mant_dig: f64::MANTISSA_DIGITS,
1663            epsilon: f64::EPSILON,
1664            radix: f64::RADIX,
1665            rounds: 1, // FE_TONEAREST
1666        };
1667    }
1668
1669    #[pystruct_sequence(name = "float_info", data = "FloatInfoData", no_attr)]
1670    pub(super) struct PyFloatInfo;
1671
1672    #[pyclass(with(PyStructSequence))]
1673    impl PyFloatInfo {}
1674
1675    #[pystruct_sequence_data]
1676    pub(super) struct HashInfoData {
1677        width: usize,
1678        modulus: PyUHash,
1679        inf: PyHash,
1680        nan: PyHash,
1681        imag: PyHash,
1682        algorithm: &'static str,
1683        hash_bits: usize,
1684        seed_bits: usize,
1685        cutoff: usize,
1686    }
1687
1688    impl HashInfoData {
1689        const INFO: Self = {
1690            use rustpython_common::hash::*;
1691            Self {
1692                width: core::mem::size_of::<PyHash>() * 8,
1693                modulus: MODULUS,
1694                inf: INF,
1695                nan: NAN,
1696                imag: IMAG,
1697                algorithm: ALGO,
1698                hash_bits: HASH_BITS,
1699                seed_bits: SEED_BITS,
1700                cutoff: 0, // no small string optimizations
1701            }
1702        };
1703    }
1704
1705    #[pystruct_sequence(name = "hash_info", data = "HashInfoData", no_attr)]
1706    pub(super) struct PyHashInfo;
1707
1708    #[pyclass(with(PyStructSequence))]
1709    impl PyHashInfo {}
1710
1711    #[pystruct_sequence_data]
1712    pub(super) struct IntInfoData {
1713        bits_per_digit: usize,
1714        sizeof_digit: usize,
1715        default_max_str_digits: usize,
1716        str_digits_check_threshold: usize,
1717    }
1718
1719    impl IntInfoData {
1720        const INFO: Self = Self {
1721            bits_per_digit: 30, //?
1722            sizeof_digit: core::mem::size_of::<u32>(),
1723            default_max_str_digits: 4300,
1724            str_digits_check_threshold: 640,
1725        };
1726    }
1727
1728    #[pystruct_sequence(name = "int_info", data = "IntInfoData", no_attr)]
1729    pub(super) struct PyIntInfo;
1730
1731    #[pyclass(with(PyStructSequence))]
1732    impl PyIntInfo {}
1733
1734    #[derive(Default, Debug)]
1735    #[pystruct_sequence_data]
1736    pub struct VersionInfoData {
1737        major: usize,
1738        minor: usize,
1739        micro: usize,
1740        releaselevel: &'static str,
1741        serial: usize,
1742    }
1743
1744    impl VersionInfoData {
1745        pub const VERSION: Self = Self {
1746            major: version::MAJOR,
1747            minor: version::MINOR,
1748            micro: version::MICRO,
1749            releaselevel: version::RELEASELEVEL,
1750            serial: version::SERIAL,
1751        };
1752
1753        pub const IMPLEMENTATION: Self = Self {
1754            major: version::MAJOR_IMPL,
1755            minor: version::MINOR_IMPL,
1756            micro: version::MICRO_IMPL,
1757            releaselevel: version::RELEASELEVEL_IMPL,
1758            serial: version::SERIAL_IMPL,
1759        };
1760    }
1761
1762    #[pystruct_sequence(name = "version_info", data = "VersionInfoData", no_attr)]
1763    pub struct PyVersionInfo;
1764
1765    #[pyclass(with(PyStructSequence))]
1766    impl PyVersionInfo {
1767        #[pyslot]
1768        fn slot_new(
1769            _cls: crate::builtins::type_::PyTypeRef,
1770            _args: crate::function::FuncArgs,
1771            vm: &crate::VirtualMachine,
1772        ) -> crate::PyResult {
1773            Err(vm.new_type_error("cannot create 'sys.version_info' instances"))
1774        }
1775    }
1776
1777    #[cfg(windows)]
1778    #[derive(Default, Debug)]
1779    #[pystruct_sequence_data]
1780    pub(super) struct WindowsVersionData {
1781        major: u32,
1782        minor: u32,
1783        build: u32,
1784        platform: u32,
1785        service_pack: String,
1786        #[pystruct_sequence(skip)]
1787        service_pack_major: u16,
1788        #[pystruct_sequence(skip)]
1789        service_pack_minor: u16,
1790        #[pystruct_sequence(skip)]
1791        suite_mask: u16,
1792        #[pystruct_sequence(skip)]
1793        product_type: u8,
1794        #[pystruct_sequence(skip)]
1795        platform_version: (u32, u32, u32),
1796    }
1797
1798    #[cfg(windows)]
1799    #[pystruct_sequence(name = "getwindowsversion", data = "WindowsVersionData", no_attr)]
1800    pub(super) struct PyWindowsVersion;
1801
1802    #[cfg(windows)]
1803    #[pyclass(with(PyStructSequence))]
1804    impl PyWindowsVersion {
1805        #[pyslot]
1806        fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1807            Err(vm.new_type_error("cannot create 'sys.getwindowsversion' instances"))
1808        }
1809    }
1810
1811    #[derive(Debug)]
1812    #[pystruct_sequence_data(try_from_object)]
1813    pub struct UnraisableHookArgsData {
1814        pub exc_type: PyTypeRef,
1815        pub exc_value: PyObjectRef,
1816        pub exc_traceback: PyObjectRef,
1817        pub err_msg: PyObjectRef,
1818        pub object: PyObjectRef,
1819    }
1820
1821    #[pystruct_sequence(name = "UnraisableHookArgs", data = "UnraisableHookArgsData", no_attr)]
1822    pub struct PyUnraisableHookArgs;
1823
1824    #[pyclass(with(PyStructSequence))]
1825    impl PyUnraisableHookArgs {}
1826
1827    pub(crate) fn run_audit_hooks(
1828        event: &Py<PyStr>,
1829        args: &PyObject,
1830        vm: &VirtualMachine,
1831    ) -> PyResult<()> {
1832        let hooks = vm.state.audit_hooks.lock().clone();
1833
1834        if hooks.is_empty() {
1835            return Ok(());
1836        }
1837
1838        for hook in hooks {
1839            call_audit_hook(&hook, event.to_owned().into(), args, vm)?;
1840        }
1841
1842        Ok(())
1843    }
1844
1845    fn audit_hook_can_trace(hook: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
1846        match hook.get_attr("__cantrace__", vm) {
1847            Ok(can_trace) => can_trace.try_to_bool(vm),
1848            Err(exc)
1849                if exc
1850                    .class()
1851                    .fast_issubclass(vm.ctx.exceptions.attribute_error) =>
1852            {
1853                Ok(false)
1854            }
1855            Err(exc) => Err(exc),
1856        }
1857    }
1858
1859    fn call_audit_hook(
1860        hook: &PyObject,
1861        event: PyObjectRef,
1862        args: &PyObject,
1863        vm: &VirtualMachine,
1864    ) -> PyResult<()> {
1865        // Tracing is suppressed while dispatching Python audit hooks,
1866        // except for hooks that explicitly opt in with __cantrace__.
1867        vm.enter_tracing();
1868        let can_trace = audit_hook_can_trace(hook, vm);
1869        let result = match can_trace {
1870            Ok(can_trace) => {
1871                if can_trace {
1872                    vm.leave_tracing();
1873                }
1874                let result = hook.call((event, args.to_owned()), vm).map(|_| ());
1875                if can_trace {
1876                    vm.enter_tracing();
1877                }
1878                result
1879            }
1880            Err(exc) => Err(exc),
1881        };
1882
1883        vm.leave_tracing();
1884        result
1885    }
1886
1887    #[pyfunction]
1888    fn audit(event: PyStrRef, args: PosArgs, vm: &VirtualMachine) -> PyResult<()> {
1889        if vm.state.audit_hooks.lock().is_empty() {
1890            return Ok(());
1891        }
1892
1893        let args_tup: PyObjectRef = vm.ctx.new_tuple(args.into_vec()).into();
1894        run_audit_hooks(&event, &args_tup, vm)
1895    }
1896
1897    #[pyfunction]
1898    fn addaudithook(AuditHookArgs { hook }: AuditHookArgs, vm: &VirtualMachine) -> PyResult<()> {
1899        let args: PyObjectRef = vm.ctx.new_tuple(vec![]).into();
1900        let event: PyObjectRef = vm.ctx.new_str("sys.addaudithook").into();
1901
1902        // Hooks are append-only: append only once every hook present has been notified,
1903        // notifying any added by other threads meanwhile. Python hooks run unlocked.
1904        let mut notified = 0;
1905        loop {
1906            let pending = {
1907                let mut hooks = vm.state.audit_hooks.lock();
1908                if hooks.len() == notified {
1909                    hooks.push(hook);
1910                    return Ok(());
1911                }
1912                hooks[notified..].to_vec()
1913            };
1914            for existing_hook in pending {
1915                if let Err(exc) = call_audit_hook(&existing_hook, event.clone(), &args, vm) {
1916                    return if exc
1917                        .class()
1918                        .fast_issubclass(vm.ctx.exceptions.exception_type)
1919                    {
1920                        Ok(())
1921                    } else {
1922                        Err(exc)
1923                    };
1924                }
1925                notified += 1;
1926            }
1927        }
1928    }
1929}
1930
1931fn sys_module_doc() -> Option<&'static str> {
1932    let raw = crate::function::plain_doc(sys::DOC)?;
1933    #[cfg(windows)]
1934    {
1935        Some(raw)
1936    }
1937    #[cfg(not(windows))]
1938    {
1939        use std::sync::OnceLock;
1940        static FILTERED: OnceLock<String> = OnceLock::new();
1941        let filtered = FILTERED.get_or_init(|| {
1942            let mut out = String::new();
1943            for line in raw.split_inclusive('\n') {
1944                if !line.contains("[Windows only]") {
1945                    out.push_str(line);
1946                }
1947            }
1948            out
1949        });
1950        Some(filtered.as_str())
1951    }
1952}
1953
1954pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) {
1955    module.__init_methods(vm).unwrap();
1956    sys::module_exec(vm, module).unwrap();
1957
1958    let modules = vm.ctx.new_dict();
1959    modules
1960        .set_item("sys", module.to_owned().into(), vm)
1961        .unwrap();
1962    modules
1963        .set_item("builtins", builtins.to_owned().into(), vm)
1964        .unwrap();
1965
1966    // Create sys._jit submodule
1967    let jit_def = sys_jit::module_def(&vm.ctx);
1968    let jit_module = jit_def.create_module(vm).unwrap();
1969
1970    extend_module!(vm, module, {
1971        "__doc__" => sys_module_doc().to_pyobject(vm),
1972        "modules" => modules,
1973        "_jit" => jit_module,
1974    });
1975}
1976
1977pub(crate) fn set_bootstrap_stderr(vm: &VirtualMachine) -> PyResult<()> {
1978    let stderr = sys::BootstrapStderr.into_ref(&vm.ctx);
1979    let stderr_obj: crate::PyObjectRef = stderr.into();
1980    vm.sys_module.set_attr("stderr", stderr_obj.clone(), vm)?;
1981    vm.sys_module.set_attr("__stderr__", stderr_obj, vm)?;
1982    Ok(())
1983}
1984
1985/// Similar to PySys_WriteStderr in CPython.
1986///
1987/// # Usage
1988///
1989/// ```rust,ignore
1990/// writeln!(sys::PyStderr(vm), "foo bar baz :)");
1991/// ```
1992///
1993/// Unlike writing to a `std::io::Write` with the `write[ln]!()` macro, there's no error condition here;
1994/// this is intended to be a replacement for the `eprint[ln]!()` macro, so `write!()`-ing to PyStderr just
1995/// returns `()`.
1996pub struct PyStderr<'vm>(pub &'vm VirtualMachine);
1997
1998impl PyStderr<'_> {
1999    pub fn write_fmt(&self, args: core::fmt::Arguments<'_>) {
2000        use crate::py_io::Write;
2001
2002        let vm = self.0;
2003        if let Ok(stderr) = get_stderr(vm) {
2004            let mut stderr = crate::py_io::PyWriter(stderr, vm);
2005            if let Ok(()) = stderr.write_fmt(args) {
2006                return;
2007            }
2008        }
2009        eprint!("{args}")
2010    }
2011}
2012
2013pub fn get_stdin(vm: &VirtualMachine) -> PyResult {
2014    vm.sys_module
2015        .get_attr("stdin", vm)
2016        .map_err(|_| vm.new_runtime_error("lost sys.stdin"))
2017}
2018pub fn get_stdout(vm: &VirtualMachine) -> PyResult {
2019    vm.sys_module
2020        .get_attr("stdout", vm)
2021        .map_err(|_| vm.new_runtime_error("lost sys.stdout"))
2022}
2023pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
2024    vm.sys_module
2025        .get_attr("stderr", vm)
2026        .map_err(|_| vm.new_runtime_error("lost sys.stderr"))
2027}
2028
2029pub(crate) fn sysconfigdata_name() -> String {
2030    format!(
2031        "_sysconfigdata_{}_{}_{}",
2032        sys::ABIFLAGS,
2033        sys::PLATFORM.to_string_lossy(),
2034        sys::multiarch()
2035    )
2036}