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
1 change: 0 additions & 1 deletion Lib/test/test_ctypes/test_win32_com_foreign_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ class IPersist(IUnknown):

self.assertEqual(0, ppst.Release())

@unittest.expectedFailure # TODO: RUSTPYTHON; - COM iid parameter handling not implemented
def test_with_paramflags_and_iid(self):
class IUnknown(c_void_p):
QueryInterface = proto_query_interface(None, IID_IUnknown)
Expand Down
160 changes: 159 additions & 1 deletion crates/host_env/src/ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1835,7 +1835,11 @@ pub const HRESULT_S_OK: i32 = crate::windows::HRESULT_S_OK;

#[cfg(windows)]
pub fn format_error_message(code: Option<u32>) -> Option<String> {
crate::windows::format_error_message(code)
let code = code
.filter(|&code| code != 0)
.unwrap_or_else(crate::windows::get_last_error);
crate::windows::format_error_message_with_language(code, 1 << 10)
.map(|message| message.trim_end().to_owned())
}

#[cfg(windows)]
Expand Down Expand Up @@ -3860,3 +3864,157 @@ mod tests {
}
}
}

/// Owned UTF-16 fields returned by COM's error-information interfaces.
#[cfg(windows)]
#[derive(Default)]
pub struct ComErrorDetails {
pub description: Option<Vec<u16>>,
pub source: Option<Vec<u16>>,
pub helpfile: Option<Vec<u16>>,
pub help_context: u32,
pub progid: Option<Vec<u16>>,
}

/// Format a COM error like ctypes' FormatError (retain trailing punctuation).
#[cfg(windows)]
pub fn format_com_error_message(status: i32) -> Option<String> {
crate::windows::format_error_message_with_language(status as u32, 1 << 10)
.map(|message| message.trim_end().to_owned())
}

/// Obtain the error information associated with a failed COM method call.
///
/// The caller must detach its interpreter while this runs: COM methods can
/// call back into Python. No borrowed Python buffers may be held here.
///
/// # Safety
/// `com_ptr` must identify a live IUnknown-compatible COM interface for the
/// duration of this call. `iid` is the native byte representation of its GUID.
#[cfg(windows)]
pub unsafe fn com_error_details(com_ptr: usize, iid: &[u8; 16]) -> ComErrorDetails {
use windows_sys::core::GUID;

type QueryInterface =
unsafe extern "system" fn(*mut c_void, *const GUID, *mut *mut c_void) -> i32;
type Release = unsafe extern "system" fn(*mut c_void) -> u32;
type SupportsErrorInfo = unsafe extern "system" fn(*mut c_void, *const GUID) -> i32;
type GetGuid = unsafe extern "system" fn(*mut c_void, *mut GUID) -> i32;
type GetString = unsafe extern "system" fn(*mut c_void, *mut *mut u16) -> i32;
type GetContext = unsafe extern "system" fn(*mut c_void, *mut u32) -> i32;

struct ComRef(*mut c_void);
impl Drop for ComRef {
fn drop(&mut self) {
// SAFETY: ComRef owns one reference on an IUnknown-compatible
// interface, whose third vtable entry is Release.
unsafe {
let release: Release = core::mem::transmute(method(self.0, 2));
release(self.0);
}
}
}

unsafe fn method(interface: *mut c_void, index: usize) -> usize {
// SAFETY: the caller provides a live interface and a valid slot for
// that interface's documented COM ABI.
unsafe { *(*(interface as *const *const usize)).add(index) }
}

unsafe fn string_field(interface: *mut c_void, index: usize) -> Option<Vec<u16>> {
let mut value = core::ptr::null_mut();
// SAFETY: IErrorInfo slots 4–6 all return an owned BSTR through an
// initialized output pointer. Free even if the method reports failure.
unsafe {
let get: GetString = core::mem::transmute(method(interface, index));
get(interface, &mut value);
if value.is_null() {
return None;
}
let units = core::slice::from_raw_parts(value, sys_string_len(value));
// CPython builds these fields from NUL-terminated wide strings.
let end = units
.iter()
.position(|&unit| unit == 0)
.unwrap_or(units.len());
let result = units[..end].to_vec();
sys_free_string(value);
Some(result)
}
}

let mut details = ComErrorDetails::default();
if com_ptr == 0 {
return details;
}
// IID_ISupportErrorInfo = {DF0B3D60-548F-101B-8E65-08002B2BD119}.
let support_iid = GUID {
data1: 0xdf0b3d60,
data2: 0x548f,
data3: 0x101b,
data4: [0x8e, 0x65, 0x08, 0x00, 0x2b, 0x2b, 0xd1, 0x19],
};
// SAFETY: GUID is a 16-byte POD value; the input need not be aligned.
let iid = unsafe { core::ptr::read_unaligned(iid.as_ptr().cast::<GUID>()) };
let interface = com_ptr as *mut c_void;
let mut support = core::ptr::null_mut();
// SAFETY: the live input interface implements IUnknown. A successful
// QueryInterface returns one owned ISupportErrorInfo reference.
let status = unsafe {
let query: QueryInterface = core::mem::transmute(method(interface, 0));
query(interface, &support_iid, &mut support)
};
if status < 0 || support.is_null() {
return details;
}
let support = ComRef(support);
// SAFETY: ISupportErrorInfo's fourth slot accepts the requested IID.
let status = unsafe {
let supports: SupportsErrorInfo = core::mem::transmute(method(support.0, 3));
supports(support.0, &iid)
};
drop(support);
if status < 0 {
return details;
}
let Some(error_info) = get_error_info() else {
return details;
};
if error_info.is_null() {
return details;
}
let error_info = ComRef(error_info);
let mut guid = GUID {
data1: 0,
data2: 0,
data3: 0,
data4: [0; 8],
};
// SAFETY: GetErrorInfo supplied a live IErrorInfo. Its slots have the
// documented signatures, and all output storage is initialized.
let guid_status = unsafe {
details.description = string_field(error_info.0, 5);
let get_guid: GetGuid = core::mem::transmute(method(error_info.0, 3));
let status = get_guid(error_info.0, &mut guid);
let get_context: GetContext = core::mem::transmute(method(error_info.0, 7));
get_context(error_info.0, &mut details.help_context);
details.helpfile = string_field(error_info.0, 6);
details.source = string_field(error_info.0, 4);
status
};
drop(error_info);
if guid_status >= 0 {
// SAFETY: GUID is POD; copy the initialized native representation.
let guid_bytes =
unsafe { core::ptr::read_unaligned((&guid as *const GUID).cast::<[u8; 16]>()) };
if let Some(progid) = prog_id_from_clsid(&guid_bytes) {
// SAFETY: ProgIDFromCLSID returns a NUL-terminated allocation
// owned by this call and released with CoTaskMemFree.
unsafe {
details.progid = Some(WideCStr::from_ptr_str(progid).as_slice().to_vec());
co_task_mem_free(progid.cast());
}
}
}
details
}
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ impl Representable for PySet {
}

