Skip to content

Commit 8365b4b

Browse files
tgross35DorianNiemiecSVRJS
authored andcommitted
Introduce a new c_enum macro
Our current `e!` macro makes it easy to run into UB if C headers add a variant that isn't represented in the Rust version. Add a path to migrate away from this by introducing the `c_enum!` macro which represents a C enum as Rust constants and a type alias. Part of [1]. [1]: rust-lang#4419 (backport <rust-lang#4420>) (cherry picked from commit 62051ca)
1 parent fcc0db8 commit 8365b4b

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

src/macros.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ macro_rules! missing {
193193

194194
/// Implement `Clone` and `Copy` for an enum, as well as `Debug`, `Eq`, `Hash`, and
195195
/// `PartialEq` if the `extra_traits` feature is enabled.
196+
// FIXME(#4419): Replace all uses of `e!` with `c_enum!`
196197
macro_rules! e {
197198
($(
198199
$(#[$attr:meta])*
@@ -210,6 +211,48 @@ macro_rules! e {
210211
)*);
211212
}
212213

214+
/// Represent a C enum as Rust constants and a type.
215+
///
216+
/// C enums can't soundly be mapped to Rust enums since C enums are allowed to have duplicates or
217+
/// unlisted values, but this is UB in Rust. This enum doesn't implement any traits, its main
218+
/// purpose is to calculate the correct enum values.
219+
///
220+
/// See <https://github.com/rust-lang/libc/issues/4419> for more.
221+
macro_rules! c_enum {
222+
(
223+
$(#[repr($repr:ty)])?
224+
$ty_name:ident {
225+
$($variant:ident $(= $value:literal)?,)+
226+
}
227+
) => {
228+
pub type $ty_name = c_enum!(@ty $($repr)?);
229+
c_enum!(@one; $ty_name; 0; $($variant $(= $value)?,)+);
230+
};
231+
232+
// Matcher for a single variant
233+
(@one; $_ty_name:ident; $_idx:expr;) => {};
234+
(
235+
@one; $ty_name:ident; $default_val:expr;
236+
$variant:ident $(= $value:literal)?,
237+
$($tail:tt)*
238+
) => {
239+
pub const $variant: $ty_name = {
240+
#[allow(unused_variables)]
241+
let r = $default_val;
242+
$(let r = $value;)?
243+
r
244+
};
245+
246+
// The next value is always one more than the previous value, unless
247+
// set explicitly.
248+
c_enum!(@one; $ty_name; $variant + 1; $($tail)*);
249+
};
250+
251+
// Use a specific type if provided, otherwise default to `c_uint`
252+
(@ty $repr:ty) => { $repr };
253+
(@ty) => { $crate::c_uint };
254+
}
255+
213256
// This is a pretty horrible hack to allow us to conditionally mark some functions as 'const',
214257
// without requiring users of this macro to care "libc_const_extern_fn".
215258
//
@@ -359,3 +402,76 @@ macro_rules! deprecated_mach {
359402
)*
360403
}
361404
}
405+
406+
#[cfg(test)]
407+
mod tests {
408+
#[test]
409+
fn c_enumbasic() {
410+
// By default, variants get sequential values.
411+
c_enum! {
412+
e {
413+
VAR0,
414+
VAR1,
415+
VAR2,
416+
}
417+
}
418+
419+
assert_eq!(VAR0, 0_u32);
420+
assert_eq!(VAR1, 1_u32);
421+
assert_eq!(VAR2, 2_u32);
422+
}
423+
424+
#[test]
425+
fn c_enumrepr() {
426+
// By default, variants get sequential values.
427+
c_enum! {
428+
#[repr(u16)]
429+
e {
430+
VAR0,
431+
}
432+
}
433+
434+
assert_eq!(VAR0, 0_u16);
435+
}
436+
437+
#[test]
438+
fn c_enumset_value() {
439+
// Setting an explicit value resets the count.
440+
c_enum! {
441+
e {
442+
VAR2 = 2,
443+
VAR3,
444+
VAR4,
445+
}
446+
}
447+
448+
assert_eq!(VAR2, 2_u32);
449+
assert_eq!(VAR3, 3_u32);
450+
assert_eq!(VAR4, 4_u32);
451+
}
452+
453+
#[test]
454+
fn c_enummultiple_set_value() {
455+
// C enums always take one more than the previous value, unless set to a specific
456+
// value. Duplicates are allowed.
457+
c_enum! {
458+
e {
459+
VAR0,
460+
VAR2_0 = 2,
461+
VAR3_0,
462+
VAR4_0,
463+
VAR2_1 = 2,
464+
VAR3_1,
465+
VAR4_1,
466+
}
467+
}
468+
469+
assert_eq!(VAR0, 0_u32);
470+
assert_eq!(VAR2_0, 2_u32);
471+
assert_eq!(VAR3_0, 3_u32);
472+
assert_eq!(VAR4_0, 4_u32);
473+
assert_eq!(VAR2_1, 2_u32);
474+
assert_eq!(VAR3_1, 3_u32);
475+
assert_eq!(VAR4_1, 4_u32);
476+
}
477+
}

0 commit comments

Comments
 (0)