Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down
6 changes: 5 additions & 1 deletion crates/stdlib/src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,15 @@ mod fcntl {
#[pyfunction]
fn ioctl(
io::Fildes(fd): io::Fildes,
request: u32,
request: i64,
arg: OptionalArg<Either<Either<ArgMemoryBuffer, ArgStrOrBytesLike>, i32>>,
mutate_flag: OptionalArg<bool>,
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) => {
Expand Down
12 changes: 11 additions & 1 deletion crates/stdlib/src/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2293,45 +2293,55 @@ 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
sock: PyObjectRef,
// SSL context
context: PyRwLock<PyRef<PySSLContext>>,
// Server-side or client-side
#[pytraverse(skip)]
server_side: bool,
// Server hostname for SNI
#[pytraverse(skip)]
server_hostname: PyRwLock<Option<String>>,
// TLS connection state
#[pytraverse(skip)]
connection: PyMutex<Option<TlsConnection>>,
// Handshake completed flag
#[pytraverse(skip)]
handshake_done: PyMutex<bool>,
// Session was reused (for session resumption tracking)
#[pytraverse(skip)]
session_was_reused: PyMutex<bool>,
// Owner (SSLSocket instance that owns this _SSLSocket)
owner: PyRwLock<Option<PyObjectRef>>,
// Session for resumption
session: PyRwLock<Option<PyObjectRef>>,
// Verified certificate chain (built during verification)
#[allow(dead_code)]
#[pytraverse(skip)]
verified_chain: PyRwLock<Option<Vec<CertificateDer<'static>>>>,
// MemoryBIO mode (optional)
incoming_bio: Option<PyRef<PyMemoryBIO>>,
outgoing_bio: Option<PyRef<PyMemoryBIO>>,
// SNI certificate resolver state (for server-side only)
#[pytraverse(skip)]
sni_state: PyRwLock<Option<Arc<ParkingMutex<SniCertName>>>>,
// Pending context change (for SNI callback deferred handling)
pending_context: PyRwLock<Option<PyRef<PySSLContext>>>,
// Buffer to store ClientHello for connection recreation
#[pytraverse(skip)]
client_hello_buffer: PyMutex<Option<Vec<u8>>>,
// Shutdown state for tracking close-notify exchange
#[pytraverse(skip)]
shutdown_state: PyMutex<ShutdownState>,
// 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<ParkingRwLock<Option<String>>>,
}

Expand Down
7 changes: 5 additions & 2 deletions crates/vm/src/builtins/super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Constructor for PySuper {
#[derive(FromArgs)]
pub struct InitArgs {
#[pyarg(positional, optional)]
py_type: OptionalArg<PyTypeRef>,
py_type: OptionalArg<PyObjectRef>,
#[pyarg(positional, optional)]
py_obj: OptionalArg<PyObjectRef>,
}
Expand All @@ -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::<PyType>()
.map_err(|_| vm.new_type_error("super() argument 1 must be a type"))?;
(ty, py_obj.unwrap_or_none(vm))
} else {
let frame = vm
Expand Down
132 changes: 132 additions & 0 deletions crates/vm/src/stdlib/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
// Convert the argument to a list/tuple and extract elements
let seq: Vec<crate::PyObjectRef> = 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::<ExceptHookArgs>().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)]
Expand Down
14 changes: 13 additions & 1 deletion crates/vm/src/warn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("<sys>"), 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::<crate::builtins::PyDict>()
.map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary"))
})?;
(globals, vm.ctx.intern_str("<sys>"), 0)
};

let registry = if let Ok(registry) = globals.get_item(__warningregistry__, vm) {
Expand Down
Loading