Skip to content
Open
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
31 changes: 21 additions & 10 deletions crates/vm/src/stdlib/_ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -388,17 +388,26 @@ impl Debug for PyCFuncPtr {
}
}

// c_void_p accepts only P-tagged argument wrappers.
fn carg_pointer(arg: &PyObject) -> Option<usize> {
let carg = arg.downcast_ref::<CArgObject>()?;
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<usize> {
// Try CArgObject first - extract the wrapped pointer value, applying offset
if let Some(carg) = arg.downcast_ref::<super::_ctypes::CArgObject>() {
if carg.offset != 0
&& let Some(cdata) = carg.obj.downcast_ref::<PyCData>()
{
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::<PyCPointer>() {
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions extra_tests/snippets/stdlib_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading