Skip to content

Commit 93699b8

Browse files
authored
Merge pull request #607 from nicholasbishop/bishop-protocol-macro-3
Add `unsafe_protocol` macro and drop use of the unstable `negative_impls` feature
2 parents 2af0775 + 19e29dc commit 93699b8

40 files changed

+211
-237
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## uefi - [Unreleased]
44

55
### Added
6+
67
- Implementations for the trait `EqStrUntilNul` now allow `?Sized` inputs. This means that
78
you can write `some_cstr16.eq_str_until_nul("test")` instead of
89
`some_cstr16.eq_str_until_nul(&"test")` now.
@@ -14,14 +15,22 @@
1415
- Added an `core::error::Error` implementation for `Error` to ease
1516
integration with error-handling crates. (requires the **unstable** feature)
1617
- Added partial support for the TCG protocols for TPM devices under `uefi::proto::tcg`.
18+
- Added the `unsafe_protocol` macro to provide a slightly nicer way to
19+
implement protocols.
1720

1821
### Changed
1922

2023
- `UnalignedSlice` now implements `Clone`, and the `Debug` impl now
2124
prints the elements instead of the internal fields.
25+
- The unstable `negative_impls` feature is no longer required to use this library.
2226

2327
### Removed
2428

29+
- The `unsafe_guid` attribute macro and `Protocol` derive macro have
30+
been removed. For implementing protocols, use the `unsafe_protocol`
31+
macro instead. For any other implementations of the `Identify` trait,
32+
implement it directly.
33+
2534
## uefi-macros - [Unreleased]
2635

2736
## uefi-services - [Unreleased]

CONTRIBUTING.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ in the order the UEFI spec requires.
8181
Each protocol also has a Globally Unique Identifier (in the C API, they're usually
8282
found in a `EFI_*_PROTOCOL_GUID` define). In Rust, we store the GUID as an associated
8383
constant, by implementing the unsafe trait `uefi::proto::Identify`. For convenience,
84-
this is done through the `unsafe_guid` macro.
84+
this is done through the `unsafe_protocol` macro.
8585

8686
Finally, you should derive the `Protocol` trait. This is a marker trait,
8787
extending `Identify`, which is used as a generic bound in the functions which retrieve
@@ -92,8 +92,7 @@ An example protocol declaration:
9292
```rust
9393
/// Protocol which does something.
9494
#[repr(C)]
95-
#[unsafe_guid("abcdefgh-1234-5678-9012-123456789abc")]
96-
#[derive(Protocol)]
95+
#[unsafe_protocol("abcdefgh-1234-5678-9012-123456789abc")]
9796
pub struct NewProtocol {
9897
some_entry_point: extern "efiapi" fn(
9998
this: *const NewProtocol,

uefi-macros/src/lib.rs

+60-64
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,10 @@ use proc_macro::TokenStream;
77
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
88
use quote::{quote, ToTokens, TokenStreamExt};
99
use syn::{
10-
parse::{Parse, ParseStream},
11-
parse_macro_input, parse_quote,
12-
spanned::Spanned,
13-
DeriveInput, Error, FnArg, Generics, Ident, ItemFn, ItemType, LitStr, Pat, Visibility,
10+
parse_macro_input, parse_quote, spanned::Spanned, Error, Fields, FnArg, Ident, ItemFn,
11+
ItemStruct, LitStr, Pat, Visibility,
1412
};
1513

16-
/// Parses a type definition, extracts its identifier and generic parameters
17-
struct TypeDefinition {
18-
ident: Ident,
19-
generics: Generics,
20-
}
21-
22-
impl Parse for TypeDefinition {
23-
fn parse(input: ParseStream) -> syn::Result<Self> {
24-
if let Ok(d) = DeriveInput::parse(input) {
25-
Ok(Self {
26-
ident: d.ident,
27-
generics: d.generics,
28-
})
29-
} else if let Ok(t) = ItemType::parse(input) {
30-
Ok(Self {
31-
ident: t.ident,
32-
generics: t.generics,
33-
})
34-
} else {
35-
Err(input.error("Input is not an alias, enum, struct or union definition"))
36-
}
37-
}
38-
}
39-
4014
macro_rules! err {
4115
($span:expr, $message:expr $(,)?) => {
4216
Error::new($span.span(), $message).to_compile_error()
@@ -46,28 +20,70 @@ macro_rules! err {
4620
};
4721
}
4822

49-
/// `unsafe_guid` attribute macro, implements the `Identify` trait for any type
50-
/// (mostly works like a custom derive, but also supports type aliases)
23+
/// Attribute macro for marking structs as UEFI protocols.
24+
///
25+
/// The macro takes one argument, a GUID string.
26+
///
27+
/// The macro can only be applied to a struct, and the struct must have
28+
/// named fields (i.e. not a unit or tuple struct). It implements the
29+
/// [`Protocol`] trait and the `unsafe` [`Identify`] trait for the
30+
/// struct. It also adds a hidden field that causes the struct to be
31+
/// marked as [`!Send` and `!Sync`][send-and-sync].
32+
///
33+
/// # Safety
34+
///
35+
/// The caller must ensure that the correct GUID is attached to the
36+
/// type. An incorrect GUID could lead to invalid casts and other
37+
/// unsound behavior.
38+
///
39+
/// # Example
40+
///
41+
/// ```
42+
/// use uefi::{Identify, guid};
43+
/// use uefi::proto::unsafe_protocol;
44+
///
45+
/// #[unsafe_protocol("12345678-9abc-def0-1234-56789abcdef0")]
46+
/// struct ExampleProtocol {}
47+
///
48+
/// assert_eq!(ExampleProtocol::GUID, guid!("12345678-9abc-def0-1234-56789abcdef0"));
49+
/// ```
50+
///
51+
/// [`Identify`]: https://docs.rs/uefi/latest/uefi/trait.Identify.html
52+
/// [`Protocol`]: https://docs.rs/uefi/latest/uefi/proto/trait.Protocol.html
53+
/// [send-and-sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html
5154
#[proc_macro_attribute]
52-
pub fn unsafe_guid(args: TokenStream, input: TokenStream) -> TokenStream {
53-
// Parse the arguments and input using Syn
55+
pub fn unsafe_protocol(args: TokenStream, input: TokenStream) -> TokenStream {
56+
// Parse `args` as a GUID string.
5457
let (time_low, time_mid, time_high_and_version, clock_seq_and_variant, node) =
5558
match parse_guid(parse_macro_input!(args as LitStr)) {
5659
Ok(data) => data,
5760
Err(tokens) => return tokens.into(),
5861
};
5962

60-
let mut result: TokenStream2 = input.clone().into();
63+
let item_struct = parse_macro_input!(input as ItemStruct);
6164

62-
let type_definition = parse_macro_input!(input as TypeDefinition);
63-
64-
// At this point, we know everything we need to implement Identify
65-
let ident = &type_definition.ident;
66-
let (impl_generics, ty_generics, where_clause) = type_definition.generics.split_for_impl();
65+
let ident = &item_struct.ident;
66+
let struct_attrs = &item_struct.attrs;
67+
let struct_vis = &item_struct.vis;
68+
let struct_fields = if let Fields::Named(struct_fields) = &item_struct.fields {
69+
&struct_fields.named
70+
} else {
71+
return err!(item_struct, "Protocol struct must used named fields").into();
72+
};
73+
let struct_generics = &item_struct.generics;
74+
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
75+
76+
quote! {
77+
#(#struct_attrs)*
78+
#struct_vis struct #ident #struct_generics {
79+
// Add a hidden field with `PhantomData` of a raw
80+
// pointer. This has the implicit side effect of making the
81+
// struct !Send and !Sync.
82+
_no_send_or_sync: ::core::marker::PhantomData<*const u8>,
83+
#struct_fields
84+
}
6785

68-
result.append_all(quote! {
6986
unsafe impl #impl_generics ::uefi::Identify for #ident #ty_generics #where_clause {
70-
#[doc(hidden)]
7187
const GUID: ::uefi::Guid = ::uefi::Guid::from_values(
7288
#time_low,
7389
#time_mid,
@@ -76,8 +92,10 @@ pub fn unsafe_guid(args: TokenStream, input: TokenStream) -> TokenStream {
7692
#node,
7793
);
7894
}
79-
});
80-
result.into()
95+
96+
impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {}
97+
}
98+
.into()
8199
}
82100

