Skip to content

Commit 7599bda

Browse files
ngoldbaumdavidhewitt
authored andcommitted
Use a critical section to serialize adding builtins to globals (#4921)
* add test that panics because __builtins__ isn't available * use a critical section to serialize adding __builtins__ to __globals__ * add release note * use safe APIs instead of PyDict_Contains
1 parent 0ab9dc6 commit 7599bda

File tree

2 files changed

+70
-24
lines changed

2 files changed

+70
-24
lines changed

newsfragments/4921.fixed.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Reenabled a workaround for situations where CPython incorrectly does not add `__builtins__` to `__globals__` in code executed by `Python::py_run`.

src/marker.rs

+69-24
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@
117117
//! [`Rc`]: std::rc::Rc
118118
//! [`Py`]: crate::Py
119119
use crate::conversion::IntoPyObject;
120-
#[cfg(any(doc, not(Py_3_10)))]
121120
use crate::err::PyErr;
122121
use crate::err::{self, PyResult};
123122
use crate::ffi_ptr_ext::FfiPtrExt;
@@ -649,30 +648,34 @@ impl<'py> Python<'py> {
649648
};
650649
let locals = locals.unwrap_or(globals);
651650

652-
#[cfg(not(Py_3_10))]
653-
{
654-
// If `globals` don't provide `__builtins__`, most of the code will fail if Python
655-
// version is <3.10. That's probably not what user intended, so insert `__builtins__`
656-
// for them.
657-
//
658-
// See also:
659-
// - https://github.com/python/cpython/pull/24564 (the same fix in CPython 3.10)
660-
// - https://github.com/PyO3/pyo3/issues/3370
661-
let builtins_s = crate::intern!(self, "__builtins__").as_ptr();
662-
let has_builtins = unsafe { ffi::PyDict_Contains(globals.as_ptr(), builtins_s) };
663-
if has_builtins == -1 {
664-
return Err(PyErr::fetch(self));
665-
}
666-
if has_builtins == 0 {
667-
// Inherit current builtins.
668-
let builtins = unsafe { ffi::PyEval_GetBuiltins() };
669-
670-
// `PyDict_SetItem` doesn't take ownership of `builtins`, but `PyEval_GetBuiltins`
671-
// seems to return a borrowed reference, so no leak here.
672-
if unsafe { ffi::PyDict_SetItem(globals.as_ptr(), builtins_s, builtins) } == -1 {
673-
return Err(PyErr::fetch(self));
651+
// If `globals` don't provide `__builtins__`, most of the code will fail if Python
652+
// version is <3.10. That's probably not what user intended, so insert `__builtins__`
653+
// for them.
654+
//
655+
// See also:
656+
// - https://github.com/python/cpython/pull/24564 (the same fix in CPython 3.10)
657+
// - https://github.com/PyO3/pyo3/issues/3370
658+
let builtins_s = crate::intern!(self, "__builtins__");
659+
let has_builtins = globals.contains(builtins_s)?;
660+
if !has_builtins {
661+
crate::sync::with_critical_section(globals, || {
662+
// check if another thread set __builtins__ while this thread was blocked on the critical section
663+
let has_builtins = globals.contains(builtins_s)?;
664+
if !has_builtins {
665+
// Inherit current builtins.
666+
let builtins = unsafe { ffi::PyEval_GetBuiltins() };
667+
668+
// `PyDict_SetItem` doesn't take ownership of `builtins`, but `PyEval_GetBuiltins`
669+
// seems to return a borrowed reference, so no leak here.
670+
if unsafe {
671+
ffi::PyDict_SetItem(globals.as_ptr(), builtins_s.as_ptr(), builtins)
672+
} == -1
673+
{
674+
return Err(PyErr::fetch(self));
675+
}
674676
}
675-
}
677+
Ok(())
678+
})?;
676679
}
677680

678681
let code_obj = unsafe {
@@ -1018,4 +1021,46 @@ mod tests {
10181021
assert!(matches!(namespace.get_item("__builtins__"), Ok(Some(..))));
10191022
})
10201023
}
1024+
1025+
#[cfg(feature = "macros")]
1026+
#[test]
1027+
fn test_py_run_inserts_globals_2() {
1028+
#[crate::pyclass(crate = "crate")]
1029+
#[derive(Clone)]
1030+
struct CodeRunner {
1031+
code: CString,
1032+
}
1033+
1034+
impl CodeRunner {
1035+
fn reproducer(&mut self, py: Python<'_>) -> PyResult<()> {
1036+
let variables = PyDict::new(py);
1037+
variables.set_item("cls", Py::new(py, self.clone())?)?;
1038+
1039+
py.run(self.code.as_c_str(), Some(&variables), None)?;
1040+
Ok(())
1041+
}
1042+
}
1043+
1044+
#[crate::pymethods(crate = "crate")]
1045+
impl CodeRunner {
1046+
fn func(&mut self, py: Python<'_>) -> PyResult<()> {
1047+
py.import("math")?;
1048+
Ok(())
1049+
}
1050+
}
1051+
1052+
let mut runner = CodeRunner {
1053+
code: CString::new(
1054+
r#"
1055+
cls.func()
1056+
"#
1057+
.to_string(),
1058+
)
1059+
.unwrap(),
1060+
};
1061+
1062+
Python::with_gil(|py| {
1063+
runner.reproducer(py).unwrap();
1064+
});
1065+
}
10211066
}

0 commit comments

Comments
 (0)