-
Notifications
You must be signed in to change notification settings - Fork 110
Other proc macros can break the soundness of our custom derives #388
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
Comments
I verified that attribute macros can change the definition of an item after derives on it have already run: use transform::insert_field;
trait A {}
trait B {}
#[derive(Debug)]
#[insert_field(baz = "u32")]
struct Foo {
bar: i32,
}
fn main() {
println!("{:?}", Foo {
bar: 10,
baz: 100,
});
} prints:
Which demonstrates that even though use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Meta, Lit, Expr, Visibility, token::Colon, Data, Fields, Field, parse_quote};
#[proc_macro_attribute]
pub fn insert_field(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let meta = parse_macro_input!(attr as Meta);
let Meta::NameValue(name_value) = meta else { panic!("expected name-value") };
let name = name_value.path.get_ident().expect("name to be an identifier");
let Expr::Lit(lit) = &name_value.value else { panic!("expected literal") };
let Lit::Str(ty_name) = &lit.lit else { panic!("expected string") };
let ident = Ident::new(&ty_name.value(), Span::call_site());
let mut item = parse_macro_input!(item as DeriveInput);
let Data::Struct(struct_) = &mut item.data else { panic!("expected struct") };
let Fields::Named(fields) = &mut struct_.fields else { panic!("expected fields") };
fields.named.push(Field {
attrs: Vec::new(),
vis: Visibility::Inherited,
mutability: syn::FieldMutability::None,
ident: Some(name.clone()),
colon_token: Some(Colon { spans: [Span::call_site()]}),
ty: parse_quote!(#ident),
});
let output = quote! {
#item
};
output.into()
} |
The status quo is that proc macros evaluate from the outside in. We should confirm this is specified, and do what we can to mitigate it. We could defend against @djkoloski's example by also emitting code that destructures the annotated type, thus ensuring that there would be a compile error if the definition changed. However, imagine a proc-macro attribute that only removed (or tampered) with |
IIUC, guaranteeing evaluation order should be enough to mitigate the "unknown attribute" problem: We just ensure that we're placed in a location that evaluates after any attribute macros. That still leaves open the question of shadowing attributes by name - e.g., introducing a proc macro attribute called Another thought: Does the token stream emitted by a proc macro attribute include the proc macro attribute annotation? If not, we should expect that any proc macro attributes which execute before us will no longer be present in the token stream that we see. This should mean that a proc macro attribute which shadows |
I've confirmed that it's not possible to shadow
|
Phew |
cc @reinerp |
A user ran into this: #1497 (comment) |
Said user jumping in since it appears I found the wrong thread ;) To make things more concrete, here's an MRE repo which illustrates one of the holes when interacting with the bitfield-struct crate. |
It looks like your code has compilation errors even if I remove all the zerocopy bits: use bitfield_struct::bitfield;
// Enum that's using an integer representation,
// but does not cover the full range.
// Thus, it must be TryFromBytes.
#[repr(u8)]
pub enum IntBackedEnum {
VariantA = 0,
VariantB = 1,
VariantC = 26,
}
// The trouble seems to come when we further use bitfield-struct
// to shove this into a packed field.
#[bitfield(u8)]
struct BitfieldWithEnum {
// We're bit packing and only care about 6 bits!
#[bits(6)]
enum_value: IntBackedEnum,
#[bits(2)]
other_field: u8,
}
// And here's a second example of breakage.
// If you derive TryFromBytes before the bitfield macro,
// compilation fails.
// It works when you flip the order (bitfield macro first).
#[bitfield(u8)]
struct BitfieldWithInteger {
// We're bit packing and only care about 6 bits!
#[bits(6)]
enum_value: u8,
#[bits(2)]
other_field: u8,
}
|
d'oh! I feel kind of silly ;) My example included a custom enum which needs to have some magic methods for the bitfield to work. I have just pushed a correction that will not compile as-is, but will compile if you flip the order of the bitfield and derive macros. (Though now there's another problem: that the other crate doesn't support "failable" conversion, but that's out of scope here). So, in the end, it looks like there is only one issue that I'm highlighing in the MRE: that the ordering can affect whether the generated code is valid. |
Ah okay, yeah unfortunately that is the limitation we're aware of. We should probably reach our derives to detect unrecognized attributes and bail with an error. |
This issue tracks soundness holes in our custom derives introduced by other proc macros. Tasks:
#[cfg_attr(foo, repr(...)]
might cause our derive to either fail to notice arepr
or notice arepr
which is removed in some compilationsrepr
, and trick our custom derives into thinking it's the "real"repr
attribute. This seems not to be a hole for two reasons:#[repr(...)]
, rustc currently complains that the name is ambiguous, and fails compilation. We need to confirm that this behavior is guaranteed.repr
attribute which is actually a proc macro will be evaluated first, and the token stream passed to our custom derive will not include it - we'll only see "real"repr
attributes.Misc Notes
#[serde(...)]
) seems to depend on attribute evaluation order.The text was updated successfully, but these errors were encountered: