diff --git a/crates/libs/bindgen/src/config/mod.rs b/crates/libs/bindgen/src/config/mod.rs index 13005e68f24..8d94a97e0c2 100644 --- a/crates/libs/bindgen/src/config/mod.rs +++ b/crates/libs/bindgen/src/config/mod.rs @@ -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, diff --git a/crates/libs/bindgen/src/lib.rs b/crates/libs/bindgen/src/lib.rs index 510d8b48c46..b0e324ac4a8 100644 --- a/crates/libs/bindgen/src/lib.rs +++ b/crates/libs/bindgen/src/lib.rs @@ -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. | /// /// @@ -362,6 +363,9 @@ where "--implement" => { builder.implement(); } + "--noexcept" => { + builder.noexcept(); + } "--specific-deps" => { builder.specific_deps(); } @@ -439,6 +443,7 @@ pub struct Bindgen { no_toml: bool, package: bool, implement: bool, + noexcept: bool, specific_deps: bool, sys: bool, typedef: bool, @@ -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; @@ -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, diff --git a/crates/libs/bindgen/src/types/delegate.rs b/crates/libs/bindgen/src/types/delegate.rs index abec4ee3a00..e83d583cf5e 100644 --- a/crates/libs/bindgen/src/types/delegate.rs +++ b/crates/libs/bindgen/src/types/delegate.rs @@ -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 diff --git a/crates/libs/bindgen/src/types/interface.rs b/crates/libs/bindgen/src/types/interface.rs index 84e452d36ac..326cb1366df 100644 --- a/crates/libs/bindgen/src/types/interface.rs +++ b/crates/libs/bindgen/src/types/interface.rs @@ -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 { diff --git a/crates/libs/bindgen/src/types/method.rs b/crates/libs/bindgen/src/types/method.rs index 925dc43bd15..edf85c208ea 100644 --- a/crates/libs/bindgen/src/types/method.rs +++ b/crates/libs/bindgen/src/types/method.rs @@ -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 @@ -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); @@ -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() { @@ -533,7 +545,7 @@ impl Method { if noexcept { quote! { let hresult__ = #vcall; - debug_assert!(hresult__.0 == 0); + #assert_success } } else { quote! { @@ -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 { @@ -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__) } } diff --git a/crates/tests/fixtures/harness/data/bindgen/noexcept/expected.rs b/crates/tests/fixtures/harness/data/bindgen/noexcept/expected.rs new file mode 100644 index 00000000000..0be30d7c53b --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/noexcept/expected.rs @@ -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::(); + } + 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() -> Self { + unsafe extern "system" fn MethodInt32( + 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( + 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::(), + MethodInt32: MethodInt32::, + ReturnInt32: ReturnInt32::, + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::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, + } +} diff --git a/crates/tests/fixtures/harness/data/bindgen/noexcept/fixture.toml b/crates/tests/fixtures/harness/data/bindgen/noexcept/fixture.toml new file mode 100644 index 00000000000..75b0ddf1620 --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/noexcept/fixture.toml @@ -0,0 +1,5 @@ +filter = "Test" +no_allow = true +no_comment = true +noexcept = true +specific_deps = true diff --git a/crates/tests/fixtures/harness/data/bindgen/noexcept/input.rdl b/crates/tests/fixtures/harness/data/bindgen/noexcept/input.rdl new file mode 100644 index 00000000000..bd762506f62 --- /dev/null +++ b/crates/tests/fixtures/harness/data/bindgen/noexcept/input.rdl @@ -0,0 +1,7 @@ +#[winrt] +mod Test { + interface ITest { + fn MethodInt32(&self, value: i32); + fn ReturnInt32(&self) -> i32; + } +} diff --git a/crates/tests/fixtures/harness/tests/fixtures.rs b/crates/tests/fixtures/harness/tests/fixtures.rs index b7eb4919e6c..e896f5a506e 100644 --- a/crates/tests/fixtures/harness/tests/fixtures.rs +++ b/crates/tests/fixtures/harness/tests/fixtures.rs @@ -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. /// @@ -120,6 +120,7 @@ struct FixtureConfig { filter: Option, no_allow: bool, no_comment: bool, + noexcept: bool, specific_deps: bool, references: Vec, /// For the `winmd_to_rdl` group: the prebuilt winmd (or directory) the @@ -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)), @@ -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"));