Replace unsafe pointer cast with PyMutex in PyCode (#7055)

Add `source_path: PyMutex<&'static PyStrInterned>` field to
`PyCode` for interior mutability, replacing the UB-inducing
`#[allow(invalid_reference_casting)]` + `write_volatile` pattern
in `update_code_filenames`. Update all read sites across the
codebase to use the new `source_path()` accessor method.
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-02-09 14:01:39 +09:00
1 parent b8de793420
commit bc4e396bf6
10 files changed
+33 -26

No files matched your search

+2 -2
View File
@@ -252,7 +252,7 @@ mod decl {
/// Dump a single frame's info to fd (signal-safe), reading live data.
#[cfg(any(unix, windows))]
fn dump_frame_from_raw(fd: i32, frame: &Frame) {
let filename = frame.code.source_path.as_str();
let filename = frame.code.source_path().as_str();
let funcname = frame.code.obj_name.as_str();
let lasti = frame.lasti();
let lineno = if lasti == 0 {
@@ -328,7 +328,7 @@ mod decl {
#[cfg(any(unix, windows))]
fn dump_frame_from_ref(fd: i32, frame: &crate::vm::PyRef<Frame>) {
let funcname = frame.code.obj_name.as_str();
let filename = frame.code.source_path.as_str();
let filename = frame.code.source_path().as_str();
let lineno = if frame.lasti() == 0 {
frame.code.first_line_number.map(|n| n.get()).unwrap_or(1) as u32
} else {
+19 -5
View File
@@ -15,6 +15,7 @@ use alloc::fmt;
use core::{borrow::Borrow, ops::Deref};
use malachite_bigint::BigInt;
use num_traits::Zero;
use rustpython_common::lock::PyMutex;
use rustpython_compiler_core::{OneIndexed, bytecode::CodeUnits, bytecode::PyCodeLocationInfoKind};
/// State for iterating through code address ranges
@@ -323,6 +324,7 @@ impl<B: AsRef<[u8]>> IntoCodeObject for frozen::FrozenCodeObject<B> {
#[pyclass(module = false, name = "code")]
pub struct PyCode {
pub code: CodeObject,
source_path: PyMutex<&'static PyStrInterned>,
}
impl Deref for PyCode {
@@ -333,8 +335,20 @@ impl Deref for PyCode {
}
impl PyCode {
pub const fn new(code: CodeObject) -> Self {
Self { code }
pub fn new(code: CodeObject) -> Self {
let sp = code.source_path;
Self {
code,
source_path: PyMutex::new(sp),
}
}
pub fn source_path(&self) -> &'static PyStrInterned {
*self.source_path.lock()
}
pub fn set_source_path(&self, new: &'static PyStrInterned) {
*self.source_path.lock() = new;
}
pub fn from_pyc_path(path: &std::path::Path, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
let name = match path.file_stem() {
@@ -397,7 +411,7 @@ impl Representable for PyCode {
"<code object {} at {:#x} file {:?}, line {}>",
code.obj_name,
zelf.get_id(),
code.source_path.as_str(),
zelf.source_path().as_str(),
code.first_line_number.map_or(-1, |n| n.get() as i32)
))
}
@@ -572,7 +586,7 @@ impl PyCode {
#[pygetset]
pub fn co_filename(&self) -> PyStrRef {
self.code.source_path.to_owned()
self.source_path().to_owned()
}
#[pygetset]
@@ -906,7 +920,7 @@ impl PyCode {
let source_path = match co_filename {
OptionalArg::Present(source_path) => source_path,
OptionalArg::Missing => self.code.source_path.to_owned(),
OptionalArg::Missing => self.source_path().to_owned(),
};
let first_line_number = match co_firstlineno {
+1 -1
View File
@@ -131,7 +131,7 @@ impl serde::Serialize for PyTraceback {
let mut struc = s.serialize_struct("PyTraceback", 3)?;
struc.serialize_field("name", self.frame.code.obj_name.as_str())?;
struc.serialize_field("lineno", &self.lineno.get())?;
struc.serialize_field("filename", self.frame.code.source_path.as_str())?;
struc.serialize_field("filename", self.frame.code.source_path().as_str())?;
struc.end()
}
}
+1 -1
View File
@@ -383,7 +383,7 @@ fn write_traceback_entry<W: Write>(
output: &mut W,
tb_entry: &Py<PyTraceback>,
) -> Result<(), W::Error> {
let filename = tb_entry.frame.code.source_path.as_str();
let filename = tb_entry.frame.code.source_path().as_str();
writeln!(
output,
r##" File "{}", line {}, in {}"##,
+1 -1
View File
@@ -3223,7 +3223,7 @@ impl ExecutingFrame<'_> {
self.lasti(),
self.code.obj_name,
op_name,
self.code.source_path
self.code.source_path()
);
}
self.state.stack.drain(stack_len - count..).map(|obj| {
+2 -2
View File
@@ -168,7 +168,7 @@ pub fn import_code_obj(
if set_file_attr {
attrs.set_item(
identifier!(vm, __file__),
code_obj.source_path.to_object(),
code_obj.source_path().to_object(),
vm,
)?;
}
@@ -195,7 +195,7 @@ fn remove_importlib_frames_inner(
return (None, false);
};
let file_name = traceback.frame.code.source_path.as_str();
let file_name = traceback.frame.code.source_path().as_str();
let (inner_tb, mut now_in_importlib) =
remove_importlib_frames_inner(vm, traceback.next.lock().clone(), always_trim);
+4 -11
View File
@@ -229,7 +229,7 @@ mod _imp {
#[pyfunction]
fn _fix_co_filename(code: PyRef<PyCode>, path: PyStrRef, vm: &VirtualMachine) {
let old_name = code.code.source_path;
let old_name = code.source_path();
let new_name = vm.ctx.intern_str(path.as_str());
super::update_code_filenames(&code, old_name, new_name);
}
@@ -288,18 +288,11 @@ fn update_code_filenames(
old_name: &'static PyStrInterned,
new_name: &'static PyStrInterned,
) {
if !core::ptr::eq(code.code.source_path, old_name)
&& code.code.source_path.as_str() != old_name.as_str()
{
let current = code.source_path();
if !core::ptr::eq(current, old_name) && current.as_str() != old_name.as_str() {
return;
}
// SAFETY: called during import before the code object is shared.
// Mutates co_filename in place.
#[allow(invalid_reference_casting)]
unsafe {
let source_path_ptr = &code.code.source_path as *const _ as *mut &'static PyStrInterned;
core::ptr::write_volatile(source_path_ptr, new_name);
}
code.set_source_path(new_name);
for constant in code.code.constants.iter() {
let obj: &crate::PyObject = constant.borrow();
if let Some(inner_code) = obj.downcast_ref::<PyCode>() {
+1 -1
View File
@@ -4971,7 +4971,7 @@ mod _io {
&& let Some(frame) = vm.current_frame()
&& let Some(stdlib_dir) = vm.state.config.paths.stdlib_dir.as_deref()
{
let path = frame.code.source_path.as_str();
let path = frame.code.source_path().as_str();
if !path.starts_with(stdlib_dir) {
stacklevel = stacklevel.saturating_sub(1);
}
+1 -1
View File
@@ -658,7 +658,7 @@ impl Context {
pub fn new_code(&self, code: impl code::IntoCodeObject) -> PyRef<PyCode> {
let code = code.into_code_object(self);
PyRef::new_ref(PyCode { code }, self.types.code_type.to_owned(), None)
PyRef::new_ref(PyCode::new(code), self.types.code_type.to_owned(), None)
}
}
+1 -1
View File
@@ -578,7 +578,7 @@ fn setup_context(
}
let (globals, filename, lineno) = if let Some(f) = f {
(f.globals.clone(), f.code.source_path, f.f_lineno())
(f.globals.clone(), f.code.source_path(), f.f_lineno())
} else if let Some(frame) = vm.current_frame() {
// We have a frame but it wasn't found during stack walking
(frame.globals.clone(), vm.ctx.intern_str("<sys>"), 1)