Skip to content

Commit 57b9910

Browse files
committed
Implement maybe_pyc_file and .pyc file execution
1 parent c9bf8df commit 57b9910

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

crates/vm/src/version.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,13 @@ pub fn get_git_datetime() -> String {
126126

127127
format!("{date} {time}")
128128
}
129+
130+
/// Returns the magic number for .pyc files (4 bytes from git revision)
131+
pub fn get_magic_number() -> Vec<u8> {
132+
let mut magic = get_git_revision().into_bytes();
133+
magic.truncate(4);
134+
if magic.len() != 4 {
135+
magic = rustpython_common::rand::os_random::<4>().to_vec();
136+
}
137+
magic
138+
}

crates/vm/src/vm/compile.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,20 @@ impl VirtualMachine {
7575
// Consider to use enum to distinguish `path`
7676
// https://github.com/RustPython/RustPython/pull/6276#discussion_r2529849479
7777

78-
// TODO: check .pyc here
79-
let pyc = false;
78+
let pyc = maybe_pyc_file(path);
8079
if pyc {
81-
todo!("running pyc is not implemented yet");
80+
// pyc file execution
81+
set_main_loader(&module_dict, path, "SourcelessFileLoader", self)?;
82+
let loader = module_dict.get_item("__loader__", self)?;
83+
let get_code = loader.get_attr("get_code", self)?;
84+
let code_obj = get_code.call((identifier!(self, __main__).to_owned(),), self)?;
85+
let code = code_obj
86+
.downcast::<PyCode>()
87+
.map_err(|_| self.new_runtime_error("Bad code object in .pyc file".to_owned()))?;
88+
self.run_code_obj(code, scope)?;
8289
} else {
8390
if path != "<stdin>" {
84-
set_main_loader(&module_dict, path, self)?;
91+
set_main_loader(&module_dict, path, "SourceFileLoader", self)?;
8592
}
8693
// TODO: replace to something equivalent to py_run_file
8794
match std::fs::read_to_string(path) {
@@ -125,16 +132,55 @@ impl VirtualMachine {
125132
}
126133
}
127134

128-
fn set_main_loader(module_dict: &PyDictRef, filename: &str, vm: &VirtualMachine) -> PyResult<()> {
135+
fn set_main_loader(
136+
module_dict: &PyDictRef,
137+
filename: &str,
138+
loader_name: &str,
139+
vm: &VirtualMachine,
140+
) -> PyResult<()> {
129141
vm.import("importlib.machinery", 0)?;
130142
let sys_modules = vm.sys_module.get_attr(identifier!(vm, modules), vm)?;
131143
let machinery = sys_modules.get_item("importlib.machinery", vm)?;
132-
let loader_class = machinery.get_attr("SourceFileLoader", vm)?;
144+
let loader_name = vm.ctx.new_str(loader_name);
145+
let loader_class = machinery.get_attr(&loader_name, vm)?;
133146
let loader = loader_class.call((identifier!(vm, __main__).to_owned(), filename), vm)?;
134147
module_dict.set_item("__loader__", loader, vm)?;
135148
Ok(())
136149
}
137150

151+
/// Check whether a file is maybe a pyc file.
152+
///
153+
/// Detection is performed by:
154+
/// 1. Checking if the filename ends with ".pyc"
155+
/// 2. If not, reading the first 2 bytes and comparing with the magic number
156+
fn maybe_pyc_file(path: &str) -> bool {
157+
// 1. Check if filename ends with ".pyc"
158+
if path.ends_with(".pyc") {
159+
return true;
160+
}
161+
let magic_number = crate::version::get_magic_number();
162+
maybe_pyc_file_with_magic(path, &magic_number).unwrap_or(false)
163+
}
164+
165+
fn maybe_pyc_file_with_magic(path: &str, magic_number: &[u8]) -> std::io::Result<bool> {
166+
// 2. For non-.pyc extension, check magic number
167+
let path_obj = std::path::Path::new(path);
168+
if !path_obj.is_file() {
169+
return Ok(false);
170+
}
171+
172+
let mut file = std::fs::File::open(path)?;
173+
let mut buf = [0u8; 2];
174+
175+
use std::io::Read;
176+
if file.read(&mut buf)? != 2 || magic_number.len() < 2 {
177+
return Ok(false);
178+
}
179+
180+
// Compare with first 2 bytes of magic number (half magic)
181+
Ok(buf == magic_number[..2])
182+
}
183+
138184
fn get_importer(path: &str, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
139185
let path_importer_cache = vm.sys_module.get_attr("path_importer_cache", vm)?;
140186
let path_importer_cache = PyDictRef::try_from_object(vm, path_importer_cache)?;

0 commit comments

Comments
 (0)