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
| 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.
Recursing caused *const T1Bar to be translated as struct const struct T1Bar *, #4617 removes basic_c_type so it's not a problem.
tgross35
left a comment
There was a problem hiding this comment.
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.
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.
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.
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::Signaturesince 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.
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.
Actually, with #4594 (comment) this all goes away right?
ae8f57a to
17c3633
Compare
da92f22 to
c6a7386
Compare
tgross35
left a comment
There was a problem hiding this comment.
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.
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.
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.
Where is the *const coming from? Could you paste the code and error?
There was a problem hiding this comment.
There was a problem hiding this comment.
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 satisfiedThere was a problem hiding this comment.
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.
Done.
Also what is cheri?
There was a problem hiding this comment.
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
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 T1Barwas being translated asstruct *const T1Bar, that has been fixed, although in a hacky way.Sources
Checklist
libc-test/semverhave been updated*LASTor*MAXareincluded (see #3131)
cd libc-test && cargo test --target mytarget);especially relevant for platforms that may not be checked in CI