Repository: eclipse-iceoryx/iceoryx2 (Rust)

Issue:
Add `repr(C)` check to `ZeroCopySend` derive macro
Types that can be sent to another process in a zero-copy manner must have a uniform memory representation, i.e. they are annotated with `#[repr(C)]`. The `ZeroCopySend` derive macro should check if the type is annotated with this attribute and fail at compile time if not.


Discussion:

Patch:
diff --git a/iceoryx2-bb/container/src/semantic_string.rs b/iceoryx2-bb/container/src/semantic_string.rs
index ab93dcde9..a73f22210 100644
--- a/iceoryx2-bb/container/src/semantic_string.rs
+++ b/iceoryx2-bb/container/src/semantic_string.rs
@@ -391,6 +391,7 @@ macro_rules! semantic_string {
      /// representations like paths for instance (`/tmp` == `/tmp/`)
      normalize: $normalize:expr} => {
         $(#[$documentation])*
+        #[repr(C)]
         #[derive(Debug, Clone, Eq, PartialOrd, Ord, ZeroCopySend)]
         pub struct $string_name {
             value: iceoryx2_bb_container::byte_string::FixedSizeByteString<$capacity>
diff --git a/iceoryx2-bb/derive-macros/src/lib.rs b/iceoryx2-bb/derive-macros/src/lib.rs
index 12afed7fd..601abd275 100644
--- a/iceoryx2-bb/derive-macros/src/lib.rs
+++ b/iceoryx2-bb/derive-macros/src/lib.rs
@@ -105,9 +105,8 @@ pub fn placement_default_derive(input: TokenStream) -> TokenStream {
 }
 
 /// Implements the [`iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend`] trait when all fields of
-/// the struct or enum implement it. A type name can be optionally set with the helper attribute `type_name`.
-///
-/// ## Example with a struct:
+/// the struct implement it and the struct is annotated with `repr(C)`. A type name can be optionally
+/// set with the helper attribute `type_name`.
 ///
 /// ```
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
@@ -115,6 +114,7 @@ pub fn placement_default_derive(input: TokenStream) -> TokenStream {
 ///
 /// fn needs_zero_copy_send_type<T: ZeroCopySend>(_: &T) {}
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name("MyTypeName")]
 /// struct MyZeroCopySendStruct {
@@ -129,6 +129,7 @@ pub fn placement_default_derive(input: TokenStream) -> TokenStream {
 /// needs_zero_copy_send_type(&x);
 /// assert_eq!(unsafe { MyZeroCopySendStruct::type_name() }, "MyTypeName");
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name("GeometricShape")]
 /// enum Shape {
@@ -151,11 +152,11 @@ pub fn zero_copy_send_derive(input: TokenStream) -> TokenStream {
     let ast = parse_macro_input!(input as DeriveInput);
     let struct_name = &ast.ident;
 
-    // check attribute
+    // check for type_name attribute
     let attributes: &Vec<_> = &ast
         .attrs
         .iter()
-        .filter(|a| a.path().segments.len() == 1 && a.path().segments[0].ident == "type_name")
+        .filter(|a| a.path().is_ident("type_name"))
         .collect();
     if attributes.len() > 1 {
         panic!("Too many attributes provided for ZeroCopySend trait.");
@@ -181,6 +182,19 @@ pub fn zero_copy_send_derive(input: TokenStream) -> TokenStream {
         }
     };
 
+    // check for repr(C) attribute
+    let has_repr_c = &ast.attrs.iter().any(|a| {
+        a.path().is_ident("repr")
+            && a.parse_args::<syn::Meta>()
+                .ok()
+                .map(|meta| meta.path().is_ident("C"))
+                .unwrap_or(false)
+    });
+    if !has_repr_c {
+        panic!("`#[derive(ZeroCopySend)]` requires the type to be annotated with #[repr(C)]");
+    }
+
+    // implement ZeroCopySend
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
 
     let zero_copy_send_impl = match ast.data {
diff --git a/iceoryx2-bb/posix/src/clock.rs b/iceoryx2-bb/posix/src/clock.rs
index b8668c48b..96615a0de 100644
--- a/iceoryx2-bb/posix/src/clock.rs
+++ b/iceoryx2-bb/posix/src/clock.rs
@@ -66,14 +66,14 @@ impl From<TimeError> for NanosleepError {
 
 /// Represents different low level clocks.
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, ZeroCopySend, Serialize, Deserialize)]
-#[repr(i32)]
+#[repr(C)]
 pub enum ClockType {
     /// represents a steady clock which does not change when the system time
     /// is adjusted
-    Monotonic = posix::CLOCK_MONOTONIC as _,
+    Monotonic,
     /// Clock which represents the current system time. Can change when the  system time is
     /// adjusted.
-    Realtime = posix::CLOCK_REALTIME as _,
+    Realtime,
 }
 
 impl Default for ClockType {
@@ -100,6 +100,13 @@ impl ClockType {
             &[ClockType::Realtime]
         }
     }
+
+    fn as_i32(&self) -> i32 {
+        match self {
+            ClockType::Monotonic => posix::CLOCK_MONOTONIC as _,
+            ClockType::Realtime => posix::CLOCK_REALTIME as _,
+        }
+    }
 }
 
 /// Trait to convert constructs which represent time into the c pendant [`posix::timespec`]
@@ -181,6 +188,7 @@ impl TimeBuilder {
 }
 
 /// Represents time under a specified [`ClockType`]
+#[repr(C)]
 #[derive(
     Default, Clone, Copy, Eq, PartialEq, Hash, Debug, ZeroCopySend, Serialize, Deserialize,
 )]
@@ -206,7 +214,7 @@ impl Time {
         };
 
         handle_errno!(TimeError, from "Time::now",
-            errno_source unsafe { posix::clock_gettime(clock_type as _, &mut current_time).into() },
+            errno_source unsafe { posix::clock_gettime(clock_type.as_i32() as _, &mut current_time).into() },
             success Errno::ESUCCES => Time { clock_type,
                                              seconds: current_time.tv_sec as u64,
                                              nanoseconds: current_time.tv_nsec as u32},
@@ -325,7 +333,7 @@ pub fn nanosleep_with_clock(
     handle_errno!(NanosleepError, from "nanosleep_with_clock",
         errno_source unsafe {
             let e = posix::clock_nanosleep(
-                clock_type as _,
+                clock_type.as_i32() as _,
                 posix::CLOCK_TIMER_ABSTIME,
                 &timeout,
                 &mut time_left,
diff --git a/iceoryx2-cal/src/zero_copy_connection/mod.rs b/iceoryx2-cal/src/zero_copy_connection/mod.rs
index 3bed68c86..a16b05b80 100644
--- a/iceoryx2-cal/src/zero_copy_connection/mod.rs
+++ b/iceoryx2-cal/src/zero_copy_connection/mod.rs
@@ -113,6 +113,7 @@ impl core::fmt::Display for ZeroCopyReleaseError {
 
 impl core::error::Error for ZeroCopyReleaseError {}
 
+#[repr(C)]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ZeroCopySend)]
 pub struct ChannelId(usize);
 
diff --git a/iceoryx2/src/service/static_config/message_type_details.rs b/iceoryx2/src/service/static_config/message_type_details.rs
index e81d3c14a..1c4f131c5 100644
--- a/iceoryx2/src/service/static_config/message_type_details.rs
+++ b/iceoryx2/src/service/static_config/message_type_details.rs
@@ -22,6 +22,7 @@ use crate::constants::MAX_TYPE_NAME_LENGTH;
 
 /// Defines if the type is a slice with a runtime-size ([`TypeVariant::Dynamic`])
 /// or if its a type that satisfies [`Sized`] ([`TypeVariant::FixedSize`]).
+#[repr(C)]
 #[derive(
     Default, Debug, Clone, Copy, Eq, Hash, PartialEq, ZeroCopySend, Serialize, Deserialize,
 )]


Test patch:
diff --git a/iceoryx2-bb/derive-macros/src/zero_copy_send_compile_tests.rs b/iceoryx2-bb/derive-macros/src/zero_copy_send_compile_tests.rs
index 5a43e1a31..5f55a7945 100644
--- a/iceoryx2-bb/derive-macros/src/zero_copy_send_compile_tests.rs
+++ b/iceoryx2-bb/derive-macros/src/zero_copy_send_compile_tests.rs
@@ -12,11 +12,21 @@
 
 /// === Structs ===
 
+/// ``` compile_fail
+/// use iceoryx2_bb_derive_macros::ZeroCopySend;
+///
+/// #[derive(ZeroCopySend)]
+/// struct Foo(u16);
+/// ```
+#[cfg(doctest)]
+fn zero_copy_send_derive_does_not_work_when_type_is_not_annotated_with_repr_c() {}
+
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
 /// struct Foo(u16);
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct NamedTestStruct {
 ///     val1: u64,
@@ -31,6 +41,7 @@ fn zero_copy_send_derive_does_not_work_for_named_struct_when_not_all_members_imp
 ///
 /// struct Foo(u16);
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct UnnamedTestStruct(i32, u32, Foo);
 /// ```
@@ -40,6 +51,7 @@ fn zero_copy_send_derive_does_not_work_for_unnamed_struct_when_not_all_members_i
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct GenericNamedTestStruct<T1, T2> {
 ///     val1: T1,
@@ -54,6 +66,7 @@ fn zero_copy_send_derive_does_not_work_for_generic_named_struct_when_members_do_
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 /// use iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct GenericNamedTestStruct<T1: ZeroCopySend, T2> {
 ///     val1: T1,
@@ -68,6 +81,7 @@ fn zero_copy_send_derive_does_not_work_for_generic_named_struct_when_not_all_mem
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct GenericUnnamedTestStruct<T1, T2>(T1, T2);
 /// ```
@@ -80,6 +94,7 @@ fn zero_copy_send_derive_does_not_work_for_generic_unnamed_struct_when_members_d
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 /// use iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct GenericUnnamedTestStruct<T1: ZeroCopySend, T2>(T1, T2);
 /// ```
@@ -91,6 +106,7 @@ fn zero_copy_send_derive_does_not_work_for_generic_unnamed_struct_when_not_all_m
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[name("abc")]
 /// struct TestStruct(u8, i32);
@@ -101,6 +117,7 @@ fn zero_copy_send_derive_does_not_work_with_wrong_attribute() {}
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name("abc")]
 /// #[type_name("def")]
@@ -112,6 +129,7 @@ fn zero_copy_send_derive_does_not_work_with_more_than_one_attribute() {}
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name]
 /// struct TestStruct(u8, i32);
@@ -122,6 +140,7 @@ fn zero_copy_send_derive_does_not_work_without_attribute_argument() {}
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name()]
 /// struct TestStruct(u8, i32);
@@ -132,6 +151,7 @@ fn zero_copy_send_derive_does_not_work_with_empty_attribute_argument() {}
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name("abc" "def")]
 /// struct TestStruct(u8, i32);
@@ -142,6 +162,7 @@ fn zero_copy_send_derive_does_not_work_with_more_than_one_attribute_argument() {
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name("abc", "def")]
 /// struct TestStruct(u8, i32);
@@ -152,6 +173,7 @@ fn zero_copy_send_derive_does_not_work_with_more_than_one_attribute_argument_com
 /// ``` compile_fail
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// #[type_name(abc)]
 /// struct TestStruct(u8, i32);
@@ -165,6 +187,7 @@ fn zero_copy_send_derive_does_not_work_with_non_string_literal_attribute() {}
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 /// use iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// enum TestEnum {
 ///     Variant1,
@@ -190,6 +213,7 @@ fn zero_copy_send_derive_works_for_enum_with_various_variant_types() {}
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 /// use iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// enum TestEnum<T: ZeroCopySend, U: ZeroCopySend> {
 ///     Variant1,
@@ -227,11 +251,13 @@ fn zero_copy_send_derive_works_for_empty_enum() {}
 /// use iceoryx2_bb_derive_macros::ZeroCopySend;
 /// use iceoryx2_bb_elementary::zero_copy_send::ZeroCopySend;
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// struct InnerStruct {
 ///     field: u32,
 /// }
 ///
+/// #[repr(C)]
 /// #[derive(ZeroCopySend)]
 /// enum TestEnum {
 ///     Variant1,
diff --git a/iceoryx2-bb/derive-macros/tests/zero_copy_send_tests.rs b/iceoryx2-bb/derive-macros/tests/zero_copy_send_tests.rs
index 2c43cfa2f..c0d7a1d0c 100644
--- a/iceoryx2-bb/derive-macros/tests/zero_copy_send_tests.rs
+++ b/iceoryx2-bb/derive-macros/tests/zero_copy_send_tests.rs
@@ -24,12 +24,14 @@ mod zero_copy_send {
     struct Foo(u16);
     unsafe impl ZeroCopySend for Foo {}
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     struct NamedTestStruct {
         _val1: u64,
         _val2: Foo,
     }
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     #[type_name("Nala")]
     struct NamedTestStructWithAttr {
@@ -38,13 +40,16 @@ mod zero_copy_send {
     }
 
     #[allow(dead_code)]
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     struct UnnamedTestStruct(i32, u32, Foo);
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     #[type_name("Hypnotoad")]
     struct UnnamedTestStructWithAttr(i32, u32, Foo);
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     struct GenericNamedTestStruct<T1, T2>
     where
@@ -55,6 +60,7 @@ mod zero_copy_send {
         _val2: T2,
     }
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     #[type_name("Wolf")]
     struct GenericNamedTestStructWithAttr<T1, T2>
@@ -66,12 +72,14 @@ mod zero_copy_send {
         _val2: T2,
     }
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     struct GenericUnnamedTestStruct<T1, T2>(T1, T2)
     where
         T1: ZeroCopySend,
         T2: ZeroCopySend;
 
+    #[repr(C)]
     #[derive(ZeroCopySend)]
     #[type_name("Smeik")]
     struct GenericUnnamedTestStructWithAttr<T1, T2>(T1, T2)
diff --git a/iceoryx2/tests/service_publish_subscribe_tests.rs b/iceoryx2/tests/service_publish_subscribe_tests.rs
index bc8f06e64..bbfaba54d 100644
--- a/iceoryx2/tests/service_publish_subscribe_tests.rs
+++ b/iceoryx2/tests/service_publish_subscribe_tests.rs
@@ -38,6 +38,7 @@ mod service_publish_subscribe {
     use iceoryx2_bb_testing::watchdog::Watchdog;
 
     #[derive(Debug, ZeroCopySend)]
+    #[repr(C)]
     struct SomeUserHeader {
         value: [u64; 1024],
     }