if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
let name = (class_name != "set").then_some(class_name);
let name = (!class.is(vm.ctx.types.set_type)).then_some(class_name);
zelf.inner.repr(name, vm)
} else {
Ok(Wtf8Buf::from(format!("{class_name}(...)")))
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2631,7 +2631,7 @@ pub(super) mod types {
// We need this method, because of how `CPython` copies `init`
// from `BaseException` in `SimpleExtendsException` macro.
// See: `BaseException_new`
if *cls.name() == *vm.ctx.exceptions.os_error.name() {
if cls.is(vm.ctx.exceptions.os_error) {
let args_vec = args.args.to_vec();
let len = args_vec.len();
if (2..=5).contains(&len) {
Expand Down
115 changes: 46 additions & 69 deletions crates/vm/src/stdlib/_ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ pub(crate) mod _ctypes {
use crate::convert::ToPyObject;
use crate::function::{ArgStrictInt, Either, OptionalArg};
use crate::types::Representable;
#[cfg(windows)]
use crate::types::{Constructor, Initializer};
use crate::{AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use num_traits::ToPrimitive;

Expand Down Expand Up @@ -294,76 +296,49 @@ pub(crate) mod _ctypes {
)
}

#[cfg(target_os = "windows")]
#[pyattr(name = "COMError", once)]
fn com_error(vm: &VirtualMachine) -> PyTypeRef {
use crate::PyObject;
use crate::builtins::type_::PyAttributes;
use crate::function::FuncArgs;
use crate::types::{PyTypeFlags, PyTypeSlots};

// Sets hresult, text, details as instance attributes in __init__
// This function has InitFunc signature for direct slots.init use
fn comerror_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
let (hresult, text, details): (
Option<PyObjectRef>,
Option<PyObjectRef>,
Option<PyObjectRef>,
) = args.bind(vm)?;
let hresult = hresult.unwrap_or_else(|| vm.ctx.none());
let text = text.unwrap_or_else(|| vm.ctx.none());
let details = details.unwrap_or_else(|| vm.ctx.none());

// Set instance attributes
zelf.set_attr("hresult", hresult, vm)?;
zelf.set_attr("text", text.clone(), vm)?;
zelf.set_attr("details", details.clone(), vm)?;
/// Raised when a COM method call failed.
#[cfg(windows)]
#[pyattr]
#[pyexception(name = "COMError", module = "_ctypes", base = crate::builtins::PyException)]
#[derive(Debug)]
#[repr(transparent)]
struct PyComError(crate::builtins::PyException);

// self.args = args[1:] = (text, details)
// via: PyObject_SetAttrString(self, "args", PySequence_GetSlice(args, 1, size))
let args_tuple: PyObjectRef = vm.ctx.new_tuple(vec![text, details]).into();
zelf.set_attr("args", args_tuple, vm)?;
#[cfg(windows)]
#[pyexception(with(Initializer), flags(HEAPTYPE))]
impl PyComError {}

#[cfg(windows)]
impl Initializer for PyComError {
type Args = crate::function::FuncArgs;

fn slot_init(
zelf: &crate::PyObject,
args: Self::Args,
vm: &VirtualMachine,
) -> PyResult<()> {
let (hresult, text, details): (PyObjectRef, PyObjectRef, PyObjectRef) =
args.bind(vm)?;
let full_args = vec![hresult.clone(), text.clone(), details.clone()];
// Match the observable attribute assignments before restoring the
// base payload directly, without invoking an overriding descriptor.
let short_args: PyObjectRef =
vm.ctx.new_tuple(vec![text.clone(), details.clone()]).into();
zelf.set_attr("args", short_args, vm)?;
zelf.set_attr("hresult", hresult, vm)?;
zelf.set_attr("text", text, vm)?;
zelf.set_attr("details", details, vm)?;
crate::builtins::PyBaseException::slot_init(
zelf,
crate::function::FuncArgs::from(full_args),
vm,
)?;
Ok(())
}

// Create exception type with IMMUTABLETYPE flag
let mut attrs = PyAttributes::default();
attrs.insert(
vm.ctx.intern_str("__module__"),
vm.ctx.new_str("_ctypes").into(),
);
attrs.insert(
vm.ctx.intern_str("__doc__"),
vm.ctx
.new_str("Raised when a COM method call failed.")
.into(),
);

// Create slots with IMMUTABLETYPE flag
let slots = PyTypeSlots {
name: "COMError",
flags: crate::types::AtomicPyTypeFlags::from_plain(
PyTypeFlags::HEAP_TYPE_DICT_IMMUTABLE,
),
..PyTypeSlots::default()
};

let exc_type = PyType::new_heap(
"COMError",
vec![vm.ctx.exceptions.exception_type.to_owned()],
attrs,
slots,
vm.ctx.types.type_type.to_owned(),
&vm.ctx,
)
.unwrap();

// Set our custom init after new_heap, which runs init_slots that would
// otherwise overwrite slots.init with init_wrapper (due to __init__ in MRO).
exc_type.slots.init.store(Some(comerror_init));

exc_type
fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
unreachable!("slot_init is defined")
}
}

#[pyfunction]
Expand Down Expand Up @@ -608,7 +583,7 @@ pub(crate) mod _ctypes {

#[cfg(target_os = "windows")]
#[pyfunction(name = "_check_HRESULT")]
fn check_hresult(hr: i32, vm: &VirtualMachine) -> PyResult<i32> {
pub(super) fn check_hresult(hr: i32, vm: &VirtualMachine) -> PyResult<i32> {
if hr < 0 {
let errno = crate::host_env::os::winerror_to_errno(hr);
let message = crate::host_env::uuid::format_error_message(hr);
Expand Down Expand Up @@ -1042,9 +1017,11 @@ pub(crate) mod _ctypes {

#[cfg(windows)]
#[pyfunction(name = "FormatError")]
fn format_error_func(code: OptionalArg<u32>, _vm: &VirtualMachine) -> String {
rustpython_host_env::ctypes::format_error_message(code.into_option())
.unwrap_or_else(|| "<no description>".to_string())
fn format_error_func(code: OptionalArg<i32>, _vm: &VirtualMachine) -> String {
rustpython_host_env::ctypes::format_error_message(
code.into_option().map(|code| code as u32),
)
.unwrap_or_else(|| "<no description>".to_string())
}

#[cfg(windows)]
Expand Down
Loading
Loading