83101
/// Create a `Guid` at compile time.
@@ -164,28 +182,6 @@ fn parse_guid(guid_lit: LitStr) -> Result<(u32, u16, u16, u16, u64), TokenStream
164182
))
165183
}
166184

167-
/// Custom derive for the `Protocol` trait
168-
#[proc_macro_derive(Protocol)]
169-
pub fn derive_protocol(item: TokenStream) -> TokenStream {
170-
// Parse the input using Syn
171-
let item = parse_macro_input!(item as DeriveInput);
172-
173-
// Then implement Protocol
174-
let ident = item.ident.clone();
175-
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
176-
let result = quote! {
177-
// Mark this as a `Protocol` implementation
178-
impl #impl_generics ::uefi::proto::Protocol for #ident #ty_generics #where_clause {}
179-
180-
// Most UEFI functions expect to be called on the bootstrap processor.
181-
impl #impl_generics !Send for #ident #ty_generics #where_clause {}
182-
183-
// Most UEFI functions do not support multithreaded access.
184-
impl #impl_generics !Sync for #ident #ty_generics #where_clause {}
185-
};
186-
result.into()
187-
}
188-
189185
/// Get the name of a function's argument at `arg_index`.
190186
fn get_function_arg_name(f: &ItemFn, arg_index: usize, errors: &mut TokenStream2) -> Option<Ident> {
191187
if let Some(FnArg::Typed(arg)) = f.sig.inputs.iter().nth(arg_index) {

uefi-macros/tests/ui/guid.rs

-19
This file was deleted.

uefi-macros/tests/ui/guid.stderr

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use uefi::Guid;
2+
use uefi_macros::guid;
3+
4+
// Error span should point to the second group.
5+
const BadHexGroup2: Guid = guid!("aaaaaaaa-Gaaa-aaaa-aaaa-aaaaaaaaaaaa");
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: GUID component "Gaaa" is not a hexadecimal number
2+
--> tests/ui/guid_bad_hex_group2.rs:5:44
3+
|
4+
5 | const BadHexGroup2: Guid = guid!("aaaaaaaa-Gaaa-aaaa-aaaa-aaaaaaaaaaaa");
5+
| ^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use uefi::Guid;
2+
use uefi_macros::guid;
3+
4+
// Error span should point to the fifth group.
5+
const BadHexGroup5: Guid = guid!("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaG");
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: GUID component "aaaaaaaaaaaG" is not a hexadecimal number
2+
--> tests/ui/guid_bad_hex_group5.rs:5:59
3+
|
4+
5 | const BadHexGroup5: Guid = guid!("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaG");
5+
| ^^^^^^^^^^^^
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use uefi::Guid;
2+
use uefi_macros::guid;
3+
4+
// Fail because the length is wrong.
5+
const TooShort: Guid = guid!("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa");
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa" is not a canonical GUID string (expected 36 bytes, found 35)
2+
--> tests/ui/guid_bad_length.rs:5:30
3+
|
4+
5 | const TooShort: Guid = guid!("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa");
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

uefi-test-runner/src/boot/misc.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use core::ffi::c_void;
22
use core::ptr::{self, NonNull};
33

4-
use uefi::proto::Protocol;
4+
use uefi::proto::unsafe_protocol;
55
use uefi::table::boot::{BootServices, EventType, SearchType, TimerTrigger, Tpl};
6-
use uefi::{unsafe_guid, Event, Identify};
6+
use uefi::{Event, Identify};
77

88
pub fn test(bt: &BootServices) {
99
info!("Testing timer...");
@@ -80,8 +80,7 @@ fn test_watchdog(bt: &BootServices) {
8080
}
8181

8282
/// Dummy protocol for tests
83-
#[unsafe_guid("1a972918-3f69-4b5d-8cb4-cece2309c7f5")]
84-
#[derive(Protocol)]
83+
#[unsafe_protocol("1a972918-3f69-4b5d-8cb4-cece2309c7f5")]
8584
struct TestProtocol {}
8685

8786
unsafe extern "efiapi" fn _test_notify(_event: Event, _context: Option<NonNull<c_void>>) {

uefi-test-runner/src/main.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![no_std]
22
#![no_main]
33
#![feature(abi_efiapi)]
4-
#![feature(negative_impls)]
54

65
#[macro_use]
76
extern crate log;

uefi/src/data_types/guid.rs

+8-25
Original file line numberDiff line numberDiff line change
@@ -114,35 +114,29 @@ impl fmt::Display for Guid {
114114
/// this trait is a building block to interface them in uefi-rs.
115115
///
116116
/// You should never need to use the `Identify` trait directly, but instead go
117-
/// for more specific traits such as `Protocol` or `FileProtocolInfo`, which
117+
/// for more specific traits such as [`Protocol`] or [`FileProtocolInfo`], which
118118
/// indicate in which circumstances an `Identify`-tagged type should be used.
119119
///
120+
/// For the common case of implementing this trait for a protocol, use
121+
/// the [`unsafe_protocol`] macro.
122+
///
120123
/// # Safety
121124
///
122125
/// Implementing `Identify` is unsafe because attaching an incorrect GUID to a
123126
/// type can lead to type unsafety on both the Rust and UEFI side.
124127
///
125-
/// You can derive `Identify` for a type using the `unsafe_guid` procedural
126-
/// macro, which is exported by this module. This macro mostly works like a
127-
/// custom derive, but also supports type aliases. It takes a GUID in canonical
128-
/// textual format as an argument, and is used in the following way:
129-
///
130-
/// ```
131-
/// use uefi::unsafe_guid;
132-
/// #[unsafe_guid("12345678-9abc-def0-1234-56789abcdef0")]
133-
/// struct Emptiness;
134-
/// ```
128+
/// [`Protocol`]: crate::proto::Protocol
129+
/// [`FileProtocolInfo`]: crate::proto::media::file::FileProtocolInfo
130+
/// [`unsafe_protocol`]: crate::proto::unsafe_protocol
135131
pub unsafe trait Identify {
136132
/// Unique protocol identifier.
137133
const GUID: Guid;
138134
}
139135

140-
pub use uefi_macros::unsafe_guid;
141-
142136
#[cfg(test)]
143137
mod tests {
144138
use super::*;
145-
use uefi::{guid, unsafe_guid};
139+
use uefi::guid;
146140

147141
#[test]
148142
fn test_guid_display() {
@@ -163,17 +157,6 @@ mod tests {
163157
);
164158
}
165159

166-
#[test]
167-
fn test_unsafe_guid_macro() {
168-
#[unsafe_guid("12345678-9abc-def0-1234-56789abcdef0")]
169-
struct X;
170-
171-
assert_eq!(
172-
X::GUID,
173-
Guid::from_values(0x12345678, 0x9abc, 0xdef0, 0x1234, 0x56789abcdef0)
174-
);
175-
}
176-
177160
#[test]
178161
fn test_to_from_bytes() {
179162
#[rustfmt::skip]

uefi/src/data_types/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ pub type VirtualAddress = u64;
121121

122122
mod guid;
123123
pub use self::guid::Guid;
124-
pub use self::guid::{unsafe_guid, Identify};
124+
pub use self::guid::Identify;
125125

126126
pub mod chars;
127127
pub use self::chars::{Char16, Char8};

0 commit comments

Comments
 (0)