Skip to content

Commit

Permalink
Serialize #[repr(packed)] structs by copying out their fields
Browse files Browse the repository at this point in the history
This assumes that #[repr(packed)] types are also Copy, which is a thing
we can't verify in this context. At least we're in good company: the
built-in #[derive]s all require #[derive(Copy)] for packed structs.

Further discussion:
  m4b#46 (comment)
  • Loading branch information
willglynn committed Nov 2, 2018
1 parent ad6df6e commit 2a73c26
Showing 1 changed file with 80 additions and 17 deletions.
97 changes: 80 additions & 17 deletions scroll_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@ extern crate syn;

use proc_macro::TokenStream;

fn is_repr_packed(ast: &syn::DeriveInput) -> bool {
use proc_macro2::{Delimiter,TokenTree};

ast.attrs.iter()
.find(|attr| {
// find an Attribute with ident == "repr"
attr.path.segments.len() == 1 &&
&attr.path.segments[0].ident.to_string() == "repr"
})
.map(|repr| {
// if found, see if it's "(packed)"
// this doesn't seem ideal, but it does work
match repr.tts.clone().into_iter().next() {
Some(TokenTree::Group(group)) => {
if group.delimiter() == Delimiter::Parenthesis {
match group.stream().into_iter().next() {
Some(TokenTree::Ident(ident)) => {
&ident.to_string() == "packed"
}
_ => false,
}
} else {
false
}
},
_ => false,
}
})
// in the absence of #[repr], we're not packed
.unwrap_or(false)
}

fn impl_struct(name: &syn::Ident, fields: &syn::FieldsNamed) -> proc_macro2::TokenStream {
let items: Vec<_> = fields.named.iter().map(|f| {
let ident = &f.ident;
Expand Down Expand Up @@ -70,21 +102,35 @@ pub fn derive_pread(input: TokenStream) -> TokenStream {
gen.into()
}

fn impl_try_into_ctx(name: &syn::Ident, fields: &syn::FieldsNamed) -> proc_macro2::TokenStream {
fn impl_try_into_ctx(name: &syn::Ident, fields: &syn::FieldsNamed, is_packed: bool) -> proc_macro2::TokenStream {
let items: Vec<_> = fields.named.iter().map(|f| {
let ident = &f.ident;
let ty = &f.ty;
match *ty {
syn::Type::Array(_) => {
quote! {
for i in 0..self.#ident.len() {
dst.gwrite_with(&self.#ident[i], offset, ctx)?;
if is_packed {
quote! {
for i in 0..self.#ident.len() {
dst.gwrite_with(&{self.#ident[i]}, offset, ctx)?;
}
}
} else {
quote! {
for i in 0..self.#ident.len() {
dst.gwrite_with(&self.#ident[i], offset, ctx)?;
}
}
}
},
_ => {
quote! {
dst.gwrite_with(&self.#ident, offset, ctx)?
if is_packed {
quote! {
dst.gwrite_with(&{self.#ident}, offset, ctx)?
}
} else {
quote! {
dst.gwrite_with(&self.#ident, offset, ctx)?
}
}
}
}
Expand All @@ -110,7 +156,7 @@ fn impl_pwrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
syn::Data::Struct(ref data) => {
match data.fields {
syn::Fields::Named(ref fields) => {
impl_try_into_ctx(name, fields)
impl_try_into_ctx(name, fields, is_repr_packed(&ast))
},
_ => {
panic!("Pwrite can only be derived for a regular struct with public fields")
Expand Down Expand Up @@ -256,26 +302,43 @@ pub fn derive_ioread(input: TokenStream) -> TokenStream {
gen.into()
}

fn impl_into_ctx(name: &syn::Ident, fields: &syn::FieldsNamed) -> proc_macro2::TokenStream {
fn impl_into_ctx(name: &syn::Ident, fields: &syn::FieldsNamed, is_packed: bool) -> proc_macro2::TokenStream {
let items: Vec<_> = fields.named.iter().map(|f| {
let ident = &f.ident;
let ty = &f.ty;
let size = quote! { ::scroll::export::mem::size_of::<#ty>() };
match *ty {
syn::Type::Array(ref array) => {
let arrty = &array.elem;
quote! {
let size = ::scroll::export::mem::size_of::<#arrty>();
for i in 0..self.#ident.len() {
dst.cwrite_with(&self.#ident[i], *offset, ctx);
*offset += size;
if is_packed {
quote! {
let size = ::scroll::export::mem::size_of::<#arrty>();
for i in 0..self.#ident.len() {
dst.cwrite_with(&{self.#ident[i]}, *offset, ctx);
*offset += size;
}
}
} else {
quote! {
let size = ::scroll::export::mem::size_of::<#arrty>();
for i in 0..self.#ident.len() {
dst.cwrite_with(&self.#ident[i], *offset, ctx);
*offset += size;
}
}
}
},
_ => {
quote! {
dst.cwrite_with(&self.#ident, *offset, ctx);
*offset += #size;
if is_packed {
quote! {
dst.cwrite_with(&{self.#ident}, *offset, ctx);
*offset += #size;
}
} else {
quote! {
dst.cwrite_with(&self.#ident, *offset, ctx);
*offset += #size;
}
}
}
}
Expand All @@ -300,7 +363,7 @@ fn impl_iowrite(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
syn::Data::Struct(ref data) => {
match data.fields {
syn::Fields::Named(ref fields) => {
impl_into_ctx(name, fields)
impl_into_ctx(name, fields, is_repr_packed(&ast))
},
_ => {
panic!("IOwrite can only be derived for a regular struct with public fields")
Expand Down

0 comments on commit 2a73c26

Please sign in to comment.