From 980cbe61999bc30d97b501871cb16843af689c96 Mon Sep 17 00:00:00 2001 From: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> Date: Sun, 18 Oct 2026 11:41:26 +0300 Subject: [PATCH] Honor stored ctypes byref addresses Assisted-by: Codex:GPT-6 --- crates/vm/src/stdlib/_ctypes/function.rs | 31 ++++++++++++++++-------- extra_tests/snippets/stdlib_ctypes.py | 26 ++++++++++++++++++++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/stdlib/_ctypes/function.rs b/crates/vm/src/stdlib/_ctypes/function.rs index 689d13b6e81..a1d085fa0c3 100644 --- a/crates/vm/src/stdlib/_ctypes/function.rs +++ b/crates/vm/src/stdlib/_ctypes/function.rs @@ -27,7 +27,7 @@ use rustpython_host_env::ctypes::ComMethodError; use rustpython_host_env::ctypes::{ CTypeLayout, CallError, CallOptions, CallRet, CallValue, FfiCif, FfiCodePtr, FfiType, RawMemoryView, RawMemoryViewError, StringAtError, call, ffi_pointer_type, ffi_type_from_code, - ffi_void_type, has_pointer_width, offset_address, pointer_bytes, pointer_format, pointer_size, + ffi_void_type, has_pointer_width, pointer_bytes, pointer_format, pointer_size, simple_type_is_pointer, write_pointer_to_buffer_at, write_prefix_limited, }; @@ -388,17 +388,26 @@ impl Debug for PyCFuncPtr { } } +// c_void_p accepts only P-tagged argument wrappers. +fn carg_pointer(arg: &PyObject) -> Option { + let carg = arg.downcast_ref::()?; + if carg.tag != b'P' { + return None; + } + match &carg.value { + CArgValue::Pointer(address) => Some(*address), + CArgValue::Typed { code: 'P', bytes } => { + Some(rustpython_host_env::ctypes::read_pointer_from_buffer(bytes)) + } + _ => None, + } +} + /// Extract pointer value from a ctypes argument (c_void_p conversion) fn extract_ptr_from_arg(arg: &PyObject, vm: &VirtualMachine) -> PyResult { - // Try CArgObject first - extract the wrapped pointer value, applying offset - if let Some(carg) = arg.downcast_ref::() { - if carg.offset != 0 - && let Some(cdata) = carg.obj.downcast_ref::() - { - let base = cdata.buffer.read().as_ptr() as usize; - return Ok(offset_address(base, carg.offset)); - } - return extract_ptr_from_arg(&carg.obj, vm); + // byref() has already stored the address, including its byte offset. + if let Some(address) = carg_pointer(arg) { + return Ok(address); } // Try to get pointer value from various ctypes types if let Some(ptr) = arg.downcast_ref::() { @@ -549,6 +558,8 @@ pub(super) fn cast_impl( let ptr_value: usize = if vm.is_none(obj) { // None → NULL pointer 0 + } else if let Some(address) = carg_pointer(obj) { + address } else if let Ok(int_val) = obj.try_int(vm) { // int/long → direct pointer value int_val.as_bigint().to_usize().unwrap_or(0) diff --git a/extra_tests/snippets/stdlib_ctypes.py b/extra_tests/snippets/stdlib_ctypes.py index cd3d0206124..4b5dc677031 100644 --- a/extra_tests/snippets/stdlib_ctypes.py +++ b/extra_tests/snippets/stdlib_ctypes.py @@ -469,4 +469,30 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str: gc.collect() assert roundtrip(41) == 42 +# byref carries an address, including its byte offset, rather than the value +# stored in the referenced object. +pair = (ctypes.c_int * 2)(42, 99) +for offset in (0, ctypes.sizeof(ctypes.c_int)): + ref = ctypes.byref(pair, offset) + assert ctypes.cast(ref, ctypes.c_void_p).value == ctypes.addressof(pair) + offset + assert ( + ctypes.string_at(ref, ctypes.sizeof(ctypes.c_int)) + == bytes(pair)[offset : offset + ctypes.sizeof(ctypes.c_int)] + ) +zero = ctypes.c_size_t(0) +assert ctypes.string_at(ctypes.byref(zero), ctypes.sizeof(zero)) == bytes(zero) +param = ctypes.c_void_p.from_param(ctypes.addressof(zero)) +assert ctypes.cast(param, ctypes.c_void_p).value == ctypes.addressof(zero) +assert ctypes.string_at(param, ctypes.sizeof(zero)) == bytes(zero) +for read in ( + lambda p: ctypes.cast(p, ctypes.c_void_p), + lambda p: ctypes.string_at(p, 0), +): + try: + read(ctypes.c_char_p.from_param(b"x")) + except (TypeError, ctypes.ArgumentError): + pass + else: + raise AssertionError("non-pointer argument wrapper accepted") + print("done")