rustpython_vm/stdlib/
sys.rs

1use crate::{builtins::PyModule, convert::ToPyObject, Py, PyResult, VirtualMachine};
2
3pub(crate) use sys::{UnraisableHookArgs, __module_def, DOC, MAXSIZE, MULTIARCH};
4
5#[pymodule]
6mod sys {
7    use crate::{
8        builtins::{
9            PyBaseExceptionRef, PyDictRef, PyNamespace, PyStr, PyStrRef, PyTupleRef, PyTypeRef,
10        },
11        common::{
12            ascii,
13            hash::{PyHash, PyUHash},
14        },
15        convert::ToPyObject,
16        frame::FrameRef,
17        function::{FuncArgs, OptionalArg, PosArgs},
18        stdlib::{builtins, warnings::warn},
19        types::PyStructSequence,
20        version,
21        vm::{Settings, VirtualMachine},
22        AsObject, PyObject, PyObjectRef, PyRef, PyRefExact, PyResult,
23    };
24    use num_traits::ToPrimitive;
25    use std::{
26        env::{self, VarError},
27        path,
28        sync::atomic::Ordering,
29    };
30
31    // not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu)
32    // but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly,
33    // https://github.com/python/cpython/blob/3.8/configure.ac#L725
34    pub(crate) const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");
35
36    #[pyattr(name = "_rustpython_debugbuild")]
37    const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions);
38
39    #[pyattr(name = "abiflags")]
40    pub(crate) const ABIFLAGS: &str = "";
41    #[pyattr(name = "api_version")]
42    const API_VERSION: u32 = 0x0; // what C api?
43    #[pyattr(name = "copyright")]
44    const COPYRIGHT: &str = "Copyright (c) 2019 RustPython Team";
45    #[pyattr(name = "float_repr_style")]
46    const FLOAT_REPR_STYLE: &str = "short";
47    #[pyattr(name = "_framework")]
48    const FRAMEWORK: &str = "";
49    #[pyattr(name = "hexversion")]
50    const HEXVERSION: usize = version::VERSION_HEX;
51    #[pyattr(name = "maxsize")]
52    pub(crate) const MAXSIZE: isize = isize::MAX;
53    #[pyattr(name = "maxunicode")]
54    const MAXUNICODE: u32 = std::char::MAX as u32;
55    #[pyattr(name = "platform")]
56    pub(crate) const PLATFORM: &str = {
57        cfg_if::cfg_if! {
58            if #[cfg(any(target_os = "linux", target_os = "android"))] {
59                // Android is linux as well. see https://bugs.python.org/issue32637
60                "linux"
61            } else if #[cfg(target_os = "macos")] {
62                "darwin"
63            } else if #[cfg(windows)] {
64                "win32"
65            } else if #[cfg(target_os = "wasi")] {
66                "wasi"
67            } else {
68                "unknown"
69            }
70        }
71    };
72    #[pyattr(name = "ps1")]
73    const PS1: &str = ">>>>> ";
74    #[pyattr(name = "ps2")]
75    const PS2: &str = "..... ";
76
77    #[cfg(windows)]
78    #[pyattr(name = "_vpath")]
79    const VPATH: Option<&'static str> = None; // TODO: actual VPATH value
80
81    #[cfg(windows)]
82    #[pyattr(name = "dllhandle")]
83    const DLLHANDLE: usize = 0;
84
85    #[pyattr]
86    fn default_prefix(_vm: &VirtualMachine) -> &'static str {
87        // TODO: the windows one doesn't really make sense
88        if cfg!(windows) {
89            "C:"
90        } else {
91            "/usr/local"
92        }
93    }
94    #[pyattr]
95    fn prefix(vm: &VirtualMachine) -> &'static str {
96        option_env!("RUSTPYTHON_PREFIX").unwrap_or_else(|| default_prefix(vm))
97    }
98    #[pyattr]
99    fn base_prefix(vm: &VirtualMachine) -> &'static str {
100        option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm))
101    }
102    #[pyattr]
103    fn exec_prefix(vm: &VirtualMachine) -> &'static str {
104        option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm))
105    }
106    #[pyattr]
107    fn base_exec_prefix(vm: &VirtualMachine) -> &'static str {
108        option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| exec_prefix(vm))
109    }
110    #[pyattr]
111    fn platlibdir(_vm: &VirtualMachine) -> &'static str {
112        option_env!("RUSTPYTHON_PLATLIBDIR").unwrap_or("lib")
113    }
114
115    // alphabetical order with segments of pyattr and others
116
117    #[pyattr]
118    fn argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
119        vm.state
120            .settings
121            .argv
122            .iter()
123            .map(|arg| vm.ctx.new_str(arg.clone()).into())
124            .collect()
125    }
126
127    #[pyattr]
128    fn builtin_module_names(vm: &VirtualMachine) -> PyTupleRef {
129        let mut module_names: Vec<_> = vm.state.module_inits.keys().cloned().collect();
130        module_names.push("sys".into());
131        module_names.push("builtins".into());
132        module_names.sort();
133        vm.ctx.new_tuple(
134            module_names
135                .into_iter()
136                .map(|n| vm.ctx.new_str(n).into())
137                .collect(),
138        )
139    }
140
141    #[pyattr]
142    fn byteorder(vm: &VirtualMachine) -> PyStrRef {
143        // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian
144        vm.ctx
145            .intern_str(if cfg!(target_endian = "little") {
146                "little"
147            } else if cfg!(target_endian = "big") {
148                "big"
149            } else {
150                "unknown"
151            })
152            .to_owned()
153    }
154
155    #[pyattr]
156    fn _base_executable(vm: &VirtualMachine) -> PyObjectRef {
157        let ctx = &vm.ctx;
158        if let Ok(var) = env::var("__PYVENV_LAUNCHER__") {
159            ctx.new_str(var).into()
160        } else {
161            executable(vm)
162        }
163    }
164
165    #[pyattr]
166    fn dont_write_bytecode(vm: &VirtualMachine) -> bool {
167        !vm.state.settings.write_bytecode
168    }
169
170    #[pyattr]
171    fn executable(vm: &VirtualMachine) -> PyObjectRef {
172        let ctx = &vm.ctx;
173        #[cfg(not(target_arch = "wasm32"))]
174        {
175            if let Some(exec_path) = env::args_os().next() {
176                if let Ok(path) = which::which(exec_path) {
177                    return ctx
178                        .new_str(
179                            path.into_os_string()
180                                .into_string()
181                                .unwrap_or_else(|p| p.to_string_lossy().into_owned()),
182                        )
183                        .into();
184                }
185            }
186        }
187        if let Some(exec_path) = env::args().next() {
188            let path = path::Path::new(&exec_path);
189            if !path.exists() {
190                return ctx.new_str(ascii!("")).into();
191            }
192            if path.is_absolute() {
193                return ctx.new_str(exec_path).into();
194            }
195            if let Ok(dir) = env::current_dir() {
196                if let Ok(dir) = dir.into_os_string().into_string() {
197                    return ctx
198                        .new_str(format!(
199                            "{}/{}",
200                            dir,
201                            exec_path.strip_prefix("./").unwrap_or(&exec_path)
202                        ))
203                        .into();
204                }
205            }
206        }
207        ctx.none()
208    }
209
210    #[pyattr]
211    fn _git(vm: &VirtualMachine) -> PyTupleRef {
212        vm.new_tuple((
213            ascii!("RustPython"),
214            version::get_git_identifier(),
215            version::get_git_revision(),
216        ))
217    }
218
219    #[pyattr]
220    fn implementation(vm: &VirtualMachine) -> PyRef<PyNamespace> {
221        // TODO: Add crate version to this namespace
222        let ctx = &vm.ctx;
223        py_namespace!(vm, {
224            "name" => ctx.new_str(ascii!("rustpython")),
225            "cache_tag" => ctx.new_str(ascii!("rustpython-01")),
226            "_multiarch" => ctx.new_str(MULTIARCH.to_owned()),
227            "version" => version_info(vm),
228            "hexversion" => ctx.new_int(version::VERSION_HEX),
229        })
230    }
231
232    #[pyattr]
233    fn meta_path(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
234        Vec::new()
235    }
236
237    #[pyattr]
238    fn orig_argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
239        env::args().map(|arg| vm.ctx.new_str(arg).into()).collect()
240    }
241
242    #[pyattr]
243    fn path(vm: &VirtualMachine) -> Vec<PyObjectRef> {
244        vm.state
245            .settings
246            .path_list
247            .iter()
248            .map(|path| vm.ctx.new_str(path.clone()).into())
249            .collect()
250    }
251
252    #[pyattr]
253    fn path_hooks(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
254        Vec::new()
255    }
256
257    #[pyattr]
258    fn path_importer_cache(vm: &VirtualMachine) -> PyDictRef {
259        vm.ctx.new_dict()
260    }
261
262    #[pyattr]
263    fn pycache_prefix(vm: &VirtualMachine) -> PyObjectRef {
264        vm.ctx.none()
265    }
266
267    #[pyattr]
268    fn version(_vm: &VirtualMachine) -> String {
269        version::get_version()
270    }
271
272    #[cfg(windows)]
273    #[pyattr]
274    fn winver(_vm: &VirtualMachine) -> String {
275        // Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility
276        version::get_winver_number()
277    }
278
279    #[pyattr]
280    fn _xoptions(vm: &VirtualMachine) -> PyDictRef {
281        let ctx = &vm.ctx;
282        let xopts = ctx.new_dict();
283        for (key, value) in &vm.state.settings.xoptions {
284            let value = value.as_ref().map_or_else(
285                || ctx.new_bool(true).into(),
286                |s| ctx.new_str(s.clone()).into(),
287            );
288            xopts.set_item(&**key, value, vm).unwrap();
289        }
290        xopts
291    }
292
293    #[pyattr]
294    fn warnoptions(vm: &VirtualMachine) -> Vec<PyObjectRef> {
295        vm.state
296            .settings
297            .warnoptions
298            .iter()
299            .map(|s| vm.ctx.new_str(s.clone()).into())
300            .collect()
301    }
302
303    #[pyfunction]
304    fn audit(_args: FuncArgs) {
305        // TODO: sys.audit implementation
306    }
307
308    #[pyfunction]
309    fn exit(code: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
310        let code = code.unwrap_or_none(vm);
311        Err(vm.new_exception(vm.ctx.exceptions.system_exit.to_owned(), vec![code]))
312    }
313
314    #[pyfunction]
315    fn exception(vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
316        vm.topmost_exception()
317    }
318
319    #[pyfunction(name = "__displayhook__")]
320    #[pyfunction]
321    fn displayhook(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
322        // Save non-None values as "_"
323        if vm.is_none(&obj) {
324            return Ok(());
325        }
326        // set to none to avoid recursion while printing
327        vm.builtins.set_attr("_", vm.ctx.none(), vm)?;
328        // TODO: catch encoding errors
329        let repr = obj.repr(vm)?.into();
330        builtins::print(PosArgs::new(vec![repr]), Default::default(), vm)?;
331        vm.builtins.set_attr("_", obj, vm)?;
332        Ok(())
333    }
334
335    #[pyfunction(name = "__excepthook__")]
336    #[pyfunction]
337    fn excepthook(
338        exc_type: PyObjectRef,
339        exc_val: PyObjectRef,
340        exc_tb: PyObjectRef,
341        vm: &VirtualMachine,
342    ) -> PyResult<()> {
343        let exc = vm.normalize_exception(exc_type, exc_val, exc_tb)?;
344        let stderr = super::get_stderr(vm)?;
345        vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc)
346    }
347
348    #[pyfunction(name = "__breakpointhook__")]
349    #[pyfunction]
350    pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
351        let env_var = std::env::var("PYTHONBREAKPOINT")
352            .and_then(|env_var| {
353                if env_var.is_empty() {
354                    Err(VarError::NotPresent)
355                } else {
356                    Ok(env_var)
357                }
358            })
359            .unwrap_or_else(|_| "pdb.set_trace".to_owned());
360
361        if env_var.eq("0") {
362            return Ok(vm.ctx.none());
363        };
364
365        let print_unimportable_module_warn = || {
366            warn(
367                vm.ctx.exceptions.runtime_warning,
368                format!("Ignoring unimportable $PYTHONBREAKPOINT: \"{env_var}\"",),
369                0,
370                vm,
371            )
372            .unwrap();
373            Ok(vm.ctx.none())
374        };
375
376        let last = match env_var.rsplit_once('.') {
377            Some((_, last)) => last,
378            None if !env_var.is_empty() => env_var.as_str(),
379            _ => return print_unimportable_module_warn(),
380        };
381
382        let (module_path, attr_name) = if last == env_var {
383            ("builtins", env_var.as_str())
384        } else {
385            (&env_var[..(env_var.len() - last.len() - 1)], last)
386        };
387
388        let module = match vm.import(&vm.ctx.new_str(module_path), 0) {
389            Ok(module) => module,
390            Err(_) => {
391                return print_unimportable_module_warn();
392            }
393        };
394
395        match vm.get_attribute_opt(module, &vm.ctx.new_str(attr_name)) {
396            Ok(Some(hook)) => hook.as_ref().call(args, vm),
397            _ => print_unimportable_module_warn(),
398        }
399    }
400
401    #[pyfunction]
402    fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
403        match vm.topmost_exception() {
404            Some(exception) => vm.split_exception(exception),
405            None => (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()),
406        }
407    }
408
409    #[pyattr]
410    fn flags(vm: &VirtualMachine) -> PyTupleRef {
411        Flags::from_settings(&vm.state.settings).into_struct_sequence(vm)
412    }
413
414    #[pyattr]
415    fn float_info(vm: &VirtualMachine) -> PyTupleRef {
416        PyFloatInfo::INFO.into_struct_sequence(vm)
417    }
418
419    #[pyfunction]
420    fn getdefaultencoding() -> &'static str {
421        crate::codecs::DEFAULT_ENCODING
422    }
423
424    #[pyfunction]
425    fn getrefcount(obj: PyObjectRef) -> usize {
426        obj.strong_count()
427    }
428
429    #[pyfunction]
430    fn getrecursionlimit(vm: &VirtualMachine) -> usize {
431        vm.recursion_limit.get()
432    }
433
434    #[derive(FromArgs)]
435    struct GetsizeofArgs {
436        obj: PyObjectRef,
437        #[pyarg(any, optional)]
438        default: Option<PyObjectRef>,
439    }
440
441    #[pyfunction]
442    fn getsizeof(args: GetsizeofArgs, vm: &VirtualMachine) -> PyResult {
443        let sizeof = || -> PyResult<usize> {
444            let res = vm.call_special_method(&args.obj, identifier!(vm, __sizeof__), ())?;
445            let res = res.try_index(vm)?.try_to_primitive::<usize>(vm)?;
446            Ok(res + std::mem::size_of::<PyObject>())
447        };
448        sizeof()
449            .map(|x| vm.ctx.new_int(x).into())
450            .or_else(|err| args.default.ok_or(err))
451    }
452
453    #[pyfunction]
454    fn getfilesystemencoding(_vm: &VirtualMachine) -> String {
455        // TODO: implement non-utf-8 mode.
456        "utf-8".to_owned()
457    }
458
459    #[cfg(not(windows))]
460    #[pyfunction]
461    fn getfilesystemencodeerrors(_vm: &VirtualMachine) -> String {
462        "surrogateescape".to_owned()
463    }
464
465    #[cfg(windows)]
466    #[pyfunction]
467    fn getfilesystemencodeerrors(_vm: &VirtualMachine) -> String {
468        "surrogatepass".to_owned()
469    }
470
471    #[pyfunction]
472    fn getprofile(vm: &VirtualMachine) -> PyObjectRef {
473        vm.profile_func.borrow().clone()
474    }
475
476    #[pyfunction]
477    fn _getframe(offset: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult<FrameRef> {
478        let offset = offset.into_option().unwrap_or(0);
479        if offset > vm.frames.borrow().len() - 1 {
480            return Err(vm.new_value_error("call stack is not deep enough".to_owned()));
481        }
482        let idx = vm.frames.borrow().len() - offset - 1;
483        let frame = &vm.frames.borrow()[idx];
484        Ok(frame.clone())
485    }
486
487    #[pyfunction]
488    fn gettrace(vm: &VirtualMachine) -> PyObjectRef {
489        vm.trace_func.borrow().clone()
490    }
491
492    #[cfg(windows)]
493    #[pyfunction]
494    fn getwindowsversion(vm: &VirtualMachine) -> PyResult<crate::builtins::tuple::PyTupleRef> {
495        use std::ffi::OsString;
496        use std::os::windows::ffi::OsStringExt;
497        use windows_sys::Win32::System::SystemInformation::{
498            GetVersionExW, OSVERSIONINFOEXW, OSVERSIONINFOW,
499        };
500
501        let mut version: OSVERSIONINFOEXW = unsafe { std::mem::zeroed() };
502        version.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
503        let result = unsafe {
504            let osvi = &mut version as *mut OSVERSIONINFOEXW as *mut OSVERSIONINFOW;
505            // SAFETY: GetVersionExW accepts a pointer of OSVERSIONINFOW, but windows-sys crate's type currently doesn't allow to do so.
506            // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw#parameters
507            GetVersionExW(osvi)
508        };
509
510        if result == 0 {
511            return Err(vm.new_os_error("failed to get windows version".to_owned()));
512        }
513
514        let service_pack = {
515            let (last, _) = version
516                .szCSDVersion
517                .iter()
518                .take_while(|&x| x != &0)
519                .enumerate()
520                .last()
521                .unwrap_or((0, &0));
522            let sp = OsString::from_wide(&version.szCSDVersion[..last]);
523            sp.into_string()
524                .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))?
525        };
526        Ok(WindowsVersion {
527            major: version.dwMajorVersion,
528            minor: version.dwMinorVersion,
529            build: version.dwBuildNumber,
530            platform: version.dwPlatformId,
531            service_pack,
532            service_pack_major: version.wServicePackMajor,
533            service_pack_minor: version.wServicePackMinor,
534            suite_mask: version.wSuiteMask,
535            product_type: version.wProductType,
536            platform_version: (
537                version.dwMajorVersion,
538                version.dwMinorVersion,
539                version.dwBuildNumber,
540            ), // TODO Provide accurate version, like CPython impl
541        }
542        .into_struct_sequence(vm))
543    }
544
545    fn _unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) -> PyResult<()> {
546        use super::PyStderr;
547
548        let stderr = PyStderr(vm);
549        if !vm.is_none(&unraisable.object) {
550            if !vm.is_none(&unraisable.err_msg) {
551                write!(stderr, "{}: ", unraisable.err_msg.str(vm)?);
552            } else {
553                write!(stderr, "Exception ignored in: ");
554            }
555            // exception in del will be ignored but printed
556            let repr = &unraisable.object.repr(vm);
557            let str = match repr {
558                Ok(v) => v.to_string(),
559                Err(_) => format!(
560                    "<object {} repr() failed>",
561                    unraisable.object.class().name()
562                ),
563            };
564            writeln!(stderr, "{str}");
565        } else if !vm.is_none(&unraisable.err_msg) {
566            writeln!(stderr, "{}:", unraisable.err_msg.str(vm)?);
567        }
568
569        // TODO: print received unraisable.exc_traceback
570        let tb_module = vm.import("traceback", 0)?;
571        let print_stack = tb_module.get_attr("print_stack", vm)?;
572        print_stack.call((), vm)?;
573
574        if vm.is_none(unraisable.exc_type.as_object()) {
575            // TODO: early return, but with what error?
576        }
577        assert!(unraisable
578            .exc_type
579            .fast_issubclass(vm.ctx.exceptions.base_exception_type));
580
581        // TODO: print module name and qualname
582
583        if !vm.is_none(&unraisable.exc_value) {
584            write!(stderr, "{}: ", unraisable.exc_type);
585            if let Ok(str) = unraisable.exc_value.str(vm) {
586                write!(stderr, "{}", str.as_str());
587            } else {
588                write!(stderr, "<exception str() failed>");
589            }
590        }
591        writeln!(stderr);
592        // TODO: call file.flush()
593
594        Ok(())
595    }
596
597    #[pyattr]
598    #[pyfunction(name = "__unraisablehook__")]
599    fn unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) {
600        if let Err(e) = _unraisablehook(unraisable, vm) {
601            let stderr = super::PyStderr(vm);
602            writeln!(
603                stderr,
604                "{}",
605                e.as_object()
606                    .repr(vm)
607                    .unwrap_or_else(|_| vm.ctx.empty_str.to_owned())
608                    .as_str()
609            );
610        }
611    }
612
613    #[pyattr]
614    fn hash_info(vm: &VirtualMachine) -> PyTupleRef {
615        PyHashInfo::INFO.into_struct_sequence(vm)
616    }
617
618    #[pyfunction]
619    fn intern(s: PyRefExact<PyStr>, vm: &VirtualMachine) -> PyRef<PyStr> {
620        vm.ctx.intern_str(s).to_owned()
621    }
622
623    #[pyattr]
624    fn int_info(vm: &VirtualMachine) -> PyTupleRef {
625        PyIntInfo::INFO.into_struct_sequence(vm)
626    }
627
628    #[pyfunction]
629    fn get_int_max_str_digits(vm: &VirtualMachine) -> usize {
630        vm.state.int_max_str_digits.load()
631    }
632
633    #[pyfunction]
634    fn set_int_max_str_digits(maxdigits: usize, vm: &VirtualMachine) -> PyResult<()> {
635        let threshold = PyIntInfo::INFO.str_digits_check_threshold;
636        if maxdigits == 0 || maxdigits >= threshold {
637            vm.state.int_max_str_digits.store(maxdigits);
638            Ok(())
639        } else {
640            let error = format!("maxdigits must be 0 or larger than {:?}", threshold);
641            Err(vm.new_value_error(error))
642        }
643    }
644
645    #[pyfunction]
646    fn is_finalizing(vm: &VirtualMachine) -> bool {
647        vm.state.finalizing.load(Ordering::Acquire)
648    }
649
650    #[pyfunction]
651    fn setprofile(profilefunc: PyObjectRef, vm: &VirtualMachine) {
652        vm.profile_func.replace(profilefunc);
653        update_use_tracing(vm);
654    }
655
656    #[pyfunction]
657    fn setrecursionlimit(recursion_limit: i32, vm: &VirtualMachine) -> PyResult<()> {
658        let recursion_limit = recursion_limit
659            .to_usize()
660            .filter(|&u| u >= 1)
661            .ok_or_else(|| {
662                vm.new_value_error(
663                    "recursion limit must be greater than or equal to one".to_owned(),
664                )
665            })?;
666        let recursion_depth = vm.current_recursion_depth();
667
668        if recursion_limit > recursion_depth {
669            vm.recursion_limit.set(recursion_limit);
670            Ok(())
671        } else {
672            Err(vm.new_recursion_error(format!(
673                "cannot set the recursion limit to {recursion_limit} at the recursion depth {recursion_depth}: the limit is too low"
674            )))
675        }
676    }
677
678    #[pyfunction]
679    fn settrace(tracefunc: PyObjectRef, vm: &VirtualMachine) {
680        vm.trace_func.replace(tracefunc);
681        update_use_tracing(vm);
682    }
683
684    #[cfg(feature = "threading")]
685    #[pyattr]
686    fn thread_info(vm: &VirtualMachine) -> PyTupleRef {
687        PyThreadInfo::INFO.into_struct_sequence(vm)
688    }
689
690    #[pyattr]
691    fn version_info(vm: &VirtualMachine) -> PyTupleRef {
692        VersionInfo::VERSION.into_struct_sequence(vm)
693    }
694
695    fn update_use_tracing(vm: &VirtualMachine) {
696        let trace_is_none = vm.is_none(&vm.trace_func.borrow());
697        let profile_is_none = vm.is_none(&vm.profile_func.borrow());
698        let tracing = !(trace_is_none && profile_is_none);
699        vm.use_tracing.set(tracing);
700    }
701
702    #[pyfunction]
703    fn set_coroutine_origin_tracking_depth(depth: i32, vm: &VirtualMachine) -> PyResult<()> {
704        if depth < 0 {
705            return Err(vm.new_value_error("depth must be >= 0".to_owned()));
706        }
707        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.with(|cell| cell.set(depth as _));
708        Ok(())
709    }
710
711    #[pyfunction]
712    fn get_coroutine_origin_tracking_depth() -> i32 {
713        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.with(|cell| cell.get()) as _
714    }
715
716    #[derive(FromArgs)]
717    struct SetAsyncgenHooksArgs {
718        #[pyarg(any, optional)]
719        firstiter: OptionalArg<Option<PyObjectRef>>,
720        #[pyarg(any, optional)]
721        finalizer: OptionalArg<Option<PyObjectRef>>,
722    }
723
724    #[pyfunction]
725    fn set_asyncgen_hooks(args: SetAsyncgenHooksArgs, vm: &VirtualMachine) -> PyResult<()> {
726        if let Some(Some(finalizer)) = args.finalizer.as_option() {
727            if !finalizer.is_callable() {
728                return Err(vm.new_type_error(format!(
729                    "callable finalizer expected, got {:.50}",
730                    finalizer.class().name()
731                )));
732            }
733        }
734
735        if let Some(Some(firstiter)) = args.firstiter.as_option() {
736            if !firstiter.is_callable() {
737                return Err(vm.new_type_error(format!(
738                    "callable firstiter expected, got {:.50}",
739                    firstiter.class().name()
740                )));
741            }
742        }
743
744        if let Some(finalizer) = args.finalizer.into_option() {
745            crate::vm::thread::ASYNC_GEN_FINALIZER.with(|cell| {
746                cell.replace(finalizer);
747            });
748        }
749        if let Some(firstiter) = args.firstiter.into_option() {
750            crate::vm::thread::ASYNC_GEN_FIRSTITER.with(|cell| {
751                cell.replace(firstiter);
752            });
753        }
754
755        Ok(())
756    }
757
758    #[pyclass(no_attr, name = "asyncgen_hooks")]
759    #[derive(PyStructSequence)]
760    pub(super) struct PyAsyncgenHooks {
761        firstiter: PyObjectRef,
762        finalizer: PyObjectRef,
763    }
764
765    #[pyclass(with(PyStructSequence))]
766    impl PyAsyncgenHooks {}
767
768    #[pyfunction]
769    fn get_asyncgen_hooks(vm: &VirtualMachine) -> PyAsyncgenHooks {
770        PyAsyncgenHooks {
771            firstiter: crate::vm::thread::ASYNC_GEN_FIRSTITER
772                .with(|cell| cell.borrow().clone().to_pyobject(vm)),
773            finalizer: crate::vm::thread::ASYNC_GEN_FINALIZER
774                .with(|cell| cell.borrow().clone().to_pyobject(vm)),
775        }
776    }
777
778    /// sys.flags
779    ///
780    /// Flags provided through command line arguments or environment vars.
781    #[pyclass(no_attr, name = "flags", module = "sys")]
782    #[derive(Debug, PyStructSequence)]
783    pub(super) struct Flags {
784        /// -d
785        debug: u8,
786        /// -i
787        inspect: u8,
788        /// -i
789        interactive: u8,
790        /// -O or -OO
791        optimize: u8,
792        /// -B
793        dont_write_bytecode: u8,
794        /// -s
795        no_user_site: u8,
796        /// -S
797        no_site: u8,
798        /// -E
799        ignore_environment: u8,
800        /// -v
801        verbose: u8,
802        /// -b
803        bytes_warning: u64,
804        /// -q
805        quiet: u8,
806        /// -R
807        hash_randomization: u8,
808        /// -I
809        isolated: u8,
810        /// -X dev
811        dev_mode: bool,
812        /// -X utf8
813        utf8_mode: u8,
814        /// -X int_max_str_digits=number
815        int_max_str_digits: i64,
816        /// -P, `PYTHONSAFEPATH`
817        safe_path: bool,
818        /// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
819        warn_default_encoding: u8,
820    }
821
822    #[pyclass(with(PyStructSequence))]
823    impl Flags {
824        fn from_settings(settings: &Settings) -> Self {
825            Self {
826                debug: settings.debug as u8,
827                inspect: settings.inspect as u8,
828                interactive: settings.interactive as u8,
829                optimize: settings.optimize,
830                dont_write_bytecode: (!settings.write_bytecode) as u8,
831                no_user_site: (!settings.user_site_directory) as u8,
832                no_site: (!settings.import_site) as u8,
833                ignore_environment: settings.ignore_environment as u8,
834                verbose: settings.verbose,
835                bytes_warning: settings.bytes_warning,
836                quiet: settings.quiet as u8,
837                hash_randomization: settings.hash_seed.is_none() as u8,
838                isolated: settings.isolated as u8,
839                dev_mode: settings.dev_mode,
840                utf8_mode: settings.utf8_mode,
841                int_max_str_digits: settings.int_max_str_digits,
842                safe_path: settings.safe_path,
843                warn_default_encoding: settings.warn_default_encoding as u8,
844            }
845        }
846
847        #[pyslot]
848        fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
849            Err(vm.new_type_error("cannot create 'sys.flags' instances".to_owned()))
850        }
851    }
852
853    #[cfg(feature = "threading")]
854    #[pyclass(no_attr, name = "thread_info")]
855    #[derive(PyStructSequence)]
856    pub(super) struct PyThreadInfo {
857        name: Option<&'static str>,
858        lock: Option<&'static str>,
859        version: Option<&'static str>,
860    }
861
862    #[cfg(feature = "threading")]
863    #[pyclass(with(PyStructSequence))]
864    impl PyThreadInfo {
865        const INFO: Self = PyThreadInfo {
866            name: crate::stdlib::thread::_thread::PYTHREAD_NAME,
867            // As I know, there's only way to use lock as "Mutex" in Rust
868            // with satisfying python document spec.
869            lock: Some("mutex+cond"),
870            version: None,
871        };
872    }
873
874    #[pyclass(no_attr, name = "float_info")]
875    #[derive(PyStructSequence)]
876    pub(super) struct PyFloatInfo {
877        max: f64,
878        max_exp: i32,
879        max_10_exp: i32,
880        min: f64,
881        min_exp: i32,
882        min_10_exp: i32,
883        dig: u32,
884        mant_dig: u32,
885        epsilon: f64,
886        radix: u32,
887        rounds: i32,
888    }
889
890    #[pyclass(with(PyStructSequence))]
891    impl PyFloatInfo {
892        const INFO: Self = PyFloatInfo {
893            max: f64::MAX,
894            max_exp: f64::MAX_EXP,
895            max_10_exp: f64::MAX_10_EXP,
896            min: f64::MIN_POSITIVE,
897            min_exp: f64::MIN_EXP,
898            min_10_exp: f64::MIN_10_EXP,
899            dig: f64::DIGITS,
900            mant_dig: f64::MANTISSA_DIGITS,
901            epsilon: f64::EPSILON,
902            radix: f64::RADIX,
903            rounds: 1, // FE_TONEAREST
904        };
905    }
906
907    #[pyclass(no_attr, name = "hash_info")]
908    #[derive(PyStructSequence)]
909    pub(super) struct PyHashInfo {
910        width: usize,
911        modulus: PyUHash,
912        inf: PyHash,
913        nan: PyHash,
914        imag: PyHash,
915        algorithm: &'static str,
916        hash_bits: usize,
917        seed_bits: usize,
918        cutoff: usize,
919    }
920
921    #[pyclass(with(PyStructSequence))]
922    impl PyHashInfo {
923        const INFO: Self = {
924            use rustpython_common::hash::*;
925            PyHashInfo {
926                width: std::mem::size_of::<PyHash>() * 8,
927                modulus: MODULUS,
928                inf: INF,
929                nan: NAN,
930                imag: IMAG,
931                algorithm: ALGO,
932                hash_bits: HASH_BITS,
933                seed_bits: SEED_BITS,
934                cutoff: 0, // no small string optimizations
935            }
936        };
937    }
938
939    #[pyclass(no_attr, name = "int_info")]
940    #[derive(PyStructSequence)]
941    pub(super) struct PyIntInfo {
942        bits_per_digit: usize,
943        sizeof_digit: usize,
944        default_max_str_digits: usize,
945        str_digits_check_threshold: usize,
946    }
947
948    #[pyclass(with(PyStructSequence))]
949    impl PyIntInfo {
950        const INFO: Self = PyIntInfo {
951            bits_per_digit: 30, //?
952            sizeof_digit: std::mem::size_of::<u32>(),
953            default_max_str_digits: 4300,
954            str_digits_check_threshold: 640,
955        };
956    }
957
958    #[pyclass(no_attr, name = "version_info")]
959    #[derive(Default, Debug, PyStructSequence)]
960    pub struct VersionInfo {
961        major: usize,
962        minor: usize,
963        micro: usize,
964        releaselevel: &'static str,
965        serial: usize,
966    }
967
968    #[pyclass(with(PyStructSequence))]
969    impl VersionInfo {
970        pub const VERSION: VersionInfo = VersionInfo {
971            major: version::MAJOR,
972            minor: version::MINOR,
973            micro: version::MICRO,
974            releaselevel: version::RELEASELEVEL,
975            serial: version::SERIAL,
976        };
977        #[pyslot]
978        fn slot_new(
979            _cls: crate::builtins::type_::PyTypeRef,
980            _args: crate::function::FuncArgs,
981            vm: &crate::VirtualMachine,
982        ) -> crate::PyResult {
983            Err(vm.new_type_error("cannot create 'sys.version_info' instances".to_owned()))
984        }
985    }
986
987    #[cfg(windows)]
988    #[pyclass(no_attr, name = "getwindowsversion")]
989    #[derive(Default, Debug, PyStructSequence)]
990    pub(super) struct WindowsVersion {
991        major: u32,
992        minor: u32,
993        build: u32,
994        platform: u32,
995        service_pack: String,
996        service_pack_major: u16,
997        service_pack_minor: u16,
998        suite_mask: u16,
999        product_type: u8,
1000        platform_version: (u32, u32, u32),
1001    }
1002
1003    #[cfg(windows)]
1004    #[pyclass(with(PyStructSequence))]
1005    impl WindowsVersion {}
1006
1007    #[pyclass(no_attr, name = "UnraisableHookArgs")]
1008    #[derive(Debug, PyStructSequence, TryIntoPyStructSequence)]
1009    pub struct UnraisableHookArgs {
1010        pub exc_type: PyTypeRef,
1011        pub exc_value: PyObjectRef,
1012        pub exc_traceback: PyObjectRef,
1013        pub err_msg: PyObjectRef,
1014        pub object: PyObjectRef,
1015    }
1016
1017    #[pyclass(with(PyStructSequence))]
1018    impl UnraisableHookArgs {}
1019}
1020
1021pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) {
1022    sys::extend_module(vm, module).unwrap();
1023
1024    let modules = vm.ctx.new_dict();
1025    modules
1026        .set_item("sys", module.to_owned().into(), vm)
1027        .unwrap();
1028    modules
1029        .set_item("builtins", builtins.to_owned().into(), vm)
1030        .unwrap();
1031    extend_module!(vm, module, {
1032        "__doc__" => sys::DOC.to_owned().to_pyobject(vm),
1033        "modules" => modules,
1034    });
1035}
1036
1037/// Similar to PySys_WriteStderr in CPython.
1038///
1039/// # Usage
1040///
1041/// ```rust,ignore
1042/// writeln!(sys::PyStderr(vm), "foo bar baz :)");
1043/// ```
1044///
1045/// Unlike writing to a `std::io::Write` with the `write[ln]!()` macro, there's no error condition here;
1046/// this is intended to be a replacement for the `eprint[ln]!()` macro, so `write!()`-ing to PyStderr just
1047/// returns `()`.
1048pub struct PyStderr<'vm>(pub &'vm VirtualMachine);
1049
1050impl PyStderr<'_> {
1051    pub fn write_fmt(&self, args: std::fmt::Arguments<'_>) {
1052        use crate::py_io::Write;
1053
1054        let vm = self.0;
1055        if let Ok(stderr) = get_stderr(vm) {
1056            let mut stderr = crate::py_io::PyWriter(stderr, vm);
1057            if let Ok(()) = stderr.write_fmt(args) {
1058                return;
1059            }
1060        }
1061        eprint!("{args}")
1062    }
1063}
1064
1065pub fn get_stdin(vm: &VirtualMachine) -> PyResult {
1066    vm.sys_module
1067        .get_attr("stdin", vm)
1068        .map_err(|_| vm.new_runtime_error("lost sys.stdin".to_owned()))
1069}
1070pub fn get_stdout(vm: &VirtualMachine) -> PyResult {
1071    vm.sys_module
1072        .get_attr("stdout", vm)
1073        .map_err(|_| vm.new_runtime_error("lost sys.stdout".to_owned()))
1074}
1075pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
1076    vm.sys_module
1077        .get_attr("stderr", vm)
1078        .map_err(|_| vm.new_runtime_error("lost sys.stderr".to_owned()))
1079}
1080
1081pub(crate) fn sysconfigdata_name() -> String {
1082    format!(
1083        "_sysconfigdata_{}_{}_{}",
1084        sys::ABIFLAGS,
1085        sys::PLATFORM,
1086        sys::MULTIARCH
1087    )
1088}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.