Skip to main content

rustpython_vm/vm/
python_run.rs

1//! Python code execution functions.
2
3use crate::{
4    AsObject, PyRef, PyResult, VirtualMachine,
5    builtins::PyCode,
6    compiler::{self},
7    scope::Scope,
8};
9
10impl VirtualMachine {
11    /// PyRun_SimpleString
12    ///
13    /// Execute a string of Python code in a new scope with builtins.
14    pub fn run_simple_string(&self, source: &str) -> PyResult {
15        let scope = self.new_scope_with_builtins();
16        self.run_string(scope, source, "<string>".to_owned())
17    }
18
19    /// PyRun_String
20    ///
21    /// Execute a string of Python code with explicit scope and source path.
22    pub fn run_string(&self, scope: Scope, source: &str, source_path: String) -> PyResult {
23        let code_obj = self
24            .compile(source, compiler::Mode::Exec, source_path)
25            .map_err(|err| self.new_syntax_error(&err, Some(source)))?;
26        // linecache._register_code(code, source, filename)
27        let _ = self.register_code_in_linecache(&code_obj, source);
28        self.run_code_obj(code_obj, scope)
29    }
30
31    /// Register a code object's source in linecache._interactive_cache
32    /// so that traceback can display source lines and caret indicators.
33    fn register_code_in_linecache(&self, code: &PyRef<PyCode>, source: &str) -> PyResult<()> {
34        let linecache = self.import("linecache", 0)?;
35        let register = linecache.get_attr("_register_code", self)?;
36        let source_str = self.ctx.new_str(source);
37        let filename = self.ctx.new_str(code.source_path().as_str());
38        register.call((code.as_object().to_owned(), source_str, filename), self)?;
39        Ok(())
40    }
41
42    #[deprecated(note = "use run_string instead")]
43    pub fn run_code_string(&self, scope: Scope, source: &str, source_path: String) -> PyResult {
44        self.run_string(scope, source, source_path)
45    }
46
47    pub fn run_block_expr(&self, scope: Scope, source: &str) -> PyResult {
48        let code_obj = self
49            .compile(source, compiler::Mode::BlockExpr, "<embedded>".to_owned())
50            .map_err(|err| self.new_syntax_error(&err, Some(source)))?;
51        self.run_code_obj(code_obj, scope)
52    }
53}
54
55#[cfg(feature = "host_env")]
56mod file_run {
57    use crate::{
58        Py, PyResult, VirtualMachine,
59        builtins::{PyCode, PyDict},
60        compiler::{self},
61        scope::Scope,
62    };
63
64    impl VirtualMachine {
65        /// _PyRun_AnyFileObject (internal)
66        ///
67        /// Execute a Python file. Currently always delegates to run_simple_file
68        /// (interactive mode is handled separately in shell.rs).
69        ///
70        /// Note: This is an internal function. Use `run_file` for the public interface.
71        #[doc(hidden)]
72        pub fn run_any_file(&self, scope: Scope, path: &str) -> PyResult<()> {
73            let path = if path.is_empty() { "???" } else { path };
74            self.run_simple_file(scope, path)
75        }
76
77        /// _PyRun_SimpleFileObject
78        ///
79        /// Execute a Python file with __main__ module setup.
80        /// Sets __file__ and __cached__ before execution, removes them after.
81        fn run_simple_file(&self, scope: Scope, path: &str) -> PyResult<()> {
82            self.with_simple_run(path, |module_dict| {
83                self.run_simple_file_inner(module_dict, scope, path)
84            })
85        }
86
87        fn run_simple_file_inner(
88            &self,
89            module_dict: &Py<PyDict>,
90            scope: Scope,
91            path: &str,
92        ) -> PyResult<()> {
93            let pyc = maybe_pyc_file(path);
94            if pyc {
95                // pyc file execution
96                set_main_loader(module_dict, path, "SourcelessFileLoader", self)?;
97                let loader = module_dict.get_item("__loader__", self)?;
98                let get_code = loader.get_attr("get_code", self)?;
99                let code_obj = get_code.call((identifier!(self, __main__).to_owned(),), self)?;
100                let code = code_obj
101                    .downcast::<PyCode>()
102                    .map_err(|_| self.new_runtime_error("Bad code object in .pyc file"))?;
103                self.run_code_obj(code, scope)?;
104            } else {
105                if path != "<stdin>" {
106                    set_main_loader(module_dict, path, "SourceFileLoader", self)?;
107                }
108                match std::fs::read_to_string(path) {
109                    Ok(source) => {
110                        let code_obj = self
111                            .compile(&source, compiler::Mode::Exec, path.to_owned())
112                            .map_err(|err| self.new_syntax_error(&err, Some(&source)))?;
113                        self.run_code_obj(code_obj, scope)?;
114                    }
115                    Err(err) => {
116                        return Err(self.new_os_error(err.to_string()));
117                    }
118                }
119            }
120            Ok(())
121        }
122
123        // #[deprecated(note = "use rustpython::run_file instead; if this changes causes problems, please report an issue.")]
124        pub fn run_script(&self, scope: Scope, path: &str) -> PyResult<()> {
125            self.run_any_file(scope, path)
126        }
127    }
128
129    fn set_main_loader(
130        module_dict: &Py<PyDict>,
131        filename: &str,
132        loader_name: &str,
133        vm: &VirtualMachine,
134    ) -> PyResult<()> {
135        vm.import("importlib.machinery", 0)?;
136        let sys_modules = vm.sys_module.get_attr(identifier!(vm, modules), vm)?;
137        let machinery = sys_modules.get_item("importlib.machinery", vm)?;
138        let loader_name = vm.ctx.new_str(loader_name);
139        let loader_class = machinery.get_attr(&loader_name, vm)?;
140        let loader = loader_class.call((identifier!(vm, __main__).to_owned(), filename), vm)?;
141        module_dict.set_item("__loader__", loader, vm)?;
142        Ok(())
143    }
144
145    /// Check whether a file is maybe a pyc file.
146    ///
147    /// Detection is performed by:
148    /// 1. Checking if the filename ends with ".pyc"
149    /// 2. If not, reading the first 2 bytes and comparing with the magic number
150    fn maybe_pyc_file(path: &str) -> bool {
151        if path.ends_with(".pyc") {
152            return true;
153        }
154        maybe_pyc_file_with_magic(path).unwrap_or(false)
155    }
156
157    fn maybe_pyc_file_with_magic(path: &str) -> std::io::Result<bool> {
158        let path_obj = std::path::Path::new(path);
159        if !path_obj.is_file() {
160            return Ok(false);
161        }
162
163        let mut file = std::fs::File::open(path)?;
164        let mut buf = [0u8; 2];
165
166        use std::io::Read;
167        if file.read(&mut buf)? != 2 {
168            return Ok(false);
169        }
170
171        // Read only two bytes of the magic. If the file was opened in
172        // text mode, the bytes 3 and 4 of the magic (\r\n) might not
173        // be read as they are on disk.
174        Ok(crate::import::check_pyc_magic_number_bytes(&buf))
175    }
176}