-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ctest: add foreign fn tests #4594
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
Conversation
While it does work, it's definitely very dirty, but I'm not sure how to improve it more. |
ctest-next/src/template.rs
Outdated
@@ -482,36 +583,97 @@ impl<'a> TranslateHelper<'a> { | |||
fn basic_c_type(&self, ty: &syn::Type) -> Result<String, TranslationError> { | |||
let type_name = match ty { | |||
syn::Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(), | |||
syn::Type::Ptr(p) => self.basic_c_type(&p.elem)?, | |||
syn::Type::Reference(r) => self.basic_c_type(&r.elem)?, | |||
// Using recursion here causes breakage. |
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.
What breaks here?
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.
Recursing caused *const T1Bar
to be translated as struct const struct T1Bar *
, #4617 removes basic_c_type
so it's not a problem.
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.
Sorry this has been waiting so long, busy week. I'll have more time to review this weekend.
ctest-next/src/template.rs
Outdated
pub(crate) fn parse_signature_to_bare_fn( | ||
&self, | ||
signature: &str, | ||
abi: &str, | ||
) -> Result<syn::TypeBareFn, TranslationError> { | ||
let (_, s) = signature.split_once('(').unwrap(); | ||
let type_str = format!("type T = unsafe extern \"{abi}\" fn({s};"); | ||
let item: syn::ItemType = syn::parse_str(&type_str).map_err(|_| { | ||
TranslationError::new( | ||
TranslationErrorKind::UnsupportedType, | ||
signature, | ||
Span::call_site(), | ||
) | ||
})?; | ||
if let syn::Type::BareFn(f) = item.ty.deref() { | ||
return Ok(f.clone()); | ||
} | ||
panic!("`parse_signature_to_bare_fn` returned a non function type.") | ||
} |
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.
What are you trying to guard against here with the panic?
String manipulation is never very robust, and you have syn
types for everything already. Why not construct a TypeBareFn
from Signature
? This also allows you to reject specific syntax that you don't want to support.
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.
All the string manipulation is doing is removing the name of the function from the signature (since that isn't allowed in bare fn types). I can't do that directly from syn::Signature
since it's not allowed to have an empty Ident
.
The panic should actually be an unreachable though.
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.
AST -> string -> modify -> parse to AST
is just a definite code smell to avoid: remember that things like the below, while ugly, is completely valid Rust syntax that this crate may have to deal with in some places :)
fn foo<const N: (usize)>() {}
fn bar<T: (Copy)>() {}
static FOO: (fn()) = foo::<{ (10) }>;
Doing things at an AST level can be more complicated. But that's a good thing: it makes you think about what needs to get passed, what gets modified, and what gets rejected.
I can't do that directly from
syn::Signature
since it's not allowed to have an emptyIdent
.
Think about it a bit differently: both your input and your output are valid Rust code, so syn must have a representation for it. You just need to figure out what it is and how to translate between them.
In this case, your input is Signature
and the output is TypeBareFn
. Most things have a 1:1 mapping that you just copy over (you'll need to iterate the args). For anything that doesn't copy over, you should consider if it should be rejected (constness, async, generics, etc).
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.
Another option here is to use quote
, something like this:
let mut args = Vec::new();
for arg in sig.inputs {
match arg {
FnArg::Typed(pat) => args.push(pat),
FnArg::Receiver(_) => panic!("`self` args are not supported: got `{arg}`),
}
}
let ty = quote! { fn(#(#args),*) };
That's a bit easier than building a TypeBareFn
from scratch, but still avoids string hazards.
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.
Actually, with #4594 (comment) this all goes away right?
ae8f57a
to
17c3633
Compare
da92f22
to
c6a7386
Compare
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.
I think some more can be dropped here; if so, the rest lgtm with a couple remaining nits
ctest-next/templates/test.rs
Outdated
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() }; | ||
let expected: unsafe extern "C" fn() = unsafe { | ||
mem::transmute({{ item.id }} as *const ()) | ||
}; |
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.
No transmute
needed, you can compare function pointers of different types:
let a: unsafe extern "C" fn(usize) -> *mut c_void = libc::malloc;
let b: unsafe extern "C" fn(*mut c_void) = libc::free;
assert!(!std::ptr::fn_addr_eq(a, b));
the let
binding might be needed so it gives you the right thing.
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.
That doesn't work, it fails with FnPtr
not implemented for *const ()
. It also doesn't work with {{ item.id }}
directly.
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.
Where is the *const
coming from? Could you paste the code and error?
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.
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.
That's because abort
is a function that has no parameters. If it does then it fails with error (taken from ctest-test):
expected fn pointer `unsafe extern "C" fn() -> ()`
found fn item `unsafe extern "C" fn(*mut c_void, *mut c_void)
snippet causing that:
let actual = unsafe { ctest_foreign_fn__{{ item.id }}() };
let expected: unsafe extern "C" fn() = {{ item.id }};
check_same(ptr::fn_addr_eq(expected, actual), true, "{{ item.id }} function pointer");
Dropping the type coercion of unsafe extern "C" fn()
completely results in (taken from ctest-next)
error[E0277]: the trait bound `unsafe extern "C" fn(usize) -> *mut c_void` {foo::malloc}: FnPtr is not satisfied
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.
Ah, darn, so we do need the type to do the coercion. Alright, let's just do the safe thing; skip the transmute (to avoid some unsafe
), just as
cast both function pointers to a u64
and compare those. Rust allows for this https://doc.rust-lang.org/std/primitive.fn.html#casting-to-and-from-integers (I'm not exactly clear whether or not C does).
That may not work on cheri, but it will be a while before we need to cross that bridge.
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.
Done.
Also what is cheri?
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.
An experimental arch that has 128-bit pointers :D that's not the address space though, the top half is a tag about what the pointer is allowed to do. (Pretty interesting to look into if you are interested in that sort of thing)
eed6d85
to
6867007
Compare
bfb1cf9
to
ac91046
Compare
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.
Awesome, thank you!
ac91046
to
30831f5
Compare
Description
Adds support for testing foreign functions. A lot of hard to track bugs had to be fixed to do this:
*const T1Bar
was being translated asstruct *const T1Bar
, that has been fixed, although in a hacky way.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