Visitar URL original
Fix ctypes function pointer casts on Python 3.14 by youknowdot · Pull Request #8965 · RustPython/RustPython · GitHub
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
Preserve callable addresses in ctypes function pointer casts
Read the stored executable address when casting a function pointer and accept function pointer types as cast destinations. The existing ownership dictionary retains the original callback through a cast roundtrip.

Extracted independently on the unchanged Python 3.14 target, without typed callback argument or broader py_object transport changes. One unique callback-address/ownership regression extends the existing ctypes snippet; canonical coverage was checked for duplicates.

Assisted-by: Codex:model-version-unavailable
  • Loading branch information
youknowdot committed Oct 4, 2026
commit e8bd96dc6c32c259c53a43ddbf4ba2c13ff65c64
8 changes: 7 additions & 1 deletion crates/vm/src/stdlib/_ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,10 @@ fn cast_check_pointertype(ctype: &PyObject, vm: &VirtualMachine) -> bool {
return true;
}

// PyCFuncPtrTypeObject_Check - TODO
// PyCFuncPtrTypeObject_Check
if ctype.class().fast_issubclass(PyCFuncPtrType::static_type()) {
return true;
}

// simple pointer types via StgInfo.proto (c_void_p, c_char_p, etc.)
if let Ok(type_attr) = ctype.get_attr("_type_", vm)
Expand Down Expand Up @@ -562,6 +565,9 @@ pub(super) fn cast_impl(
// Simple type (c_void_p, c_char_p, etc.) → value from buffer
let buffer = simple.0.buffer.read();
rustpython_host_env::ctypes::read_pointer_from_buffer(&buffer)
} else if let Some(funcptr) = obj.downcast_ref::<PyCFuncPtr>() {
// A function pointer stores the executable address in its buffer.
funcptr.get_func_ptr()
} else if let Some(cdata) = obj.downcast_ref::<PyCData>() {
// Array, Structure, Union → buffer address (b_ptr)
cdata.buffer.read().as_ptr() as usize
Expand Down
11 changes: 11 additions & 0 deletions extra_tests/snippets/stdlib_ctypes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ctypes
import gc
import itertools
import os as _os
import sys as _sys
Expand Down Expand Up @@ -458,4 +459,14 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
else:
raise AssertionError("an unallocatable array was created")

# A function-pointer cast retains the callback and its executable address.
Unary = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
callback = Unary(lambda value: value + 1)
address = ctypes.cast(callback, ctypes.c_void_p)
roundtrip = ctypes.cast(address, Unary)
assert roundtrip._objects[id(callback)] is callback
del callback, address
gc.collect()
assert roundtrip(41) == 42

print("done")
Loading