Skip to content

Commit 134b89c

Browse files
authored
Audit C enum for safety and use integer newtypes where needed (#55)
Also provides a general convenience macro for defining such newtypes and an extended discussion of safety contracts in comments.
1 parent 5e48989 commit 134b89c

File tree

13 files changed

+257
-173
lines changed

13 files changed

+257
-173
lines changed

src/data_types/enums.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//! This module provides tooling that facilitates dealing with C-style enums
2+
//!
3+
//! C-style enums and Rust-style enums are quite different. There are things
4+
//! which one allows, but not the other, and vice versa. In an FFI context, two
5+
//! aspects of C-style enums are particularly bothersome to us:
6+
//!
7+
//! - They allow a caller to send back an unknown enum variant. In Rust, the
8+
//! mere act of storing such a variant in a variable is undefined behavior.
9+
//! - They have an implicit conversion to integers, which is often used as a
10+
//! more portable alternative to C bitfields or as a way to count the amount
11+
//! of variants of an enumerated type. Rust enums do not model this well.
12+
//!
13+
//! Therefore, in many cases, C enums are best modeled as newtypes of integers
14+
//! featuring a large set of associated constants instead of as Rust enums. This
15+
//! module provides facilities to simplify this kind of FFI.
16+
17+
/// Interface a C-style enum as an integer newtype.
18+
///
19+
/// This macro implements Debug for you, the way you would expect it to work on
20+
/// Rust enums (printing the variant name instead of its integer value). It also
21+
/// derives Clone, Copy, Eq and PartialEq, since that always makes sense for
22+
/// C-style enums and is used by the implementation. If you want anything else
23+
/// to be derived, you can ask for it by adding extra derives as shown in the
24+
/// example below.
25+
///
26+
/// One minor annoyance is that since variants will be translated into
27+
/// associated constants in a separate impl block, you need to discriminate
28+
/// which attributes should go on the type and which should go on the impl
29+
/// block. The latter should go on the right-hand side of the arrow operator.
30+
///
31+
/// Usage example:
32+
/// ```
33+
/// newtype_enum! {
34+
/// #[derive(Cmp, PartialCmp)]
35+
/// pub enum UnixBool: i32 => #[allow(missing_docs)] {
36+
/// FALSE = 0,
37+
/// TRUE = 1,
38+
/// /// Nobody expects the Unix inquisition!
39+
/// FILE_NOT_FOUND = -1,
40+
/// }}
41+
/// ```
42+
#[macro_export]
43+
macro_rules! newtype_enum {
44+
(
45+
$(#[$type_attrs:meta])*
46+
pub enum $type:ident : $base_integer:ty => $(#[$impl_attrs:meta])* {
47+
$(
48+
$(#[$variant_attrs:meta])*
49+
$variant:ident = $value:expr,
50+
)*
51+
}
52+
) => {
53+
$(#[$type_attrs])*
54+
#[repr(transparent)]
55+
#[derive(Clone, Copy, Eq, PartialEq)]
56+
pub struct $type($base_integer);
57+
58+
$(#[$impl_attrs])*
59+
#[allow(unused)]
60+
impl $type {
61+
$(
62+
$(#[$variant_attrs])*
63+
pub const $variant: $type = $type($value);
64+
)*
65+
}
66+
67+
impl core::fmt::Debug for $type {
68+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
69+
match *self {
70+
// Display variants by their name, like Rust enums do
71+
$(
72+
$type::$variant => write!(f, stringify!($variant)),
73+
)*
74+
75+
// Display unknown variants in tuple struct format
76+
$type(unknown) => {
77+
write!(f, "{}({})", stringify!($type), unknown)
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}

src/data_types/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ pub struct Event(*mut c_void);
1212

1313
mod guid;
1414
pub use self::guid::Guid;
15+
16+
#[macro_use]
17+
mod enums;

src/error/status.rs

Lines changed: 40 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,23 @@
1-
// The error codes are unportable, but that's how the spec defines them.
2-
#![allow(clippy::enum_clike_unportable_variant)]
3-
41
use super::Result;
52
use core::ops;
63
use ucs2;
74

5+
/// Bit indicating that an UEFI status code is an error
6+
const ERROR_BIT: usize = 1 << (core::mem::size_of::<usize>() * 8 - 1);
7+
8+
newtype_enum! {
89
/// UEFI uses status codes in order to report successes, errors, and warnings.
910
///
1011
/// Unfortunately, the spec allows and encourages implementation-specific
1112
/// non-portable status codes. Therefore, these cannot be modeled as a Rust
1213
/// enum, as injecting an unknown value in a Rust enum is undefined behaviour.
1314
///
1415
/// For lack of a better option, we therefore model them as a newtype of usize.
15-
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
16-
#[repr(transparent)]
1716
#[must_use]
18-
pub struct Status(usize);
19-
20-
/// Macro to make implementation of status codes easier
21-
macro_rules! status_codes {
22-
( $( $(#[$attr:meta])*
23-
$status:ident = $code:expr, )*
24-
) => {
25-
#[allow(unused)]
26-
impl Status {
27-
$( $(#[$attr])*
28-
pub const $status: Status = Status($code); )*
29-
}
30-
}
31-
}
32-
//
33-
status_codes! {
17+
pub enum Status: usize => {
3418
/// The operation completed successfully.
3519
SUCCESS = 0,
20+
3621
/// The string contained characters that could not be rendered and were skipped.
3722
WARN_UNKNOWN_GLYPH = 1,
3823
/// The handle was closed, but the file was not deleted.
@@ -47,94 +32,77 @@ status_codes! {
4732
WARN_FILE_SYSTEM = 6,
4833
/// The operation will be processed across a system reset.
4934
WARN_RESET_REQUIRED = 7,
50-
}
51-
52-
/// Bit indicating that a status code is an error
53-
const ERROR_BIT: usize = 1 << (core::mem::size_of::<usize>() * 8 - 1);
5435

55-
/// Macro to make implementation of error codes easier
56-
macro_rules! error_codes {
57-
( $( $(#[$attr:meta])*
58-
$status:ident = $error_code:expr, )*
59-
) => {
60-
status_codes! { $(
61-
$(#[$attr])*
62-
$status = $error_code | ERROR_BIT,
63-
)* }
64-
}
65-
}
66-
//
67-
error_codes! {
6836
/// The image failed to load.
69-
LOAD_ERROR = 1,
37+
LOAD_ERROR = ERROR_BIT | 1,
7038
/// A parameter was incorrect.
71-
INVALID_PARAMETER = 2,
39+
INVALID_PARAMETER = ERROR_BIT | 2,
7240
/// The operation is not supported.
73-
UNSUPPORTED = 3,
41+
UNSUPPORTED = ERROR_BIT | 3,
7442
/// The buffer was not the proper size for the request.
75-
BAD_BUFFER_SIZE = 4,
43+
BAD_BUFFER_SIZE = ERROR_BIT | 4,
7644
/// The buffer is not large enough to hold the requested data.
7745
/// The required buffer size is returned in the appropriate parameter.
78-
BUFFER_TOO_SMALL = 5,
46+
BUFFER_TOO_SMALL = ERROR_BIT | 5,
7947
/// There is no data pending upon return.
80-
NOT_READY = 6,
48+
NOT_READY = ERROR_BIT | 6,
8149
/// The physical device reported an error while attempting the operation.
82-
DEVICE_ERROR = 7,
50+
DEVICE_ERROR = ERROR_BIT | 7,
8351
/// The device cannot be written to.
84-
WRITE_PROTECTED = 8,
52+
WRITE_PROTECTED = ERROR_BIT | 8,
8553
/// A resource has run out.
86-
OUT_OF_RESOURCES = 9,
54+
OUT_OF_RESOURCES = ERROR_BIT | 9,
8755
/// An inconstency was detected on the file system.
88-
VOLUME_CORRUPTED = 10,
56+
VOLUME_CORRUPTED = ERROR_BIT | 10,
8957
/// There is no more space on the file system.
90-
VOLUME_FULL = 11,
58+
VOLUME_FULL = ERROR_BIT | 11,
9159
/// The device does not contain any medium to perform the operation.
92-
NO_MEDIA = 12,
60+
NO_MEDIA = ERROR_BIT | 12,
9361
/// The medium in the device has changed since the last access.
94-
MEDIA_CHANGED = 13,
62+
MEDIA_CHANGED = ERROR_BIT | 13,
9563
/// The item was not found.
96-
NOT_FOUND = 14,
64+
NOT_FOUND = ERROR_BIT | 14,
9765
/// Access was denied.
98-
ACCESS_DENIED = 15,
66+
ACCESS_DENIED = ERROR_BIT | 15,
9967
/// The server was not found or did not respond to the request.
100-
NO_RESPONSE = 16,
68+
NO_RESPONSE = ERROR_BIT | 16,
10169
/// A mapping to a device does not exist.
102-
NO_MAPPING = 17,
70+
NO_MAPPING = ERROR_BIT | 17,
10371
/// The timeout time expired.
104-
TIMEOUT = 18,
72+
TIMEOUT = ERROR_BIT | 18,
10573
/// The protocol has not been started.
106-
NOT_STARTED = 19,
74+
NOT_STARTED = ERROR_BIT | 19,
10775
/// The protocol has already been started.
108-
ALREADY_STARTED = 20,
76+
ALREADY_STARTED = ERROR_BIT | 20,
10977
/// The operation was aborted.
110-
ABORTED = 21,
78+
ABORTED = ERROR_BIT | 21,
11179
/// An ICMP error occurred during the network operation.
112-
ICMP_ERROR = 22,
80+
ICMP_ERROR = ERROR_BIT | 22,
11381
/// A TFTP error occurred during the network operation.
114-
TFTP_ERROR = 23,
82+
TFTP_ERROR = ERROR_BIT | 23,
11583
/// A protocol error occurred during the network operation.
116-
PROTOCOL_ERROR = 24,
84+
PROTOCOL_ERROR = ERROR_BIT | 24,
11785
/// The function encountered an internal version that was
11886
/// incompatible with a version requested by the caller.
119-
INCOMPATIBLE_VERSION = 25,
87+
INCOMPATIBLE_VERSION = ERROR_BIT | 25,
12088
/// The function was not performed due to a security violation.
121-
SECURITY_VIOLATION = 26,
89+
SECURITY_VIOLATION = ERROR_BIT | 26,
12290
/// A CRC error was detected.
123-
CRC_ERROR = 27,
91+
CRC_ERROR = ERROR_BIT | 27,
12492
/// Beginning or end of media was reached
125-
END_OF_MEDIA = 28,
93+
END_OF_MEDIA = ERROR_BIT | 28,
12694
/// The end of the file was reached.
127-
END_OF_FILE = 31,
95+
END_OF_FILE = ERROR_BIT | 31,
12896
/// The language specified was invalid.
129-
INVALID_LANGUAGE = 32,
97+
INVALID_LANGUAGE = ERROR_BIT | 32,
13098
/// The security status of the data is unknown or compromised and
13199
/// the data must be updated or replaced to restore a valid security status.
132-
COMPROMISED_DATA = 33,
100+
COMPROMISED_DATA = ERROR_BIT | 33,
133101
/// There is an address conflict address allocation
134-
IP_ADDRESS_CONFLICT = 34,
102+
IP_ADDRESS_CONFLICT = ERROR_BIT | 34,
135103
/// A HTTP error occurred during the network operation.
136-
HTTP_ERROR = 35,
137-
}
104+
HTTP_ERROR = ERROR_BIT | 35,
105+
}}
138106

139107
impl Status {
140108
/// Returns true if status code indicates success.

src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@
3232
#![warn(missing_docs, unused)]
3333
#![deny(clippy::all)]
3434

35-
mod error;
36-
pub use self::error::{Result, Status};
37-
35+
#[macro_use]
3836
mod data_types;
3937
pub use self::data_types::{Event, Guid, Handle};
4038

39+
mod error;
40+
pub use self::error::{Result, Status};
41+
4142
pub mod table;
4243

4344
pub mod proto;

src/proto/console/gop.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ struct ModeData {
295295
#[repr(u32)]
296296
pub enum PixelFormat {
297297
/// Each pixel is 32-bit long, with 24-bit RGB, and the last byte is reserved.
298-
RGB,
298+
RGB = 0,
299299
/// Each pixel is 32-bit long, with 24-bit BGR, and the last byte is reserved.
300300
BGR,
301301
/// Custom pixel format, check the associated bitmask.
@@ -305,6 +305,10 @@ pub enum PixelFormat {
305305
/// This means you will have to use the `blt` function which will
306306
/// convert the graphics data to the device's internal pixel format.
307307
BltOnly,
308+
// SAFETY: UEFI also defines a PixelFormatMax variant, and states that all
309+
// valid enum values are guaranteed to be smaller. Since that is the
310+
// case, adding a new enum variant would be a breaking change, so it
311+
// is safe to model this C enum as a Rust enum.
308312
}
309313

310314
/// Bitmask used to indicate which bits of a pixel represent a given color.

src/proto/console/serial.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ pub enum Parity {
210210
Mark,
211211
/// Space parity
212212
Space,
213+
// SAFETY: The serial protocol is very old, and new parity modes are very
214+
// unlikely to be added at this point in time. Therefore, modeling
215+
// this C enum as a Rust enum seems safe.
213216
}
214217

215218
/// Number of stop bits per character.
@@ -224,4 +227,7 @@ pub enum StopBits {
224227
OneFive,
225228
/// 2 stop bits
226229
Two,
230+
// SAFETY: The serial protocol is very old, and new stop bit modes are very
231+
// unlikely to be added at this point in time. Therefore, modeling
232+
// this C enum as a Rust enum seems safe.
227233
}

0 commit comments

Comments
 (0)