From 292f87e226754562ed5f72e626e03f1c2bdde996 Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Sun, 17 May 2026 14:59:51 +0200 Subject: [PATCH] fix `PyFrame::builtins` type confusion issue --- newsfragments/6052.fixed.md | 1 + src/types/frame.rs | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 newsfragments/6052.fixed.md diff --git a/newsfragments/6052.fixed.md b/newsfragments/6052.fixed.md new file mode 100644 index 00000000000..a4144212aea --- /dev/null +++ b/newsfragments/6052.fixed.md @@ -0,0 +1 @@ +Fixed issue that `PyFrame::builtins` could return a non-dict object. diff --git a/src/types/frame.rs b/src/types/frame.rs index c3002397333..c503186f5aa 100644 --- a/src/types/frame.rs +++ b/src/types/frame.rs @@ -149,11 +149,17 @@ impl<'py> PyFrameMethods<'py> for Bound<'py, PyFrame> { // - `self` is a `PyFrameObject` // - `PyFrame_GetBuiltins` returns an owned reference // - the result can not be null - // - the result is a dict object unsafe { ffi::PyFrame_GetBuiltins(self.as_ptr().cast()) .assume_owned_unchecked(self.py()) - .cast_into_unchecked() + .cast_into() + // The result is expected (and documented) to be a dict object, however it is + // possible for Python code to overwrite `__builtins__` with any arbitrary object. + // As reasonable code should never do this, we panic here for correctness in case + // the type does not match. + // + // See https://github.com/PyO3/pyo3/issues/6048 + .expect("`PyFrame_GetBuiltins` returns a `dict`") } } @@ -304,6 +310,27 @@ def outer(): }) } + #[test] + #[cfg(all(Py_3_11, not(Py_LIMITED_API)))] + #[should_panic(expected = "`PyFrame_GetBuiltins` returns a `dict`")] + fn test_frame_builtins_panics_on_type_error() { + use crate::types::PyAnyMethods as _; + Python::attach(|py| -> PyResult<()> { + let sys = py.import("sys")?; + let globals = PyDict::new(py); + globals.set_item("getframe", sys.getattr("_getframe")?)?; + globals.set_item("__builtins__", py.eval(c"object()", None, None)?)?; + py.run(c"frame = getframe()", Some(&globals), None)?; + let frame: Bound<'_, PyFrame> = globals.get_item("frame")?.cast_into()?; + + // This should panic, as `__builtins__` is not a dict + let _builtins = frame.builtins(); + + Ok(()) + }) + .unwrap(); + } + #[test] #[cfg(all(Py_3_11, not(Py_LIMITED_API)))] fn test_frame_get_globals() {