Skip to content

Commit 0eddee5

Browse files
authored
fix fcntl, sslsocket traverse, super , excepthook (#6623)
* fix fcntl * fix traverse * fix super * excepthook * fix warn
1 parent 546d35b commit 0eddee5

File tree

6 files changed

+166
-7
lines changed

6 files changed

+166
-7
lines changed

Lib/test/test_super.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,6 @@ def test_super_argcount(self):
344344
with self.assertRaisesRegex(TypeError, "expected at most"):
345345
super(int, int, int)
346346

347-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found."
348347
def test_super_argtype(self):
349348
with self.assertRaisesRegex(TypeError, "argument 1 must be a type"):
350349
super(1, int)
@@ -409,7 +408,6 @@ def method(self):
409408
with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"):
410409
C().method()
411410

412-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "argument 1 must be a type" does not match "Expected type 'type' but 'int' found."
413411
def test_bad_first_arg(self):
414412
class C:
415413
def method(self):

crates/stdlib/src/fcntl.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,15 @@ mod fcntl {
9292
#[pyfunction]
9393
fn ioctl(
9494
io::Fildes(fd): io::Fildes,
95-
request: u32,
95+
request: i64,
9696
arg: OptionalArg<Either<Either<ArgMemoryBuffer, ArgStrOrBytesLike>, i32>>,
9797
mutate_flag: OptionalArg<bool>,
9898
vm: &VirtualMachine,
9999
) -> PyResult {
100+
// Convert to unsigned - handles both positive u32 values and negative i32 values
101+
// that represent the same bit pattern (e.g., TIOCSWINSZ on some platforms).
102+
// First truncate to u32 (takes lower 32 bits), then zero-extend to c_ulong.
103+
let request = (request as u32) as libc::c_ulong;
100104
let arg = arg.unwrap_or_else(|| Either::B(0));
101105
match arg {
102106
Either::A(buf_kind) => {

crates/stdlib/src/ssl.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2293,45 +2293,55 @@ mod _ssl {
22932293

22942294
// SSLSocket - represents a TLS-wrapped socket
22952295
#[pyattr]
2296-
#[pyclass(name = "_SSLSocket", module = "ssl")]
2296+
#[pyclass(name = "_SSLSocket", module = "ssl", traverse)]
22972297
#[derive(Debug, PyPayload)]
22982298
pub(crate) struct PySSLSocket {
22992299
// Underlying socket
23002300
sock: PyObjectRef,
23012301
// SSL context
23022302
context: PyRwLock<PyRef<PySSLContext>>,
23032303
// Server-side or client-side
2304+
#[pytraverse(skip)]
23042305
server_side: bool,
23052306
// Server hostname for SNI
2307+
#[pytraverse(skip)]
23062308
server_hostname: PyRwLock<Option<String>>,
23072309
// TLS connection state
2310+
#[pytraverse(skip)]
23082311
connection: PyMutex<Option<TlsConnection>>,
23092312
// Handshake completed flag
2313+
#[pytraverse(skip)]
23102314
handshake_done: PyMutex<bool>,
23112315
// Session was reused (for session resumption tracking)
2316+
#[pytraverse(skip)]
23122317
session_was_reused: PyMutex<bool>,
23132318
// Owner (SSLSocket instance that owns this _SSLSocket)
23142319
owner: PyRwLock<Option<PyObjectRef>>,
23152320
// Session for resumption
23162321
session: PyRwLock<Option<PyObjectRef>>,
23172322
// Verified certificate chain (built during verification)
23182323
#[allow(dead_code)]
2324+
#[pytraverse(skip)]
23192325
verified_chain: PyRwLock<Option<Vec<CertificateDer<'static>>>>,
23202326
// MemoryBIO mode (optional)
23212327
incoming_bio: Option<PyRef<PyMemoryBIO>>,
23222328
outgoing_bio: Option<PyRef<PyMemoryBIO>>,
23232329
// SNI certificate resolver state (for server-side only)
2330+
#[pytraverse(skip)]
23242331
sni_state: PyRwLock<Option<Arc<ParkingMutex<SniCertName>>>>,
23252332
// Pending context change (for SNI callback deferred handling)
23262333
pending_context: PyRwLock<Option<PyRef<PySSLContext>>>,
23272334
// Buffer to store ClientHello for connection recreation
2335+
#[pytraverse(skip)]
23282336
client_hello_buffer: PyMutex<Option<Vec<u8>>>,
23292337
// Shutdown state for tracking close-notify exchange
2338+
#[pytraverse(skip)]
23302339
shutdown_state: PyMutex<ShutdownState>,
23312340
// Deferred client certificate verification error (for TLS 1.3)
23322341
// Stores error message if client cert verification failed during handshake
23332342
// Error is raised on first I/O operation after handshake
23342343
// Using Arc to share with the certificate verifier
2344+
#[pytraverse(skip)]
23352345
deferred_cert_error: Arc<ParkingRwLock<Option<String>>>,
23362346
}
23372347

crates/vm/src/builtins/super.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl Constructor for PySuper {
6161
#[derive(FromArgs)]
6262
pub struct InitArgs {
6363
#[pyarg(positional, optional)]
64-
py_type: OptionalArg<PyTypeRef>,
64+
py_type: OptionalArg<PyObjectRef>,
6565
#[pyarg(positional, optional)]
6666
py_obj: OptionalArg<PyObjectRef>,
6767
}
@@ -75,7 +75,10 @@ impl Initializer for PySuper {
7575
vm: &VirtualMachine,
7676
) -> PyResult<()> {
7777
// Get the type:
78-
let (typ, obj) = if let OptionalArg::Present(ty) = py_type {
78+
let (typ, obj) = if let OptionalArg::Present(ty_obj) = py_type {
79+
let ty = ty_obj
80+
.downcast::<PyType>()
81+
.map_err(|_| vm.new_type_error("super() argument 1 must be a type"))?;
7982
(ty, py_obj.unwrap_or_none(vm))
8083
} else {
8184
let frame = vm

crates/vm/src/stdlib/thread.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,138 @@ pub(crate) mod _thread {
400400
vm.state.thread_count.load()
401401
}
402402

403+
/// ExceptHookArgs - simple class to hold exception hook arguments
404+
/// This allows threading.py to import _excepthook and _ExceptHookArgs from _thread
405+
#[pyattr]
406+
#[pyclass(module = "_thread", name = "_ExceptHookArgs")]
407+
#[derive(Debug, PyPayload)]
408+
struct ExceptHookArgs {
409+
exc_type: crate::PyObjectRef,
410+
exc_value: crate::PyObjectRef,
411+
exc_traceback: crate::PyObjectRef,
412+
thread: crate::PyObjectRef,
413+
}
414+
415+
#[pyclass(with(Constructor))]
416+
impl ExceptHookArgs {
417+
#[pygetset]
418+
fn exc_type(&self) -> crate::PyObjectRef {
419+
self.exc_type.clone()
420+
}
421+
422+
#[pygetset]
423+
fn exc_value(&self) -> crate::PyObjectRef {
424+
self.exc_value.clone()
425+
}
426+
427+
#[pygetset]
428+
fn exc_traceback(&self) -> crate::PyObjectRef {
429+
self.exc_traceback.clone()
430+
}
431+
432+
#[pygetset]
433+
fn thread(&self) -> crate::PyObjectRef {
434+
self.thread.clone()
435+
}
436+
}
437+
438+
impl Constructor for ExceptHookArgs {
439+
// Takes a single iterable argument like namedtuple
440+
type Args = (crate::PyObjectRef,);
441+
442+
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
443+
// Convert the argument to a list/tuple and extract elements
444+
let seq: Vec<crate::PyObjectRef> = args.0.try_to_value(vm)?;
445+
if seq.len() != 4 {
446+
return Err(vm.new_type_error(format!(
447+
"_ExceptHookArgs expected 4 arguments, got {}",
448+
seq.len()
449+
)));
450+
}
451+
Ok(Self {
452+
exc_type: seq[0].clone(),
453+
exc_value: seq[1].clone(),
454+
exc_traceback: seq[2].clone(),
455+
thread: seq[3].clone(),
456+
})
457+
}
458+
}
459+
460+
/// Handle uncaught exception in Thread.run()
461+
#[pyfunction]
462+
fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
463+
// Type check: args must be _ExceptHookArgs
464+
let args = args.downcast::<ExceptHookArgs>().map_err(|_| {
465+
vm.new_type_error(
466+
"_thread._excepthook argument type must be _ExceptHookArgs".to_owned(),
467+
)
468+
})?;
469+
470+
let exc_type = args.exc_type.clone();
471+
let exc_value = args.exc_value.clone();
472+
let exc_traceback = args.exc_traceback.clone();
473+
let thread = args.thread.clone();
474+
475+
// Silently ignore SystemExit (identity check)
476+
if exc_type.is(vm.ctx.exceptions.system_exit.as_ref()) {
477+
return Ok(());
478+
}
479+
480+
// Get stderr - fall back to thread._stderr if sys.stderr is None
481+
let file = match vm.sys_module.get_attr("stderr", vm) {
482+
Ok(stderr) if !vm.is_none(&stderr) => stderr,
483+
_ => {
484+
if vm.is_none(&thread) {
485+
// do nothing if sys.stderr is None and thread is None
486+
return Ok(());
487+
}
488+
let thread_stderr = thread.get_attr("_stderr", vm)?;
489+
if vm.is_none(&thread_stderr) {
490+
// do nothing if sys.stderr is None and sys.stderr was None
491+
// when the thread was created
492+
return Ok(());
493+
}
494+
thread_stderr
495+
}
496+
};
497+
498+
// Print "Exception in thread {thread.name}:"
499+
let thread_name = if !vm.is_none(&thread) {
500+
thread
501+
.get_attr("name", vm)
502+
.ok()
503+
.and_then(|n| n.str(vm).ok())
504+
.map(|s| s.as_str().to_owned())
505+
} else {
506+
None
507+
};
508+
let name = thread_name.unwrap_or_else(|| format!("{}", get_ident()));
509+
510+
let _ = vm.call_method(
511+
&file,
512+
"write",
513+
(format!("Exception in thread {}:\n", name),),
514+
);
515+
516+
// Display the traceback
517+
if let Ok(traceback_mod) = vm.import("traceback", 0)
518+
&& let Ok(print_exc) = traceback_mod.get_attr("print_exception", vm)
519+
{
520+
use crate::function::KwArgs;
521+
let kwargs: KwArgs = vec![("file".to_owned(), file.clone())]
522+
.into_iter()
523+
.collect();
524+
let _ = print_exc.call_with_args(
525+
crate::function::FuncArgs::new(vec![exc_type, exc_value, exc_traceback], kwargs),
526+
vm,
527+
);
528+
}
529+
530+
// Flush file
531+
let _ = vm.call_method(&file, "flush", ());
532+
Ok(())
533+
}
534+
403535
#[pyattr]
404536
#[pyclass(module = "thread", name = "_local")]
405537
#[derive(Debug, PyPayload)]

crates/vm/src/warn.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,20 @@ fn setup_context(
404404

405405
let (globals, filename, lineno) = if let Some(f) = f {
406406
(f.globals.clone(), f.code.source_path, f.f_lineno())
407+
} else if let Some(frame) = vm.current_frame() {
408+
// We have a frame but it wasn't found during stack walking
409+
(frame.globals.clone(), vm.ctx.intern_str("<sys>"), 1)
407410
} else {
408-
(vm.current_globals().clone(), vm.ctx.intern_str("sys"), 1)
411+
// No frames on the stack - use sys.__dict__ (interp->sysdict)
412+
let globals = vm
413+
.sys_module
414+
.as_object()
415+
.get_attr(identifier!(vm, __dict__), vm)
416+
.and_then(|d| {
417+
d.downcast::<crate::builtins::PyDict>()
418+
.map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary"))
419+
})?;
420+
(globals, vm.ctx.intern_str("<sys>"), 0)
409421
};
410422

411423
let registry = if let Ok(registry) = globals.get_item(__warningregistry__, vm) {

0 commit comments

Comments
 (0)