-
-
Notifications
You must be signed in to change notification settings - Fork 80
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
Derive macros are unsound #281
Comments
I'm not remembering what Derive proc macros are allowed to emit. Can they create code other code besides the trait impl they're for? Otherwise, we might have to do the other option mentioned in the zerocopy thread and bail out on all unknown attributes. BUT that would be extremely uncool. |
Yes, derive macros can emit arbitrary items, essentially. (IIRC this is already used in That said, Is it documented anywhere that it is intentional for derive macros to see the "original" tokens when an item has been modified by an attribute macro? My first instinct would be to call it a Rust bug that the derive macro does not get the correct |
i believe the exact ordering of the attributes matters, unfortunately |
use the_macros::*;
fn main() {
{
#[derive(PrintTokens)]
#[add_fields({y: u32})]
struct Foo {
x: u32,
}
print_tokens()
}
{
#[add_fields({y: u32})]
#[derive(PrintTokens)]
struct Foo {
x: u32,
}
print_tokens()
}
}
Okay, the original bug report made it sound like Rust never applied attribute macros before giving the item to derive macros, but yeah it does if it is before the derive (since it applies macros outside-in) and otherwise the attribute macro is still in the input.
|
Actually after some testing, this won't work in general. Even ignoring all the false positives it would have, there are also false negatives: there's no way in general to differentiate between our own helper attributes for other traits (like false negative exampleuse adversarial_macros::transparent; // inserts a `y: Infallible` field, e.g., or any 1-ZST with a nontrivial safety invariant
use bytemuck::{Zeroable};
#[derive(Clone, Copy, Zeroable, TransparentWrapper)]
#[transparent(u32)] // fine, made inert by `derive(TransparentWrapper)`
#[repr(transparent)]
struct Foo {
x: u32
}
#[derive(Clone, Copy, Zeroable)]
#[transparent(u32)] // unsound false negative, adds a field after `Zeroable` derive macro runs
#[repr(transparent)]
struct Foo {
x: u32
} From inside the Edit: exampleuse adversarial_macros::transparent;
#[derive(Debug, Clone, Copy)]
#[derive(TransparentWrapper)] // comment this out for a different error
#[transparent(u32)]
#[derive(Zeroable)]
#[repr(transparent)]
struct Foo {
x: u32,
}
|
Another wrinkle: For fields, we can check the types using some #[derive(Zeroable)]
#[repr(u32)]
#[adversarial_macro] // changes discriminant so none are 0
enum Foo {
X(i32) = 0,
} Is there even any way to detect this at compile time? Edit: For a fieldless enum you can |
It's sort of the inverse: I don't believe we have fixed the order of macro evaluation for proc macros in a way that is particularly useful or provides any real guarantees to macro authors, in any direction. |
I came across google/zerocopy#388 (comment) by chance. The same applies to this crate:
Segfaults. Solutions might involve generating a function that asserts (by compiling) that the types seen by the proc macro are the same as the types in the final type and that the final type contains no additional fields.
The text was updated successfully, but these errors were encountered: