Skip to content

Commit 9abcd5d

Browse files
authored
fix: handle rolling introspection member layout (#641)
Signed-off-by: Esteve Fernandez <esteve@apache.org>
1 parent 6f8496b commit 9abcd5d

1 file changed

Lines changed: 261 additions & 30 deletions

File tree

rclrs/src/dynamic_message/message_structure.rs

Lines changed: 261 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{ffi::CStr, num::NonZeroUsize};
1+
use std::{ffi::CStr, mem, num::NonZeroUsize, os::raw::c_char, os::raw::c_void};
22

33
use super::TypeErasedSequence;
44
use crate::rcl_bindings::{
@@ -61,6 +61,44 @@ pub struct MessageFieldInfo {
6161
pub(crate) offset: usize,
6262
}
6363

64+
type ResizeFunction = Option<unsafe extern "C" fn(arg1: *mut c_void, size: usize) -> bool>;
65+
66+
#[repr(C)]
67+
#[derive(Clone, Copy)]
68+
struct MessageMemberPrefix {
69+
name_: *const c_char,
70+
type_id_: u8,
71+
string_upper_bound_: usize,
72+
members_: *const rosidl_message_type_support_t,
73+
is_key_: u8,
74+
is_array_: u8,
75+
array_size_: usize,
76+
is_upper_bound_: u8,
77+
offset_: u32,
78+
default_value_: *const c_void,
79+
size_function: *const c_void,
80+
get_const_function: *const c_void,
81+
get_function: *const c_void,
82+
fetch_function: *const c_void,
83+
assign_function: *const c_void,
84+
resize_function: *const c_void,
85+
}
86+
87+
impl MessageMemberPrefix {
88+
fn is_valid_for_message(&self, message_size: usize) -> bool {
89+
use rosidl_typesupport_introspection_c_field_types::*;
90+
91+
!self.name_.is_null()
92+
&& (rosidl_typesupport_introspection_c__ROS_TYPE_FLOAT as u8
93+
..=rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE as u8)
94+
.contains(&self.type_id_)
95+
&& [self.is_key_, self.is_array_, self.is_upper_bound_]
96+
.iter()
97+
.all(|value| *value <= 1)
98+
&& usize::try_from(self.offset_).is_ok_and(|offset| offset < message_size)
99+
}
100+
}
101+
64102
/// A description of the structure of a message.
65103
///
66104
/// Namely, the list of fields and their types.
@@ -196,38 +234,75 @@ impl MessageFieldInfo {
196234
// That function must be unsafe, since it is possible to safely create a garbage non-null
197235
// pointer and store it in a rosidl_message_member_t.
198236
unsafe fn from(rosidl_message_member: &rosidl_message_member_t) -> Self {
199-
debug_assert!(!rosidl_message_member.name_.is_null());
200-
let name = /*unsafe*/ { CStr::from_ptr(rosidl_message_member.name_) }
201-
.to_string_lossy()
202-
.into_owned();
203-
let value_kind = match (
237+
Self::from_parts(
238+
rosidl_message_member.name_,
239+
rosidl_message_member.type_id_,
240+
rosidl_message_member.string_upper_bound_,
241+
rosidl_message_member.members_,
204242
rosidl_message_member.is_array_,
205-
rosidl_message_member.resize_function.is_some(),
243+
rosidl_message_member.array_size_,
206244
rosidl_message_member.is_upper_bound_,
207-
) {
245+
rosidl_message_member.resize_function,
246+
rosidl_message_member.offset_,
247+
)
248+
}
249+
250+
unsafe fn from_prefix(rosidl_message_member: &MessageMemberPrefix) -> Self {
251+
let resize_function: ResizeFunction = if rosidl_message_member.resize_function.is_null() {
252+
None
253+
} else {
254+
Some(mem::transmute::<
255+
*const c_void,
256+
unsafe extern "C" fn(*mut c_void, usize) -> bool,
257+
>(rosidl_message_member.resize_function))
258+
};
259+
260+
Self::from_parts(
261+
rosidl_message_member.name_,
262+
rosidl_message_member.type_id_,
263+
rosidl_message_member.string_upper_bound_,
264+
rosidl_message_member.members_,
265+
rosidl_message_member.is_array_ != 0,
266+
rosidl_message_member.array_size_,
267+
rosidl_message_member.is_upper_bound_ != 0,
268+
resize_function,
269+
rosidl_message_member.offset_,
270+
)
271+
}
272+
273+
unsafe fn from_parts(
274+
name: *const c_char,
275+
type_id: u8,
276+
string_upper_bound: usize,
277+
members: *const rosidl_message_type_support_t,
278+
is_array: bool,
279+
array_size: usize,
280+
is_upper_bound: bool,
281+
resize_function: ResizeFunction,
282+
offset: u32,
283+
) -> Self {
284+
debug_assert!(!name.is_null());
285+
let name = /*unsafe*/ { CStr::from_ptr(name) }
286+
.to_string_lossy()
287+
.into_owned();
288+
let value_kind = match (is_array, resize_function.is_some(), is_upper_bound) {
208289
(false, _, _) => ValueKind::Simple,
209-
(true, false, _) => ValueKind::Array {
210-
length: rosidl_message_member.array_size_,
211-
},
290+
(true, false, _) => ValueKind::Array { length: array_size },
212291
(true, true, false) => {
213-
assert_eq!(rosidl_message_member.array_size_, 0);
292+
assert_eq!(array_size, 0);
214293
ValueKind::Sequence
215294
}
216295
(true, true, true) => ValueKind::BoundedSequence {
217-
upper_bound: rosidl_message_member.array_size_,
296+
upper_bound: array_size,
218297
},
219298
};
220299
Self {
221300
name,
222-
base_type: BaseType::new(
223-
rosidl_message_member.type_id_,
224-
NonZeroUsize::new(rosidl_message_member.string_upper_bound_),
225-
rosidl_message_member.members_,
226-
),
301+
base_type: BaseType::new(type_id, NonZeroUsize::new(string_upper_bound), members),
227302
value_kind,
228-
string_upper_bound: rosidl_message_member.string_upper_bound_,
229-
resize_function: rosidl_message_member.resize_function,
230-
offset: usize::try_from(rosidl_message_member.offset_).unwrap(),
303+
string_upper_bound,
304+
resize_function,
305+
offset: usize::try_from(offset).unwrap(),
231306
}
232307
}
233308
}
@@ -251,6 +326,61 @@ impl MessageFieldInfo {
251326
// ========================= impl for MessageStructure =========================
252327

253328
impl MessageStructure {
329+
unsafe fn message_member_stride(message_members: &rosidl_message_members_t) -> usize {
330+
let current_stride = mem::size_of::<rosidl_message_member_t>();
331+
let prefix_stride = mem::size_of::<MessageMemberPrefix>();
332+
if message_members.member_count_ < 2 {
333+
return current_stride;
334+
}
335+
336+
let base = message_members.members_ as *const u8;
337+
for stride in [current_stride, prefix_stride] {
338+
let candidate = &*(base.add(stride) as *const MessageMemberPrefix);
339+
if candidate.is_valid_for_message(message_members.size_of_) {
340+
return stride;
341+
}
342+
}
343+
344+
current_stride
345+
}
346+
347+
cfg_if::cfg_if! {
348+
if #[cfg(any(ros_distro = "humble", ros_distro = "jazzy", ros_distro = "kilted"))] {
349+
unsafe fn message_fields_from_rosidl_message_members(
350+
message_members: &rosidl_message_members_t,
351+
num_fields: usize,
352+
) -> Vec<MessageFieldInfo> {
353+
(0..num_fields)
354+
.map(|i| {
355+
// SAFETY: This is an array as per the documentation
356+
let rosidl_message_member: &rosidl_message_member_t =
357+
/*unsafe*/ { &*message_members.members_.add(i) };
358+
// SAFETY: This is a valid string pointer
359+
MessageFieldInfo::from(rosidl_message_member)
360+
})
361+
.collect()
362+
}
363+
} else {
364+
unsafe fn message_fields_from_rosidl_message_members(
365+
message_members: &rosidl_message_members_t,
366+
num_fields: usize,
367+
) -> Vec<MessageFieldInfo> {
368+
// Rolling and newer distros may be built with bindings whose `MessageMember`
369+
// trailing fields differ from the runtime introspection layout, so use the stable
370+
// prefix and detect the runtime array stride before reading field names.
371+
let stride = Self::message_member_stride(message_members);
372+
let base = message_members.members_ as *const u8;
373+
(0..num_fields)
374+
.map(|i| {
375+
let rosidl_message_member =
376+
&*(base.add(i * stride) as *const MessageMemberPrefix);
377+
MessageFieldInfo::from_prefix(rosidl_message_member)
378+
})
379+
.collect()
380+
}
381+
}
382+
}
383+
254384
/// Parses the C struct containing a list of fields.
255385
// That function must be unsafe, since it is possible to safely create a garbage non-null
256386
// pointer and store it in a rosidl_message_members_t.
@@ -259,15 +389,8 @@ impl MessageStructure {
259389
) -> Self {
260390
debug_assert!(!message_members.members_.is_null());
261391
let num_fields: usize = usize::try_from(message_members.member_count_).unwrap();
262-
let mut fields: Vec<_> = (0..num_fields)
263-
.map(|i| {
264-
// SAFETY: This is an array as per the documentation
265-
let rosidl_message_member: &rosidl_message_member_t =
266-
/*unsafe*/ { &*message_members.members_.add(i) };
267-
// SAFETY: This is a valid string pointer
268-
MessageFieldInfo::from(rosidl_message_member)
269-
})
270-
.collect();
392+
let mut fields =
393+
Self::message_fields_from_rosidl_message_members(message_members, num_fields);
271394
fields.sort_by_key(|field_info| field_info.offset);
272395
// SAFETY: Immediate conversion into owned string.
273396
let namespace = /*unsafe*/ {
@@ -1153,4 +1276,112 @@ mod tests {
11531276
let _ = msg.get("basic_types_values");
11541277
let _ = msg.get_mut("basic_types_values");
11551278
}
1279+
1280+
#[test]
1281+
fn message_member_stride_prefers_prefix_layout_when_needed() {
1282+
use rosidl_typesupport_introspection_c_field_types::*;
1283+
use std::{alloc::Layout, mem::size_of, ptr};
1284+
1285+
let namespace = c"test_msgs/msg";
1286+
let message_name = c"Compat";
1287+
let first_name = c"first";
1288+
let second_name = c"second";
1289+
1290+
let prefix_stride = size_of::<MessageMemberPrefix>();
1291+
let runtime_stride = size_of::<rosidl_message_member_t>();
1292+
if runtime_stride <= prefix_stride {
1293+
return;
1294+
}
1295+
1296+
let layout = Layout::from_size_align(
1297+
runtime_stride + prefix_stride,
1298+
std::mem::align_of::<MessageMemberPrefix>(),
1299+
)
1300+
.unwrap();
1301+
// SAFETY: The allocation is large enough and properly aligned for MessageMemberPrefix.
1302+
let bytes = unsafe { std::alloc::alloc_zeroed(layout) };
1303+
assert!(!bytes.is_null());
1304+
let members_ptr = bytes as *mut MessageMemberPrefix;
1305+
1306+
let first = MessageMemberPrefix {
1307+
name_: first_name.as_ptr(),
1308+
type_id_: rosidl_typesupport_introspection_c__ROS_TYPE_BOOLEAN as u8,
1309+
string_upper_bound_: 0,
1310+
members_: ptr::null(),
1311+
is_key_: 0,
1312+
is_array_: 0,
1313+
array_size_: 0,
1314+
is_upper_bound_: 0,
1315+
offset_: 0,
1316+
default_value_: ptr::null(),
1317+
size_function: ptr::null(),
1318+
get_const_function: ptr::null(),
1319+
get_function: ptr::null(),
1320+
fetch_function: ptr::null(),
1321+
assign_function: ptr::null(),
1322+
resize_function: ptr::null(),
1323+
};
1324+
let second = MessageMemberPrefix {
1325+
name_: second_name.as_ptr(),
1326+
type_id_: rosidl_typesupport_introspection_c__ROS_TYPE_UINT8 as u8,
1327+
string_upper_bound_: 0,
1328+
members_: ptr::null(),
1329+
is_key_: 0,
1330+
is_array_: 0,
1331+
array_size_: 0,
1332+
is_upper_bound_: 0,
1333+
offset_: 1,
1334+
default_value_: ptr::null(),
1335+
size_function: ptr::null(),
1336+
get_const_function: ptr::null(),
1337+
get_function: ptr::null(),
1338+
fetch_function: ptr::null(),
1339+
assign_function: ptr::null(),
1340+
resize_function: ptr::null(),
1341+
};
1342+
unsafe {
1343+
ptr::write(members_ptr, first);
1344+
// Leave the bytes at `runtime_stride` zeroed. Writing a fake runtime-stride member
1345+
// there would overlap this prefix-stride member and corrupt the candidate that the
1346+
// test expects to be selected.
1347+
ptr::write(bytes.add(prefix_stride) as *mut MessageMemberPrefix, second);
1348+
}
1349+
1350+
cfg_if::cfg_if! {
1351+
if #[cfg(ros_distro = "humble")] {
1352+
let message_members = crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers_s {
1353+
message_namespace_: namespace.as_ptr(),
1354+
message_name_: message_name.as_ptr(),
1355+
member_count_: 2,
1356+
size_of_: 2,
1357+
members_: members_ptr as *const rosidl_message_member_t,
1358+
init_function: None,
1359+
fini_function: None,
1360+
};
1361+
} else {
1362+
let message_members = crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers_s {
1363+
message_namespace_: namespace.as_ptr(),
1364+
message_name_: message_name.as_ptr(),
1365+
member_count_: 2,
1366+
size_of_: 2,
1367+
has_any_key_member_: false,
1368+
members_: members_ptr as *const rosidl_message_member_t,
1369+
init_function: None,
1370+
fini_function: None,
1371+
};
1372+
}
1373+
};
1374+
1375+
assert_eq!(
1376+
unsafe { MessageStructure::message_member_stride(&message_members) },
1377+
size_of::<MessageMemberPrefix>()
1378+
);
1379+
1380+
let second_prefix = unsafe { &*(bytes.add(prefix_stride) as *const MessageMemberPrefix) };
1381+
let field_info = unsafe { MessageFieldInfo::from_prefix(second_prefix) };
1382+
assert_eq!(field_info.name, "second");
1383+
assert_eq!(field_info.base_type, BaseType::Uint8);
1384+
1385+
unsafe { std::alloc::dealloc(bytes, layout) };
1386+
}
11561387
}

0 commit comments

Comments
 (0)