Skip to content

macros: allow to create empty cstr8/16 slices #786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2023
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
any effect.
- The `unsafe_protocol` macro now accepts the path of a `Guid` constant in
addition to a string literal.
- The `cstr8` and the `cstr16` macros now both accept `(nothing)` and `""`
(empty inputs) to create valid empty strings. They include the null-byte.

## uefi-services - [Unreleased]

Expand Down
39 changes: 35 additions & 4 deletions uefi-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,26 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
/// # Example
/// ```
/// # use uefi_macros::cstr8;
/// // Empty string
/// assert_eq!(cstr8!().to_u16_slice_with_nul(), [0]);
/// assert_eq!(cstr8!("").to_u16_slice_with_nul(), [0]);
/// // Non-empty string
/// assert_eq!(cstr8!("test").to_bytes_with_nul(), [116, 101, 115, 116, 0]);
/// ```
#[proc_macro]
pub fn cstr8(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Accept empty input.
if input.is_empty() {
return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into();
}
let input: LitStr = parse_macro_input!(input);
let input = input.value();
// Accept "" input.
if input.is_empty() {
return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into();
}

// Accept any non-empty string input.
match input
.chars()
.map(u8::try_from)
Expand All @@ -283,14 +297,28 @@ pub fn cstr8(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// This will throw a compile error if an invalid character is in the passed string.
///
/// # Example
/// ```
/// ```rust
/// # use uefi_macros::cstr16;
/// // Empty string
/// assert_eq!(cstr16!().to_u16_slice_with_nul(), [0]);
/// assert_eq!(cstr16!("").to_u16_slice_with_nul(), [0]);
/// // Non-empty string
/// assert_eq!(cstr16!("test €").to_u16_slice_with_nul(), [116, 101, 115, 116, 32, 8364, 0]);
/// ```
#[proc_macro]
pub fn cstr16(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Accept empty input.
if input.is_empty() {
return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into();
}
let input: LitStr = parse_macro_input!(input);
let input = input.value();
// Accept "" input.
if input.is_empty() {
return quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[0]) }).into();
}

// Accept any non-empty string input.
match input
.chars()
.map(|c| u16::try_from(c as u32))
Expand All @@ -299,8 +327,11 @@ pub fn cstr16(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Ok(c) => {
quote!(unsafe { ::uefi::CStr16::from_u16_with_nul_unchecked(&[ #(#c),* , 0 ]) }).into()
}
Err(_) => syn::Error::new_spanned(input, "invalid character in string")
.into_compile_error()
.into(),
Err(_) => syn::Error::new_spanned(
input,
"There are UTF-8 characters that can't be transformed to UCS-2 character",
)
.into_compile_error()
.into(),
}
}
21 changes: 21 additions & 0 deletions uefi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,24 @@ pub(crate) mod mem;
pub(crate) mod polyfill;

mod util;

#[cfg(test)]
// Crates that create procedural macros can't unit test the macros they export.
// Therefore, we do some tests here.
mod macro_tests {
use uefi_macros::{cstr16, cstr8};

#[test]
fn cstr8_macro_literal() {
let _empty1 = cstr8!();
let _empty2 = cstr8!("");
let _regular = cstr8!("foobar");
}

#[test]
fn cstr16_macro_literal() {
let _empty1 = cstr16!();
let _empty2 = cstr16!("");
let _regular = cstr16!("foobar");
}
}