From 1e0f75a72a63eafa537e1d516853d2a177687848 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 22 Jul 2026 19:27:03 +0200 Subject: [PATCH 1/3] Fix some pyo3 testcase against the c-api --- crates/capi/src/import.rs | 21 ++++++++++++++++++++- crates/capi/src/methodobject.rs | 14 ++++++++++++++ crates/capi/src/object.rs | 15 +++++++++++++-- crates/capi/src/pycapsule.rs | 23 +++++++++++++++++++++++ crates/vm/src/builtins/capsule.rs | 4 +++- 5 files changed, 73 insertions(+), 4 deletions(-) diff --git a/crates/capi/src/import.rs b/crates/capi/src/import.rs index 3a8dae651c9..d0a4acbbb9c 100644 --- a/crates/capi/src/import.rs +++ b/crates/capi/src/import.rs @@ -8,7 +8,18 @@ use rustpython_vm::import::import_code_obj; pub unsafe extern "C" fn PyImport_Import(name: *mut PyObject) -> *mut PyObject { with_vm(|vm| { let name = unsafe { (&*name).try_downcast_ref::(vm)? }; - vm.import(name, 0) + vm.import(name, 0)?; + + let sys_modules = vm + .sys_module + .get_attr(rustpython_vm::identifier!(vm, modules), vm)?; + let sys_modules = sys_modules.try_downcast_ref::(vm)?; + + if let Some(module) = sys_modules.get_item_opt(name, vm)? { + Ok(module) + } else { + vm.import(name, 0) + } }) } @@ -74,4 +85,12 @@ mod tests { let _module = py.import("types").unwrap(); }) } + + #[test] + fn import_sub_module() { + Python::attach(|py| { + let module = py.import("collections.abc").unwrap(); + module.getattr("Sequence").unwrap(); + }) + } } diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index cc3676ef51a..2be54ca8939 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -53,6 +53,7 @@ pub(crate) fn build_method_def( let flags = PyMethodFlags::from_bits(ml.ml_flags as u32) .ok_or_else(|| vm.new_system_error("PyMethodDef contains unknown flags"))?; + let has_self = has_self && !flags.contains(PyMethodFlags::STATIC); let method = ml.ml_meth; @@ -359,4 +360,17 @@ mod tests { ); }) } + + #[test] + fn wrap_static_no_args_function() { + #[pyfunction()] + fn f() {} + + Python::attach(|py| { + let module = PyModule::new(py, "test_wrap_pyfunction_forms").unwrap(); + + let func = wrap_pyfunction!(f, &module).unwrap(); + func.call0().unwrap(); + }); + } } diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index eabfbef23a1..b364b0b7c11 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -217,7 +217,7 @@ pub unsafe extern "C" fn PyObject_HasAttrWithError( with_vm(|vm| { let obj = unsafe { &*obj }; let name = unsafe { &*attr_name }.try_downcast_ref::(vm)?; - obj.has_attr(name, vm) + Ok(vm.get_attribute_opt(obj.to_owned(), name)?.is_some()) }) } @@ -272,7 +272,7 @@ pub unsafe extern "C" fn PyObject_HasAttrStringWithError( with_vm(|vm| { let obj = unsafe { &*obj }; let name = unsafe { attr_name.try_as_str(vm) }?; - obj.has_attr(name, vm) + Ok(vm.get_attribute_opt(obj.to_owned(), name)?.is_some()) }) } @@ -591,4 +591,15 @@ mod tests { assert!(dict.get_item("foo").is_ok()); }) } + + #[test] + fn hasattr() { + Python::attach(|py| { + let x = 5i32.into_pyobject(py).unwrap(); + assert!(x.is_instance_of::()); + + assert!(x.hasattr("to_bytes").unwrap()); + assert!(!x.hasattr("bbbbbbytes").unwrap()); + }) + } } diff --git a/crates/capi/src/pycapsule.rs b/crates/capi/src/pycapsule.rs index b36dea3d946..f5b792eec4d 100644 --- a/crates/capi/src/pycapsule.rs +++ b/crates/capi/src/pycapsule.rs @@ -48,6 +48,11 @@ pub unsafe extern "C" fn PyCapsule_GetContext(capsule: *mut PyObject) -> *mut c_ let capsule = unsafe { &*capsule } .downcast_ref_if_exact::(vm) .ok_or_else(|| vm.new_value_error("Invalid capsule"))?; + + if capsule.pointer().is_null() { + return Err(vm.new_value_error("Capsule has null pointer")); + } + Ok(capsule.context()) }) } @@ -143,6 +148,7 @@ fn checked_capsule<'a>( #[cfg(test)] mod tests { + use pyo3::ffi; use pyo3::prelude::*; use pyo3::types::PyCapsule; @@ -156,4 +162,21 @@ mod tests { assert_eq!(unsafe { ptr.cast::().as_ref() }, "Some data"); }) } + + #[test] + fn capsule_context_on_invalid_capsule() { + Python::attach(|py| { + let cap = PyCapsule::new_with_value(py, 123u32, c"name").unwrap(); + + // Invalidate the capsule + // SAFETY: intentionally breaking the capsule for testing + unsafe { + ffi::PyCapsule_SetPointer(cap.as_ptr(), core::ptr::null_mut()); + } + + // context() on invalid capsule should fail + let result = cap.context(); + assert!(result.is_err()); + }); + } } diff --git a/crates/vm/src/builtins/capsule.rs b/crates/vm/src/builtins/capsule.rs index 43efa0fb214..553fca89978 100644 --- a/crates/vm/src/builtins/capsule.rs +++ b/crates/vm/src/builtins/capsule.rs @@ -76,7 +76,9 @@ impl Representable for PyCapsule { impl Destructor for PyCapsule { fn del(zelf: &Py, _vm: &VirtualMachine) -> PyResult<()> { - if let Some(destructor) = zelf.destructor() { + if !zelf.pointer().is_null() + && let Some(destructor) = zelf.destructor() + { unsafe { destructor(zelf.as_object().as_raw().cast_mut()) }; } Ok(()) From 9949eb41b7be7006e82c24bf2ef0865e64befa25 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 22 Jul 2026 19:58:23 +0200 Subject: [PATCH 2/3] Simplify --- crates/capi/src/import.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/capi/src/import.rs b/crates/capi/src/import.rs index d0a4acbbb9c..71496ff7637 100644 --- a/crates/capi/src/import.rs +++ b/crates/capi/src/import.rs @@ -1,25 +1,15 @@ use crate::util::CStrExt; use crate::{PyObject, pystate::with_vm}; use core::ffi::c_char; -use rustpython_vm::builtins::{PyCode, PyDict, PyModule, PyStr}; +use rustpython_vm::builtins::{PyCode, PyDict, PyModule, PyStr, PyTuple}; use rustpython_vm::import::import_code_obj; #[unsafe(no_mangle)] pub unsafe extern "C" fn PyImport_Import(name: *mut PyObject) -> *mut PyObject { with_vm(|vm| { let name = unsafe { (&*name).try_downcast_ref::(vm)? }; - vm.import(name, 0)?; - - let sys_modules = vm - .sys_module - .get_attr(rustpython_vm::identifier!(vm, modules), vm)?; - let sys_modules = sys_modules.try_downcast_ref::(vm)?; - - if let Some(module) = sys_modules.get_item_opt(name, vm)? { - Ok(module) - } else { - vm.import(name, 0) - } + let from_list = PyTuple::new_ref_typed(vec![vm.ctx.new_str("*")], &vm.ctx); + vm.import_from(name, &from_list, 0) }) } From 37db0306dc2e1030f2eb135b61ce43b0d31b6b52 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 22 Jul 2026 20:22:42 +0200 Subject: [PATCH 3/3] Spelling --- crates/capi/src/object.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index b364b0b7c11..641dbe3ef9f 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -598,6 +598,7 @@ mod tests { let x = 5i32.into_pyobject(py).unwrap(); assert!(x.is_instance_of::()); + // spell-checker:ignore bbbbbbytes assert!(x.hasattr("to_bytes").unwrap()); assert!(!x.hasattr("bbbbbbytes").unwrap()); })