diff --git a/src/sema/types.rs b/src/sema/types.rs index 2be049536a..bb7c9b5c23 100644 --- a/src/sema/types.rs +++ b/src/sema/types.rs @@ -19,9 +19,11 @@ use num_traits::{One, Zero}; use petgraph::algo::{all_simple_paths, tarjan_scc}; use petgraph::stable_graph::IndexType; use petgraph::Directed; +use phf::{phf_set, Set}; use solang_parser::diagnostics::Note; use solang_parser::{doccomment::DocComment, pt, pt::CodeLocation}; use std::collections::HashSet; +use std::ops::MulAssign; use std::{fmt::Write, ops::Mul}; type Graph = petgraph::Graph<(), usize, Directed, usize>; @@ -281,7 +283,10 @@ fn check_infinite_struct_size(graph: &Graph, nodes: Vec, ns: &mut Namespa let mut infinite_edge = false; for edge in graph.edges_connecting(a.into(), b.into()) { match &ns.structs[a].fields[*edge.weight()].ty { - Type::Array(_, dims) if dims.first() != Some(&ArrayLength::Dynamic) => {} + Type::Array(_, dims) if dims.iter().any(|dim| *dim == ArrayLength::Dynamic) => { + continue + } + Type::Array(_, _) => {} Type::Struct(StructType::UserDefined(_)) => {} _ => continue, } @@ -360,6 +365,7 @@ fn find_struct_recursion(ns: &mut Namespace) { check_recursive_struct_field(n.index(), &graph, ns); } } + for n in 0..ns.structs.len() { let mut notes = vec![]; for field in ns.structs[n].fields.iter().filter(|f| f.infinite_size) { @@ -1556,7 +1562,12 @@ impl Type { /// which is not accounted for. pub fn solana_storage_size(&self, ns: &Namespace) -> BigInt { match self { - Type::Array(_, dims) if dims.first() == Some(&ArrayLength::Dynamic) => 4.into(), + Type::Array(_, dims) if dims.iter().any(|dim| *dim == ArrayLength::Dynamic) => { + let mut size = dynamic_array_size(dims); + // A pointer is four bytes on Solana + size.mul_assign(4); + size + } Type::Array(ty, dims) => ty.solana_storage_size(ns).mul( dims.iter() .map(|d| match d { @@ -1684,8 +1695,12 @@ impl Type { Type::Value => BigInt::from(ns.value_length), Type::Uint(n) | Type::Int(n) => BigInt::from(n / 8), Type::Rational => unreachable!(), - Type::Array(_, dims) if dims.first() == Some(&ArrayLength::Dynamic) => { - BigInt::from(4) + Type::Array(_, dims) if dims.iter().any(|dim| *dim == ArrayLength::Dynamic) => { + let mut size = dynamic_array_size(dims); + + // A pointer is four bytes on Solana + size.mul_assign(4); + size } Type::Array(ty, dims) => { let pointer_size = BigInt::from(4); @@ -1733,7 +1748,9 @@ impl Type { .filter(|f| !f.infinite_size) .map(|f| f.ty.storage_slots(ns)) .sum(), - Type::Array(_, dims) if dims.first() == Some(&ArrayLength::Dynamic) => 1.into(), + Type::Array(_, dims) if dims.iter().any(|dim| *dim == ArrayLength::Dynamic) => { + dynamic_array_size(dims) + } Type::Array(ty, dims) => { let one = 1.into(); ty.storage_slots(ns) @@ -1764,7 +1781,7 @@ impl Type { Type::Value => BigInt::from(ns.value_length), Type::Uint(n) | Type::Int(n) => BigInt::from(n / 8), Type::Rational => unreachable!(), - Type::Array(_, dims) if dims.first() == Some(&ArrayLength::Dynamic) => { + Type::Array(_, dims) if dims.iter().any(|dim| *dim == ArrayLength::Dynamic) => { BigInt::from(4) } Type::Array(ty, _) => { @@ -2151,10 +2168,28 @@ impl Type { /// These names cannot be used on Windows, even with an extension. /// shamelessly stolen from cargo +static WINDOWS_KEYWORDS: Set<&'static str> = phf_set! { + "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", + "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", +}; fn is_windows_reserved(name: &str) -> bool { - [ - "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", - "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", - ] - .contains(&name.to_ascii_lowercase().as_str()) + WINDOWS_KEYWORDS.contains(name.to_ascii_lowercase().as_str()) +} + +/// This function calculates the size of a dynamic array. +/// The reasoning is the following: +/// An array `uint [2][][3][1]` is a `void * foo[3][1]`-like in C, so its size +/// in storage is 3*1*ptr_size. Each pointer points to a `uint[2]` so whatever comes before the +/// ultimate empty square bracket does not matter. +fn dynamic_array_size(dims: &[ArrayLength]) -> BigInt { + let mut result = BigInt::one(); + for dim in dims.iter().rev() { + match dim { + ArrayLength::Fixed(num) => result.mul_assign(num), + ArrayLength::Dynamic => break, + ArrayLength::AnyFixed => unreachable!("unknown dimension"), + } + } + + result } diff --git a/tests/contract_testcases/solana/structs/parse_structs.sol b/tests/contract_testcases/solana/structs/parse_structs.sol new file mode 100644 index 0000000000..53caf131a4 --- /dev/null +++ b/tests/contract_testcases/solana/structs/parse_structs.sol @@ -0,0 +1,29 @@ +contract hatchling { + struct A { + A [][2][1] b; + } + struct B { + B [2][][1] b; + } + struct C { + C [2][1][] b; + } + + A private n1; + B private n2; + C private n3; + + constructor() {} + + function foo(uint a, uint b) public { + + } +} + +// ---- Expect: diagnostics ---- +// warning: 12:5-17: storage variable 'n1' has never been used +// warning: 13:5-17: storage variable 'n2' has never been used +// warning: 14:5-17: storage variable 'n3' has never been used +// warning: 18:5-40: function can be declared 'pure' +// warning: 18:23-24: function parameter 'a' is unused +// warning: 18:31-32: function parameter 'b' is unused \ No newline at end of file