Skip to main content

rustpython_vm/vm/
python_run.rs

1//! Python code execution functions.
2
3use crate::{
4    Py, PyObjectRef, 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>")
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: &str) -> PyResult {
23        let code_obj = self
24            .compile(source, compiler::Mode::Exec, source_path)
25            .map_err(|err| err.into_pyexception(self, 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    pub fn register_code_in_linecache(&self, code: &Py<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(
39            (PyObjectRef::from(code.to_owned()), source_str, filename),
40            self,
41        )?;
42        Ok(())
43    }
44
45    #[deprecated(note = "use run_string instead")]
46    pub fn run_code_string(&self, scope: Scope, source: &str, source_path: &str) -> PyResult {
47        self.run_string(scope, source, source_path)
48    }
49
50    pub fn run_block_expr(&self, scope: Scope, source: &str) -> PyResult {
51        let code_obj = self
52            .compile(source, compiler::Mode::BlockExpr, "<embedded>")
53            .map_err(|err| err.into_pyexception(self, Some(source)))?;
54        self.run_code_obj(code_obj, scope)
55    }
56}
57
58#[cfg(feature = "host_env")]
59mod file_run {
60    use crate::{
61        AsObject, Py, PyResult, VirtualMachine,
62        builtins::{PyBaseExceptionRef, PyCode, PyDict},
63        compiler::{self},
64        scope::Scope,
65    };
66
67    impl VirtualMachine {
68        /// _PyRun_AnyFileObject (internal)
69        ///
70        /// Execute a Python file. Currently always delegates to run_simple_file
71        /// (interactive mode is handled separately in shell.rs).
72        ///
73        /// Note: This is an internal function. Use `run_file` for the public interface.
74        #[doc(hidden)]
75        pub fn run_any_file(&self, scope: Scope, path: &str) -> PyResult<()> {
76            let path = if path.is_empty() { "???" } else { path };
77            self.run_simple_file(scope, path)
78        }
79
80        /// _PyRun_SimpleFileObject
81        ///
82        /// Execute a Python file with __main__ module setup.
83        /// Sets __file__ and __cached__ before execution, removes them after.
84        fn run_simple_file(&self, scope: Scope, path: &str) -> PyResult<()> {
85            self.with_simple_run(path, |module_dict| {
86                self.run_simple_file_inner(module_dict, scope, path)
87            })
88        }
89
90        fn run_simple_file_inner(
91            &self,
92            module_dict: &Py<PyDict>,
93            scope: Scope,
94            path: &str,
95        ) -> PyResult<()> {
96            let pyc = maybe_pyc_file(path);
97            if pyc {
98                // pyc file execution
99                set_main_loader(module_dict, path, "SourcelessFileLoader", self)?;
100                let loader = module_dict.get_item("__loader__", self)?;
101                let get_code = loader.get_attr("get_code", self)?;
102                let code_obj = get_code.call((identifier!(self, __main__).to_owned(),), self)?;
103                let code = code_obj
104                    .downcast::<PyCode>()
105                    .map_err(|_| self.new_runtime_error("Bad code object in .pyc file"))?;
106                self.run_code_obj(code, scope)?;
107            } else {
108                if path != "<stdin>" {
109                    set_main_loader(module_dict, path, "SourceFileLoader", self)?;
110                }
111                match crate::host_env::fs::read(path) {
112                    Ok(source_bytes) => {
113                        if let Some(null_at) = source_bytes.iter().position(|&b| b == 0) {
114                            return Err(null_byte_syntax_error(self, path, &source_bytes, null_at));
115                        }
116                        #[cfg(feature = "parser")]
117                        // Match compile() by honoring BOMs and encoding cookies in files.
118                        let source = self.decode_source_bytes(&source_bytes, path, false)?;
119                        #[cfg(not(feature = "parser"))]
120                        let source = String::from_utf8(source_bytes)
121                            .map_err(|err| self.new_os_error(err.to_string()))?;
122                        let code_obj = self
123                            .compile(&source, compiler::Mode::Exec, path)
124                            .map_err(|err| err.into_pyexception(self, Some(&source)))?;
125                        self.run_code_obj(code_obj, scope)?;
126                    }
127                    Err(err) => {
128                        return Err(self.new_os_error(err.to_string()));
129                    }
130                }
131            }
132            Ok(())
133        }
134
135        // #[deprecated(note = "use rustpython::run_file instead; if this changes causes problems, please report an issue.")]
136        pub fn run_script(&self, scope: Scope, path: &str) -> PyResult<()> {
137            self.run_any_file(scope, path)
138        }
139    }
140
141    fn null_byte_syntax_error(
142        vm: &VirtualMachine,
143        path: &str,
144        source_bytes: &[u8],
145        null_at: usize,
146    ) -> PyBaseExceptionRef {
147        let before = &source_bytes[..null_at];
148        let lineno = before.iter().filter(|&&b| b == b'\n').count() + 1;
149        let line_start = before
150            .iter()
151            .rposition(|&b| b == b'\n')
152            .map_or(0, |i| i + 1);
153        let line = String::from_utf8_lossy(&source_bytes[line_start..null_at]);
154        let syntax_error = vm.new_exception_msg(
155            vm.ctx.exceptions.syntax_error.to_owned(),
156            "source code cannot contain null bytes".into(),
157        );
158        let obj = syntax_error.as_object();
159        obj.set_attr("filename", vm.ctx.new_str(path), vm).unwrap();
160        obj.set_attr("lineno", vm.ctx.new_int(lineno), vm).unwrap();
161        obj.set_attr("text", vm.ctx.new_str(format!("{line}\n")), vm)
162            .unwrap();
163        syntax_error
164    }
165
166    fn set_main_loader(
167        module_dict: &Py<PyDict>,
168        filename: &str,
169        loader_name: &str,
170        vm: &VirtualMachine,
171    ) -> PyResult<()> {
172        vm.import("importlib.machinery", 0)?;
173        let sys_modules = vm.sys_module.get_attr(identifier!(vm, modules), vm)?;
174        let machinery = sys_modules.get_item("importlib.machinery", vm)?;
175        let loader_name = vm.ctx.new_str(loader_name);
176        let loader_class = machinery.get_attr(&loader_name, vm)?;
177        let loader = loader_class.call((identifier!(vm, __main__).to_owned(), filename), vm)?;
178        module_dict.set_item("__loader__", loader, vm)?;
179        Ok(())
180    }
181
182    /// Check whether a file is maybe a pyc file.
183    ///
184    /// Detection is performed by:
185    /// 1. Checking if the filename ends with ".pyc"
186    /// 2. If not, reading the first 2 bytes and comparing with the magic number
187    fn maybe_pyc_file(path: &str) -> bool {
188        if path.ends_with(".pyc") {
189            return true;
190        }
191        maybe_pyc_file_with_magic(path).unwrap_or(false)
192    }
193
194    fn maybe_pyc_file_with_magic(path: &str) -> std::io::Result<bool> {
195        let path_obj = std::path::Path::new(path);
196        if !path_obj.is_file() {
197            return Ok(false);
198        }
199
200        let mut file = crate::host_env::fs::open(path)?;
201        let mut buf = [0u8; 2];
202
203        use std::io::{Read, Seek, SeekFrom};
204        let n = file.read(&mut buf)?;
205        // /dev/fd/N shares the file offset across every open of that path.
206        // Restore it so a later read of the same script still starts at 0.
207        let _ = file.seek(SeekFrom::Start(0));
208        if n != 2 {
209            return Ok(false);
210        }
211
212        // Read only two bytes of the magic. If the file was opened in
213        // text mode, the bytes 3 and 4 of the magic (\r\n) might not
214        // be read as they are on disk.
215        Ok(crate::import::check_pyc_magic_number_bytes(&buf))
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use crate::object::AsObject;
222    use rustpython_vm::Interpreter;
223
224    fn interpreter() -> Interpreter {
225        Interpreter::without_stdlib(Default::default())
226    }
227
228    #[test]
229    fn block_expr_return_const() {
230        interpreter().enter(|vm| {
231            let scope = vm.new_scope_with_builtins();
232            let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "1"));
233            let value = vm.unwrap_pyresult(value.try_int(vm));
234            let value: u32 = vm.unwrap_pyresult(value.try_to_primitive(vm));
235            assert_eq!(value, 1);
236        })
237    }
238
239    #[test]
240    fn block_expr_return_nonconst() {
241        interpreter().enter(|vm| {
242            let scope = vm.new_scope_with_builtins();
243            vm.unwrap_pyresult(scope.globals.set_item("x", vm.new_pyobj(3), vm));
244            let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "2 + x"));
245            let value = vm.unwrap_pyresult(value.try_int(vm));
246            let value: u32 = vm.unwrap_pyresult(value.try_to_primitive(vm));
247            assert_eq!(value, 5);
248        })
249    }
250
251    #[test]
252    fn block_expr_return_function_def() {
253        interpreter().enter(|vm| {
254            let scope = vm.new_scope_with_builtins();
255            let value =
256                vm.unwrap_pyresult(vm.run_block_expr(scope.clone(), "def f():\n    return 7"));
257            vm.unwrap_pyresult(scope.globals.set_item("returned", value, vm));
258            let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "returned is f"));
259            assert!(value.is(&vm.ctx.true_value));
260        })
261    }
262
263    #[test]
264    fn block_expr_return_class_def() {
265        interpreter().enter(|vm| {
266            let scope = vm.new_scope_with_builtins();
267            let value =
268                vm.unwrap_pyresult(vm.run_block_expr(scope.clone(), "class C:\n    value = 11"));
269            vm.unwrap_pyresult(scope.globals.set_item("returned", value, vm));
270            let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "returned is C"));
271            assert!(value.is(&vm.ctx.true_value));
272        })
273    }
274}