From c00fd032d0d9e59c31c9c8bd6488f1fcf00cc5e2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 1 Jan 2026 23:52:17 +0900 Subject: [PATCH 1/5] fix fcntl --- crates/stdlib/src/fcntl.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index 822faeeeda..477c1f5421 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -92,11 +92,15 @@ mod fcntl { #[pyfunction] fn ioctl( io::Fildes(fd): io::Fildes, - request: u32, + request: i64, arg: OptionalArg, i32>>, mutate_flag: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + // Convert to unsigned - handles both positive u32 values and negative i32 values + // that represent the same bit pattern (e.g., TIOCSWINSZ on some platforms). + // First truncate to u32 (takes lower 32 bits), then zero-extend to c_ulong. + let request = (request as u32) as libc::c_ulong; let arg = arg.unwrap_or_else(|| Either::B(0)); match arg { Either::A(buf_kind) => { From cc3287b51f95f4067a57ab006cce889f4f739e95 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 1 Jan 2026 23:53:59 +0900 Subject: [PATCH 2/5] fix traverse --- crates/stdlib/src/ssl.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index b90176a62f..c22dd303c5 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -2293,7 +2293,7 @@ mod _ssl { // SSLSocket - represents a TLS-wrapped socket #[pyattr] - #[pyclass(name = "_SSLSocket", module = "ssl")] + #[pyclass(name = "_SSLSocket", module = "ssl", traverse)] #[derive(Debug, PyPayload)] pub(crate) struct PySSLSocket { // Underlying socket @@ -2301,14 +2301,19 @@ mod _ssl { // SSL context context: PyRwLock>, // Server-side or client-side + #[pytraverse(skip)] server_side: bool, // Server hostname for SNI + #[pytraverse(skip)] server_hostname: PyRwLock>, // TLS connection state + #[pytraverse(skip)] connection: PyMutex>, // Handshake completed flag + #[pytraverse(skip)] handshake_done: PyMutex, // Session was reused (for session resumption tracking) + #[pytraverse(skip)] session_was_reused: PyMutex, // Owner (SSLSocket instance that owns this _SSLSocket) owner: PyRwLock>, @@ -2316,22 +2321,27 @@ mod _ssl { session: PyRwLock>, // Verified certificate chain (built during verification) #[allow(dead_code)] + #[pytraverse(skip)] verified_chain: PyRwLock>>>, // MemoryBIO mode (optional) incoming_bio: Option>, outgoing_bio: Option>, // SNI certificate resolver state (for server-side only) + #[pytraverse(skip)] sni_state: PyRwLock>>>, // Pending context change (for SNI callback deferred handling) pending_context: PyRwLock>>, // Buffer to store ClientHello for connection recreation + #[pytraverse(skip)] client_hello_buffer: PyMutex>>, // Shutdown state for tracking close-notify exchange + #[pytraverse(skip)] shutdown_state: PyMutex, // Deferred client certificate verification error (for TLS 1.3) // Stores error message if client cert verification failed during handshake // Error is raised on first I/O operation after handshake // Using Arc to share with the certificate verifier + #[pytraverse(skip)] deferred_cert_error: Arc>>, } From f9f1b09845bf5682b40d07994f5b1ea44f45bf0a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 1 Jan 2026 23:54:16 +0900 Subject: [PATCH 3/5] fix super --- Lib/test/test_super.py | 2 -- crates/vm/src/builtins/super.rs | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 76eda799da..5548f4c71a 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -344,7 +344,6 @@ def test_super_argcount(self): with self.assertRaisesRegex(TypeError, "expected at most"): super(int, int, int) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." def test_super_argtype(self): with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): super(1, int) @@ -409,7 +408,6 @@ def method(self): with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"): C().method() - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found." def test_bad_first_arg(self): class C: def method(self): diff --git a/crates/vm/src/builtins/super.rs b/crates/vm/src/builtins/super.rs index f0d873abfb..893509bc6d 100644 --- a/crates/vm/src/builtins/super.rs +++ b/crates/vm/src/builtins/super.rs @@ -61,7 +61,7 @@ impl Constructor for PySuper { #[derive(FromArgs)] pub struct InitArgs { #[pyarg(positional, optional)] - py_type: OptionalArg, + py_type: OptionalArg, #[pyarg(positional, optional)] py_obj: OptionalArg, } @@ -75,7 +75,10 @@ impl Initializer for PySuper { vm: &VirtualMachine, ) -> PyResult<()> { // Get the type: - let (typ, obj) = if let OptionalArg::Present(ty) = py_type { + let (typ, obj) = if let OptionalArg::Present(ty_obj) = py_type { + let ty = ty_obj + .downcast::() + .map_err(|_| vm.new_type_error("super() argument 1 must be a type"))?; (ty, py_obj.unwrap_or_none(vm)) } else { let frame = vm From 39420724d1810f9d0466cdf56a4f451b7de7ebdf Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 1 Jan 2026 23:54:59 +0900 Subject: [PATCH 4/5] excepthook --- crates/vm/src/stdlib/thread.rs | 132 +++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index eb88191165..b7f00537c2 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -400,6 +400,138 @@ pub(crate) mod _thread { vm.state.thread_count.load() } + /// ExceptHookArgs - simple class to hold exception hook arguments + /// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread + #[pyattr] + #[pyclass(module = "_thread", name = "_ExceptHookArgs")] + #[derive(Debug, PyPayload)] + struct ExceptHookArgs { + exc_type: crate::PyObjectRef, + exc_value: crate::PyObjectRef, + exc_traceback: crate::PyObjectRef, + thread: crate::PyObjectRef, + } + + #[pyclass(with(Constructor))] + impl ExceptHookArgs { + #[pygetset] + fn exc_type(&self) -> crate::PyObjectRef { + self.exc_type.clone() + } + + #[pygetset] + fn exc_value(&self) -> crate::PyObjectRef { + self.exc_value.clone() + } + + #[pygetset] + fn exc_traceback(&self) -> crate::PyObjectRef { + self.exc_traceback.clone() + } + + #[pygetset] + fn thread(&self) -> crate::PyObjectRef { + self.thread.clone() + } + } + + impl Constructor for ExceptHookArgs { + // Takes a single iterable argument like namedtuple + type Args = (crate::PyObjectRef,); + + fn py_new(_cls: &Py, args: Self::Args, vm: &VirtualMachine) -> PyResult { + // Convert the argument to a list/tuple and extract elements + let seq: Vec = args.0.try_to_value(vm)?; + if seq.len() != 4 { + return Err(vm.new_type_error(format!( + "_ExceptHookArgs expected 4 arguments, got {}", + seq.len() + ))); + } + Ok(Self { + exc_type: seq[0].clone(), + exc_value: seq[1].clone(), + exc_traceback: seq[2].clone(), + thread: seq[3].clone(), + }) + } + } + + /// Handle uncaught exception in Thread.run() + #[pyfunction] + fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Type check: args must be _ExceptHookArgs + let args = args.downcast::().map_err(|_| { + vm.new_type_error( + "_thread._excepthook argument type must be _ExceptHookArgs".to_owned(), + ) + })?; + + let exc_type = args.exc_type.clone(); + let exc_value = args.exc_value.clone(); + let exc_traceback = args.exc_traceback.clone(); + let thread = args.thread.clone(); + + // Silently ignore SystemExit (identity check) + if exc_type.is(vm.ctx.exceptions.system_exit.as_ref()) { + return Ok(()); + } + + // Get stderr - fall back to thread._stderr if sys.stderr is None + let file = match vm.sys_module.get_attr("stderr", vm) { + Ok(stderr) if !vm.is_none(&stderr) => stderr, + _ => { + if vm.is_none(&thread) { + // do nothing if sys.stderr is None and thread is None + return Ok(()); + } + let thread_stderr = thread.get_attr("_stderr", vm)?; + if vm.is_none(&thread_stderr) { + // do nothing if sys.stderr is None and sys.stderr was None + // when the thread was created + return Ok(()); + } + thread_stderr + } + }; + + // Print "Exception in thread {thread.name}:" + let thread_name = if !vm.is_none(&thread) { + thread + .get_attr("name", vm) + .ok() + .and_then(|n| n.str(vm).ok()) + .map(|s| s.as_str().to_owned()) + } else { + None + }; + let name = thread_name.unwrap_or_else(|| format!("{}", get_ident())); + + let _ = vm.call_method( + &file, + "write", + (format!("Exception in thread {}:\n", name),), + ); + + // Display the traceback + if let Ok(traceback_mod) = vm.import("traceback", 0) + && let Ok(print_exc) = traceback_mod.get_attr("print_exception", vm) + { + use crate::function::KwArgs; + let kwargs: KwArgs = vec![("file".to_owned(), file.clone())] + .into_iter() + .collect(); + let _ = print_exc.call_with_args( + crate::function::FuncArgs::new(vec![exc_type, exc_value, exc_traceback], kwargs), + vm, + ); + } + + // Flush file + let _ = vm.call_method(&file, "flush", ()); + Ok(()) + } + #[pyattr] #[pyclass(module = "thread", name = "_local")] #[derive(Debug, PyPayload)] From c0489c6e824920febd178b0e6211025a0310491b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 1 Jan 2026 23:56:54 +0900 Subject: [PATCH 5/5] fix warn --- crates/vm/src/warn.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index 09d48078e5..b7249da19f 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -404,8 +404,20 @@ fn setup_context( let (globals, filename, lineno) = if let Some(f) = f { (f.globals.clone(), f.code.source_path, f.f_lineno()) + } else if let Some(frame) = vm.current_frame() { + // We have a frame but it wasn't found during stack walking + (frame.globals.clone(), vm.ctx.intern_str(""), 1) } else { - (vm.current_globals().clone(), vm.ctx.intern_str("sys"), 1) + // No frames on the stack - use sys.__dict__ (interp->sysdict) + let globals = vm + .sys_module + .as_object() + .get_attr(identifier!(vm, __dict__), vm) + .and_then(|d| { + d.downcast::() + .map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary")) + })?; + (globals, vm.ctx.intern_str(""), 0) }; let registry = if let Ok(registry) = globals.get_item(__warningregistry__, vm) {