From e8bd96dc6c32c259c53a43ddbf4ba2c13ff65c64 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sun, 4 Oct 2026 23:53:01 +0900 Subject: [PATCH] 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 --- crates/vm/src/stdlib/_ctypes/function.rs | 8 +++++++- extra_tests/snippets/stdlib_ctypes.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/vm/src/stdlib/_ctypes/function.rs b/crates/vm/src/stdlib/_ctypes/function.rs index e67e3871d14..6066310d534 100644 --- a/crates/vm/src/stdlib/_ctypes/function.rs +++ b/crates/vm/src/stdlib/_ctypes/function.rs @@ -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) @@ -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::() { + // A function pointer stores the executable address in its buffer. + funcptr.get_func_ptr() } else if let Some(cdata) = obj.downcast_ref::() { // Array, Structure, Union → buffer address (b_ptr) cdata.buffer.read().as_ptr() as usize diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index 827fc598826..cd3d0206124 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -1,4 +1,5 @@ import ctypes +import gc import itertools import os as _os import sys as _sys @@ -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")