Skip to content

Commit

Permalink
Fix recursive struct issue
Browse files Browse the repository at this point in the history
Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
  • Loading branch information
LucasSte committed Sep 13, 2023
1 parent e1a3ada commit b351eef
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 11 deletions.
57 changes: 46 additions & 11 deletions src/sema/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -281,7 +283,10 @@ fn check_infinite_struct_size(graph: &Graph, nodes: Vec<usize>, 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,
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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, _) => {
Expand Down Expand Up @@ -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
}
29 changes: 29 additions & 0 deletions tests/contract_testcases/solana/structs/parse_structs.sol
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit b351eef

Please sign in to comment.