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: 1 addition & 0 deletions crates/libs/bindgen/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct Config<'a> {
pub sys_fn_ptrs: bool,
pub sys_fn_extern: bool,
pub implement: bool,
pub noexcept: bool,
pub specific_deps: bool,
pub derive: &'a Derive,
pub link: &'a str,
Expand Down
20 changes: 20 additions & 0 deletions crates/libs/bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub fn builder() -> Bindgen {
/// | `--sys-fn-ptrs` | Additionally generates function pointers for sys-style Rust bindings. |
/// | `--sys-fn-extern` | Generates extern declarations rather than link macros for sys-style Rust bindings. |
/// | `--implement` | Includes implementation traits for WinRT interfaces. |
/// | `--noexcept` | Assumes all WinRT methods do not raise exceptions. |
/// | `--link` | Overrides the default `windows-link` implementation for system calls. |
///
///
Expand Down Expand Up @@ -362,6 +363,9 @@ where
"--implement" => {
builder.implement();
}
"--noexcept" => {
builder.noexcept();
}
"--specific-deps" => {
builder.specific_deps();
}
Expand Down Expand Up @@ -439,6 +443,7 @@ pub struct Bindgen {
no_toml: bool,
package: bool,
implement: bool,
noexcept: bool,
specific_deps: bool,
sys: bool,
typedef: bool,
Expand Down Expand Up @@ -592,6 +597,20 @@ impl Bindgen {
self
}

/// Assume that all WinRT methods do not raise exceptions, regardless of whether
/// they have the `NoExceptionAttribute`. This causes bindgen to emit infallible
/// signatures for HRESULT-returning WinRT methods, skipping `Result` propagation.
///
/// Methods that genuinely carry `NoExceptionAttribute` use a `debug_assert!` to
/// validate the success contract, since the metadata guarantees they cannot
/// fail. Methods that are only assumed to be infallible because of this flag
/// instead use a real `assert!` so that an unexpected failure is caught even in
/// release builds rather than silently producing a zeroed result.
pub fn noexcept(&mut self) -> &mut Self {
self.noexcept = true;
self
}

/// Use specific crate dependencies rather than `windows-core`.
pub fn specific_deps(&mut self) -> &mut Self {
self.specific_deps = true;
Expand Down Expand Up @@ -771,6 +790,7 @@ impl Bindgen {
sys_fn_ptrs: self.sys_fn_ptrs,
sys_fn_extern: self.sys_fn_extern,
implement: self.implement,
noexcept: self.noexcept,
specific_deps: self.specific_deps,
link,
warnings: &warnings,
Expand Down
2 changes: 1 addition & 1 deletion crates/libs/bindgen/src/types/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl Delegate {
quote! { F: Fn #signature + Send + 'static }
};

let invoke_upcall = method.write_upcall(quote! { (this.invoke) }, false, config.reader);
let invoke_upcall = method.write_upcall(quote! { (this.invoke) }, false, config);

quote! {
#definition
Expand Down
2 changes: 1 addition & 1 deletion crates/libs/bindgen/src/types/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ impl Interface {
let name = names.add(method.def);
let signature = method.write_abi(config, true);
let call = quote! { #impl_name::#name };
let upcall = method.write_upcall(call, true, config.reader);
let upcall = method.write_upcall(call, true, config);

quote! {
unsafe extern "system" fn #name<#constraints Identity: #impl_name <#(#generics,)*>, const OFFSET: isize> (#signature) -> windows_core::HRESULT {
Expand Down
28 changes: 20 additions & 8 deletions crates/libs/bindgen/src/types/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ impl Method {
.write(config, not)
}

pub fn write_upcall(&self, inner: TokenStream, this: bool, reader: &Reader) -> TokenStream {
let noexcept = self.def.has_attribute("NoExceptionAttribute");
pub fn write_upcall(&self, inner: TokenStream, this: bool, config: &Config) -> TokenStream {
let reader = config.reader;
let noexcept = config.noexcept || self.def.has_attribute("NoExceptionAttribute");

let invoke_args = self.signature
.params
Expand Down Expand Up @@ -145,7 +146,7 @@ impl Method {
named_params: bool,
has_this: bool,
) -> TokenStream {
let noexcept = self.def.has_attribute("NoExceptionAttribute");
let noexcept = config.noexcept || self.def.has_attribute("NoExceptionAttribute");

let params = self.signature.params.iter().map(|p| {
let default_type = p.write_default(config);
Expand Down Expand Up @@ -496,7 +497,18 @@ impl Method {
}
};

let noexcept = self.def.has_attribute("NoExceptionAttribute");
let has_noexcept_attr = self.def.has_attribute("NoExceptionAttribute");
let noexcept = config.noexcept || has_noexcept_attr;
// When the method genuinely carries `NoExceptionAttribute` the metadata
// contract guarantees success, so `debug_assert!` is sufficient. When
// `noexcept` is only being assumed because of the `--noexcept` flag we
// have no such guarantee, so use a real `assert!` that survives release
// builds rather than silently returning a zeroed-out value.
let assert_success = if has_noexcept_attr {
quote! { debug_assert!(hresult__.0 == 0); }
} else {
quote! { assert!(hresult__.0 == 0); }
};

let return_type = if noexcept {
if self.signature.return_type.is_interface() {
Expand Down Expand Up @@ -533,7 +545,7 @@ impl Method {
if noexcept {
quote! {
let hresult__ = #vcall;
debug_assert!(hresult__.0 == 0);
#assert_success
}
} else {
quote! {
Expand All @@ -546,7 +558,7 @@ impl Method {
quote! {
let mut result__ = core::mem::MaybeUninit::zeroed();
let hresult__ = #vcall;
debug_assert!(hresult__.0 == 0);
#assert_success
result__.assume_init()
}
} else {
Expand All @@ -563,14 +575,14 @@ impl Method {
quote! {
let mut result__ = core::mem::zeroed();
let hresult__ = #vcall;
debug_assert!(hresult__.0 == 0);
#assert_success
result__
}
} else {
quote! {
let mut result__ = core::mem::zeroed();
let hresult__ = #vcall;
debug_assert!(hresult__.0 == 0);
#assert_success
core::mem::transmute(result__)
}
}
Expand Down
85 changes: 85 additions & 0 deletions crates/tests/fixtures/harness/data/bindgen/noexcept/expected.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
pub mod Test {
windows_core::imp::define_interface!(ITest, ITest_Vtbl, 0x98189505_75e7_5c31_b3ea_20572501df90);
impl windows_core::RuntimeType for ITest {
const SIGNATURE: windows_core::imp::ConstBuffer =
windows_core::imp::ConstBuffer::for_interface::<Self>();
}
windows_core::imp::interface_hierarchy!(
ITest,
windows_core::IUnknown,
windows_core::IInspectable
);
impl ITest {
pub fn MethodInt32(&self, value: i32) {
unsafe {
let hresult__ = (windows_core::Interface::vtable(self).MethodInt32)(
windows_core::Interface::as_raw(self),
value,
);
assert!(hresult__.0 == 0);
}
}
pub fn ReturnInt32(&self) -> i32 {
unsafe {
let mut result__ = core::mem::zeroed();
let hresult__ = (windows_core::Interface::vtable(self).ReturnInt32)(
windows_core::Interface::as_raw(self),
&mut result__,
);
assert!(hresult__.0 == 0);
result__
}
}
}
impl windows_core::RuntimeName for ITest {
const NAME: &'static str = "Test.ITest";
}
pub trait ITest_Impl: windows_core::IUnknownImpl {
fn MethodInt32(&self, value: i32);
fn ReturnInt32(&self) -> i32;
}
impl ITest_Vtbl {
pub const fn new<Identity: ITest_Impl, const OFFSET: isize>() -> Self {
unsafe extern "system" fn MethodInt32<Identity: ITest_Impl, const OFFSET: isize>(
this: *mut core::ffi::c_void,
value: i32,
) -> windows_core::HRESULT {
unsafe {
let this: &Identity =
&*((this as *const *const ()).offset(OFFSET) as *const Identity);
ITest_Impl::MethodInt32(this, value);
windows_core::HRESULT(0)
}
}
unsafe extern "system" fn ReturnInt32<Identity: ITest_Impl, const OFFSET: isize>(
this: *mut core::ffi::c_void,
result__: *mut i32,
) -> windows_core::HRESULT {
unsafe {
let this: &Identity =
&*((this as *const *const ()).offset(OFFSET) as *const Identity);
let ok__ = ITest_Impl::ReturnInt32(this);
result__.write(ok__);
windows_core::HRESULT(0)
}
}
Self {
base__: windows_core::IInspectable_Vtbl::new::<Identity, ITest, OFFSET>(),
MethodInt32: MethodInt32::<Identity, OFFSET>,
ReturnInt32: ReturnInt32::<Identity, OFFSET>,
}
}
pub fn matches(iid: &windows_core::GUID) -> bool {
iid == &<ITest as windows_core::Interface>::IID
}
}
#[repr(C)]
#[doc(hidden)]
pub struct ITest_Vtbl {
pub base__: windows_core::IInspectable_Vtbl,
pub MethodInt32:
unsafe extern "system" fn(*mut core::ffi::c_void, i32) -> windows_result::HRESULT,
pub ReturnInt32:
unsafe extern "system" fn(*mut core::ffi::c_void, *mut i32) -> windows_result::HRESULT,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
filter = "Test"
no_allow = true
no_comment = true
noexcept = true
specific_deps = true
7 changes: 7 additions & 0 deletions crates/tests/fixtures/harness/data/bindgen/noexcept/input.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[winrt]
mod Test {
interface ITest {
fn MethodInt32(&self, value: i32);
fn ReturnInt32(&self) -> i32;
}
}
9 changes: 7 additions & 2 deletions crates/tests/fixtures/harness/tests/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ impl Fixture {
}

/// A deliberately tiny key=value parser for `fixture.toml`. We only need a
/// handful of declarative knobs (filter, no_allow, no_comment, specific_deps,
/// references); pulling in a full TOML dependency for that would be
/// handful of declarative knobs (filter, no_allow, no_comment, noexcept,
/// specific_deps, references); pulling in a full TOML dependency for that would be
/// disproportionate. The format is a strict subset of TOML so authors can
/// add real TOML structure later without breaking existing fixtures.
///
Expand All @@ -120,6 +120,7 @@ struct FixtureConfig {
filter: Option<String>,
no_allow: bool,
no_comment: bool,
noexcept: bool,
specific_deps: bool,
references: Vec<String>,
/// For the `winmd_to_rdl` group: the prebuilt winmd (or directory) the
Expand Down Expand Up @@ -170,6 +171,7 @@ impl FixtureConfig {
"filter" => cfg.filter = Some(parse_string(value)),
"no_allow" => cfg.no_allow = parse_bool(value),
"no_comment" => cfg.no_comment = parse_bool(value),
"noexcept" => cfg.noexcept = parse_bool(value),
"specific_deps" => cfg.specific_deps = parse_bool(value),
"references" => cfg.references = parse_string_list(value),
"winmd_input" => cfg.winmd_input = Some(parse_string(value)),
Expand Down Expand Up @@ -377,6 +379,9 @@ fn run_bindgen(f: &Fixture) {
if cfg.specific_deps {
bindgen.specific_deps();
}
if cfg.noexcept {
bindgen.noexcept();
}
bindgen.write().unwrap();

diff_or_update(&actual_rs, &f.input("expected.rs"));
Expand Down
Loading