{"record":{"id":"8bc8f70325af23a0","repo":"PyO3/pyo3","slug":"pyframe-getbuiltins-returns-a-dict","errorCode":null,"errorMessage":"`PyFrame_GetBuiltins` returns a `dict`","messagePattern":"`PyFrame_GetBuiltins` returns a `dict`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/types/frame.rs","lineNumber":162,"sourceCode":"\n    #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]\n    fn builtins(&self) -> Bound<'py, PyDict> {\n        // SAFETY:\n        // - we're attached to the interpreter\n        // - `self` is a `PyFrameObject`\n        // - `PyFrame_GetBuiltins` returns an owned reference\n        // - the result can not be null\n        unsafe {\n            ffi::PyFrame_GetBuiltins(self.as_ptr().cast())\n                .assume_owned_unchecked(self.py())\n                .cast_into()\n                // The result is expected (and documented) to be a dict object, however it is\n                // possible for Python code to overwrite `__builtins__` with any arbitrary object.\n                // As reasonable code should never do this, we panic here for correctness in case\n                // the type does not match.\n                //\n                // See https://github.com/PyO3/pyo3/issues/6048\n                .expect(\"`PyFrame_GetBuiltins` returns a `dict`\")\n        }\n    }\n\n    #[cfg(all(Py_3_11, not(Py_LIMITED_API)))]\n    fn globals(&self) -> Bound<'py, PyDict> {\n        // SAFETY:\n        // - we're attached to the interpreter\n        // - `self` is a `PyFrameObject`\n        // - `PyFrame_GetGlobals` returns an owned reference\n        // - the result can not be null\n        // - the result is a dict object\n        unsafe {\n            ffi::PyFrame_GetGlobals(self.as_ptr().cast())\n                .assume_owned_unchecked(self.py())\n                .cast_into_unchecked()\n        }\n    }\n","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/src/types/frame.rs#L144-L180","documentation":"`PyFrame::builtins()` calls the C-API `PyFrame_GetBuiltins` and expects the result to be a `dict` (the documented type). Python code can legally overwrite a frame/module's `__builtins__` with an arbitrary object, so pyo3 panics for correctness instead of mis-typing the result. This affects Python 3.11+ with the non-limited C API.","triggerScenarios":"Inspecting frames (via `sys._getframe`, profilers, tracers, `inspect` module bridges) where `__builtins__` was replaced with a non-dict object; executing code compiled with `exec(code, {'__builtins__': something_not_a_dict})` and then reading `frame.builtins()` through pyo3.","commonSituations":"Sandboxes or instrumentation frameworks that substitute `__builtins__` (e.g. RestrictedPython-style setups, test harnesses, coverage/tracing tools) combined with Rust frame introspection.","solutions":["Do not overwrite `__builtins__` with a non-dict; if you must inject globals, keep it a dict (e.g. `{'__builtins__': real_builtins_dict, ...}`)","Restore the standard dict for `__builtins__` before code paths that use pyo3 frame introspection","Avoid pyo3 `PyFrame::builtins()` on frames from sandboxed/exec-with-custom-builtins code; read the attribute dynamically and type-check instead","Track/upgrade pyo3 (issue pyo3#6048) in case this becomes a fallible API"],"exampleFix":"# before\nexec(code, {'__builtins__': MyFakeBuiltins()})\n# after\nimport builtins\nglobals_dict = {'__builtins__': builtins.__dict__}\nexec(code, globals_dict)","handlingStrategy":"validation","validationCode":"# Python: ensure __builtins__ is a real dict before frame introspection\nimport builtins\nassert isinstance(globals_dict.get('__builtins__'), dict), 'keep __builtins__ a dict'","typeGuard":"def has_dict_builtins(frame) -> bool:\n    return isinstance(frame.f_builtins, dict)","tryCatchPattern":null,"preventionTips":["Never replace __builtins__ with a non-dict object","If sandboxing, pass {'__builtins__': builtins.__dict__} to exec","Avoid pyo3 frame.builtins() on sandboxed frames","Track pyo3#6048 for a fallible API"],"tags":["rust","pyo3","frame","builtins","type-mismatch"],"backgroundTag":"builtins-not-a-dict","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}