Skip to content

Commit

Permalink
[eclipse-iceoryx#491] Ensure generated strings are null terminated c …
Browse files Browse the repository at this point in the history
…strings
  • Loading branch information
orecham committed Dec 23, 2024
1 parent 4ccdcbd commit 55dbcf7
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion iceoryx2-bb/derive-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ proc-macro = true
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
iceoryx2-bb-elementary = { workspace = true }

[dev-dependencies]
iceoryx2-bb-testing = { workspace = true }
4 changes: 3 additions & 1 deletion iceoryx2-bb/elementary/src/as_string_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use std::ffi::CStr;

pub trait AsStringLiteral {
fn as_str_literal(&self) -> &'static str;
fn as_str_literal(&self) -> &'static CStr;
}
1 change: 1 addition & 0 deletions iceoryx2-ffi/ffi-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ proc-macro = true
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
iceoryx2-bb-elementary = { workspace = true }
52 changes: 31 additions & 21 deletions iceoryx2-ffi/ffi-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT

use proc_macro::TokenStream;
use proc_macro2::Literal;
use proc_macro2::TokenTree;
use quote::{format_ident, quote};
use syn::{
Expand Down Expand Up @@ -229,7 +228,7 @@ fn parse_attribute_args(args: TokenStream) -> Args {
///
/// # Example
/// ```
/// use iceoryx2_bb_derive_macros::StringLiteral;
/// use iceoryx2_ffi_macros::StringLiteral;
/// use iceoryx2_bb_elementary::AsStringLiteral;
///
/// #[derive(StringLiteral)]
Expand All @@ -240,18 +239,20 @@ fn parse_attribute_args(args: TokenStream) -> Args {
/// }
///
/// let v1 = MyEnum::VariantOne;
/// assert_eq!(v1.as_str_literal(), "custom variant one\0");
/// assert_eq!(v1.as_str_literal(), c"custom variant one");
///
/// let v2 = MyEnum::VariantTwo;
/// assert_eq!(v2.as_str_literal(), "variant two\0");
/// assert_eq!(v2.as_str_literal(), c"variant two");
/// ```
///
#[proc_macro_derive(StringLiteral, attributes(CustomString))]
pub fn string_literal_derive(input: TokenStream) -> TokenStream {

let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

// Generate implementation converting enums to a string representation
// Generate implementation of `AsStringLiteral` for all enum variants.
let as_string_literal_impl = match input.data {
Data::Enum(ref data_enum) => {
let enum_to_string_mapping = data_enum.variants.iter().map(|variant| {
Expand All @@ -260,28 +261,34 @@ pub fn string_literal_derive(input: TokenStream) -> TokenStream {
.attrs
.iter()
.find_map(|attr| {
// Use provided `CustomString` if present, otherwise return None to trigger
// `or_else` logic.
if !attr.path().is_ident("CustomString") {
return None;
}
// Get the value of CustomString as a string literal
match attr.meta.require_name_value() {
Ok(meta) => match &meta.value {
Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) => {
let mut value = lit.value();
value.push('\0');
Some(Literal::string(&value))
let value = lit.value();
Some(quote! {
// The following is unsafe because the compiler cannot confirm the
// string is null terminated.
// However, since this code appends the null termination itself, the
// code is ensured safe.
unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#value, "\0").as_bytes()) }
})
}
_ => None,
},
_ => None,
}
})
.unwrap_or_else(|| {
// If no CustomString, generates default string literal in the form
// MyEnum::MyVariantName => 'my variant name'
let mut enum_string_literal = enum_name
// No `CustomString` provided. Convert the variant name from
// "UpperCamelCase" to "lowercase with spaces".
let enum_string_literal = enum_name
.to_string()
.chars()
.fold(String::new(), |mut acc, c| {
Expand All @@ -297,11 +304,16 @@ pub fn string_literal_derive(input: TokenStream) -> TokenStream {
c => c.to_ascii_lowercase(),
})
.collect::<String>();
enum_string_literal.push('\0');
Literal::string(&enum_string_literal)

quote! {
// The following is unsafe because the compiler cannot confirm the
// string is null terminated.
// However, since this code appends the null termination itself, the
// code is ensured safe.
unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#enum_string_literal, "\0").as_bytes()) }
}
});

// Maps each enum variant to its string representation
match &variant.fields {
Fields::Unit => {
quote! {
Expand All @@ -321,24 +333,21 @@ pub fn string_literal_derive(input: TokenStream) -> TokenStream {
}
});

// Generate the mapping for the enum variant
quote! {
fn as_str_literal(&self) -> &'static str {
fn as_str_literal(&self) -> &'static ::std::ffi::CStr {
match self {
#(#enum_to_string_mapping,)*
}
}
}
}
_ => {
// Does not work for non-enum types
let err =
syn::Error::new_spanned(&input, "AsStringLiteral can only be derived for enums");
let err = syn::Error::new_spanned(&input, "AsStringLiteral can only be derived for enums");
return err.to_compile_error().into();
}
};

// Implement the trait with the generated implementation
// Provide the generated trait implementation for the enum.
let expanded = quote! {
impl #impl_generics AsStringLiteral for #name #type_generics #where_clause {
#as_string_literal_impl
Expand All @@ -347,3 +356,4 @@ pub fn string_literal_derive(input: TokenStream) -> TokenStream {

TokenStream::from(expanded)
}

0 comments on commit 55dbcf7

Please sign in to comment.