-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ctest: add tests for field size, offset, and field ptr #4561
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
base: main
Are you sure you want to change the base?
Conversation
…es, and signededness of aliases.
ctest-next/src/template.rs
Outdated
#[derive(Clone, Debug)] | ||
pub(crate) struct TestFieldPtr { | ||
pub test_name: BoxStr, | ||
pub id: BoxStr, | ||
pub field: Field, | ||
pub c_field: BoxStr, | ||
pub volatile_keyword: BoxStr, | ||
pub c_signature: BoxStr, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub(crate) struct TestFieldSizeOffset { | ||
pub test_name: BoxStr, | ||
pub id: BoxStr, | ||
pub field: Field, | ||
pub c_field: BoxStr, | ||
pub c_ty: BoxStr, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, I think these should go after TestConst
since they're used later in the templates. Same for the new field_*
functions.
ctest-next/templates/test.c
Outdated
{{ item.volatile_keyword }}{{ item.c_signature }} { | ||
return &b->{{ item.c_field }}; | ||
} | ||
{%- endfor +%} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing \n
ctest-next/templates/test.c
Outdated
// The name of the function remains `ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}` | ||
{{ item.volatile_keyword }}{{ item.c_signature }} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the args and return type be split to separate fields so this is directly visible rather than a comment? So
{{ item.volatile_keyword }} {{ item.c_ret_ty }} ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}({{ item.c_args }})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see the problem, function pointer syntax is horrid.
I'd suggest eliminating this with a typedef when you're dealing with function pointers:
typedef ret_ty (*some_name)(argty1, argty2);
some_name ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(...) { /* ... */ }
some_name
can be something like ctest_field_ty__{{ item.id }}__{{ item.field.ident() }}
ctest-next/templates/test.rs
Outdated
check_same(field_ptr as *mut _, | ||
ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(ty_ptr), | ||
"field type {{ item.field.ident() }} of {{ item.id }}"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block can be split a bit so we can see what unsafe
is handling. Something like:
let field_ptr = &raw const ((*ty_ptr).{{ item.field.ident() }})
should get its ownunsafe
block that says it doesn't actually readty_ptr
(AIUI requiringunsafe
for this op is a limitation of theunsafe
check, can't cause UB)- Getting the size and offset from C should each get their own
unsafe
block assigned to a variable - The
read_unaligned
function should get its own block - The rest should be able to exist outside of
unsafe
ctest-next/templates/test.rs
Outdated
fn ctest_field_ptr__{{ item.id }}__{{ item.field.ident() }}(a: *const {{ item.id }}) -> *mut u8; | ||
} | ||
|
||
unsafe { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the above, everything except let field_ptr = ...
and let c_field_ptr = ctest_field_ptr__...(...)
doesn't need to be in unsafe
ctest-next/templates/test.rs
Outdated
#[expect(unused_imports)] | ||
use std::mem::{MaybeUninit, offset_of}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be allow
rather than expect
? Because when there are field tests, it's not actually unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With these two small changes, the size+offset changes look good to me. I do have some more feedback for the field pointer tests though; maybe want to split that off to another PR so the rest of this can merge now?
ctest-next/src/template.rs
Outdated
for struct_ in helper.ffi_items.structs() { | ||
for field in &struct_.fields { | ||
let should_skip_field_tests = helper | ||
.generator | ||
.skips | ||
.iter() | ||
.any(|f| f(&MapInput::StructField(struct_, field))); | ||
|
||
if should_skip_field_tests { | ||
continue; | ||
} | ||
|
||
let item = TestFieldSizeOffset { | ||
test_name: field_size_offset_test_ident(struct_.ident(), field.ident()), | ||
id: struct_.ident().into(), | ||
c_ty: helper.c_type(struct_)?.into(), | ||
field: field.clone(), | ||
c_field: helper | ||
.c_ident(MapInput::StructField(struct_, field)) | ||
.into_boxed_str(), | ||
}; | ||
self.field_size_offset_tests.push(item.clone()); | ||
self.test_idents.push(item.test_name); | ||
} | ||
} | ||
|
||
for union_ in helper.ffi_items.unions() { | ||
for field in &union_.fields { | ||
let should_skip_field_tests = helper | ||
.generator | ||
.skips | ||
.iter() | ||
.any(|f| f(&MapInput::UnionField(union_, field))); | ||
|
||
if should_skip_field_tests { | ||
continue; | ||
} | ||
|
||
let item = TestFieldSizeOffset { | ||
test_name: field_size_offset_test_ident(union_.ident(), field.ident()), | ||
id: union_.ident().into(), | ||
c_ty: helper.c_type(union_)?.into(), | ||
field: field.clone(), | ||
c_field: helper | ||
.c_ident(MapInput::UnionField(union_, field)) | ||
.into_boxed_str(), | ||
}; | ||
self.field_size_offset_tests.push(item.clone()); | ||
self.test_idents.push(item.test_name); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since structs and unions use the same field types, I think this could be collapsed to a single iterator. Something like:
let should_skip = |map_input| helper
.generator
.skips
.iter()
.any(|f| f(map_input));
let struct_fields = helper.ffi_items.structs().iter()
.flat_map(|st| st.fields().map(|field| (st, field))
.filter(|(st, field)| should_skip(&MapInput::StructField(st, field))
.map(|(st, field)| field);
let union_fields = helper.ffi_items.unions().iter()
.flat_map(|un| un.fields().map(|field| (un, field))
.filter(|(un, field)| should_skip(&MapInput::UnionField(un, field))
.map(|(un, field)| field);
for field in struct_fields.chain(union_fields) {
// ...
}
Something similar for populate_field_ptr_tests
ctest-next/templates/test.rs
Outdated
let ty_ptr = unsafe { &raw const (*uninit_ty).{{ item.field.ident() }} }; | ||
let val = unsafe { ty_ptr.read_unaligned() }; | ||
|
||
let ctest_field_offset = unsafe { ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() }; | ||
check_same(offset_of!({{ item.id }}, {{ item.field.ident() }}) as u64, ctest_field_offset, | ||
"field offset {{ item.field.ident() }} of {{ item.id }}"); | ||
|
||
let ctest_field_size = unsafe { ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Safety comments
let ty_ptr = unsafe { &raw const (*uninit_ty).{{ item.field.ident() }} }; | |
let val = unsafe { ty_ptr.read_unaligned() }; | |
let ctest_field_offset = unsafe { ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() }; | |
check_same(offset_of!({{ item.id }}, {{ item.field.ident() }}) as u64, ctest_field_offset, | |
"field offset {{ item.field.ident() }} of {{ item.id }}"); | |
let ctest_field_size = unsafe { ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() }; | |
// SAFETY: we assume the field access doesn't wrap | |
let ty_ptr = unsafe { &raw const (*uninit_ty).{{ item.field.ident() }} }; | |
// SAFETY: we assume that all zeros is a valid bitpattern for `ty_ptr`, otherwise the | |
// test should be skipped. | |
let val = unsafe { ty_ptr.read_unaligned() }; | |
// SAFETY: FFI call with no preconditions | |
let ctest_field_offset = unsafe { ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}() }; | |
check_same(offset_of!({{ item.id }}, {{ item.field.ident() }}) as u64, ctest_field_offset, | |
"field offset {{ item.field.ident() }} of {{ item.id }}"); | |
// SAFETY: FFI call with no preconditions | |
let ctest_field_size = unsafe { ctest_size_of__{{ item.id }}__{{ item.field.ident() }}() }; |
It's fine to review the field ptr checks now, splitting it would be harder. |
Description
Adds tests for checking if the size and offset of a field is the same in Rust and C, as well as the pointer to that field.
Sources
Checklist
libc-test/semver
have been updated*LAST
or*MAX
areincluded (see #3131)
cd libc-test && cargo test --target mytarget
);especially relevant for platforms that may not be checked in CI