From 1052e613edce6735343451eacdb896e5d59cb60b Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sun, 25 Oct 2026 22:15:38 -0700 Subject: [PATCH] Preserve interpreter-boundary diagnostics and syslog restrictions (#9365) * Preserve Unicode in thread unraisable messages Keep the callable representation as WTF-8 for unraisable hooks and audit callbacks. Preserve the existing String entry point and stderr fallback; this does not claim lossless stderr rendering. Assisted-by: OpenAI Codex:model unavailable * Use the WTF-8 string conversion constructor Use the existing From<&str> implementation to copy the message prefix without changing Unicode semantics. Assisted-by: OpenAI Codex:model unavailable * Preserve cross-interpreter marshal error causes Wrap failures from existing code/function serialization helpers as NotShareableError, matching the CPython 3.15 marshal boundary while retaining the original exception cause. No new test C API or lifecycle behavior. Assisted-by: OpenAI Codex:model-unavailable * Enforce syslog interpreter boundaries and sync queue cleanup Reject subinterpreter openlog/closelog before audit or host mutation, and reject implicit syslog opening after audit when the process logger is not already open, matching CPython 3.15 guard placement and error text. Already-open logging and setlogmask remain unchanged. Migrate concurrent/interpreters/_queues.py through scripts/update_lib to CPython d220409a13b77de7a6c30abe16ea90db01528edc, removing unused eager pickle import and exception targets. Native serialization imports pickle on demand; module import timing and debug locals may nevertheless differ. No new fixtures or interpreter lifecycle mechanisms are introduced. Assisted-by: OpenAI Codex:model version unavailable --- Lib/concurrent/interpreters/_queues.py | 7 +++---- crates/stdlib/src/syslog.rs | 11 +++++++++++ crates/vm/src/stdlib/_thread.rs | 12 ++++++------ crates/vm/src/vm/crossinterp.rs | 24 +++++++++++++++--------- crates/vm/src/vm/mod.rs | 11 ++++++++++- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Lib/concurrent/interpreters/_queues.py b/Lib/concurrent/interpreters/_queues.py index 56c947f7d..5f3ee0934 100644 --- a/Lib/concurrent/interpreters/_queues.py +++ b/Lib/concurrent/interpreters/_queues.py @@ -1,6 +1,5 @@ """Cross-interpreter Queues High Level Module.""" -import pickle import queue import time import weakref @@ -225,7 +224,7 @@ class Queue: while True: try: _queues.put(self._id, obj, unboundop) - except QueueFull as exc: + except QueueFull: if timeout is not None and time.time() >= end: raise # re-raise time.sleep(_delay) @@ -260,7 +259,7 @@ class Queue: while True: try: obj, unboundop = _queues.get(self._id) - except QueueEmpty as exc: + except QueueEmpty: if timeout is not None and time.time() >= end: raise # re-raise time.sleep(_delay) @@ -279,7 +278,7 @@ class Queue: """ try: obj, unboundop = _queues.get(self._id) - except QueueEmpty as exc: + except QueueEmpty: raise # re-raise if unboundop is not None: assert obj is None, repr(obj) diff --git a/crates/stdlib/src/syslog.rs b/crates/stdlib/src/syslog.rs index 5966dced2..cb00ee080 100644 --- a/crates/stdlib/src/syslog.rs +++ b/crates/stdlib/src/syslog.rs @@ -76,6 +76,9 @@ mod syslog { #[pyfunction] fn openlog(args: OpenLogArgs, vm: &VirtualMachine) -> PyResult<()> { + if !vm.state.is_main_interpreter() { + return Err(vm.new_runtime_error("subinterpreter can't use syslog.openlog()")); + } let logoption = args.logoption; let facility = args.facility; let ident = match args.ident.clone() { @@ -116,6 +119,11 @@ mod syslog { vm.audit("syslog.syslog", || (priority, msg.clone()))?; if !host_syslog::is_open() { + if !vm.state.is_main_interpreter() { + return Err(vm.new_runtime_error( + "subinterpreter can't use syslog.syslog() until the syslog is opened by the main interpreter", + )); + } openlog( OpenLogArgs { facility: LOG_USER, @@ -132,6 +140,9 @@ mod syslog { #[pyfunction] fn closelog(vm: &VirtualMachine) -> PyResult<()> { + if !vm.state.is_main_interpreter() { + return Err(vm.new_runtime_error("subinterpreter can't use syslog.closelog()")); + } vm.audit("syslog.closelog", || ())?; host_syslog::closelog(); diff --git a/crates/vm/src/stdlib/_thread.rs b/crates/vm/src/stdlib/_thread.rs index d086c3f76..450539908 100644 --- a/crates/vm/src/stdlib/_thread.rs +++ b/crates/vm/src/stdlib/_thread.rs @@ -805,12 +805,12 @@ pub(crate) mod _thread { func: &ArgCallable, vm: &VirtualMachine, ) { - let msg = func - .as_ref() - .repr(vm) - .ok() - .map(|repr| format!("Exception ignored in thread started by {}", repr.as_wtf8())); - vm.run_unraisable(exc, msg, vm.ctx.none()); + let msg = func.as_ref().repr(vm).ok().map(|repr| { + let mut message = Wtf8Buf::from("Exception ignored in thread started by "); + message.push_wtf8(repr.as_wtf8()); + vm.ctx.new_str(message) + }); + vm.run_unraisable_with_message(exc, msg, vm.ctx.none()); } fn run_thread(func: ArgCallable, args: FuncArgs, vm: &VirtualMachine) { diff --git a/crates/vm/src/vm/crossinterp.rs b/crates/vm/src/vm/crossinterp.rs index 3c2697be5..07f2ff538 100644 --- a/crates/vm/src/vm/crossinterp.rs +++ b/crates/vm/src/vm/crossinterp.rs @@ -2,7 +2,7 @@ //! //! Interpreters do not share `PyObject` graphs. Shareable values are converted //! to an interpreter-neutral payload and rebuilt in the destination. -// cspell:ignore unpickler +// cspell:ignore unpickler unmarshalled #[cfg(not(feature = "threading"))] use crate::protocol::BufferDescriptor; @@ -351,17 +351,23 @@ impl SharedValue { } fn marshal_dumps(obj: &PyObject, vm: &VirtualMachine) -> PyResult> { - let dumps = vm.import("marshal", 0)?.get_attr("dumps", vm)?; - let bytes = dumps.call((obj.to_owned(),), vm)?; - let bytes = bytes - .downcast::() - .map_err(|_| vm.new_type_error("marshal.dumps() did not return bytes"))?; - Ok(bytes.as_bytes().to_vec()) + let dump = || -> PyResult> { + let dumps = vm.import("marshal", 0)?.get_attr("dumps", vm)?; + let bytes = dumps.call((obj.to_owned(),), vm)?; + let bytes = bytes + .downcast::() + .map_err(|_| vm.new_type_error("marshal.dumps() did not return bytes"))?; + Ok(bytes.as_bytes().to_vec()) + }; + dump().map_err(|cause| not_shareable_error_from(vm, "object could not be marshalled", cause)) } fn marshal_loads(data: &[u8], vm: &VirtualMachine) -> PyResult { - let loads = vm.import("marshal", 0)?.get_attr("loads", vm)?; - loads.call((vm.ctx.new_bytes(data.to_vec()),), vm) + let load = || -> PyResult { + let loads = vm.import("marshal", 0)?.get_attr("loads", vm)?; + loads.call((vm.ctx.new_bytes(data.to_vec()),), vm) + }; + load().map_err(|cause| not_shareable_error_from(vm, "object could not be unmarshalled", cause)) } /// `_memoryview_from_xid`. diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index c899a8c2e..143744ec6 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1816,7 +1816,16 @@ impl VirtualMachine { #[cold] pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option, object: PyObjectRef) { - let msg = msg.map(|msg| self.ctx.new_str(msg)); + self.run_unraisable_with_message(e, msg.map(|msg| self.ctx.new_str(msg)), object); + } + + #[cold] + pub(crate) fn run_unraisable_with_message( + &self, + e: PyBaseExceptionRef, + msg: Option, + object: PyObjectRef, + ) { let write_original = || { let msg = msg.as_ref().map(|msg| msg.to_string_lossy()); self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);