use anyhow::Result;
use std::path::Path;
use syn::{Fields, Item, ItemStruct, Type};
pub struct PaddingWarning {
pub name: String,
pub packed_size: usize,
pub repr_c_size: usize,
}
pub fn find_padded_repr_c_structs(src_dir: &Path) -> Result<Vec<PaddingWarning>> {
let mut out = Vec::new();
scan(src_dir, &mut out)?;
Ok(out)
}
fn scan(dir: &Path, out: &mut Vec<PaddingWarning>) -> Result<()> {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
scan(&path, out)?;
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let Ok(src) = std::fs::read_to_string(&path) else {
continue;
};
let Ok(file) = syn::parse_file(&src) else {
continue;
};
for item in &file.items {
if let Item::Struct(s) = item {
if is_repr_c(s) && derives_shank_idl(s) {
if let Some(w) = check_padding(s) {
out.push(w);
}
}
}
}
}
Ok(())
}
pub(crate) fn is_repr_c(s: &ItemStruct) -> bool {
has_repr(s, "C")
}
pub(crate) fn is_repr_transparent(s: &ItemStruct) -> bool {
has_repr(s, "transparent")
}
fn has_repr(s: &ItemStruct, kind: &str) -> bool {
s.attrs.iter().any(|a| {
if !a.path().is_ident("repr") {
return false;
}
let mut found = false;
let _ = a.parse_nested_meta(|m| {
if m.path.is_ident(kind) {
found = true;
}
Ok(())
});
found
})
}
pub(crate) fn derives_shank_idl(s: &ItemStruct) -> bool {
s.attrs.iter().any(|a| {
if !a.path().is_ident("derive") {
return false;
}
let mut found = false;
let _ = a.parse_nested_meta(|m| {
if let Some(seg) = m.path.segments.last() {
let n = seg.ident.to_string();
if n == "ShankAccount" || n == "ShankType" {
found = true;
}
}
Ok(())
});
found
})
}
pub(crate) fn check_padding(s: &ItemStruct) -> Option<PaddingWarning> {
let Fields::Named(fields) = &s.fields else {
return None;
};
let mut sizes = Vec::new();
for f in &fields.named {
sizes.push(size_align(&f.ty)?);
}
if sizes.is_empty() {
return None;
}
let packed: usize = sizes.iter().map(|(sz, _)| sz).sum();
let mut offset = 0usize;
let mut max_align = 1usize;
for (sz, al) in &sizes {
offset = align_up(offset, *al);
offset += sz;
max_align = max_align.max(*al);
}
let repr_c = align_up(offset, max_align);
if repr_c != packed {
Some(PaddingWarning {
name: s.ident.to_string(),
packed_size: packed,
repr_c_size: repr_c,
})
} else {
None
}
}
fn align_up(offset: usize, align: usize) -> usize {
offset.div_ceil(align) * align
}
fn size_align(ty: &Type) -> Option<(usize, usize)> {
match ty {
Type::Path(p) => {
let id = p.path.segments.last()?.ident.to_string();
Some(match id.as_str() {
"u8" | "i8" | "bool" => (1, 1),
"u16" | "i16" => (2, 2),
"u32" | "i32" | "f32" => (4, 4),
"u64" | "i64" | "f64" => (8, 8),
"u128" | "i128" => (16, 16),
"Address" | "Pubkey" => (32, 1),
_ => return None,
})
}
Type::Array(arr) => {
let (elem_size, elem_align) = size_align(&arr.elem)?;
let n = array_len(&arr.len)?;
Some((elem_size * n, elem_align))
}
_ => None,
}
}
fn array_len(expr: &syn::Expr) -> Option<usize> {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(i),
..
}) = expr
{
i.base10_parse::<usize>().ok()
} else {
None
}
}