Fix panic in code.replace() with non-interned strings (#8471)

code.replace() called as_interned_str().unwrap() on its string
arguments, which panics when the caller passes a string that has not
been interned. modulefinder's replace_paths_in_code() builds a fresh
co_filename, so any use of ModuleFinder(replace_paths=...) aborted the
interpreter.

Intern the incoming strings instead, and raise TypeError rather than
panicking when a non-string appears in co_names/co_varnames/
co_cellvars/co_freevars.

Unskips test_modulefinder.test_replace_paths.

Co-authored-by: Pablo Garcia <pablito@MacBook-Neo-de-Pablo.local>
This commit is contained in:
MsfPabloandPablo Garcia authored and GitHub committed 2026-08-09 15:06:48 +09:00
1 parent d6161ac321
commit ecfc5cd578
2 files changed
+19 -21

No files matched your search

-2
View File
@@ -390,8 +390,6 @@ class ModuleFinderTest(unittest.TestCase):
os.remove(source_path)
self._do_test(bytecode_test)
# TODO: RUSTPYTHON; panics at code.rs with 'called Option::unwrap() on a None value'
@unittest.skip("TODO: RUSTPYTHON; panics in co_filename replacement")
def test_replace_paths(self):
old_path = os.path.join(self.test_dir, 'a', 'module.py')
new_path = os.path.join(self.test_dir, 'a', 'spam.py')
+19 -19
View File
@@ -1366,19 +1366,25 @@ impl PyCode {
OptionalArg::Missing => self.code.instructions.clone(),
};
let intern_all = |objs: Vec<PyObjectRef>, field: &str| -> PyResult<Box<[_]>> {
objs.into_iter()
.map(|o| {
let s = o.downcast_ref::<super::pystr::PyStr>().ok_or_else(|| {
vm.new_type_error(format!("{field} must be a tuple of strings"))
})?;
Ok(vm.ctx.intern_str(s.as_wtf8()))
})
.collect::<PyResult<Vec<_>>>()
.map(Vec::into_boxed_slice)
};
let cellvars = match co_cellvars {
OptionalArg::Present(cellvars) => cellvars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Present(cellvars) => intern_all(cellvars, "co_cellvars")?,
OptionalArg::Missing => self.code.cellvars.clone(),
};
let freevars = match co_freevars {
OptionalArg::Present(freevars) => freevars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Present(freevars) => intern_all(freevars, "co_freevars")?,
OptionalArg::Missing => self.code.freevars.clone(),
};
@@ -1411,10 +1417,10 @@ impl PyCode {
posonlyarg_count,
arg_count,
kwonlyarg_count,
source_path: source_path.as_object().as_interned_str(vm).unwrap(),
source_path: vm.ctx.intern_str(source_path.as_wtf8()),
first_line_number,
obj_name: obj_name.as_object().as_interned_str(vm).unwrap(),
qualname: qualname.as_object().as_interned_str(vm).unwrap(),
obj_name: vm.ctx.intern_str(obj_name.as_wtf8()),
qualname: vm.ctx.intern_str(qualname.as_wtf8()),
max_stackdepth,
instructions,
@@ -1422,14 +1428,8 @@ impl PyCode {
// It can be removed once we move every other code to use linetable only.
locations: self.code.locations.clone(),
constants: constants.into_iter().map(Literal).collect(),
names: names
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
varnames: varnames
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
names: intern_all(names, "co_names")?,
varnames: intern_all(varnames, "co_varnames")?,
cellvars,
freevars,
localspluskinds: self.code.localspluskinds.clone(),