diff --git a/Cargo.lock b/Cargo.lock index af2cf417..b14cd185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "analyzers" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ariadne", "graph", @@ -390,7 +390,7 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "analyzers", "ariadne", @@ -911,7 +911,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "graph" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ethers-core", "hex", @@ -1874,7 +1874,7 @@ dependencies = [ [[package]] name = "pyrometer" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ahash", "analyzers", @@ -1896,7 +1896,7 @@ dependencies = [ [[package]] name = "queries" -version = "0.2.0" +version = "0.3.0" dependencies = [ "analyzers", "ariadne", @@ -2333,7 +2333,7 @@ dependencies = [ [[package]] name = "shared" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ahash", "ethers-core", @@ -2414,7 +2414,7 @@ dependencies = [ [[package]] name = "solc-expressions" -version = "0.2.0" +version = "0.3.0" dependencies = [ "analyzers", "ethers-core", diff --git a/Cargo.toml b/Cargo.toml index 172c831a..8585a698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Brock Elmore"] license = "MIT OR Apache-2.0" diff --git a/crates/analyzers/src/bounds.rs b/crates/analyzers/src/bounds.rs index cf51782e..000807c3 100644 --- a/crates/analyzers/src/bounds.rs +++ b/crates/analyzers/src/bounds.rs @@ -2,18 +2,29 @@ use crate::{FunctionVarsBoundAnalysis, LocSpan, LocStrSpan, ReportConfig, VarBou use graph::{ elem::Elem, - nodes::{Concrete, ContextNode}, + nodes::{Concrete, ContextNode, KilledKind}, range_string::ToRangeString, GraphBackend, Range, RangeEval, SolcRange, }; use shared::{RangeArena, StorageLocation}; use ariadne::{Color, Fmt, Label, Span}; -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, +}; pub static MIN_COLOR: Color = Color::Fixed(111); pub static MAX_COLOR: Color = Color::Fixed(106); +pub fn killed_kind_color(kind: &KilledKind) -> Color { + match kind { + KilledKind::Ended => Color::Green, + KilledKind::Unreachable => Color::Rgb(255, 121, 0), + _ => Color::Red, + } +} + #[derive(PartialEq, Eq, Clone)] pub struct AnalysisItem { pub init: bool, @@ -159,9 +170,9 @@ impl RangePart { pub fn to_normal_string(&self) -> String { match self { - e @ RangePart::Equal(_) => format!(" == {}", e.to_string()), - e @ RangePart::Inclusion(..) => format!(" ∈ {}", e.to_string()), - e @ RangePart::Exclusion(_) => format!("&& ∉ {{{}}}", e.to_string()), + e @ RangePart::Equal(_) => format!(" == {}", e), + e @ RangePart::Inclusion(..) => format!(" ∈ {}", e), + e @ RangePart::Exclusion(_) => format!("&& ∉ {{{}}}", e), } } } @@ -214,16 +225,11 @@ impl From for Label { } } -impl ToString for StrippedAnalysisItem { - fn to_string(&self) -> String { - format!( +impl fmt::Display for StrippedAnalysisItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, "{}{}{}", - // match self.storage { - // Some(StorageLocation::Memory(..)) => "Memory var ", - // Some(StorageLocation::Storage(..)) => "Storage var ", - // Some(StorageLocation::Calldata(..)) => "Calldata var ", - // None => "", - // }, self.name, self.parts .iter() @@ -239,12 +245,13 @@ impl ToString for StrippedAnalysisItem { } } -impl ToString for RangePart { - fn to_string(&self) -> String { +impl fmt::Display for RangePart { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - RangePart::Equal(inner) => inner.to_string(), - RangePart::Inclusion(min, max) => format!("[ {}, {} ]", min, max), - RangePart::Exclusion(inner) => format!( + RangePart::Equal(inner) => write!(f, "{}", inner), + RangePart::Inclusion(min, max) => write!(f, "[ {}, {} ]", min, max), + RangePart::Exclusion(inner) => write!( + f, "{{{}}}", inner .iter() diff --git a/crates/analyzers/src/func_analyzer/mod.rs b/crates/analyzers/src/func_analyzer/mod.rs index 0eb3b0e3..77830421 100644 --- a/crates/analyzers/src/func_analyzer/mod.rs +++ b/crates/analyzers/src/func_analyzer/mod.rs @@ -1,3 +1,4 @@ +use crate::bounds::killed_kind_color; use crate::{ bounds::range_parts, LocStrSpan, ReportConfig, ReportDisplay, ReportKind, VarBoundAnalysis, VarBoundAnalyzer, @@ -60,19 +61,6 @@ impl<'a> FunctionVarsBoundAnalysis { .iter() .map(|var| (var.as_controllable_name(analyzer, arena).unwrap(), var)) .collect::>(); - // create the bound strings - // let atoms = ctx.dep_atoms(analyzer).unwrap(); - // println!("had {} atoms", atoms.len()); - // let mut handled_atom = vec![]; - // let mut bounds_string: Vec = vec![]; - // atoms.iter().enumerate().for_each(|(i, atom)| { - // let atom_str = atom.to_range_string(true, analyzer, arena).s; - // if !handled_atom.contains(&atom_str) { - // handled_atom.push(atom_str.clone()); - // bounds_string.push(format!("{}. {}", i + 1, atom_str)) - // } - // }); - // let bounds_string = bounds_string.into_iter().collect::>().join("\n"); let bounds_string = deps .iter() @@ -231,7 +219,7 @@ impl<'a> FunctionVarsBoundAnalysis { labels.push( Label::new(killed_loc.clone()) .with_message(kind.analysis_str()) - .with_color(Color::Red) + .with_color(killed_kind_color(kind)) .with_priority(10), ); } @@ -247,8 +235,8 @@ impl<'a> FunctionVarsBoundAnalysis { if !self_handled { labels.push( Label::new(killed_span.clone()) - .with_message(kind.analysis_str().fg(Color::Red)) - .with_color(Color::Red), + .with_message(kind.analysis_str().fg(killed_kind_color(kind))) + .with_color(killed_kind_color(kind)), ); } } @@ -332,14 +320,14 @@ pub trait FunctionVarsBoundAnalyzer: VarBoundAnalyzer + Search + AnalyzerBackend { return None; } - if !report_config.show_reverts - && matches!( - fork.underlying(self).unwrap().killed, - Some((_, KilledKind::Revert)) - ) - { - return None; - } + // if !report_config.show_reverts + // && matches!( + // fork.underlying(self).unwrap().killed, + // Some((_, KilledKind::Revert)) + // ) + // { + // return None; + // } let mut parents = fork.parent_list(self).unwrap(); parents.reverse(); parents.push(*fork); diff --git a/crates/analyzers/src/func_analyzer/report_display.rs b/crates/analyzers/src/func_analyzer/report_display.rs index 828d8c9f..731c9ef7 100644 --- a/crates/analyzers/src/func_analyzer/report_display.rs +++ b/crates/analyzers/src/func_analyzer/report_display.rs @@ -1,3 +1,4 @@ +use crate::bounds::killed_kind_color; use crate::{FunctionVarsBoundAnalysis, LocStrSpan, ReportDisplay, ReportKind}; use graph::{elem::Elem, nodes::Concrete, GraphBackend}; @@ -74,8 +75,8 @@ impl<'a> ReportDisplay for CLIFunctionVarsBoundAnalysis<'a> { if let Some((killed_span, kind)) = &self.func_var_bound_analysis.ctx_killed { report = report.with_label( Label::new(killed_span.clone()) - .with_message(kind.analysis_str().fg(Color::Red)) - .with_color(Color::Red), + .with_message(kind.analysis_str().fg(killed_kind_color(kind))) + .with_color(killed_kind_color(kind)), ); } diff --git a/crates/analyzers/src/var_analyzer/mod.rs b/crates/analyzers/src/var_analyzer/mod.rs index ed28ec7a..95818986 100644 --- a/crates/analyzers/src/var_analyzer/mod.rs +++ b/crates/analyzers/src/var_analyzer/mod.rs @@ -161,8 +161,9 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { let mut curr = cvar.first_version(self); let ctx = cvar.ctx(self); - let (func_span, func_body_span) = - if let Some(fn_call) = ctx.underlying(self).unwrap().fn_call { + let underlying = ctx.underlying(self).unwrap(); + let (func_span, func_body_span) = if let Some(fn_call) = underlying.fn_call() { + if underlying.is_ext_fn_call() { ( LocStrSpan::new(file_mapping, fn_call.underlying(self).unwrap().loc), fn_call @@ -172,18 +173,7 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { .as_ref() .map(|body| LocStrSpan::new(file_mapping, body.loc())), ) - } else if let Some(ext_fn_call) = ctx.underlying(self).unwrap().ext_fn_call { - ( - LocStrSpan::new(file_mapping, ext_fn_call.underlying(self).unwrap().loc), - ext_fn_call - .underlying(self) - .unwrap() - .body - .as_ref() - .map(|body| LocStrSpan::new(file_mapping, body.loc())), - ) } else { - let fn_call = ctx.associated_fn(self).unwrap(); ( LocStrSpan::new(file_mapping, fn_call.underlying(self).unwrap().loc), fn_call @@ -193,7 +183,19 @@ pub trait VarBoundAnalyzer: Search + AnalyzerBackend + Sized { .as_ref() .map(|body| LocStrSpan::new(file_mapping, body.loc())), ) - }; + } + } else { + let fn_call = ctx.associated_fn(self).unwrap(); + ( + LocStrSpan::new(file_mapping, fn_call.underlying(self).unwrap().loc), + fn_call + .underlying(self) + .unwrap() + .body + .as_ref() + .map(|body| LocStrSpan::new(file_mapping, body.loc())), + ) + }; let mut ba: VarBoundAnalysis = if let Some(inherited) = inherited { let mut new_ba = inherited.clone(); diff --git a/crates/analyzers/src/var_analyzer/report_display.rs b/crates/analyzers/src/var_analyzer/report_display.rs index 368f1659..0364680e 100644 --- a/crates/analyzers/src/var_analyzer/report_display.rs +++ b/crates/analyzers/src/var_analyzer/report_display.rs @@ -1,3 +1,4 @@ +use crate::bounds::killed_kind_color; use crate::{ bounds::{range_parts, AnalysisItem}, LocStrSpan, ReportDisplay, ReportKind, VarBoundAnalysis, @@ -84,8 +85,8 @@ impl ReportDisplay for VarBoundAnalysis { if let Some((killed_span, kind)) = &self.ctx_killed { report = report.with_label( Label::new(killed_span.clone()) - .with_message(kind.analysis_str().fg(Color::Red)) - .with_color(Color::Red), + .with_message(kind.analysis_str().fg(killed_kind_color(kind))) + .with_color(killed_kind_color(kind)), ); } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 7b272ef4..4be132b9 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -110,6 +110,9 @@ struct Args { #[clap(long)] pub minimize_debug: Option, + + #[clap(long, default_value = "false")] + pub debug_stack: bool, } pub fn subscriber() { @@ -243,6 +246,7 @@ fn main() { root: Root::RemappingsDirectory(env::current_dir().unwrap()), debug_panic: args.debug_panic || args.minimize_debug.is_some(), minimize_debug: args.minimize_debug, + debug_stack: args.debug_stack, ..Default::default() }; @@ -300,7 +304,6 @@ fn main() { println!("DONE ANALYZING IN: {parse_time}ms. Writing to cli..."); - // println!("Arena: {:#?}", analyzer.range_arena); if unsafe { USE_DEBUG_SITE } { use pyrometer::graph_backend::mermaid_str; use pyrometer::graph_backend::post_to_site_arena; @@ -324,7 +327,6 @@ fn main() { if args.stats { println!("{}", analyzer.stats(t_end, arena)); } - // println!("Arena: {:#?}", analyzer.range_arena); // use self.sources to fill a BTreeMap with the file_no and SourcePath.path_to_solidity_file let mut file_mapping: BTreeMap = BTreeMap::new(); @@ -366,20 +368,10 @@ fn main() { analyzer.open_mermaid(arena); } - // println!("{}", analyzer.range_arena.ranges.iter().map(|i| { - // let j = i.borrow(); - // let (min_cached, max_cached) = j.is_min_max_cached(&analyzer); - // format!("\t{j}, is cached: {min_cached}, {max_cached}\n") - // }).collect::>().join("")); - // println!("{}", analyzer.range_arena.map.iter().map(|(k, v)| { - // format!("\t{}: {}\n", k, v) - // }).collect::>().join("")); - if args.debug { return; } - // println!("getting contracts"); let all_contracts = analyzer .search_children(entry, &Edge::Contract) .into_iter() @@ -431,7 +423,10 @@ fn main() { // } else { let _t1 = std::time::Instant::now(); if args.contracts.is_empty() { - let funcs = analyzer.search_children(entry, &Edge::Func); + let mut funcs = analyzer.search_children(entry, &Edge::Func); + funcs.extend(analyzer.search_children(entry, &Edge::FallbackFunc)); + funcs.extend(analyzer.search_children(entry, &Edge::Constructor)); + funcs.extend(analyzer.search_children(entry, &Edge::ReceiveFunc)); for func in funcs.into_iter() { if !args.funcs.is_empty() { if args.funcs.iter().any(|analyze_for| { diff --git a/crates/graph/src/graph_elements.rs b/crates/graph/src/graph_elements.rs index 569befc7..d9b06887 100644 --- a/crates/graph/src/graph_elements.rs +++ b/crates/graph/src/graph_elements.rs @@ -2,7 +2,7 @@ use crate::elem::Elem; use crate::{nodes::*, VarType}; use shared::{ - AnalyzerLike, GraphDot, GraphError, GraphLike, Heirarchical, NodeIdx, RangeArena, + AnalyzerLike, FlatExpr, GraphDot, GraphError, GraphLike, Heirarchical, NodeIdx, RangeArena, RepresentationErr, }; @@ -33,6 +33,7 @@ pub trait AnalyzerBackend: FunctionParam = FunctionParam, FunctionReturn = FunctionReturn, Function = Function, + FlatExpr = FlatExpr, > + GraphBackend { fn add_concrete_var( @@ -98,9 +99,12 @@ pub enum Node { /// A concrete value (i.e. '1' or '0x111') Concrete(Concrete), /// The `msg` global in solidity - Msg(Msg), + Msg(Box), /// The `block` global in solidity - Block(Block), + Block(Box), + /// A yul-based function + YulFunction(YulFunction), + // TODO: Handle events } pub fn as_dot_str( @@ -274,6 +278,8 @@ pub enum Edge { BuiltinFunction, /// A connection from one contract to another contract UsingContract(Loc), + /// Yul function + YulFunction(usize), } impl Heirarchical for Edge { @@ -284,8 +290,8 @@ impl Heirarchical for Edge { Part | Import => 1, Contract | Ty | Field | Enum | Struct | Error | Event | Var | InheritedContract - | Modifier | FallbackFunc | Constructor | ReceiveFunc | LibraryFunction(_) - | BuiltinFunction | Func | UsingContract(_) => 2, + | Modifier | FallbackFunc | Constructor | YulFunction(_) | ReceiveFunc + | LibraryFunction(_) | BuiltinFunction | Func | UsingContract(_) => 2, Context(_) | ErrorParam | FunctionParam | FunctionReturn | FuncModifier(_) => 3, } diff --git a/crates/graph/src/nodes/block.rs b/crates/graph/src/nodes/block.rs index 8627766f..0e3c27f6 100644 --- a/crates/graph/src/nodes/block.rs +++ b/crates/graph/src/nodes/block.rs @@ -68,4 +68,7 @@ pub struct Block { pub prevrandao: Option, /// The block's timestamp pub timestamp: Option, + /// The block's blobhash + pub blobhash: Vec, + pub blobbasefee: Option, } diff --git a/crates/graph/src/nodes/builtin.rs b/crates/graph/src/nodes/builtin.rs index 98f1080c..381e24e3 100644 --- a/crates/graph/src/nodes/builtin.rs +++ b/crates/graph/src/nodes/builtin.rs @@ -35,6 +35,16 @@ impl BuiltInNode { } /// Checks if this builtin is implicitly castable to another builtin + pub fn castable_to( + &self, + other: &Self, + analyzer: &impl GraphBackend, + ) -> Result { + Ok(self + .underlying(analyzer)? + .castable_to(other.underlying(analyzer)?)) + } + pub fn implicitly_castable_to( &self, other: &Self, @@ -219,6 +229,44 @@ impl Builtin { builtins } + pub fn possible_upcast_builtins(&self) -> Vec { + let mut builtins = vec![]; + match self { + Builtin::Uint(size) => { + let mut s = *size; + while s <= 256 { + builtins.push(Builtin::Uint(s)); + s += 8; + } + } + Builtin::Int(size) => { + let mut s = *size; + while s <= 256 { + builtins.push(Builtin::Int(s)); + s += 8; + } + } + Builtin::Bytes(size) => { + let mut s = *size; + while s <= 32 { + builtins.push(Builtin::Bytes(s)); + s += 1; + } + } + Builtin::Address => builtins.push(Builtin::Address), + Builtin::AddressPayable => { + builtins.push(Builtin::Address); + builtins.push(Builtin::AddressPayable); + } + Builtin::Payable => { + builtins.push(Builtin::Address); + builtins.push(Builtin::AddressPayable); + } + _ => {} + } + builtins + } + /// Construct a [`SolcRange`] that is zero pub fn zero_range(&self) -> Option { match self { @@ -365,7 +413,7 @@ impl Builtin { } /// Checks if self is implicitly castable to another builtin - pub fn implicitly_castable_to(&self, other: &Self) -> bool { + pub fn castable_to(&self, other: &Self) -> bool { use Builtin::*; match (self, other) { (Address, Address) => true, @@ -389,6 +437,29 @@ impl Builtin { } } + /// Checks if self is implicitly castable to another builtin + pub fn implicitly_castable_to(&self, other: &Self) -> bool { + use Builtin::*; + + match (self, other) { + (Address, Address) => true, + (AddressPayable, Address) => true, + (AddressPayable, Payable) => true, + (AddressPayable, AddressPayable) => true, + (Payable, Address) => true, + (Payable, AddressPayable) => true, + (Payable, Payable) => true, + (Bool, Bool) => true, + (Rational, Rational) => true, + (DynamicBytes, DynamicBytes) => true, + (String, String) => true, + (Uint(from_size), Uint(to_size)) => from_size <= to_size, + (Int(from_size), Int(to_size)) => from_size <= to_size, + (Bytes(from_size), Bytes(to_size)) => from_size <= to_size, + _ => false, + } + } + /// Returns the max size version of this builtin pub fn max_size(&self) -> Self { use Builtin::*; diff --git a/crates/graph/src/nodes/concrete.rs b/crates/graph/src/nodes/concrete.rs index 9118b9bd..19f4eb88 100644 --- a/crates/graph/src/nodes/concrete.rs +++ b/crates/graph/src/nodes/concrete.rs @@ -28,7 +28,7 @@ impl ConcreteNode { /// Creates a version of this concrete that is max size pub fn max_size(&self, analyzer: &mut impl AnalyzerBackend) -> Result { let c = self.underlying(analyzer)?.max_size(); - Ok(analyzer.add_node(Node::Concrete(c)).into()) + Ok(analyzer.add_node(c).into()) } /// Gets the internal type of the dynamic that backs this. Panics if this is not a dynamic concrete @@ -104,6 +104,12 @@ pub enum Concrete { Array(Vec), } +impl From for Node { + fn from(c: Concrete) -> Node { + Node::Concrete(c) + } +} + impl Default for Concrete { fn default() -> Self { Concrete::Uint(0, U256::zero()) @@ -699,6 +705,43 @@ impl Concrete { } } + pub fn alt_lit_builtins(&self) -> Vec { + let mut alts = vec![]; + match self { + Concrete::Uint(size, val) => { + // literal(u160) -> address + if *size == 160 { + alts.push(Builtin::Address); + } + + // uint -> int, all size steps between + let mut new_size = *size; + let imax = U256::from(2).pow((*size - 1).into()); + // we may have to bump size by 8 bits + if val > &imax { + new_size += 8; + } + // if a valid + while new_size <= 256 { + alts.push(Builtin::Int(new_size)); + new_size += 8; + } + + // exact bytesX + let bytes_size = size / 8; + alts.push(Builtin::Bytes(bytes_size as u8)); + } + Concrete::String(_) => { + alts.push(Builtin::DynamicBytes); + } + Concrete::Bytes(_, _) => { + alts.push(Builtin::DynamicBytes); + } + _ => {} + } + alts + } + /// Converts a concrete into a `U256`. pub fn into_u256(&self) -> Option { match self { diff --git a/crates/graph/src/nodes/context/expr_ret.rs b/crates/graph/src/nodes/context/expr_ret.rs index b27930c7..94961ca9 100644 --- a/crates/graph/src/nodes/context/expr_ret.rs +++ b/crates/graph/src/nodes/context/expr_ret.rs @@ -298,4 +298,23 @@ impl ExprRet { pub fn is_empty(&self) -> bool { self.len() == 0 } + + pub fn into_sized(&self) -> [Self; N] { + self.as_vec().try_into().unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length {} but it was {}", N, v.len()) + }) + } + + pub fn implicitly_castable_to( + &self, + analyzer: &impl GraphBackend, + to_ty: &VarType, + ) -> Result { + let idx = self.expect_single()?; + let is_lit = self.has_literal(); + let Some(self_ty) = VarType::try_from_idx(analyzer, idx) else { + return Ok(false); + }; + self_ty.implicitly_castable_to(to_ty, analyzer, is_lit) + } } diff --git a/crates/graph/src/nodes/context/mod.rs b/crates/graph/src/nodes/context/mod.rs index 1d1e13d4..33c35f73 100644 --- a/crates/graph/src/nodes/context/mod.rs +++ b/crates/graph/src/nodes/context/mod.rs @@ -8,7 +8,7 @@ mod var; pub use context_tys::{CallFork, ContextCache, ModifierState}; pub use expr_ret::{ExprRet, KilledKind}; pub use node::ContextNode; -pub use underlying::Context; +pub use underlying::{Context, SubContextKind}; pub use var::{ContextVar, ContextVarNode, TmpConstruction}; // ContextNode implementations are split to ease in maintainability diff --git a/crates/graph/src/nodes/context/node.rs b/crates/graph/src/nodes/context/node.rs index 314e19d2..602f8107 100644 --- a/crates/graph/src/nodes/context/node.rs +++ b/crates/graph/src/nodes/context/node.rs @@ -4,7 +4,7 @@ use crate::{ AnalyzerBackend, AsDotStr, GraphBackend, Node, }; -use shared::{GraphError, NodeIdx, RangeArena}; +use shared::{ExprFlag, GraphError, NodeIdx, RangeArena}; use solang_parser::pt::Loc; @@ -31,6 +31,16 @@ impl ContextNode { self.associated_fn(analyzer)?.add_gas_cost(analyzer, cost) } + pub fn take_expr_flag(&self, analyzer: &mut impl AnalyzerBackend) -> Option { + self.underlying_mut(analyzer).unwrap().take_expr_flag() + } + pub fn set_expr_flag(&self, analyzer: &mut impl AnalyzerBackend, flag: ExprFlag) { + self.underlying_mut(analyzer).unwrap().set_expr_flag(flag) + } + pub fn peek_expr_flag(&self, analyzer: &impl AnalyzerBackend) -> Option { + self.underlying(analyzer).unwrap().peek_expr_flag() + } + /// Gets the total context width pub fn total_width(&self, analyzer: &mut impl AnalyzerBackend) -> Result { self.first_ancestor(analyzer)? @@ -107,7 +117,7 @@ impl ContextNode { let underlying = &mut self.underlying_mut(analyzer)?; let curr_live = underlying.number_of_live_edges; underlying.number_of_live_edges = 0; - if let Some(parent) = self.underlying(analyzer)?.parent_ctx { + if let Some(parent) = self.underlying(analyzer)?.parent_ctx() { let live_edges = &mut parent.underlying_mut(analyzer)?.number_of_live_edges; *live_edges = live_edges.saturating_sub(1 + curr_live); parent.propogate_end(analyzer)?; @@ -125,7 +135,7 @@ impl ContextNode { if !underlying.applies.contains(&applied) { underlying.applies.push(applied); } - if let Some(parent) = self.underlying(analyzer)?.parent_ctx { + if let Some(parent) = self.underlying(analyzer)?.parent_ctx() { parent.propogate_applied(applied, analyzer)?; } Ok(()) diff --git a/crates/graph/src/nodes/context/querying.rs b/crates/graph/src/nodes/context/querying.rs index 17706f9e..55c1ea37 100644 --- a/crates/graph/src/nodes/context/querying.rs +++ b/crates/graph/src/nodes/context/querying.rs @@ -38,7 +38,7 @@ impl ContextNode { let context = self.underlying(analyzer).unwrap(); if let Some(src) = context.cache.associated_source { Some(src.into()) - } else if let Some(parent_ctx) = context.parent_ctx { + } else if let Some(parent_ctx) = context.parent_ctx() { let src = parent_ctx.maybe_associated_source(analyzer)?; self.underlying_mut(analyzer) .unwrap() @@ -167,6 +167,21 @@ impl ContextNode { } } + pub fn visible_constructors( + &self, + analyzer: &mut impl AnalyzerBackend, + ) -> Result, GraphError> { + if let Some(src) = self.maybe_associated_source(analyzer) { + let contracts = src.visible_contracts(analyzer)?; + Ok(contracts + .iter() + .filter_map(|contract| contract.constructor(analyzer)) + .collect()) + } else { + Ok(vec![]) + } + } + /// Gets visible functions pub fn visible_funcs( &self, @@ -177,7 +192,7 @@ impl ContextNode { return Ok(vis.clone()); } if let Some(contract) = self.maybe_associated_contract(analyzer)? { - let mut mapping = contract.linearized_functions(analyzer)?; + let mut mapping = contract.linearized_functions(analyzer, false)?; // extend with free floating functions mapping.extend( analyzer @@ -265,10 +280,8 @@ impl ContextNode { /// Gets the associated function for the context pub fn associated_fn(&self, analyzer: &impl GraphBackend) -> Result { let underlying = self.underlying(analyzer)?; - if let Some(fn_call) = underlying.fn_call { + if let Some(fn_call) = underlying.fn_call() { Ok(fn_call) - } else if let Some(ext_fn_call) = underlying.ext_fn_call { - Ok(ext_fn_call) } else { Ok(underlying.parent_fn) } diff --git a/crates/graph/src/nodes/context/solving.rs b/crates/graph/src/nodes/context/solving.rs index fc74dae9..45500d3e 100644 --- a/crates/graph/src/nodes/context/solving.rs +++ b/crates/graph/src/nodes/context/solving.rs @@ -22,17 +22,13 @@ impl ContextNode { analyzer: &mut impl GraphBackend, arena: &mut RangeArena>, ) -> Result { - // println!("checking unreachable: {}", self.path(analyzer)); let mut solver = self.dl_solver(analyzer)?.clone(); match solver.solve_partial(analyzer, arena)? { SolveStatus::Unsat => { tracing::trace!("{} is unreachable via UNSAT", self.path(analyzer)); Ok(true) } - _e => { - // println!("other: {e:?}"); - Ok(false) - } + _e => Ok(false), } } diff --git a/crates/graph/src/nodes/context/typing.rs b/crates/graph/src/nodes/context/typing.rs index be3a1ae0..0c64b7ee 100644 --- a/crates/graph/src/nodes/context/typing.rs +++ b/crates/graph/src/nodes/context/typing.rs @@ -1,5 +1,5 @@ use crate::{ - nodes::{ContextNode, FunctionNode}, + nodes::{context::underlying::SubContextKind, ContextNode, FunctionNode, KilledKind}, AnalyzerBackend, GraphBackend, }; use shared::GraphError; @@ -9,11 +9,30 @@ impl ContextNode { pub fn is_anonymous_fn_call(&self, analyzer: &impl GraphBackend) -> Result { let underlying = self.underlying(analyzer)?; - Ok(underlying.fn_call.is_none() && underlying.ext_fn_call.is_none() && !underlying.is_fork) + Ok(matches!( + underlying.subctx_kind, + Some(SubContextKind::Loop { .. }) + )) + } + + pub fn increment_parse_idx(&self, analyzer: &mut impl AnalyzerBackend) -> usize { + let underlying_mut = self.underlying_mut(analyzer).unwrap(); + let curr = underlying_mut.parse_idx; + underlying_mut.parse_idx += 1; + curr + } + + pub fn skip_n_exprs(&self, n: usize, analyzer: &mut impl AnalyzerBackend) { + let underlying_mut = self.underlying_mut(analyzer).unwrap(); + underlying_mut.parse_idx += n; + } + + pub fn parse_idx(&self, analyzer: &impl GraphBackend) -> usize { + self.underlying(analyzer).unwrap().parse_idx } pub fn has_continuation(&self, analyzer: &impl GraphBackend) -> Result { - Ok(self.underlying(analyzer)?.continuation_of.is_some()) + Ok(self.underlying(analyzer)?.continuation_of().is_some()) } /// Returns whether this context is killed or returned @@ -23,11 +42,19 @@ impl ContextNode { || (!underlying.ret.is_empty() && underlying.modifier_state.is_none())) } - /// Returns whether the context is killed + /// Returns whether the context is returned pub fn is_returned(&self, analyzer: &impl GraphBackend) -> Result { Ok(!self.underlying(analyzer)?.ret.is_empty()) } + /// Returns whether the context is reverted + pub fn is_graceful_ended(&self, analyzer: &impl GraphBackend) -> Result { + Ok(matches!( + self.underlying(analyzer)?.killed, + Some((_, KilledKind::Ended)) + )) + } + /// Returns whether the context is killed pub fn is_killed(&self, analyzer: &impl GraphBackend) -> Result { Ok(self.underlying(analyzer)?.killed.is_some()) @@ -41,7 +68,7 @@ impl ContextNode { /// Check if this context is in an external function call pub fn is_ext_fn(&self, analyzer: &impl GraphBackend) -> Result { - Ok(self.underlying(analyzer)?.ext_fn_call.is_some()) + Ok(self.underlying(analyzer)?.is_ext_fn_call()) } /// Checks whether a function is external to the current context @@ -62,6 +89,7 @@ impl ContextNode { .underlying(analyzer)? .inherits .iter() + .filter_map(|i| i.as_ref()) .any(|inherited| *inherited == fn_ctrt)) } else { Ok(false) @@ -69,21 +97,4 @@ impl ContextNode { } } } - - /// Returns whether this context *currently* uses unchecked math - pub fn unchecked(&self, analyzer: &impl GraphBackend) -> Result { - Ok(self.underlying(analyzer)?.unchecked) - } - - /// Sets the context to use unchecked math - pub fn set_unchecked(&self, analyzer: &mut impl AnalyzerBackend) -> Result<(), GraphError> { - self.underlying_mut(analyzer)?.unchecked = true; - Ok(()) - } - - /// Sets the context to use checked math - pub fn unset_unchecked(&self, analyzer: &mut impl AnalyzerBackend) -> Result<(), GraphError> { - self.underlying_mut(analyzer)?.unchecked = false; - Ok(()) - } } diff --git a/crates/graph/src/nodes/context/underlying.rs b/crates/graph/src/nodes/context/underlying.rs index 604fa4b2..23ea3af5 100644 --- a/crates/graph/src/nodes/context/underlying.rs +++ b/crates/graph/src/nodes/context/underlying.rs @@ -4,24 +4,323 @@ use crate::{ ModifierState, }, solvers::dl::DLSolver, - AnalyzerBackend, + AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::GraphError; +use shared::{ExprFlag, GraphError, NodeIdx}; use solang_parser::pt::Loc; use std::collections::BTreeSet; +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +pub enum SubContextKind { + InternalFnCall { + caller_ctx: ContextNode, + return_into_ctx: ContextNode, + fn_called: FunctionNode, + }, + ExternalFnCall { + caller_ctx: ContextNode, + return_into_ctx: ContextNode, + fn_called: FunctionNode, + }, + Loop { + parent_ctx: ContextNode, + }, + Fork { + parent_ctx: ContextNode, + true_side: bool, + }, + FnReturn { + from_fn_call_ctx: ContextNode, + continuation_of: ContextNode, + }, + Dummy { + parent_ctx: ContextNode, + }, +} + +impl SubContextKind { + pub fn new_fork(parent_ctx: ContextNode, true_side: bool) -> Self { + SubContextKind::Fork { + parent_ctx, + true_side, + } + } + + pub fn new_fn_ret(from_fn_call_ctx: ContextNode, continuation_of: ContextNode) -> Self { + SubContextKind::FnReturn { + from_fn_call_ctx, + continuation_of, + } + } + + pub fn new_dummy(parent_ctx: ContextNode) -> Self { + SubContextKind::Dummy { parent_ctx } + } + + pub fn new_loop(parent_ctx: ContextNode) -> Self { + SubContextKind::Loop { parent_ctx } + } + + pub fn new_fn_call( + caller_ctx: ContextNode, + return_into_ctx: Option, + fn_called: FunctionNode, + is_ext: bool, + ) -> Self { + let return_into_ctx = return_into_ctx.unwrap_or(caller_ctx); + if is_ext { + SubContextKind::ExternalFnCall { + caller_ctx, + return_into_ctx, + fn_called, + } + } else { + SubContextKind::InternalFnCall { + caller_ctx, + return_into_ctx, + fn_called, + } + } + } + + pub fn parent_ctx(&self) -> ContextNode { + match self { + SubContextKind::InternalFnCall { + caller_ctx: parent_ctx, + .. + } + | SubContextKind::ExternalFnCall { + caller_ctx: parent_ctx, + .. + } + | SubContextKind::FnReturn { + from_fn_call_ctx: parent_ctx, + .. + } + | SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Dummy { parent_ctx } + | SubContextKind::Loop { parent_ctx } => *parent_ctx, + } + } + + pub fn path_ext(&self, analyzer: &impl AnalyzerBackend) -> Result { + Ok(match self { + SubContextKind::ExternalFnCall { fn_called, .. } => { + let name = fn_called.name(analyzer)?; + format!("{{ {name} }}") + } + SubContextKind::InternalFnCall { fn_called, .. } => { + let name = fn_called.name(analyzer)?; + format!("{{ {name} }}") + } + SubContextKind::Fork { true_side, .. } => format!("fork{{ {true_side} }}"), + SubContextKind::FnReturn { + continuation_of, .. + } => format!( + "resume{{ {} }}", + continuation_of.associated_fn(analyzer)?.name(analyzer)? + ), + SubContextKind::Loop { .. } => "loop".to_string(), + SubContextKind::Dummy { .. } => "".to_string(), + }) + } + + pub fn init_ctx_deps( + &self, + analyzer: &impl AnalyzerBackend, + ) -> Result, GraphError> { + Ok(self.parent_ctx().underlying(analyzer)?.ctx_deps.clone()) + } + + pub fn init_expr_ret_stack( + &self, + analyzer: &impl AnalyzerBackend, + ) -> Result, GraphError> { + Ok(match self { + SubContextKind::Fork { parent_ctx, .. } | SubContextKind::Loop { parent_ctx } => { + parent_ctx.underlying(analyzer)?.expr_ret_stack.clone() + } + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of.underlying(analyzer)?.expr_ret_stack.clone(), + _ => vec![], + }) + } + + pub fn fn_call(&self) -> Option { + match self { + SubContextKind::ExternalFnCall { fn_called, .. } => Some(*fn_called), + SubContextKind::InternalFnCall { fn_called, .. } => Some(*fn_called), + SubContextKind::Fork { .. } + | SubContextKind::Loop { .. } + | SubContextKind::Dummy { .. } + | SubContextKind::FnReturn { .. } => None, + } + } + + pub fn init_parse_idx(&self, analyzer: &impl AnalyzerBackend) -> usize { + match self { + SubContextKind::ExternalFnCall { .. } => 0, + SubContextKind::InternalFnCall { .. } => 0, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => parent_ctx.parse_idx(analyzer), + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of.parse_idx(analyzer), + } + } + + pub fn init_expr_flag(&self, analyzer: &impl AnalyzerBackend) -> Option { + match self { + SubContextKind::ExternalFnCall { .. } => None, + SubContextKind::InternalFnCall { .. } => None, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => parent_ctx.peek_expr_flag(analyzer), + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of.peek_expr_flag(analyzer), + } + } + + pub fn init_ctx_cache( + &self, + analyzer: &impl AnalyzerBackend, + ) -> Result { + let visible_funcs = match self { + SubContextKind::ExternalFnCall { .. } => None, + SubContextKind::InternalFnCall { .. } => None, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => { + parent_ctx.underlying(analyzer)?.cache.visible_funcs.clone() + } + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of + .underlying(analyzer)? + .cache + .visible_funcs + .clone(), + }; + + let visible_structs = match self { + SubContextKind::ExternalFnCall { .. } => None, + SubContextKind::InternalFnCall { .. } => None, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => parent_ctx + .underlying(analyzer)? + .cache + .visible_structs + .clone(), + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of + .underlying(analyzer)? + .cache + .visible_structs + .clone(), + }; + + let first_ancestor = match self { + SubContextKind::ExternalFnCall { .. } => None, + SubContextKind::InternalFnCall { .. } => None, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => { + parent_ctx.underlying(analyzer)?.cache.first_ancestor + } + SubContextKind::FnReturn { + continuation_of, .. + } => continuation_of.underlying(analyzer)?.cache.first_ancestor, + }; + + Ok(ContextCache { + visible_funcs, + visible_structs, + first_ancestor, + vars: Default::default(), + tmp_vars: Default::default(), + associated_source: None, + associated_contract: None, + }) + } + + pub fn depth(&self, analyzer: &impl AnalyzerBackend) -> Result { + Ok((self.parent_ctx().underlying(analyzer)?.depth as isize) + .saturating_add(self.self_depth()) + .max(0) as usize) + } + + pub fn self_depth(&self) -> isize { + match self { + SubContextKind::ExternalFnCall { .. } => 1, + SubContextKind::InternalFnCall { .. } => 1, + SubContextKind::Fork { .. } => 1, + SubContextKind::Loop { .. } => 1, + SubContextKind::Dummy { .. } => 1, + SubContextKind::FnReturn { .. } => -1, + } + } + + pub fn width(&self, analyzer: &impl AnalyzerBackend) -> Result { + Ok(self + .parent_ctx() + .underlying(analyzer)? + .width + .saturating_add(self.self_width())) + } + + pub fn self_width(&self) -> usize { + match self { + SubContextKind::ExternalFnCall { .. } => 0, + SubContextKind::InternalFnCall { .. } => 0, + SubContextKind::Fork { .. } => 1, + SubContextKind::Loop { .. } => 0, + SubContextKind::Dummy { .. } => 0, + SubContextKind::FnReturn { .. } => 0, + } + } + + pub fn continuation_of(&self) -> Option { + match self { + SubContextKind::ExternalFnCall { .. } | SubContextKind::InternalFnCall { .. } => None, + SubContextKind::Fork { parent_ctx, .. } + | SubContextKind::Loop { parent_ctx } + | SubContextKind::Dummy { parent_ctx } => Some(*parent_ctx), + SubContextKind::FnReturn { + continuation_of, .. + } => Some(*continuation_of), + } + } + + pub fn returning_ctx(&self) -> Option { + match self { + SubContextKind::ExternalFnCall { + return_into_ctx, .. + } + | SubContextKind::InternalFnCall { + return_into_ctx, .. + } => Some(*return_into_ctx), + _ => None, + } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub struct Context { + /// The current parse index of the stack + pub parse_idx: usize, /// The function associated with this context pub parent_fn: FunctionNode, /// Whether this function call is actually a modifier call pub modifier_state: Option, - /// An optional parent context (i.e. this context is a fork or subcontext of another previous context) - pub parent_ctx: Option, - pub returning_ctx: Option, - pub continuation_of: Option, + + pub subctx_kind: Option, /// Variables whose bounds are required to be met for this context fork to exist. i.e. a conditional operator /// like an if statement pub ctx_deps: BTreeSet, @@ -29,12 +328,6 @@ pub struct Context { pub path: String, /// Denotes whether this context was killed by an unsatisfiable require, assert, etc. statement pub killed: Option<(Loc, KilledKind)>, - /// Denotes whether this context is a fork of another context - pub is_fork: bool, - /// Denotes whether this context is the result of a internal function call, and points to the FunctionNode - pub fn_call: Option, - /// Denotes whether this context is the result of a internal function call, and points to the FunctionNode - pub ext_fn_call: Option, /// The child context. This is either of the form `Call(child_context)` or `Fork(world1, world2)`. Once /// a child is defined we should *never* evaluate an expression in this context. pub child: Option, @@ -48,12 +341,8 @@ pub struct Context { pub depth: usize, /// Width tracker pub width: usize, - /// A temporary stack of ExprRets for this context - pub tmp_expr: Vec>, /// The stack of ExprRets for this context pub expr_ret_stack: Vec, - /// Whether the context currently uses unchecked math - pub unchecked: bool, /// The number of live edges pub number_of_live_edges: usize, /// Caching related things @@ -62,23 +351,27 @@ pub struct Context { pub dl_solver: DLSolver, /// Functions applied (but not reparsed) in this context pub applies: Vec, + /// Current expression flag + pub expr_flag: Option, +} + +impl From for Node { + fn from(c: Context) -> Node { + Node::Context(c) + } } impl Context { /// Creates a new context from a function pub fn new(parent_fn: FunctionNode, fn_name: String, loc: Loc) -> Self { Context { + parse_idx: 0, parent_fn, - parent_ctx: None, - returning_ctx: None, - continuation_of: None, + subctx_kind: None, path: fn_name, tmp_var_ctr: 0, killed: None, ctx_deps: Default::default(), - is_fork: false, - fn_call: None, - ext_fn_call: None, child: None, ret: vec![], loc, @@ -86,39 +379,35 @@ impl Context { depth: 0, width: 0, expr_ret_stack: Vec::with_capacity(5), - tmp_expr: vec![], - unchecked: false, number_of_live_edges: 0, cache: Default::default(), dl_solver: Default::default(), applies: Default::default(), + expr_flag: None, } } /// Creates a new subcontext from an existing context pub fn new_subctx( - parent_ctx: ContextNode, - returning_ctx: Option, + subctx_kind: SubContextKind, loc: Loc, - fork_expr: Option<&str>, - fn_call: Option, - fn_ext: bool, analyzer: &mut impl AnalyzerBackend, modifier_state: Option, ) -> Result { - let mut depth = - parent_ctx.underlying(analyzer)?.depth + if fork_expr.is_some() { 0 } else { 1 }; + let parse_idx = subctx_kind.init_parse_idx(analyzer); + + let path = format!( + "{}.{}", + subctx_kind.parent_ctx().path(analyzer), + subctx_kind.path_ext(analyzer)? + ); + let ctx_deps = subctx_kind.init_ctx_deps(analyzer)?; + let expr_ret_stack = subctx_kind.init_expr_ret_stack(analyzer)?; + let cache = subctx_kind.init_ctx_cache(analyzer)?; - let width = - parent_ctx.underlying(analyzer)?.width + if fork_expr.is_some() { 1 } else { 0 }; + let depth = subctx_kind.depth(analyzer)?; - let modifier_state = if let Some(mstate) = modifier_state { - Some(mstate) - } else if fn_call.is_none() || parent_ctx.associated_fn(analyzer)? == fn_call.unwrap() { - parent_ctx.underlying(analyzer)?.modifier_state.clone() - } else { - None - }; + let width = subctx_kind.width(analyzer)?; if analyzer.max_depth() < depth { return Err(GraphError::MaxStackDepthReached(format!( @@ -127,7 +416,7 @@ impl Context { ))); } - let tw = parent_ctx.total_width(analyzer)?; + let tw = subctx_kind.parent_ctx().total_width(analyzer)?; if analyzer.max_width() < tw { return Err(GraphError::MaxStackWidthReached(format!( "Stack width limit reached: {}", @@ -135,177 +424,120 @@ impl Context { ))); } - let (fn_name, ext_fn_call, fn_call) = if let Some(fn_call) = fn_call { - if fn_ext { - (fn_call.name(analyzer)?, Some(fn_call), None) - } else { - (fn_call.name(analyzer)?, None, Some(fn_call)) - } - } else if let Some(returning_ctx) = returning_ctx { - let fn_node = returning_ctx.associated_fn(analyzer)?; - (fn_node.name(analyzer)?, None, Some(fn_node)) + let parent_fn = if let Some(cont) = subctx_kind.continuation_of() { + cont.associated_fn(analyzer)? } else { - ("anonymous_fn_call".to_string(), None, None) + subctx_kind.parent_ctx().associated_fn(analyzer)? }; - let path = format!( - "{}.{}", - parent_ctx.underlying(analyzer)?.path, - if let Some(ref fork_expr) = fork_expr { - format!("fork{{ {} }}", fork_expr) - } else if let Some(returning_ctx) = returning_ctx { - depth = depth.saturating_sub(2); - format!( - "resume{{ {} }}", - returning_ctx.associated_fn_name(analyzer)? - ) - } else { - fn_name - } - ); - - let parent_fn = parent_ctx.associated_fn(analyzer)?; - - parent_ctx.underlying_mut(analyzer)?.number_of_live_edges += 1; + let modifier_state = if let Some(mstate) = modifier_state { + Some(mstate) + } else if subctx_kind.fn_call().is_none() || Some(parent_fn) == subctx_kind.fn_call() { + subctx_kind + .parent_ctx() + .underlying(analyzer)? + .modifier_state + .clone() + } else { + None + }; tracing::trace!("new subcontext path: {path}, depth: {depth}"); Ok(Context { + parse_idx, parent_fn, - parent_ctx: Some(parent_ctx), - returning_ctx, - continuation_of: None, + subctx_kind: Some(subctx_kind), path, - is_fork: fork_expr.is_some(), - fn_call, - ext_fn_call, - ctx_deps: parent_ctx.underlying(analyzer)?.ctx_deps.clone(), + ctx_deps, + expr_ret_stack, + cache, + + tmp_var_ctr: subctx_kind.parent_ctx().underlying(analyzer)?.tmp_var_ctr, killed: None, child: None, - tmp_var_ctr: parent_ctx.underlying(analyzer)?.tmp_var_ctr, ret: vec![], loc, modifier_state, depth, width, - expr_ret_stack: if fork_expr.is_some() { - parent_ctx.underlying(analyzer)?.expr_ret_stack.clone() - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.expr_ret_stack.clone() - } else { - vec![] - }, - tmp_expr: if fork_expr.is_some() { - parent_ctx.underlying(analyzer)?.tmp_expr.clone() - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.tmp_expr.clone() - } else { - vec![] - }, - unchecked: if fork_expr.is_some() { - parent_ctx.underlying(analyzer)?.unchecked - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.unchecked - } else { - false - }, + number_of_live_edges: 0, - cache: ContextCache { - vars: Default::default(), - tmp_vars: Default::default(), - visible_funcs: if fork_expr.is_some() { - parent_ctx.underlying(analyzer)?.cache.visible_funcs.clone() - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.cache.visible_funcs.clone() - } else { - None - }, - visible_structs: if fork_expr.is_some() { - parent_ctx - .underlying(analyzer)? - .cache - .visible_structs - .clone() - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.cache.visible_structs.clone() - } else { - None - }, - first_ancestor: if fork_expr.is_some() { - parent_ctx.underlying(analyzer)?.cache.first_ancestor - } else if let Some(ret_ctx) = returning_ctx { - ret_ctx.underlying(analyzer)?.cache.first_ancestor - } else { - None - }, - associated_source: None, - associated_contract: None, - }, - dl_solver: parent_ctx.underlying(analyzer)?.dl_solver.clone(), + + dl_solver: subctx_kind + .parent_ctx() + .underlying(analyzer)? + .dl_solver + .clone(), applies: Default::default(), + expr_flag: subctx_kind.init_expr_flag(analyzer), }) } - pub fn new_loop_subctx( - parent_ctx: ContextNode, + pub fn add_subctx( + subctx_kind: SubContextKind, loc: Loc, analyzer: &mut impl AnalyzerBackend, - ) -> Result { - let depth = parent_ctx.underlying(analyzer)?.depth + 1; - - if analyzer.max_depth() < depth { - return Err(GraphError::MaxStackDepthReached(format!( - "Stack depth limit reached: {}", - depth - 1 - ))); + modifier_state: Option, + ) -> Result { + let ctx = Context::new_subctx(subctx_kind, loc, analyzer, modifier_state)?; + let ctx_node = ContextNode::from(analyzer.add_node(ctx)); + if let Some(cont) = ctx_node.underlying(analyzer)?.continuation_of() { + analyzer.add_edge(ctx_node, cont, Edge::Context(ContextEdge::Continue("TODO"))); } + Ok(ctx_node) + } - let fn_name = "loop"; + pub fn take_expr_flag(&mut self) -> Option { + std::mem::take(&mut self.expr_flag) + } + pub fn set_expr_flag(&mut self, flag: ExprFlag) { + self.expr_flag = Some(flag); + } + pub fn peek_expr_flag(&self) -> Option { + self.expr_flag + } - let path = format!("{}.{}", parent_ctx.underlying(analyzer)?.path, fn_name); + pub fn add_fork_subctxs( + analyzer: &mut impl AnalyzerBackend, + parent_ctx: ContextNode, + loc: Loc, + ) -> Result<(ContextNode, ContextNode), GraphError> { + let true_subctx_kind = SubContextKind::new_fork(parent_ctx, true); + let true_subctx = Context::add_subctx(true_subctx_kind, loc, analyzer, None)?; - let parent_fn = parent_ctx.associated_fn(analyzer)?; + let false_subctx_kind = SubContextKind::new_fork(parent_ctx, false); + let false_subctx = Context::add_subctx(false_subctx_kind, loc, analyzer, None)?; - parent_ctx.underlying_mut(analyzer)?.number_of_live_edges += 1; + parent_ctx.set_child_fork(true_subctx, false_subctx, analyzer)?; + let ctx_fork = analyzer.add_node(Node::ContextFork); + analyzer.add_edge( + ctx_fork, + parent_ctx, + Edge::Context(ContextEdge::ContextFork), + ); + analyzer.add_edge( + NodeIdx::from(true_subctx.0), + ctx_fork, + Edge::Context(ContextEdge::Subcontext), + ); + analyzer.add_edge( + NodeIdx::from(false_subctx.0), + ctx_fork, + Edge::Context(ContextEdge::Subcontext), + ); + Ok((true_subctx, false_subctx)) + } - tracing::trace!("new subcontext path: {path}, depth: {depth}"); - Ok(Context { - parent_fn, - parent_ctx: Some(parent_ctx), - path, - returning_ctx: None, - continuation_of: None, - is_fork: false, - fn_call: None, - ext_fn_call: None, - ctx_deps: parent_ctx.underlying(analyzer)?.ctx_deps.clone(), - killed: None, - child: None, - tmp_var_ctr: parent_ctx.underlying(analyzer)?.tmp_var_ctr, - ret: vec![], - loc, - modifier_state: None, - depth, - width: 0, - expr_ret_stack: parent_ctx.underlying(analyzer)?.expr_ret_stack.clone(), - tmp_expr: parent_ctx.underlying(analyzer)?.tmp_expr.clone(), - unchecked: parent_ctx.underlying(analyzer)?.unchecked, - number_of_live_edges: 0, - cache: ContextCache { - vars: parent_ctx.underlying(analyzer)?.cache.vars.clone(), - tmp_vars: Default::default(), - visible_funcs: parent_ctx.underlying(analyzer)?.cache.visible_funcs.clone(), - visible_structs: parent_ctx - .underlying(analyzer)? - .cache - .visible_structs - .clone(), - first_ancestor: parent_ctx.underlying(analyzer)?.cache.first_ancestor, - associated_source: None, - associated_contract: None, - }, - dl_solver: parent_ctx.underlying(analyzer)?.dl_solver.clone(), - applies: Default::default(), - }) + pub fn add_loop_subctx( + parent_ctx: ContextNode, + loc: Loc, + analyzer: &mut impl AnalyzerBackend, + ) -> Result { + let subctx_kind = SubContextKind::Loop { parent_ctx }; + let loop_ctx = Context::add_subctx(subctx_kind, loc, analyzer, None)?; + parent_ctx.set_child_call(loop_ctx, analyzer)?; + analyzer.add_edge(loop_ctx, parent_ctx, Edge::Context(ContextEdge::Loop)); + Ok(loop_ctx) } /// Set the child context to a fork @@ -335,4 +567,27 @@ impl Context { pub fn as_string(&mut self) -> String { "Context".to_string() } + + pub fn parent_ctx(&self) -> Option { + Some(self.subctx_kind?.parent_ctx()) + } + + pub fn fn_call(&self) -> Option { + self.subctx_kind?.fn_call() + } + + pub fn continuation_of(&self) -> Option { + self.subctx_kind?.continuation_of() + } + + pub fn returning_ctx(&self) -> Option { + self.subctx_kind?.returning_ctx() + } + + pub fn is_ext_fn_call(&self) -> bool { + matches!( + self.subctx_kind, + Some(SubContextKind::ExternalFnCall { .. }) + ) + } } diff --git a/crates/graph/src/nodes/context/var/node.rs b/crates/graph/src/nodes/context/var/node.rs index 85adfa65..7f1dcc9e 100644 --- a/crates/graph/src/nodes/context/var/node.rs +++ b/crates/graph/src/nodes/context/var/node.rs @@ -310,18 +310,13 @@ impl ContextVarNode { } } - pub fn len_var_to_array( - &self, - analyzer: &impl GraphBackend, - ) -> Result, GraphError> { - if let Some(arr) = analyzer.search_for_ancestor( - self.0.into(), - &Edge::Context(ContextEdge::AttrAccess("length")), - ) { - Ok(Some(ContextVarNode::from(arr).latest_version(analyzer))) - } else { - Ok(None) - } + pub fn len_var_to_array(&self, analyzer: &impl GraphBackend) -> Option { + let arr = analyzer + .graph() + .edges_directed(self.first_version(analyzer).into(), Direction::Outgoing) + .find(|edge| *edge.weight() == Edge::Context(ContextEdge::AttrAccess("length"))) + .map(|edge| edge.target())?; + Some(ContextVarNode::from(arr).latest_version(analyzer)) } pub fn index_to_array(&self, analyzer: &impl GraphBackend) -> Option { @@ -335,13 +330,11 @@ impl ContextVarNode { /// Goes from an index access (i.e. `x[idx]`) to the index (i.e. `idx`) pub fn index_access_to_index(&self, analyzer: &impl GraphBackend) -> Option { - let index = analyzer.find_child_exclude_via( - self.first_version(analyzer).into(), - &Edge::Context(ContextEdge::Index), - &[], - &|idx, _| Some(idx), - )?; - Some(ContextVarNode::from(index)) + analyzer + .graph() + .edges_directed(self.first_version(analyzer).0.into(), Direction::Incoming) + .find(|edge| matches!(*edge.weight(), Edge::Context(ContextEdge::Index))) + .map(|e| ContextVarNode::from(e.source())) } pub fn index_or_attr_access(&self, analyzer: &impl GraphBackend) -> Vec { diff --git a/crates/graph/src/nodes/context/var/ranging.rs b/crates/graph/src/nodes/context/var/ranging.rs index eff44095..c73e592d 100644 --- a/crates/graph/src/nodes/context/var/ranging.rs +++ b/crates/graph/src/nodes/context/var/ranging.rs @@ -332,6 +332,7 @@ impl ContextVarNode { Ok(()) } + #[tracing::instrument(level = "trace", skip_all)] pub fn try_set_range_min( &self, analyzer: &mut impl AnalyzerBackend, @@ -350,7 +351,7 @@ impl ContextVarNode { new_min.arenaize(analyzer, arena)?; - if self.is_concrete(analyzer)? { + let res = if self.is_concrete(analyzer)? { let mut new_ty = self.ty(analyzer)?.clone(); new_ty.concrete_to_builtin(analyzer)?; self.underlying_mut(analyzer)?.ty = new_ty; @@ -364,9 +365,12 @@ impl ContextVarNode { Ok(self .underlying_mut(analyzer)? .try_set_range_min(new_min, fallback)) - } + }; + self.cache_range(analyzer, arena)?; + res } + #[tracing::instrument(level = "trace", skip_all)] pub fn try_set_range_max( &self, analyzer: &mut impl AnalyzerBackend, @@ -385,7 +389,7 @@ impl ContextVarNode { new_max.arenaize(analyzer, arena)?; - if self.is_concrete(analyzer)? { + let res = if self.is_concrete(analyzer)? { let mut new_ty = self.ty(analyzer)?.clone(); new_ty.concrete_to_builtin(analyzer)?; self.underlying_mut(analyzer)?.ty = new_ty; @@ -399,7 +403,9 @@ impl ContextVarNode { Ok(self .underlying_mut(analyzer)? .try_set_range_max(new_max, fallback)) - } + }; + self.cache_range(analyzer, arena)?; + res } pub fn try_set_range_exclusions( diff --git a/crates/graph/src/nodes/context/var/underlying.rs b/crates/graph/src/nodes/context/var/underlying.rs index e8df5dba..0aab388a 100644 --- a/crates/graph/src/nodes/context/var/underlying.rs +++ b/crates/graph/src/nodes/context/var/underlying.rs @@ -39,6 +39,12 @@ impl TmpConstruction { } } +impl From for Node { + fn from(var: ContextVar) -> Self { + Node::ContextVar(var) + } +} + impl ContextVar { pub fn eq_ignore_loc(&self, other: &Self) -> bool { self.name == other.name @@ -70,16 +76,14 @@ impl ContextVar { Ok(ContextVar { loc: Some(loc), name: format!( - "tmp{}({} {} {})", + "tmp{}({} {op} {})", ctx.new_tmp(analyzer)?, lhs_cvar.name(analyzer)?, - op.to_string(), rhs_cvar.name(analyzer)? ), display_name: format!( - "({} {} {})", + "({} {op} {})", lhs_cvar.display_name(analyzer)?, - op.to_string(), rhs_cvar.display_name(analyzer)? ), storage: None, @@ -504,6 +508,10 @@ impl ContextVar { let name = e.name.clone().expect("Enum had no name").name; (name, None) } + Node::Builtin(bn) => { + let name = bn.as_string(analyzer).ok()?; + (name, None) + } Node::Var(var) => { let name = var.name.clone().expect("Variable had no name").name; let storage = if var.in_contract { diff --git a/crates/graph/src/nodes/context/variables.rs b/crates/graph/src/nodes/context/variables.rs index e9ba4098..aa019d3e 100644 --- a/crates/graph/src/nodes/context/variables.rs +++ b/crates/graph/src/nodes/context/variables.rs @@ -24,25 +24,22 @@ impl ContextNode { } /// Debug print the stack - pub fn debug_expr_stack(&self, analyzer: &impl GraphBackend) -> Result<(), GraphError> { + pub fn debug_expr_stack_str(&self, analyzer: &impl GraphBackend) -> Result { let underlying_mut = self.underlying(analyzer)?; - underlying_mut - .expr_ret_stack - .iter() - .enumerate() - .for_each(|(i, elem)| println!("{i}. {}", elem.debug_str(analyzer))); - Ok(()) + Ok(format!( + "{:#?}", + underlying_mut + .expr_ret_stack + .iter() + .rev() + .enumerate() + .map(|(i, elem)| format!("{i}. {}", elem.debug_str(analyzer))) + .collect::>() + )) } - /// Debug print the temprorary stack - pub fn debug_tmp_expr_stack(&self, analyzer: &impl GraphBackend) -> Result<(), GraphError> { - let underlying_mut = self.underlying(analyzer)?; - underlying_mut - .tmp_expr - .iter() - .enumerate() - .filter(|(_i, maybe_elem)| maybe_elem.is_some()) - .for_each(|(i, elem)| println!("{i}. {}", elem.clone().unwrap().debug_str(analyzer))); + pub fn debug_expr_stack(&self, analyzer: &impl GraphBackend) -> Result<(), GraphError> { + println!("{}", self.debug_expr_stack_str(analyzer)?); Ok(()) } @@ -105,10 +102,16 @@ impl ContextNode { name: &str, ) -> Result, GraphError> { if let Some(var) = self.var_by_name(analyzer, name) { - Ok(Some(var)) - } else if let Some(parent) = self.ancestor_in_fn(analyzer, self.associated_fn(analyzer)?)? { - parent.var_by_name_or_recurse(analyzer, name) - } else if let Some(parent) = self.underlying(analyzer)?.continuation_of { + return Ok(Some(var)); + } + + if let Some(parent) = self.ancestor_in_fn(analyzer, self.associated_fn(analyzer)?)? { + if let Some(in_parent) = parent.var_by_name_or_recurse(analyzer, name)? { + return Ok(Some(in_parent)); + } + } + + if let Some(parent) = self.underlying(analyzer)?.continuation_of() { parent.var_by_name_or_recurse(analyzer, name) } else { Ok(None) @@ -209,7 +212,6 @@ impl ContextNode { } pub fn contract_vars_referenced_global(&self, analyzer: &impl AnalyzerBackend) -> Vec { - println!("getting storage vars for: {}", self.path(analyzer)); let mut reffed_storage = self.contract_vars_referenced(analyzer); analyzer .graph() @@ -289,69 +291,21 @@ impl ContextNode { Ok(ret) } - /// Push an expression return into the temporary stack - pub fn push_tmp_expr( + pub fn kill_if_ret_killed( &self, - expr_ret: ExprRet, analyzer: &mut impl AnalyzerBackend, - ) -> Result<(), GraphError> { - let underlying_mut = self.underlying_mut(analyzer)?; - underlying_mut.tmp_expr.push(Some(expr_ret)); - Ok(()) - } - - /// Append a new expression return to an expression return - /// currently in the temporary stack - pub fn append_tmp_expr( - &self, - expr_ret: ExprRet, - analyzer: &mut impl AnalyzerBackend, - ) -> Result<(), GraphError> { - let underlying_mut = self.underlying_mut(analyzer)?; - match underlying_mut.tmp_expr.pop() { - Some(Some(s @ ExprRet::Single(_))) => { - underlying_mut - .tmp_expr - .push(Some(ExprRet::Multi(vec![s, expr_ret]))); - } - Some(Some(s @ ExprRet::SingleLiteral(_))) => { - underlying_mut - .tmp_expr - .push(Some(ExprRet::Multi(vec![s, expr_ret]))); - } - Some(Some(ExprRet::Multi(ref mut inner))) => { - inner.push(expr_ret); - underlying_mut - .tmp_expr - .push(Some(ExprRet::Multi(inner.to_vec()))); - } - Some(Some(s @ ExprRet::Null)) => { - underlying_mut - .tmp_expr - .push(Some(ExprRet::Multi(vec![s, expr_ret]))); - } - Some(Some(ExprRet::CtxKilled(kind))) => { - underlying_mut.tmp_expr = vec![Some(ExprRet::CtxKilled(kind))]; - underlying_mut.expr_ret_stack = vec![ExprRet::CtxKilled(kind)]; - } - _ => { - underlying_mut.tmp_expr.push(Some(expr_ret)); - } - } - Ok(()) - } - - /// Pops a from the temporary ExprRet stack - pub fn pop_tmp_expr( - &self, loc: Loc, - analyzer: &mut impl AnalyzerBackend, - ) -> Result, GraphError> { - let underlying_mut = self.underlying_mut(analyzer)?; - if let Some(Some(expr)) = underlying_mut.tmp_expr.pop() { - Ok(Some(self.maybe_move_expr(expr, loc, analyzer)?)) + ) -> Result { + let underlying = self.underlying(analyzer)?; + if let Some(killed_ret) = underlying + .expr_ret_stack + .iter() + .find(|ret| ret.has_killed()) + { + self.kill(analyzer, loc, killed_ret.killed_kind().unwrap())?; + Ok(true) } else { - Ok(None) + Ok(false) } } @@ -403,7 +357,6 @@ impl ContextNode { } pub fn recursive_move_struct_field( - &self, parent: ContextVarNode, field: ContextVarNode, loc: Loc, @@ -422,7 +375,7 @@ impl ContextNode { let sub_fields = field.struct_to_fields(analyzer)?; sub_fields.iter().try_for_each(|sub_field| { - self.recursive_move_struct_field(new_cvarnode, *sub_field, loc, analyzer) + Self::recursive_move_struct_field(new_cvarnode, *sub_field, loc, analyzer) }) } @@ -468,7 +421,7 @@ impl ContextNode { let fields = var.struct_to_fields(analyzer)?; fields.iter().try_for_each(|field| { - self.recursive_move_struct_field(new_cvarnode, *field, loc, analyzer) + Self::recursive_move_struct_field(new_cvarnode, *field, loc, analyzer) })?; Ok(new_cvarnode) } else { @@ -479,24 +432,28 @@ impl ContextNode { } } - /// Pop the latest expression return off the stack - #[tracing::instrument(level = "trace", skip_all)] - pub fn pop_expr_latest( + pub fn pop_n_latest_exprs( &self, + n: usize, loc: Loc, analyzer: &mut impl AnalyzerBackend, - ) -> Result, GraphError> { + ) -> Result, GraphError> { let underlying_mut = self.underlying_mut(analyzer)?; - if let Some(elem) = underlying_mut.expr_ret_stack.pop() { - tracing::trace!( - "popping var {} from: {}", - elem.debug_str(analyzer), - self.path(analyzer) - ); - Ok(Some(self.maybe_move_expr(elem, loc, analyzer)?)) - } else { - Ok(None) + + let mut res = vec![]; + for _ in 0..n { + if let Some(elem) = underlying_mut.expr_ret_stack.pop() { + res.push(elem); + } else { + return Err(GraphError::StackLengthMismatch(format!( + "Expected {n} ExprRets on stack, but had fewer" + ))); + } } + + res.into_iter() + .map(|elem| self.maybe_move_expr(elem, loc, analyzer)) + .collect::, _>>() } /// Gets local vars that were assigned from a function return diff --git a/crates/graph/src/nodes/context/versioning.rs b/crates/graph/src/nodes/context/versioning.rs index 4b96f7e5..c49a74ab 100644 --- a/crates/graph/src/nodes/context/versioning.rs +++ b/crates/graph/src/nodes/context/versioning.rs @@ -1,59 +1,18 @@ use crate::nodes::Context; -use crate::ContextEdge; -use crate::Edge; use crate::{ nodes::{CallFork, ContextNode, FunctionNode, KilledKind}, AnalyzerBackend, GraphBackend, Node, }; -use petgraph::visit::EdgeRef; -use petgraph::Direction; use shared::GraphError; use solang_parser::pt::Loc; +use super::underlying::SubContextKind; + impl ContextNode { /// Query whether this context has a parent pub fn has_parent(&self, analyzer: &impl GraphBackend) -> Result { - Ok(self.underlying(analyzer)?.parent_ctx.is_some()) - } - - /// Sets the continuation context - pub fn set_continuation_ctx( - &self, - analyzer: &mut impl AnalyzerBackend, - continuation_of_ctx: ContextNode, - ty: &'static str, - ) -> Result<(), GraphError> { - assert!( - self.0 > continuation_of_ctx.0, - "{} {}", - self.0, - continuation_of_ctx.0 - ); - - let parent_list = self.parent_list(analyzer)?; - // if `continuation_of` already has a continuation, build off that continuation if it is in the parent list - if let Some(cont) = analyzer - .graph() - .edges_directed(continuation_of_ctx.into(), Direction::Incoming) - .find(|edge| { - matches!(edge.weight(), Edge::Context(ContextEdge::Continue(_))) - && parent_list.contains(&ContextNode::from(edge.source())) - }) - .map(|edge| ContextNode::from(edge.source())) - { - self.set_continuation_ctx(analyzer, cont, ty) - } else { - analyzer.add_edge( - *self, - continuation_of_ctx, - Edge::Context(ContextEdge::Continue(ty)), - ); - self.underlying_mut(analyzer)?.continuation_of = Some(continuation_of_ctx); - self.underlying_mut(analyzer)?.cache.vars = - continuation_of_ctx.underlying(analyzer)?.cache.vars.clone(); - Ok(()) - } + Ok(self.underlying(analyzer)?.parent_ctx().is_some()) } /// Gets the first ancestor of this context @@ -63,7 +22,7 @@ impl ContextNode { ) -> Result { if let Some(first_ancestor) = self.underlying(analyzer)?.cache.first_ancestor { Ok(first_ancestor) - } else if let Some(parent) = self.underlying(analyzer)?.parent_ctx { + } else if let Some(parent) = self.underlying(analyzer)?.parent_ctx() { let first = parent.first_ancestor(analyzer)?; self.underlying_mut(analyzer)?.cache.first_ancestor = Some(first); Ok(first) @@ -88,13 +47,13 @@ impl ContextNode { analyzer: &impl GraphBackend, associated_fn: FunctionNode, ) -> Result, GraphError> { - if let Some(ret) = self.underlying(analyzer)?.returning_ctx { + if let Some(ret) = self.underlying(analyzer)?.returning_ctx() { if ret.associated_fn(analyzer)? == associated_fn { return Ok(Some(ret)); } } - if let Some(parent) = self.underlying(analyzer)?.parent_ctx { + if let Some(parent) = self.underlying(analyzer)?.parent_ctx() { if parent.associated_fn(analyzer)? == associated_fn { Ok(Some(parent)) } else if let Some(mod_state) = &parent.underlying(analyzer)?.modifier_state { @@ -197,7 +156,9 @@ impl ContextNode { match child { CallFork::Call(call) => { let call_edges = call.successful_edges(analyzer)?; - if call_edges.is_empty() && !call.is_killed(analyzer)? { + let is_graceful_ended = call.is_graceful_ended(analyzer)?; + let bad_killed = call.is_killed(analyzer)? && !is_graceful_ended; + if call_edges.is_empty() && !bad_killed { lineage.push(call) } else { lineage.extend(call_edges); @@ -205,14 +166,20 @@ impl ContextNode { } CallFork::Fork(w1, w2) => { let fork_edges = w1.successful_edges(analyzer)?; - if fork_edges.is_empty() && !w1.is_killed(analyzer)? { + let is_graceful_ended = w1.is_graceful_ended(analyzer)?; + let bad_killed = w1.is_killed(analyzer)? && !is_graceful_ended; + if fork_edges.is_empty() && !bad_killed { lineage.push(w1) } else { lineage.extend(fork_edges); } let fork_edges = w2.successful_edges(analyzer)?; - if fork_edges.is_empty() && !w2.is_killed(analyzer)? { + + let is_graceful_ended = w2.is_graceful_ended(analyzer)?; + let bad_killed = w2.is_killed(analyzer)? && !is_graceful_ended; + + if fork_edges.is_empty() && !bad_killed { lineage.push(w2) } else { lineage.extend(fork_edges); @@ -363,31 +330,25 @@ impl ContextNode { for _ in 0..end_worlds.len().saturating_sub(1) { let curr = stack.pop_front().unwrap(); - let left_ctx = Context::new_subctx( - curr, - None, + let left_subctx = Context::add_subctx( + SubContextKind::Fork { + parent_ctx: curr, + true_side: true, + }, loc, - Some("join_left"), - None, - false, analyzer, None, )?; - let left_subctx = ContextNode::from(analyzer.add_node(Node::Context(left_ctx))); - let right_ctx = Context::new_subctx( - curr, - None, + let right_subctx = Context::add_subctx( + SubContextKind::Fork { + parent_ctx: curr, + true_side: false, + }, loc, - Some("join_right"), - None, - false, analyzer, None, )?; - let right_subctx = ContextNode::from(analyzer.add_node(Node::Context(right_ctx))); curr.set_child_fork(left_subctx, right_subctx, analyzer)?; - left_subctx.set_continuation_ctx(analyzer, curr, "join_left")?; - right_subctx.set_continuation_ctx(analyzer, curr, "join_right")?; stack.push_back(left_subctx); stack.push_back(right_subctx); @@ -452,7 +413,7 @@ impl ContextNode { kill_loc: Loc, kill_kind: KilledKind, ) -> Result<(), GraphError> { - tracing::trace!("killing: {}", self.path(analyzer)); + tracing::trace!("killing: {}, {kill_kind:?}", self.path(analyzer)); if let Some(child) = self.underlying(analyzer)?.child { match child { CallFork::Call(call) => { @@ -477,7 +438,7 @@ impl ContextNode { } let context = self.underlying_mut(analyzer)?; - let parent = context.parent_ctx; + let parent = context.parent_ctx(); if context.killed.is_none() { context.killed = Some((kill_loc, kill_kind)); } @@ -507,7 +468,7 @@ impl ContextNode { if context.killed.is_none() { context.killed = Some((kill_loc, kill_kind)); } - if let Some(parent_ctx) = context.parent_ctx { + if let Some(parent_ctx) = context.parent_ctx() { parent_ctx.end_if_all_forks_ended(analyzer, kill_loc, kill_kind)?; } } @@ -522,7 +483,7 @@ impl ContextNode { ) -> Result, GraphError> { let context = self.underlying(analyzer)?; let mut parents = vec![]; - if let Some(parent_ctx) = context.parent_ctx { + if let Some(parent_ctx) = context.parent_ctx() { parents.push(parent_ctx); parents.extend(parent_ctx.parent_list(analyzer)?); } @@ -532,7 +493,7 @@ impl ContextNode { /// Gets the first context in the lineage pub fn genesis(&self, analyzer: &impl GraphBackend) -> Result { let context = self.underlying(analyzer)?; - if let Some(parent_ctx) = context.parent_ctx { + if let Some(parent_ctx) = context.parent_ctx() { parent_ctx.genesis(analyzer) } else { Ok(*self) diff --git a/crates/graph/src/nodes/contract_ty.rs b/crates/graph/src/nodes/contract_ty.rs index 42694f9b..a91138c3 100644 --- a/crates/graph/src/nodes/contract_ty.rs +++ b/crates/graph/src/nodes/contract_ty.rs @@ -108,13 +108,36 @@ impl ContractNode { self.underlying_mut(analyzer) .unwrap() .inherits - .push(ContractNode::from(*found)); + .push(Some(ContractNode::from(*found))); analyzer.add_edge(*found, *self, Edge::InheritedContract); }); + self.order_inherits(analyzer); + } + + pub fn order_inherits(&self, analyzer: &mut impl AnalyzerBackend) { + let raw_inherits = self.underlying(analyzer).unwrap().raw_inherits.clone(); + let inherits = self.underlying(analyzer).unwrap().inherits.clone(); + + let mut tmp_inherits = vec![]; + tmp_inherits.resize(inherits.len(), None); + inherits.into_iter().for_each(|inherited| { + if let Some(inherited) = inherited { + let i_name = inherited.name(analyzer).unwrap(); + let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap(); + tmp_inherits[position] = Some(inherited); + } + }); + self.underlying_mut(analyzer).unwrap().inherits = tmp_inherits; } pub fn direct_inherited_contracts(&self, analyzer: &impl GraphBackend) -> Vec { - self.underlying(analyzer).unwrap().inherits.clone() + self.underlying(analyzer) + .unwrap() + .inherits + .iter() + .filter_map(|i| i.as_ref()) + .cloned() + .collect() } pub fn all_inherited_contracts(&self, analyzer: &impl GraphBackend) -> Vec { @@ -170,6 +193,14 @@ impl ContractNode { .next() } + pub fn ordered_new_param_names(&self, analyzer: &impl GraphBackend) -> Vec { + if let Some(constructor) = self.constructor(analyzer) { + constructor.ordered_param_names(analyzer) + } else { + vec![] + } + } + /// Gets all associated storage vars from the underlying node data for the [`Contract`] pub fn direct_storage_vars(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { analyzer @@ -207,27 +238,34 @@ impl ContractNode { pub fn linearized_functions( &self, analyzer: &mut (impl Search + AnalyzerBackend), + super_func: bool, ) -> Result, GraphError> { - if let Some(funcs) = &self.underlying(analyzer)?.cached_functions { - Ok(funcs.clone()) - } else { - let mut mapping = self.funcs_mapping(analyzer); - self.direct_inherited_contracts(analyzer) - .iter() - .for_each(|inherited| { - inherited - .linearized_functions(analyzer) - .unwrap() - .iter() - .for_each(|(name, func)| { - if !mapping.contains_key(name) { - mapping.insert(name.to_string(), *func); - } - }); - }); - self.underlying_mut(analyzer)?.cached_functions = Some(mapping.clone()); - Ok(mapping) + if !super_func { + if let Some(funcs) = &self.underlying(analyzer)?.cached_functions { + return Ok(funcs.clone()); + } } + + let mut mapping = if !super_func { + self.funcs_mapping(analyzer) + } else { + Default::default() + }; + self.direct_inherited_contracts(analyzer) + .iter() + .for_each(|inherited| { + inherited + .linearized_functions(analyzer, false) + .unwrap() + .iter() + .for_each(|(name, func)| { + if !mapping.contains_key(name) { + mapping.insert(name.to_string(), *func); + } + }); + }); + self.underlying_mut(analyzer)?.cached_functions = Some(mapping.clone()); + Ok(mapping) } pub fn structs(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { @@ -351,8 +389,10 @@ pub struct Contract { pub ty: ContractTy, /// An optional name in the form of an identifier (`(Loc, String)`) pub name: Option, + /// Raw inherited strings, ordered by least base to most base + pub raw_inherits: Vec, /// A list of contracts that this contract inherits (TODO: inheritance linearization) - pub inherits: Vec, + pub inherits: Vec>, /// Cached linearized functions pub cached_functions: Option>, } @@ -371,10 +411,12 @@ impl Contract { imports: &[Option], analyzer: &impl GraphBackend, ) -> (Contract, Vec) { + let mut raw_inherits = vec![]; let mut inherits = vec![]; let mut unhandled_inherits = vec![]; con.base.iter().for_each(|base| { let inherited_name = &base.name.identifiers[0].name; + raw_inherits.push(inherited_name.clone()); let mut found = false; for contract in analyzer .search_children_exclude_via(source, &Edge::Contract, &[Edge::Func]) @@ -382,7 +424,7 @@ impl Contract { { let name = ContractNode::from(contract).name(analyzer).unwrap(); if &name == inherited_name { - inherits.push(ContractNode::from(contract)); + inherits.push(Some(ContractNode::from(contract))); found = true; break; } @@ -396,7 +438,7 @@ impl Contract { { let name = ContractNode::from(contract).name(analyzer).unwrap(); if &name == inherited_name { - inherits.push(ContractNode::from(contract)); + inherits.push(Some(ContractNode::from(contract))); found = true; break; } @@ -408,15 +450,34 @@ impl Contract { unhandled_inherits.push(inherited_name.clone()); } }); - ( - Contract { - loc: con.loc, - ty: con.ty, - name: con.name, - inherits, - cached_functions: None, - }, - unhandled_inherits, - ) + + raw_inherits.reverse(); + let mut this = Contract { + loc: con.loc, + ty: con.ty, + name: con.name, + raw_inherits, + inherits, + cached_functions: None, + }; + + this.order_inherits(analyzer); + (this, unhandled_inherits) + } + + pub fn order_inherits(&mut self, analyzer: &impl GraphBackend) { + let raw_inherits = self.raw_inherits.clone(); + let inherits = self.inherits.clone(); + + let mut tmp_inherits = vec![]; + tmp_inherits.resize(raw_inherits.len(), None); + inherits.into_iter().for_each(|inherited| { + if let Some(inherited) = inherited { + let i_name = inherited.name(analyzer).unwrap(); + let position = raw_inherits.iter().position(|raw| &i_name == raw).unwrap(); + tmp_inherits[position] = Some(inherited); + } + }); + self.inherits = tmp_inherits; } } diff --git a/crates/graph/src/nodes/debug_reconstruction.rs b/crates/graph/src/nodes/debug_reconstruction.rs index 166b6c41..f286397d 100644 --- a/crates/graph/src/nodes/debug_reconstruction.rs +++ b/crates/graph/src/nodes/debug_reconstruction.rs @@ -502,10 +502,6 @@ impl FunctionNode { &self, analyzer: &mut impl AnalyzerBackend, ) -> FuncReconstructionReqs { - println!( - "reconstruction requirements for: {}", - self.name(analyzer).unwrap() - ); FuncReconstructionReqs { storage: self.maybe_used_storage(analyzer).unwrap_or_default(), usertypes: self.maybe_used_usertypes(analyzer).unwrap_or_default(), diff --git a/crates/graph/src/nodes/func_ty.rs b/crates/graph/src/nodes/func_ty.rs index 1adbcf3d..b9359a42 100644 --- a/crates/graph/src/nodes/func_ty.rs +++ b/crates/graph/src/nodes/func_ty.rs @@ -1,5 +1,8 @@ use crate::{ - nodes::{Concrete, ContextNode, ContractNode, SourceUnitNode, SourceUnitPartNode}, + nodes::{ + Concrete, ContextNode, ContextVar, ContextVarNode, ContractNode, SourceUnitNode, + SourceUnitPartNode, YulFunctionNode, + }, range::elem::Elem, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, Node, SolcRange, VarType, }; @@ -50,6 +53,23 @@ impl FunctionNode { Ok(()) } + pub fn yul_funcs( + &self, + analyzer: &impl GraphBackend, + assembly_block_idx: usize, + ) -> Vec { + analyzer + .graph() + .edges_directed(self.0.into(), Direction::Incoming) + .filter_map(|edge| match edge.weight() { + Edge::YulFunction(asm_block) if *asm_block == assembly_block_idx => { + Some(edge.source().into()) + } + _ => None, + }) + .collect::>() + } + pub fn ty(&self, analyzer: &impl GraphBackend) -> Result { Ok(self.underlying(analyzer)?.ty) } @@ -413,6 +433,17 @@ impl FunctionNode { } } + pub fn add_params_to_ctx( + &self, + ctx: ContextNode, + analyzer: &mut impl AnalyzerBackend, + ) -> Result<(), GraphError> { + self.params(analyzer).iter().try_for_each(|param| { + let _ = param.maybe_add_to_ctx(ctx, analyzer)?; + Ok(()) + }) + } + pub fn ordered_param_names(&self, analyzer: &impl GraphBackend) -> Vec { let param_nodes = self.params(analyzer); param_nodes @@ -946,6 +977,28 @@ impl FunctionParamNode { pub fn ty(&self, analyzer: &impl GraphBackend) -> Result { Ok(self.underlying(analyzer)?.ty) } + + pub fn maybe_add_to_ctx( + &self, + ctx: ContextNode, + analyzer: &mut impl AnalyzerBackend, + ) -> Result { + if let Some(var) = + ContextVar::maybe_new_from_func_param(analyzer, self.underlying(analyzer)?.clone()) + { + let var = ContextVarNode::from(analyzer.add_node(var)); + ctx.add_var(var, analyzer)?; + analyzer.add_edge(var, ctx, Edge::Context(ContextEdge::Variable)); + analyzer.add_edge(var, ctx, Edge::Context(ContextEdge::CalldataVariable)); + if let Some(strukt) = var.maybe_struct(analyzer)? { + strukt.add_fields_to_cvar(analyzer, var.loc(analyzer).unwrap(), var)?; + } + + Ok(true) + } else { + Ok(false) + } + } } impl From for NodeIdx { diff --git a/crates/graph/src/nodes/mod.rs b/crates/graph/src/nodes/mod.rs index 5bfc3f86..e380b441 100644 --- a/crates/graph/src/nodes/mod.rs +++ b/crates/graph/src/nodes/mod.rs @@ -42,3 +42,6 @@ pub use source_unit::*; mod debug_reconstruction; pub use debug_reconstruction::*; + +mod yul_func; +pub use yul_func::*; diff --git a/crates/graph/src/nodes/msg.rs b/crates/graph/src/nodes/msg.rs index a142e54c..550279b4 100644 --- a/crates/graph/src/nodes/msg.rs +++ b/crates/graph/src/nodes/msg.rs @@ -72,7 +72,7 @@ impl Msg { "data" => { if let Some(d) = self.data.clone() { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "msg.data".to_string()) + (analyzer.add_node(c), "msg.data".to_string()) } else { let b = Builtin::DynamicBytes; let node = analyzer.builtin_or_add(b); @@ -87,10 +87,7 @@ impl Msg { "sender" => { if let Some(d) = self.sender { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "msg.sender".to_string(), - ) + (analyzer.add_node(c), "msg.sender".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -104,7 +101,7 @@ impl Msg { "sig" => { if let Some(d) = self.sig { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "msg.sig".to_string()) + (analyzer.add_node(c), "msg.sig".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Bytes(4)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -118,10 +115,7 @@ impl Msg { "value" => { if let Some(d) = self.value { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "msg.value".to_string(), - ) + (analyzer.add_node(c), "msg.value".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -135,10 +129,7 @@ impl Msg { "origin" => { if let Some(d) = self.origin { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "tx.origin".to_string(), - ) + (analyzer.add_node(c), "tx.origin".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -152,10 +143,7 @@ impl Msg { "gasprice" => { if let Some(d) = self.gasprice { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "tx.gasprice".to_string(), - ) + (analyzer.add_node(c), "tx.gasprice".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -169,7 +157,7 @@ impl Msg { "gaslimit" => { if let Some(d) = self.gaslimit { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "".to_string()) + (analyzer.add_node(c), "".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; diff --git a/crates/graph/src/nodes/struct_ty.rs b/crates/graph/src/nodes/struct_ty.rs index e739a711..498f1db1 100644 --- a/crates/graph/src/nodes/struct_ty.rs +++ b/crates/graph/src/nodes/struct_ty.rs @@ -53,17 +53,21 @@ impl StructNode { fields } - pub fn find_field( - &self, - analyzer: &impl GraphBackend, - ident: &Identifier, - ) -> Option { + pub fn ordered_new_param_names(&self, analyzer: &impl GraphBackend) -> Vec { + let fields = self.fields(analyzer); + fields + .iter() + .map(|field| field.name(analyzer).unwrap()) + .collect() + } + + pub fn find_field(&self, analyzer: &impl GraphBackend, field_name: &str) -> Option { analyzer .graph() .edges_directed(self.0.into(), Direction::Incoming) .filter(|edge| Edge::Field == *edge.weight()) .map(|edge| FieldNode::from(edge.source())) - .find(|field_node| field_node.name(analyzer).unwrap() == ident.name) + .find(|field_node| field_node.name(analyzer).unwrap() == field_name) } pub fn maybe_associated_contract(&self, analyzer: &impl GraphBackend) -> Option { @@ -200,7 +204,7 @@ impl FieldNode { .underlying(analyzer)? .name .as_ref() - .expect("Struct wasn't named") + .expect("Struct field wasn't named") .to_string()) } } diff --git a/crates/graph/src/nodes/ty_ty.rs b/crates/graph/src/nodes/ty_ty.rs index 054db2ad..9e9225ae 100644 --- a/crates/graph/src/nodes/ty_ty.rs +++ b/crates/graph/src/nodes/ty_ty.rs @@ -45,6 +45,10 @@ impl TyNode { .next() .map(ContractNode::from) } + + pub fn underlying_ty(&self, analyzer: &impl GraphBackend) -> Result { + Ok(self.underlying(analyzer)?.ty) + } } impl From for NodeIdx { diff --git a/crates/graph/src/nodes/var_ty.rs b/crates/graph/src/nodes/var_ty.rs index 42a4e948..cc26a73a 100644 --- a/crates/graph/src/nodes/var_ty.rs +++ b/crates/graph/src/nodes/var_ty.rs @@ -58,18 +58,14 @@ impl VarNode { self.underlying(analyzer)?.name.as_ref().unwrap().name ); let init = analyzer.parse_expr(arena, &expr, Some(parent)); + let underlying = self.underlying(analyzer)?; + let target_ty = VarType::try_from_idx(analyzer, underlying.ty).unwrap(); + let initer_ty = VarType::try_from_idx(analyzer, init).unwrap(); - let underlying = self.underlying(analyzer)?.clone(); let mut set = false; - if let Some(ty) = VarType::try_from_idx(analyzer, underlying.ty) { - if let Some(initer) = VarType::try_from_idx(analyzer, init) { - if let Some(initer) = initer.try_cast(&ty, analyzer)? { - if let Some(conc_idx) = initer.builtin_to_concrete_idx(analyzer, arena)? { - set = true; - self.underlying_mut(analyzer)?.initializer = Some(conc_idx); - } - } - } + if let Some(initer) = initer_ty.try_cast(&target_ty, analyzer)? { + set = true; + self.underlying_mut(analyzer)?.initializer = Some(initer.ty_idx()); } if !set { diff --git a/crates/graph/src/nodes/yul_func.rs b/crates/graph/src/nodes/yul_func.rs new file mode 100644 index 00000000..d012eca6 --- /dev/null +++ b/crates/graph/src/nodes/yul_func.rs @@ -0,0 +1,74 @@ +use crate::{nodes::Concrete, range::elem::Elem, AsDotStr, GraphBackend, Node}; + +use shared::{FlatExpr, GraphError, NodeIdx, RangeArena}; + +use solang_parser::pt::Loc; + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct YulFunctionNode(pub usize); +impl YulFunctionNode { + pub fn underlying<'a>( + &self, + analyzer: &'a impl GraphBackend, + ) -> Result<&'a YulFunction, GraphError> { + match analyzer.node(*self) { + Node::YulFunction(ty) => Ok(ty), + Node::Unresolved(ident) => Err(GraphError::UnknownVariable(format!( + "Could not find variable: {}", + ident.name + ))), + e => Err(GraphError::NodeConfusion(format!( + "Node type confusion: expected node to be YulFunctionNode but it was: {e:?}" + ))), + } + } + + pub fn name(&self, analyzer: &impl GraphBackend) -> Result { + Ok(self.underlying(analyzer)?.name.to_string()) + } + pub fn exprs(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + Ok(self.underlying(analyzer)?.exprs.clone()) + } +} + +impl From for NodeIdx { + fn from(val: YulFunctionNode) -> Self { + val.0.into() + } +} + +impl From for YulFunctionNode { + fn from(idx: NodeIdx) -> Self { + YulFunctionNode(idx.index()) + } +} + +impl AsDotStr for YulFunctionNode { + fn as_dot_str( + &self, + analyzer: &impl GraphBackend, + _arena: &mut RangeArena>, + ) -> String { + let underlying = self.underlying(analyzer).unwrap(); + format!("yul function {}", underlying.name,) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct YulFunction { + pub loc: Loc, + pub name: &'static str, + pub exprs: Vec, +} + +impl From for Node { + fn from(val: YulFunction) -> Self { + Node::YulFunction(val) + } +} + +impl YulFunction { + pub fn new(exprs: Vec, name: &'static str, loc: Loc) -> YulFunction { + YulFunction { loc, name, exprs } + } +} diff --git a/crates/graph/src/range/elem/elem_enum/arena.rs b/crates/graph/src/range/elem/elem_enum/arena.rs index d95967e5..4fba11d3 100644 --- a/crates/graph/src/range/elem/elem_enum/arena.rs +++ b/crates/graph/src/range/elem/elem_enum/arena.rs @@ -39,7 +39,7 @@ impl RangeArenaLike> for RangeArena> { format!( "{} {} {}", fmt(&expr.lhs, analyzer), - expr.op.to_string(), + expr.op, fmt(&expr.rhs, analyzer) ) } @@ -124,11 +124,6 @@ impl RangeArenaLike> for RangeArena> { self.map.insert(Elem::Null, 0); } - // println!( - // "{}\nhad cycle:\n{:?}", - // self.debug_str(analyzer), - // petgraph::dot::Dot::new(&self.to_graph(analyzer).unwrap()) // petgraph::algo::toposort(&self.to_graph(analyzer).unwrap(), None).is_err() - // ); match elem { Elem::Arena(idx) => return idx, Elem::Null => return 0, diff --git a/crates/graph/src/range/elem/elem_enum/impls.rs b/crates/graph/src/range/elem/elem_enum/impls.rs index b657ffe9..5a6bd881 100644 --- a/crates/graph/src/range/elem/elem_enum/impls.rs +++ b/crates/graph/src/range/elem/elem_enum/impls.rs @@ -23,6 +23,12 @@ impl Elem { let expr = RangeExpr::new(self, RangeOp::Mul(true), other); Self::Expr(expr) } + + pub fn wrapping_exp(self, other: Elem) -> Self { + let expr = RangeExpr::new(self, RangeOp::Exp(true), other); + Self::Expr(expr) + } + pub fn wrapping_div(self, other: Elem) -> Self { let expr = RangeExpr::new(self, RangeOp::Div(true), other); Self::Expr(expr) @@ -215,7 +221,7 @@ impl Elem { /// Creates a new range element that is one range element to the power of another pub fn pow(self, other: Self) -> Self { - let expr = RangeExpr::new(self, RangeOp::Exp, other); + let expr = RangeExpr::new(self, RangeOp::Exp(false), other); Elem::Expr(expr) } @@ -237,6 +243,12 @@ impl Elem { Elem::Expr(expr) } + /// Creates a new range element that is a slice of the lhs with the rhs + pub fn slice(self, other: Self) -> Self { + let expr = RangeExpr::new(self, RangeOp::Slice, other); + Elem::Expr(expr) + } + /// Gets the length of a memory object pub fn get_length(self) -> Self { let expr = RangeExpr::new(self, RangeOp::GetLength, Elem::Null); @@ -388,6 +400,18 @@ impl Elem { arena: &mut RangeArena>, ) -> Result, GraphError> { match (self, other) { + (Elem::Arena(_), _) => { + let (elem, idx) = self.dearenaize(arena); + let overlaps = elem.overlaps(other, eval, analyzer, arena); + self.rearenaize(elem, idx, arena); + overlaps + } + (_, Elem::Arena(_)) => { + let (elem, idx) = other.dearenaize(arena); + let overlaps = self.overlaps(&elem, eval, analyzer, arena); + other.rearenaize(elem, idx, arena); + overlaps + } (Elem::Concrete(s), Elem::Concrete(o)) => Ok(Some(o.val == s.val)), (Elem::Reference(s), Elem::Reference(o)) => { if s == o { @@ -446,7 +470,7 @@ impl Elem { rhs_min: &Self, rhs_max: &Self, eval: bool, - analyzer: &mut impl GraphBackend, + analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Result, GraphError> { match self { @@ -494,6 +518,19 @@ impl Elem { _ => Ok(Some(false)), } } + Self::Expr(_) => { + let min = self.minimize(analyzer, arena)?; + let max = self.maximize(analyzer, arena)?; + if let Some(true) = min.overlaps_dual(rhs_min, rhs_max, eval, analyzer, arena)? { + Ok(Some(true)) + } else if let Some(true) = + max.overlaps_dual(rhs_min, rhs_max, eval, analyzer, arena)? + { + Ok(Some(true)) + } else { + Ok(None) + } + } _ => Ok(None), } } @@ -539,13 +576,13 @@ impl Elem { match self { Self::Reference(Reference { idx: _, .. }) => Some(Elem::Expr(RangeExpr::new( self.clone(), - RangeOp::Not, - Elem::Null, + RangeOp::Eq, + Elem::from(false), ))), Self::Concrete(_) => Some(Elem::Expr(RangeExpr::new( self.clone(), - RangeOp::Not, - Elem::Null, + RangeOp::Eq, + Elem::from(false), ))), Self::Expr(expr) => Some(Elem::Expr(expr.inverse_if_boolean()?)), Self::ConcreteDyn(_d) => None, diff --git a/crates/graph/src/range/elem/elem_enum/traits.rs b/crates/graph/src/range/elem/elem_enum/traits.rs index a2faced0..7deabec0 100644 --- a/crates/graph/src/range/elem/elem_enum/traits.rs +++ b/crates/graph/src/range/elem/elem_enum/traits.rs @@ -88,24 +88,18 @@ impl std::fmt::Display for Elem { } Elem::Expr(RangeExpr { lhs, op, rhs, .. }) => match op { RangeOp::Min | RangeOp::Max => { - write!(f, "{}{{{}, {}}}", op.to_string(), lhs, rhs) + write!(f, "{op}{{{lhs}, {rhs}}}") } RangeOp::Cast => match &**rhs { Elem::Concrete(RangeConcrete { val, .. }) => { - write!( - f, - "{}({}, {})", - op.to_string(), - lhs, - val.as_builtin().basic_as_string() - ) + write!(f, "{op}({lhs}, {})", val.as_builtin().basic_as_string()) } - _ => write!(f, "{}({}, {})", op.to_string(), lhs, rhs), + _ => write!(f, "{op}({lhs}, {rhs})"), }, RangeOp::BitNot => { - write!(f, "~{}", lhs) + write!(f, "~{lhs}") } - _ => write!(f, "({} {} {})", lhs, op.to_string(), rhs), + _ => write!(f, "({lhs} {op} {rhs})"), }, Elem::Arena(idx) => write!(f, "arena_idx_{idx}"), Elem::Null => write!(f, ""), diff --git a/crates/graph/src/range/elem/expr/collapse.rs b/crates/graph/src/range/elem/expr/collapse.rs index 641ea576..83587bc9 100644 --- a/crates/graph/src/range/elem/expr/collapse.rs +++ b/crates/graph/src/range/elem/expr/collapse.rs @@ -3,7 +3,7 @@ use crate::elem::expr::simplify::*; use crate::{ nodes::Concrete, range::{ - elem::{Elem, RangeConcrete, RangeElem, RangeExpr, RangeOp}, + elem::{Elem, RangeElem, RangeExpr, RangeOp}, exec_traits::*, }, }; @@ -46,9 +46,9 @@ pub static FLIP_INEQ_OPS: &[RangeOp] = &[RangeOp::Lt, RangeOp::Lte, RangeOp::Gt, #[derive(Debug)] pub enum MaybeCollapsed { - Concretes(Elem, Elem), + Concretes(Elem, RangeOp, Elem), Collapsed(Elem), - Not(Elem, Elem), + Not(Elem, RangeOp, Elem), } pub fn collapse( @@ -57,10 +57,12 @@ pub fn collapse( r: Elem, arena: &mut RangeArena>, ) -> MaybeCollapsed { + tracing::trace!("collapsing: {l} {op} {r}"); + let l = if let Elem::Expr(e) = l { match collapse(*e.lhs, e.op, *e.rhs, arena) { - MaybeCollapsed::Not(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), - MaybeCollapsed::Concretes(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Not(l, op, r) => Elem::Expr(RangeExpr::new(l, op, r)), + MaybeCollapsed::Concretes(l, op, r) => Elem::Expr(RangeExpr::new(l, op, r)), MaybeCollapsed::Collapsed(e) => e, } } else { @@ -69,8 +71,8 @@ pub fn collapse( let r = if let Elem::Expr(e) = r { match collapse(*e.lhs, e.op, *e.rhs, arena) { - MaybeCollapsed::Not(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), - MaybeCollapsed::Concretes(l, r) => Elem::Expr(RangeExpr::new(l, e.op, r)), + MaybeCollapsed::Not(l, op, r) => Elem::Expr(RangeExpr::new(l, op, r)), + MaybeCollapsed::Concretes(l, op, r) => Elem::Expr(RangeExpr::new(l, op, r)), MaybeCollapsed::Collapsed(e) => e, } } else { @@ -85,20 +87,20 @@ pub fn collapse( (l @ Elem::Arena(_), r) => { let t = l.dearenaize_clone(arena); match collapse(t, op, r, arena) { - MaybeCollapsed::Not(l, r) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Not(l, op, r) => MaybeCollapsed::Not(l, op, r), + MaybeCollapsed::Concretes(l, op, r) => MaybeCollapsed::Not(l, op, r), MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), } } (l, r @ Elem::Arena(_)) => { let t = r.dearenaize_clone(arena); match collapse(l, op, t, arena) { - MaybeCollapsed::Not(l, r) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(l, r) => MaybeCollapsed::Not(l, r), + MaybeCollapsed::Not(l, op, r) => MaybeCollapsed::Not(l, op, r), + MaybeCollapsed::Concretes(l, op, r) => MaybeCollapsed::Not(l, op, r), MaybeCollapsed::Collapsed(e) => MaybeCollapsed::Collapsed(e), } } - (l @ Elem::Concrete(_), r @ Elem::Concrete(_)) => MaybeCollapsed::Concretes(l, r), + (l @ Elem::Concrete(_), r @ Elem::Concrete(_)) => MaybeCollapsed::Concretes(l, op, r), (Elem::Expr(expr), d @ Elem::Reference(_)) => { // try to collapse the expression let x = &*expr.lhs; @@ -112,7 +114,7 @@ pub fn collapse( if let Some(res) = sub_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Div(_), RangeOp::Eq) => { @@ -120,7 +122,7 @@ pub fn collapse( // (x -|/ y) == x ==> false MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Add(_), RangeOp::Eq) => { @@ -129,7 +131,7 @@ pub fn collapse( // (x +|* k) == x ==> false MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Mul(_), RangeOp::Eq) => { @@ -137,7 +139,7 @@ pub fn collapse( // (x +|* k) == x ==> false MaybeCollapsed::Collapsed(Elem::from(Concrete::from(false))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Max, RangeOp::Gte) => { @@ -145,7 +147,7 @@ pub fn collapse( // max{ x, y } >= MaybeCollapsed::Collapsed(Elem::from(Concrete::from(true))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Min, RangeOp::Lte) => { @@ -153,10 +155,10 @@ pub fn collapse( // min{ x, y } <= MaybeCollapsed::Collapsed(Elem::from(Concrete::from(true))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } - _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + _ => MaybeCollapsed::Not(Elem::Expr(expr), op, z), } } // if we have an expression, it fundamentally must have a dynamic in it @@ -173,14 +175,14 @@ pub fn collapse( if let Some(res) = sub_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Add(false), _) if ORD_OPS.contains(&op) => { if let Some(res) = add_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Eq, RangeOp::Eq) => { @@ -191,7 +193,7 @@ pub fn collapse( { MaybeCollapsed::Collapsed(Elem::Expr(expr)) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Add(l_op), RangeOp::Add(r_op)) => { @@ -208,7 +210,7 @@ pub fn collapse( } else if let Some(new) = op_fn(y, &z) { MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Add(l_op), RangeOp::Sub(r_op)) => { @@ -229,7 +231,7 @@ pub fn collapse( Elem::Expr(RangeExpr::new(x.clone(), expr.op, new)); MaybeCollapsed::Collapsed(new_expr) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } Some(std::cmp::Ordering::Less) => { @@ -248,7 +250,7 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } None => { @@ -271,7 +273,7 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } Some(std::cmp::Ordering::Less) => { @@ -290,15 +292,15 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } - None => MaybeCollapsed::Not(Elem::Expr(expr), z), + None => MaybeCollapsed::Not(Elem::Expr(expr), op, z), } } } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Sub(l_op), RangeOp::Add(r_op)) => { @@ -321,7 +323,7 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } Some(std::cmp::Ordering::Less) => { @@ -340,7 +342,7 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } None => { @@ -359,12 +361,12 @@ pub fn collapse( y.clone(), ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Mul(l_op), RangeOp::Mul(r_op)) => { @@ -390,10 +392,10 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Add(wrapping), op) if EQ_OPS.contains(&op) => { @@ -410,7 +412,7 @@ pub fn collapse( } else if let Some(new) = const_op(&z, x) { MaybeCollapsed::Collapsed(Elem::Expr(RangeExpr::new(x.clone(), op, new))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Sub(wrapping), op) if EQ_OPS.contains(&op) => { @@ -433,7 +435,7 @@ pub fn collapse( let new_expr = RangeExpr::new(y.clone(), op, new); MaybeCollapsed::Collapsed(Elem::Expr(new_expr)) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Mul(wrapping), op) if EQ_OPS.contains(&op) => { @@ -467,7 +469,7 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (RangeOp::Div(wrapping), op) if EQ_OPS.contains(&op) => { @@ -507,10 +509,10 @@ pub fn collapse( new, ))) } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (_, RangeOp::Eq) => { @@ -527,10 +529,10 @@ pub fn collapse( (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), ), - _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + _ => MaybeCollapsed::Not(Elem::Expr(expr), op, z), } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } (_, RangeOp::Neq) => { @@ -548,30 +550,30 @@ pub fn collapse( (Some(new_x), Some(new_y), Some(new_op)) => MaybeCollapsed::Collapsed( Elem::Expr(RangeExpr::new(new_x, new_op, new_y)), ), - _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + _ => MaybeCollapsed::Not(Elem::Expr(expr), op, z), } } else { - MaybeCollapsed::Not(Elem::Expr(expr), z) + MaybeCollapsed::Not(Elem::Expr(expr), op, z) } } - _ => MaybeCollapsed::Not(Elem::Expr(expr), z), + _ => MaybeCollapsed::Not(Elem::Expr(expr), op, z), } } (l @ Elem::Concrete(_), r @ Elem::Expr(_)) => { if op.commutative() { match collapse(r, op, l, arena) { MaybeCollapsed::Collapsed(inner) => MaybeCollapsed::Collapsed(inner.clone()), - MaybeCollapsed::Not(r, l) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(r, l) => MaybeCollapsed::Concretes(l, r), + MaybeCollapsed::Not(r, op, l) => MaybeCollapsed::Not(l, op, r), + MaybeCollapsed::Concretes(r, op, l) => MaybeCollapsed::Concretes(l, op, r), } } else if let Some(inv) = op.non_commutative_logical_inverse() { match collapse(r, inv, l, arena) { MaybeCollapsed::Collapsed(inner) => MaybeCollapsed::Collapsed(inner.clone()), - MaybeCollapsed::Not(r, l) => MaybeCollapsed::Not(l, r), - MaybeCollapsed::Concretes(r, l) => MaybeCollapsed::Concretes(l, r), + MaybeCollapsed::Not(r, op, l) => MaybeCollapsed::Not(l, op, r), + MaybeCollapsed::Concretes(r, op, l) => MaybeCollapsed::Concretes(l, op, r), } } else { - MaybeCollapsed::Not(l, r) + MaybeCollapsed::Not(l, op, r) } } (le @ Elem::Reference(_), c @ Elem::Concrete(_)) => { @@ -582,50 +584,38 @@ pub fn collapse( if matches!(c.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)) { MaybeCollapsed::Collapsed(le.clone()) } else { - MaybeCollapsed::Not(le, c) + MaybeCollapsed::Not(le, op, c) } } RangeOp::Mul(_) | RangeOp::Div(_) => { if matches!(c.range_ord(&one, arena), Some(std::cmp::Ordering::Equal)) { MaybeCollapsed::Collapsed(le.clone()) } else { - MaybeCollapsed::Not(le, c) + MaybeCollapsed::Not(le, op, c) } } - _ => MaybeCollapsed::Not(le, c), + _ => MaybeCollapsed::Not(le, op, c), } } (Elem::Null, real) => match op { RangeOp::Max | RangeOp::Min => MaybeCollapsed::Collapsed(real.clone()), - RangeOp::Not => match real { - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(c), - loc, - }) => MaybeCollapsed::Collapsed(Elem::Concrete(RangeConcrete::new( - Concrete::from(!c), - loc, - ))), - _ => MaybeCollapsed::Not(Elem::Null, real), - }, - _ => MaybeCollapsed::Not(Elem::Null, real), + _ => MaybeCollapsed::Not(Elem::Null, op, real), }, (real, Elem::Null) => match op { RangeOp::Max | RangeOp::Min => MaybeCollapsed::Collapsed(real.clone()), - RangeOp::Not => match real { - Elem::Concrete(RangeConcrete { - val: Concrete::Bool(c), - loc, - }) => MaybeCollapsed::Collapsed(Elem::Concrete(RangeConcrete::new( - Concrete::from(!c), - loc, - ))), - _ => MaybeCollapsed::Not(real, Elem::Null), - }, - _ => MaybeCollapsed::Not(real, Elem::Null), + _ => MaybeCollapsed::Not(real, op, Elem::Null), }, - (l, r) => return MaybeCollapsed::Not(l, r), + (l, r) => return MaybeCollapsed::Not(l, op, r), }; + match res { + MaybeCollapsed::Not(ref l, op, ref r) => tracing::trace!("not result: {l} {op} {r}"), + MaybeCollapsed::Concretes(ref l, op, ref r) => { + tracing::trace!("concrete result: {l} {op} {r}") + } + MaybeCollapsed::Collapsed(ref l) => tracing::trace!("collapsed result: {l}"), + } + match res { MaybeCollapsed::Collapsed(Elem::Expr(e)) => collapse(*e.lhs, e.op, *e.rhs, arena), other => other, diff --git a/crates/graph/src/range/elem/expr/mod.rs b/crates/graph/src/range/elem/expr/mod.rs index 22519975..ec2570a8 100644 --- a/crates/graph/src/range/elem/expr/mod.rs +++ b/crates/graph/src/range/elem/expr/mod.rs @@ -33,24 +33,24 @@ impl std::fmt::Display for RangeExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.op { RangeOp::Min | RangeOp::Max => { - write!(f, "{}{{{}, {}}}", self.op.to_string(), self.lhs, self.rhs) + write!(f, "{}{{{}, {}}}", self.op, self.lhs, self.rhs) } RangeOp::Cast => match &*self.rhs { Elem::Concrete(RangeConcrete { val, .. }) => { write!( f, "{}({}, {})", - self.op.to_string(), + self.op, self.lhs, val.as_builtin().basic_as_string() ) } - _ => write!(f, "{}({}, {})", self.op.to_string(), self.lhs, self.rhs), + _ => write!(f, "{}({}, {})", self.op, self.lhs, self.rhs), }, RangeOp::BitNot => { write!(f, "~{}", self.lhs) } - _ => write!(f, "({} {} {})", self.lhs, self.op.to_string(), self.rhs), + _ => write!(f, "({} {} {})", self.lhs, self.op, self.rhs), } } } @@ -150,9 +150,7 @@ impl RangeExpr { arena: &mut RangeArena>, ) -> Option> { if let Some(idx) = self.arena_idx(arena) { - let Some(ref mut t) = arena.ranges.get_mut(idx) else { - return None; - }; + let t = arena.ranges.get_mut(idx)?; let Elem::Expr(ref mut arenaized) = *t else { return None; }; @@ -171,9 +169,7 @@ impl RangeExpr { arena: &mut RangeArena>, ) -> Option>> { if let Some(idx) = self.arena_idx(arena) { - let Some(ref mut t) = arena.ranges.get_mut(idx) else { - return None; - }; + let t = arena.ranges.get_mut(idx)?; let Elem::Expr(ref mut arenaized) = *t else { return None; }; @@ -438,19 +434,19 @@ impl RangeElem for RangeExpr { let r = self.rhs.simplify_maximize(analyzer, arena)?; let collapsed = collapse(l, self.op, r, arena); let res = match collapsed { - MaybeCollapsed::Concretes(l, r) => { - RangeExpr::new(l, self.op, r).exec_op(true, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + RangeExpr::new(l, op, r).exec_op(true, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(l, r) => { - let res = RangeExpr::new(l, self.op, r).simplify_exec_op(true, analyzer, arena)?; + MaybeCollapsed::Not(l, op, r) => { + let res = RangeExpr::new(l, op, r).simplify_exec_op(true, analyzer, arena)?; match res { Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { - MaybeCollapsed::Concretes(l, r) => { - RangeExpr::new(l, expr.op, r).exec_op(true, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + RangeExpr::new(l, op, r).exec_op(true, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(l, r) => Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))), + MaybeCollapsed::Not(l, op, r) => Ok(Elem::Expr(RangeExpr::new(l, op, r))), }, other => Ok(other), } @@ -481,19 +477,19 @@ impl RangeElem for RangeExpr { let collapsed = collapse(l, self.op, r, arena); let res = match collapsed { - MaybeCollapsed::Concretes(l, r) => { - RangeExpr::new(l, self.op, r).exec_op(false, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + RangeExpr::new(l, op, r).exec_op(false, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(l, r) => { - let res = RangeExpr::new(l, self.op, r).simplify_exec_op(false, analyzer, arena)?; + MaybeCollapsed::Not(l, op, r) => { + let res = RangeExpr::new(l, op, r).simplify_exec_op(false, analyzer, arena)?; match res { Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { - MaybeCollapsed::Concretes(l, r) => { - return RangeExpr::new(l, self.op, r).exec_op(false, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + return RangeExpr::new(l, op, r).exec_op(false, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => return Ok(collapsed), - MaybeCollapsed::Not(l, r) => Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))), + MaybeCollapsed::Not(l, op, r) => Ok(Elem::Expr(RangeExpr::new(l, op, r))), }, other => Ok(other), } @@ -538,20 +534,19 @@ impl RangeElem for RangeExpr { let r = simp_minimize(&mut this.rhs, analyzer, arena)?; let collapsed = collapse(l, this.op, r, arena); let res = match collapsed { - MaybeCollapsed::Concretes(l, r) => { - RangeExpr::new(l, this.op, r).exec_op(false, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + RangeExpr::new(l, op, r).exec_op(false, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(l, r) => { - let res = - RangeExpr::new(l, this.op, r).simplify_exec_op(false, analyzer, arena)?; + MaybeCollapsed::Not(l, op, r) => { + let res = RangeExpr::new(l, op, r).simplify_exec_op(false, analyzer, arena)?; let idx = arena.idx_or_upsert(res.clone(), analyzer); match res { Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { - MaybeCollapsed::Concretes(l, r) => { - let exec_res = RangeExpr::new(l, expr.op, r) - .exec_op(false, analyzer, arena)?; + MaybeCollapsed::Concretes(l, op, r) => { + let exec_res = + RangeExpr::new(l, op, r).exec_op(false, analyzer, arena)?; Elem::Arena(idx).set_arenaized_flattened(false, &exec_res, arena); Ok(exec_res) } @@ -559,8 +554,8 @@ impl RangeElem for RangeExpr { Elem::Arena(idx).set_arenaized_flattened(false, &collapsed, arena); Ok(collapsed) } - MaybeCollapsed::Not(l, r) => { - Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))) + MaybeCollapsed::Not(l, op, r) => { + Ok(Elem::Expr(RangeExpr::new(l, op, r))) } }, other => Ok(other), @@ -598,20 +593,19 @@ impl RangeElem for RangeExpr { let r = simp_maximize(&mut this.rhs, analyzer, arena)?; let collapsed = collapse(l, this.op, r, arena); let res = match collapsed { - MaybeCollapsed::Concretes(l, r) => { - RangeExpr::new(l, this.op, r).exec_op(true, analyzer, arena) + MaybeCollapsed::Concretes(l, op, r) => { + RangeExpr::new(l, op, r).exec_op(true, analyzer, arena) } MaybeCollapsed::Collapsed(collapsed) => Ok(collapsed), - MaybeCollapsed::Not(l, r) => { - let res = - RangeExpr::new(l, this.op, r).simplify_exec_op(true, analyzer, arena)?; + MaybeCollapsed::Not(l, op, r) => { + let res = RangeExpr::new(l, op, r).simplify_exec_op(true, analyzer, arena)?; let idx = arena.idx_or_upsert(res.clone(), analyzer); match res { Elem::Expr(expr) => match collapse(*expr.lhs, expr.op, *expr.rhs, arena) { - MaybeCollapsed::Concretes(l, r) => { + MaybeCollapsed::Concretes(l, op, r) => { let exec_res = - RangeExpr::new(l, expr.op, r).exec_op(true, analyzer, arena)?; + RangeExpr::new(l, op, r).exec_op(true, analyzer, arena)?; Elem::Arena(idx).set_arenaized_flattened(true, &exec_res, arena); Ok(exec_res) } @@ -619,8 +613,8 @@ impl RangeElem for RangeExpr { Elem::Arena(idx).set_arenaized_flattened(true, &collapsed, arena); Ok(collapsed) } - MaybeCollapsed::Not(l, r) => { - Ok(Elem::Expr(RangeExpr::new(l, expr.op, r))) + MaybeCollapsed::Not(l, op, r) => { + Ok(Elem::Expr(RangeExpr::new(l, op, r))) } }, other => Ok(other), diff --git a/crates/graph/src/range/elem/expr/simplify/mod.rs b/crates/graph/src/range/elem/expr/simplify/mod.rs index efd55aff..42dc2ff8 100644 --- a/crates/graph/src/range/elem/expr/simplify/mod.rs +++ b/crates/graph/src/range/elem/expr/simplify/mod.rs @@ -43,7 +43,7 @@ pub(crate) fn ident_rules( _ => None, } } - RangeOp::Exp => { + RangeOp::Exp(_) => { if matches!(r.range_ord(&zero, arena), Some(std::cmp::Ordering::Equal)) { Some(Elem::from(Concrete::from(U256::one()))) } else { diff --git a/crates/graph/src/range/elem/mod.rs b/crates/graph/src/range/elem/mod.rs index dd1c7401..a388b211 100644 --- a/crates/graph/src/range/elem/mod.rs +++ b/crates/graph/src/range/elem/mod.rs @@ -11,6 +11,9 @@ pub use elem_trait::*; pub use expr::*; pub use map_or_array::*; pub use reference::*; +use shared::FlatExpr; + +use std::fmt; #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub enum MinMaxed { @@ -63,8 +66,6 @@ pub enum RangeOp { Eq, /// Not Equal Neq, - /// Logical Not - Not, /// Bitwise shift left Shl, /// Bitwise shift right @@ -84,7 +85,7 @@ pub enum RangeOp { /// Bitwise Not BitNot, /// Exponentiation - Exp, + Exp(bool), /// Concatenation Concat, /// Memcopy @@ -97,6 +98,41 @@ pub enum RangeOp { SetLength, /// Get Length of a memory object GetLength, + /// Slice a memory object + Slice, +} + +impl TryFrom for RangeOp { + type Error = (); + fn try_from(flat: FlatExpr) -> Result { + use FlatExpr::*; + let res = match flat { + Power(_, unchecked) => RangeOp::Exp(unchecked), + Multiply(_, unchecked) => RangeOp::Mul(unchecked), + Add(_, unchecked) => RangeOp::Add(unchecked), + Subtract(_, unchecked) => RangeOp::Sub(unchecked), + Divide(_, unchecked) => RangeOp::Div(unchecked), + Modulo(_) => RangeOp::Mod, + AssignMultiply(_, unchecked) => RangeOp::Mul(unchecked), + AssignAdd(_, unchecked) => RangeOp::Add(unchecked), + AssignSubtract(_, unchecked) => RangeOp::Sub(unchecked), + AssignDivide(_, unchecked) => RangeOp::Div(unchecked), + AssignModulo(_) => RangeOp::Mod, + ShiftLeft(_) => RangeOp::Shl, + ShiftRight(_) => RangeOp::Shr, + AssignShiftLeft(_) => RangeOp::Shl, + AssignShiftRight(_) => RangeOp::Shr, + BitwiseAnd(_) => RangeOp::BitAnd, + AssignAnd(_) => RangeOp::BitAnd, + BitwiseXor(_) => RangeOp::BitXor, + AssignXor(_) => RangeOp::BitXor, + BitwiseOr(_) => RangeOp::BitOr, + AssignOr(_) => RangeOp::BitOr, + BitwiseNot(_) => RangeOp::BitNot, + _ => return Err(()), + }; + Ok(res) + } } impl RangeOp { @@ -108,7 +144,7 @@ impl RangeOp { Sub(_i) => false, Div(_i) => false, Mod => false, - Exp => false, + Exp(_i) => false, Min => true, Max => true, @@ -120,7 +156,6 @@ impl RangeOp { Gte => false, And => true, Or => true, - Not => false, BitNot => false, BitAnd => false, @@ -137,6 +172,7 @@ impl RangeOp { SetIndices => false, GetIndex => false, Concat => false, + Slice => false, } } @@ -200,42 +236,59 @@ impl RangeOp { }; Some(t) } + + pub fn require_rhs(self) -> Option { + use RangeOp::*; + let t = match self { + Eq => Eq, + Neq => Neq, + Lte => Gte, + Gte => Lte, + Gt => Lt, + Lt => Gt, + other => { + tracing::trace!("Require rhs other: {other:?}"); + return None; + } + }; + Some(t) + } } -impl ToString for RangeOp { - fn to_string(&self) -> String { +impl fmt::Display for RangeOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use RangeOp::*; match self { - Add(..) => "+".to_string(), - Mul(..) => "*".to_string(), - Sub(..) => "-".to_string(), - Div(..) => "/".to_string(), - Shl => "<<".to_string(), - Shr => ">>".to_string(), - Mod => "%".to_string(), - Exp => "**".to_string(), - Min => "min".to_string(), - Max => "max".to_string(), - Lt => "<".to_string(), - Gt => ">".to_string(), - Lte => "<=".to_string(), - Gte => ">=".to_string(), - Eq => "==".to_string(), - Neq => "!=".to_string(), - Not => "!".to_string(), - And => "&&".to_string(), - Or => "||".to_string(), - Cast => "cast".to_string(), - BitAnd => "&".to_string(), - BitOr => "|".to_string(), - BitXor => "^".to_string(), - BitNot => "~".to_string(), - Concat => "concat".to_string(), - Memcopy => "memcopy".to_string(), - SetIndices => "set_indices".to_string(), - GetIndex => "get_index".to_string(), - GetLength => "get_length".to_string(), - SetLength => "set_length".to_string(), + Add(..) => write!(f, "+"), + Mul(..) => write!(f, "*"), + Sub(..) => write!(f, "-"), + Div(..) => write!(f, "/"), + Shl => write!(f, "<<"), + Shr => write!(f, ">>"), + Mod => write!(f, "%"), + Exp(_) => write!(f, "**"), + Min => write!(f, "min"), + Max => write!(f, "max"), + Lt => write!(f, "<"), + Gt => write!(f, ">"), + Lte => write!(f, "<="), + Gte => write!(f, ">="), + Eq => write!(f, "=="), + Neq => write!(f, "!="), + And => write!(f, "&&"), + Or => write!(f, "||"), + Cast => write!(f, "cast"), + BitAnd => write!(f, "&"), + BitOr => write!(f, "|"), + BitXor => write!(f, "^"), + BitNot => write!(f, "~"), + Concat => write!(f, "concat"), + Memcopy => write!(f, "memcopy"), + SetIndices => write!(f, "set_indices"), + GetIndex => write!(f, "get_index"), + GetLength => write!(f, "get_length"), + SetLength => write!(f, "set_length"), + Slice => write!(f, "slice"), } } } diff --git a/crates/graph/src/range/elem/reference.rs b/crates/graph/src/range/elem/reference.rs index 530bfc32..51cc19f9 100644 --- a/crates/graph/src/range/elem/reference.rs +++ b/crates/graph/src/range/elem/reference.rs @@ -281,7 +281,6 @@ impl RangeElem for Reference { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { - tracing::trace!("reference maximize cache hit"); if let Some(MinMaxed::Maximized(cached)) = arenaized.maximized.clone() { return Ok(*cached); } @@ -320,7 +319,6 @@ impl RangeElem for Reference { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { if let Some(MinMaxed::Minimized(cached)) = arenaized.minimized.clone() { - tracing::trace!("reference minimize cache hit"); return Ok(*cached); } } diff --git a/crates/graph/src/range/exec/bitwise.rs b/crates/graph/src/range/exec/bitwise.rs index 3f74dfb5..94f461ad 100644 --- a/crates/graph/src/range/exec/bitwise.rs +++ b/crates/graph/src/range/exec/bitwise.rs @@ -681,7 +681,6 @@ pub fn exec_bit_not( #[cfg(test)] mod tests { use super::*; - use ethers_core::types::{I256, U256}; use solang_parser::pt::Loc; #[test] diff --git a/crates/graph/src/range/exec/cast.rs b/crates/graph/src/range/exec/cast.rs index 0c02d5a9..447a2936 100644 --- a/crates/graph/src/range/exec/cast.rs +++ b/crates/graph/src/range/exec/cast.rs @@ -106,6 +106,7 @@ impl RangeCast> for RangeDyn { Some(Elem::ConcreteDyn(self.clone())) } (Some(Elem::Reference(_)), None) => Some(Elem::ConcreteDyn(self.clone())), + (Some(Elem::ConcreteDyn(_)), None) => Some(Elem::ConcreteDyn(self.clone())), (None, Some(Elem::Reference(_))) => Some(Elem::ConcreteDyn(self.clone())), (None, None) => Some(Elem::ConcreteDyn(self.clone())), _ => None, diff --git a/crates/graph/src/range/exec/exec_op.rs b/crates/graph/src/range/exec/exec_op.rs index 1f89c625..30c21604 100644 --- a/crates/graph/src/range/exec/exec_op.rs +++ b/crates/graph/src/range/exec/exec_op.rs @@ -111,7 +111,7 @@ impl ExecOp for RangeExpr { tracing::trace!( "simplifying op: {} {} {}, lhs_min: {}, lhs_max: {}, rhs_min: {}, rhs_max: {}", self.lhs, - self.op.to_string(), + self.op, self.rhs, lhs_min, lhs_max, @@ -137,7 +137,11 @@ impl ExecOp for RangeExpr { if let Some(_idx) = self.arena_idx(arena) { self.set_arenaized_flattened(maximize, ret.clone()?, arena); } - ret + + let ret = ret?; + + tracing::trace!("result: {ret}"); + Ok(ret) } fn spread( @@ -227,7 +231,7 @@ impl ExecOp for RangeExpr { "executing {}: {} {} {}, lhs_min: {}, lhs_max: {}, rhs_min: {}, rhs_max: {}", if maximize { "maximum" } else { "minimum" }, self.lhs, - self.op.to_string(), + self.op, self.rhs, lhs_min, lhs_max, @@ -236,6 +240,9 @@ impl ExecOp for RangeExpr { ); let res = match self.op { + RangeOp::Slice => Some(exec_slice( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, analyzer, arena, + )), RangeOp::GetLength => exec_get_length(&lhs_min, &lhs_max, maximize, analyzer, arena), RangeOp::GetIndex => exec_get_index(&self.lhs, &self.rhs, maximize, analyzer, arena), RangeOp::SetLength => exec_set_length(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize), @@ -261,8 +268,8 @@ impl ExecOp for RangeExpr { RangeOp::Mod => exec_mod( &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, ), - RangeOp::Exp => exec_exp( - &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, + RangeOp::Exp(unchecked) => exec_exp( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, unchecked, analyzer, arena, ), RangeOp::Min => exec_min( &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, @@ -286,9 +293,6 @@ impl ExecOp for RangeExpr { RangeOp::Or => exec_or( &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, ), - RangeOp::Not => exec_not( - &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, - ), RangeOp::BitAnd => { exec_bit_and(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, arena) } diff --git a/crates/graph/src/range/exec/math_ops/add.rs b/crates/graph/src/range/exec/math_ops/add.rs index 980ead1f..ed0e4903 100644 --- a/crates/graph/src/range/exec/math_ops/add.rs +++ b/crates/graph/src/range/exec/math_ops/add.rs @@ -315,8 +315,6 @@ pub fn exec_add( mod tests { use super::*; use crate::DummyGraph; - use ethers_core::types::U256; - use solang_parser::pt::Loc; #[test] fn uint_uint() { diff --git a/crates/graph/src/range/exec/math_ops/div.rs b/crates/graph/src/range/exec/math_ops/div.rs index 00ed109d..26382677 100644 --- a/crates/graph/src/range/exec/math_ops/div.rs +++ b/crates/graph/src/range/exec/math_ops/div.rs @@ -319,7 +319,6 @@ pub fn exec_div( mod tests { use super::*; use crate::DummyGraph; - use solang_parser::pt::Loc; #[test] fn uint_uint() { diff --git a/crates/graph/src/range/exec/math_ops/exp.rs b/crates/graph/src/range/exec/math_ops/exp.rs index aad9faeb..f4d7aa66 100644 --- a/crates/graph/src/range/exec/math_ops/exp.rs +++ b/crates/graph/src/range/exec/math_ops/exp.rs @@ -80,6 +80,7 @@ pub fn exec_exp( rhs_min: &Elem, rhs_max: &Elem, maximize: bool, + _unchecked: bool, _analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Option> { @@ -111,7 +112,7 @@ pub fn exec_exp( mod tests { use super::*; use crate::DummyGraph; - use ethers_core::types::{I256, U256}; + use ethers_core::types::I256; use solang_parser::pt::Loc; #[test] @@ -185,13 +186,15 @@ mod tests { let rhs_min = rc_uint_sized(3).into(); let rhs_max = rc_uint_sized(200).into(); - let max_result = exec_exp(&lhs_min, &lhs_max, &rhs_min, &rhs_max, true, &g, &mut arena) - .unwrap() - .maybe_concrete() - .unwrap(); + let max_result = exec_exp( + &lhs_min, &lhs_max, &rhs_min, &rhs_max, true, false, &g, &mut arena, + ) + .unwrap() + .maybe_concrete() + .unwrap(); assert_eq!(max_result.val, Concrete::Uint(8, U256::from(255))); let min_result = exec_exp( - &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, &g, &mut arena, + &lhs_min, &lhs_max, &rhs_min, &rhs_max, false, false, &g, &mut arena, ) .unwrap() .maybe_concrete() diff --git a/crates/graph/src/range/exec/math_ops/mul.rs b/crates/graph/src/range/exec/math_ops/mul.rs index a1cb33a0..54b6595d 100644 --- a/crates/graph/src/range/exec/math_ops/mul.rs +++ b/crates/graph/src/range/exec/math_ops/mul.rs @@ -287,7 +287,6 @@ pub fn exec_mul( mod tests { use super::*; use crate::DummyGraph; - use ethers_core::types::U256; use solang_parser::pt::Loc; #[test] diff --git a/crates/graph/src/range/exec/math_ops/sub.rs b/crates/graph/src/range/exec/math_ops/sub.rs index 7030e3d2..9891a8ae 100644 --- a/crates/graph/src/range/exec/math_ops/sub.rs +++ b/crates/graph/src/range/exec/math_ops/sub.rs @@ -319,8 +319,6 @@ pub fn exec_sub( mod tests { use super::*; use crate::DummyGraph; - use ethers_core::types::U256; - use solang_parser::pt::Loc; #[test] fn uint_uint() { diff --git a/crates/graph/src/range/exec/mem_ops/mem_get.rs b/crates/graph/src/range/exec/mem_ops/mem_get.rs index 62d3c99e..c2133558 100644 --- a/crates/graph/src/range/exec/mem_ops/mem_get.rs +++ b/crates/graph/src/range/exec/mem_ops/mem_get.rs @@ -9,6 +9,8 @@ use shared::RangeArena; use ethers_core::types::U256; use solang_parser::pt::Loc; +use std::collections::BTreeMap; + impl RangeMemLen for RangeDyn { fn range_get_length(&self) -> Option> { Some(*self.len.clone()) @@ -68,6 +70,94 @@ impl RangeMemGet> for Elem { } } +pub fn exec_slice( + arr_min: &Elem, + arr_max: &Elem, + start: &Elem, + end: &Elem, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, +) -> Elem { + let mut kvs = Default::default(); + // slices are exclusive + let excl_end = end.clone() - Elem::from(Concrete::from(U256::from(1))); + fn match_key( + arr: &Elem, + start_idx: &Elem, + excl_end: &Elem, + analyzer: &impl GraphBackend, + arena: &mut RangeArena>, + kvs: &mut BTreeMap, Elem>, + ) { + match arr { + Elem::Arena(_) => { + let (d, idx) = arr.dearenaize(arena); + match_key(&d, start_idx, excl_end, analyzer, arena, kvs); + arr.rearenaize(d, idx, arena); + } + Elem::Reference(_) => { + if let Ok(min) = arr.minimize(analyzer, arena) { + match_key(&min, start_idx, excl_end, analyzer, arena, kvs); + } + + if let Ok(max) = arr.maximize(analyzer, arena) { + match_key(&max, start_idx, excl_end, analyzer, arena, kvs); + } + } + Elem::ConcreteDyn(d) => { + d.val.iter().for_each(|(k, (v, _op))| { + if let Ok(Some(true)) = + k.overlaps_dual(start_idx, excl_end, true, analyzer, arena) + { + let new_k = k.clone() - start_idx.clone(); + kvs.insert(new_k, v.clone()); + } + }); + } + Elem::Concrete(c) => { + if let Some(size) = c.val.maybe_array_size() { + let min = U256::zero(); + // Iterates through concrete indices to check if RHS contains the index + let mut curr = min; + while curr < size { + let as_rc = RangeConcrete::new(Concrete::from(curr), Loc::Implicit); + let as_elem = Elem::from(as_rc.clone()); + if let Ok(Some(true)) = + as_elem.overlaps_dual(start_idx, excl_end, true, analyzer, arena) + { + if let Some(val) = c.range_get_index(&as_rc) { + let new_k = Elem::from(Concrete::from(curr)) - start_idx.clone(); + kvs.insert(new_k, val.clone()); + } + } + curr += U256::from(1); + } + } + } + Elem::Expr(_) => { + if let Ok(min) = arr.minimize(analyzer, arena) { + match_key(&min, start_idx, excl_end, analyzer, arena, kvs); + } + + if let Ok(max) = arr.maximize(analyzer, arena) { + match_key(&max, start_idx, excl_end, analyzer, arena, kvs); + } + } + _ => {} + }; + } + + match_key(arr_min, start, &excl_end, analyzer, arena, &mut kvs); + match_key(arr_max, start, &excl_end, analyzer, arena, &mut kvs); + + let len = Elem::Expr(RangeExpr::new( + end.clone(), + RangeOp::Sub(false), + start.clone(), + )); + Elem::ConcreteDyn(RangeDyn::new(len, kvs, Loc::Implicit)) +} + /// Executes the `get_length` operation given the minimum and maximum of an element. It returns either the _minimum_ bound or _maximum_ bound /// of the operation. pub fn exec_get_length( @@ -202,9 +292,7 @@ pub fn exec_get_index( mod tests { use super::*; use crate::DummyGraph; - use ethers_core::types::U256; use pretty_assertions::assert_eq; - use solang_parser::pt::Loc; #[test] fn concrete_len() { diff --git a/crates/graph/src/range/exec/mem_ops/mem_set.rs b/crates/graph/src/range/exec/mem_ops/mem_set.rs index acc6ab72..eaf3245d 100644 --- a/crates/graph/src/range/exec/mem_ops/mem_set.rs +++ b/crates/graph/src/range/exec/mem_ops/mem_set.rs @@ -261,8 +261,6 @@ pub fn exec_set_indices( #[cfg(test)] mod tests { use super::*; - - use ethers_core::types::U256; use pretty_assertions::assert_eq; use solang_parser::pt::Loc; diff --git a/crates/graph/src/range/exec/mem_ops/mod.rs b/crates/graph/src/range/exec/mem_ops/mod.rs index 2b8c69f7..d90d2a12 100644 --- a/crates/graph/src/range/exec/mem_ops/mod.rs +++ b/crates/graph/src/range/exec/mem_ops/mod.rs @@ -4,6 +4,6 @@ mod mem_set; mod memcopy; pub use concat::exec_concat; -pub use mem_get::{exec_get_index, exec_get_length}; +pub use mem_get::{exec_get_index, exec_get_length, exec_slice}; pub use mem_set::{exec_set_indices, exec_set_length}; pub use memcopy::exec_memcopy; diff --git a/crates/graph/src/range/exec/mod.rs b/crates/graph/src/range/exec/mod.rs index a12a21e8..41b76549 100644 --- a/crates/graph/src/range/exec/mod.rs +++ b/crates/graph/src/range/exec/mod.rs @@ -19,11 +19,10 @@ mod math_ops; pub use math_ops::{exec_add, exec_div, exec_exp, exec_mod, exec_mul, exec_sub}; mod truthy_ops; -pub use truthy_ops::{ - exec_and, exec_eq_neq, exec_gt, exec_gte, exec_lt, exec_lte, exec_not, exec_or, -}; +pub use truthy_ops::{exec_and, exec_eq_neq, exec_gt, exec_gte, exec_lt, exec_lte, exec_or}; mod mem_ops; pub use mem_ops::{ exec_concat, exec_get_index, exec_get_length, exec_memcopy, exec_set_indices, exec_set_length, + exec_slice, }; diff --git a/crates/graph/src/range/exec/truthy_ops/logical.rs b/crates/graph/src/range/exec/truthy_ops/logical.rs index aa4796b7..3dc2ef49 100644 --- a/crates/graph/src/range/exec/truthy_ops/logical.rs +++ b/crates/graph/src/range/exec/truthy_ops/logical.rs @@ -115,31 +115,3 @@ pub fn exec_or( Some(candidates.remove(0)) } } - -pub fn exec_not( - lhs_min: &Elem, - lhs_max: &Elem, - rhs_min: &Elem, - rhs_max: &Elem, - maximize: bool, - _analyzer: &impl GraphBackend, - arena: &mut RangeArena>, -) -> Option> { - assert!(matches!(rhs_min, Elem::Null) && matches!(rhs_max, Elem::Null)); - let candidates = vec![lhs_min.range_not(), lhs_max.range_not()]; - let mut candidates = candidates.into_iter().flatten().collect::>(); - candidates.sort_by(|a, b| match a.range_ord(b, arena) { - Some(r) => r, - _ => std::cmp::Ordering::Less, - }); - - if candidates.is_empty() { - return None; - } - - if maximize { - Some(candidates.remove(candidates.len() - 1)) - } else { - Some(candidates.remove(0)) - } -} diff --git a/crates/graph/src/range/exec/truthy_ops/mod.rs b/crates/graph/src/range/exec/truthy_ops/mod.rs index 4a100732..3c4838c1 100644 --- a/crates/graph/src/range/exec/truthy_ops/mod.rs +++ b/crates/graph/src/range/exec/truthy_ops/mod.rs @@ -1,5 +1,5 @@ mod logical; mod ord; -pub use logical::{exec_and, exec_not, exec_or}; +pub use logical::{exec_and, exec_or}; pub use ord::{exec_eq_neq, exec_gt, exec_gte, exec_lt, exec_lte}; diff --git a/crates/graph/src/range/range_string.rs b/crates/graph/src/range/range_string.rs index d845012f..8d1553ae 100644 --- a/crates/graph/src/range/range_string.rs +++ b/crates/graph/src/range/range_string.rs @@ -248,7 +248,7 @@ impl ToRangeString for RangeExpr { if matches!(self.op, RangeOp::Min | RangeOp::Max) { RangeElemString::new( - format!("{}{{{}, {}}}", self.op.to_string(), lhs_str.s, rhs_str.s), + format!("{}{{{}, {}}}", self.op, lhs_str.s, rhs_str.s), lhs_str.loc, ) } else if matches!(self.op, RangeOp::Cast) { @@ -268,7 +268,7 @@ impl ToRangeString for RangeExpr { lhs_str.loc, ), _ => RangeElemString::new( - format!("{}({}, {})", self.op.to_string(), lhs_str.s, rhs_str.s), + format!("{}({}, {})", self.op, lhs_str.s, rhs_str.s), lhs_str.loc, ), } @@ -299,7 +299,7 @@ impl ToRangeString for RangeExpr { RangeElemString::new(format!("concat({}, {})", lhs_str.s, rhs_str.s), lhs_str.loc) } else { RangeElemString::new( - format!("{} {} {}", lhs_str.s, self.op.to_string(), rhs_str.s), + format!("{} {} {}", lhs_str.s, self.op, rhs_str.s), lhs_str.loc, ) } diff --git a/crates/graph/src/range/solc_range.rs b/crates/graph/src/range/solc_range.rs index 3ba41dcd..5d4156e6 100644 --- a/crates/graph/src/range/solc_range.rs +++ b/crates/graph/src/range/solc_range.rs @@ -365,7 +365,8 @@ impl SolcRange { RangeOp::Gte => &Self::gte_dyn, RangeOp::Eq => &Self::eq_dyn, RangeOp::Neq => &Self::neq_dyn, - RangeOp::Exp => &Self::exp_dyn, + RangeOp::Exp(false) => &Self::exp_dyn, + RangeOp::Exp(true) => &Self::wrapping_exp_dyn, RangeOp::BitAnd => &Self::bit_and_dyn, RangeOp::BitOr => &Self::bit_or_dyn, RangeOp::BitXor => &Self::bit_xor_dyn, @@ -421,6 +422,14 @@ impl SolcRange { ) } + pub fn wrapping_exp_dyn(self, other: ContextVarNode) -> Self { + Self::new( + self.min.wrapping_exp(Elem::from(other)), + self.max.wrapping_exp(Elem::from(other)), + self.exclusions, + ) + } + pub fn exp_dyn(self, other: ContextVarNode) -> Self { Self::new( self.min.pow(Elem::from(other)), diff --git a/crates/graph/src/solvers/atoms.rs b/crates/graph/src/solvers/atoms.rs index c414f885..b9b7cdfb 100644 --- a/crates/graph/src/solvers/atoms.rs +++ b/crates/graph/src/solvers/atoms.rs @@ -269,7 +269,8 @@ pub static LIA_OPS: &[RangeOp] = &[ RangeOp::Div(true), RangeOp::Div(false), RangeOp::Mod, - RangeOp::Exp, + RangeOp::Exp(false), + RangeOp::Exp(true), ]; pub trait Atomize { @@ -307,10 +308,11 @@ impl Atomize for Elem { Elem::Concrete(_) | Elem::Reference(_) => AtomOrPart::Part(self.clone()), Elem::ConcreteDyn(_) => AtomOrPart::Part(self.clone()), _e @ Elem::Expr(expr) => { - // println!("collapsing: {e}"); match collapse(*expr.lhs.clone(), expr.op, *expr.rhs.clone(), arena) { - MaybeCollapsed::Concretes(_l, _r) => { - let exec_res = expr.exec_op(true, analyzer, arena).unwrap(); + MaybeCollapsed::Concretes(l, op, r) => { + let exec_res = RangeExpr::new(l, op, r) + .exec_op(true, analyzer, arena) + .unwrap(); return exec_res.atoms_or_part(Some(self), analyzer, arena); } MaybeCollapsed::Collapsed(elem) => { @@ -324,7 +326,6 @@ impl Atomize for Elem { expr.rhs.atoms_or_part(Some(self), analyzer, arena), ) { (ref lp @ AtomOrPart::Part(ref l), ref rp @ AtomOrPart::Part(ref r)) => { - // println!("part part"); match (l, r) { (_, Elem::Arena(_)) => todo!(), (Elem::Arena(_), _) => todo!(), @@ -383,16 +384,12 @@ impl Atomize for Elem { } } (AtomOrPart::Atom(l_atom), r @ AtomOrPart::Part(_)) => { - // println!("atom part"); - AtomOrPart::Atom(l_atom.add_rhs(expr.op, r)) } (l @ AtomOrPart::Part(_), AtomOrPart::Atom(r_atom)) => { - // println!("part atom"); AtomOrPart::Atom(r_atom.add_lhs(expr.op, l)) } (AtomOrPart::Atom(l_atoms), AtomOrPart::Atom(r_atoms)) => { - // println!("atom atom"); AtomOrPart::Atom(r_atoms.add_lhs(expr.op, AtomOrPart::Atom(l_atoms))) } } @@ -410,14 +407,12 @@ impl Atomize for Elem { use Elem::*; tracing::trace!("atomize: {}", self); match self { - Reference(_) => None, //{ println!("was dyn"); None}, - Null => None, //{ println!("was null"); None}, - Concrete(_c) => None, //{ println!("was conc: {}", _c.val.as_human_string()); None }, - ConcreteDyn(_) => None, //{ println!("was concDyn"); None}, + Reference(_) => None, + Null => None, + Concrete(_c) => None, + ConcreteDyn(_) => None, Expr(_) => { - // println!("atomized: was expr"); let AtomOrPart::Atom(mut a) = self.atoms_or_part(None, analyzer, arena) else { - // println!("returning none"); return None; }; a.update_max_ty(); diff --git a/crates/graph/src/solvers/brute.rs b/crates/graph/src/solvers/brute.rs index 7bdb130a..e2a67f96 100644 --- a/crates/graph/src/solvers/brute.rs +++ b/crates/graph/src/solvers/brute.rs @@ -112,13 +112,6 @@ impl BruteBinSearchSolver { // Sometimes a storage variable will be split due to a context fork. We recombine them here atomic_idxs.sort(); atomic_idxs.dedup(); - // atomic_idxs.iter().for_each(|dep| { - // println!( - // "atomic dep: {} - {}", - // dep.display_name(analyzer).unwrap(), - // dep.0 - // ) - // }); // let atomics = atomic_idxs; let mut storage_atomics: BTreeMap> = BTreeMap::default(); let mut calldata_atomics = vec![]; @@ -199,7 +192,6 @@ impl BruteBinSearchSolver { let range = &self.atomic_ranges[atomic]; let mut min = range.evaled_range_min(analyzer, arena).unwrap(); min.cache_minimize(analyzer, arena).unwrap(); - // println!("min: {}", min.minimize(analyzer).unwrap().to_range_string(false, analyzer, arena).s); let mut max = range.evaled_range_max(analyzer, arena).unwrap(); max.cache_maximize(analyzer, arena).unwrap(); let mut mid = (min.clone() + max.clone()) / Elem::from(Concrete::from(U256::from(2))); @@ -265,7 +257,6 @@ impl BruteBinSearchSolver { analyzer: &mut impl GraphBackend, arena: &mut RangeArena>, ) -> bool { - // println!("lowering mid"); // move the high to high + mid / 2 // reset the mid let mut curr_lmr = self.lmrs[i].clone(); @@ -321,9 +312,6 @@ impl SolcSolver for BruteBinSearchSolver { .ranges .iter() .filter_map(|(_dep, range)| { - // println!("dep: {}", dep.display_name(analyzer).unwrap()); - - // println!("atom: {atom:#?}"); if let Some(atom) = range.min.atomize(analyzer, arena) { Some(atom) } else { @@ -397,7 +385,6 @@ impl SolcSolver for BruteBinSearchSolver { .collect() } } - // println!("solved for: {:#?}", atomic_solves); if atomic_solves.len() == self.atomics.len() { return Ok(AtomicSolveStatus::Sat(atomic_solves)); @@ -459,13 +446,11 @@ impl SolcSolver for BruteBinSearchSolver { }); }); new_range.cache_eval(analyzer, arena).unwrap(); - // println!("{}, original range: [{}, {}], new range: [{}, {}]", dep.display_name(analyzer).unwrap(), range.min, range.max, new_range.min_cached.clone().unwrap(), new_range.max_cached.clone().unwrap()); new_range.sat(analyzer, arena) }); if all_good { Ok(AtomicSolveStatus::Sat(mapping)) } else { - // println!("thought we solved but we didnt"); Ok(AtomicSolveStatus::Indeterminate) } } else { @@ -483,7 +468,6 @@ impl SolcSolver for BruteBinSearchSolver { analyzer: &mut impl AnalyzerBackend, arena: &mut RangeArena>, ) -> Result { - // println!("recurse check for: {}", self.atomics[i].idxs[0].display_name(analyzer).unwrap()); if i >= self.lmrs.len() { return Ok(false); } @@ -653,7 +637,6 @@ impl SolcSolver for BruteBinSearchSolver { analyzer: &mut impl GraphBackend, arena: &mut RangeArena>, ) -> Result<(bool, Option), GraphError> { - // println!("checking: {}, conc: {}, {}", this.atomics[solved_for_idx].idxs[0].display_name(analyzer).unwrap(), conc.maximize(analyzer, arena)?.to_range_string(true, analyzer, arena).s, conc.minimize(analyzer)?.to_range_string(false, analyzer, arena).s); solved_atomics.push(solved_for_idx); let mut new_ranges = BTreeMap::default(); this.intermediate_atomic_ranges.insert( @@ -686,7 +669,6 @@ impl SolcSolver for BruteBinSearchSolver { match dl_solver.solve_partial(analyzer, arena)? { SolveStatus::Unsat => { - // println!("TRUE UNSAT"); return Ok((false, None)); } SolveStatus::Sat { @@ -762,50 +744,18 @@ impl SolcSolver for BruteBinSearchSolver { SolcRange::new(val.clone().into(), val.into(), vec![]), ); }); - // println!("new solves: {atomic_solves:#?}"); for dep in this.deps.iter() { let range = this .intermediate_ranges .get(dep) .expect("No range for dep?"); - // if dep.display_name(analyzer).unwrap() == "(p2 < (61 * p3)) == true" { - // println!("range: {:#?}\n{:#?}", range.min, range.max); - // println!("simplified range: {:#?}\n{:#?}", range.min.simplify_minimize(&mut vec![], analyzer), range.max.simplify_maximize(&mut vec![], analyzer)); - // } - // println!("atomizing dep: {}", dep.display_name(analyzer).unwrap()); - // println!("min atomized: {:#?}, max atomized: {:#?}", range.min.simplify_minimize(&mut vec![], analyzer)?.atomize(), range.max.simplify_maximize(&mut vec![], analyzer)?.atomize()); if solved_dep.idxs.contains(dep) { - // println!("FOR SOLVED DEP"); continue; } // check that the concrete value doesn't break any let mut new_range = range.clone(); - // check if const now - // if let Some((Some(idx), const_ineq)) = new_range.min.maybe_const_inequality() { - // println!("min const ineq: {} for {}", const_ineq.maybe_concrete().unwrap().val.as_human_string(), ContextVarNode::from(idx).display_name(analyzer).unwrap()); - - // if let Some(position) = this.atomics.iter().position(|atomic| atomic.idxs.contains(&ContextVarNode::from(idx))) { - // // check and return) - // if !solved_atomics.contains(&position) { - // println!("inner min const ineq"); - // return check_for_lmr(this, position, &this.atomics[position].clone(), const_ineq, solved_atomics, analyzer); - // } - // } - - // } - // if let Some((Some(idx), const_ineq)) = new_range.max.maybe_const_inequality() { - // println!("max const ineq: {} for {} ({}), {:#?}", const_ineq.maybe_concrete().unwrap().val.as_human_string(), ContextVarNode::from(idx).display_name(analyzer).unwrap(), idx.index(), this.atomics); - // if let Some(position) = this.atomics.iter().position(|atomic| atomic.idxs.contains(&ContextVarNode::from(idx))) { - // // check and return - // if !solved_atomics.contains(&position) { - // println!("inner max const ineq"); - // return check_for_lmr(this, position, &this.atomics[position].clone(), const_ineq, solved_atomics, analyzer); - // } - // } - // } - // check if the new range is dependent on the solved variable let is_dependent_on_solved = new_range .dependent_on(analyzer, arena) @@ -818,17 +768,6 @@ impl SolcSolver for BruteBinSearchSolver { continue; } - // println!("new range for {} dependent_on: {:?}, replacing {:?}, is dependent on solved: {is_dependent_on_solved}", dep.display_name(analyzer).unwrap(), new_range.dependent_on(), solved_dep.idxs); - // println!("dep {}:\n\tinitial range: [{}, {}],\n\tcurr range: [{}, {}]", - // dep.display_name(analyzer).unwrap(), - // dep.evaled_range_min(analyzer, arena)?.unwrap().to_range_string(false, analyzer, arena).s, - // dep.evaled_range_max(analyzer, arena)?.unwrap().to_range_string(true, analyzer, arena).s, - // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, - // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, - // // new_range.range_min() - // ); - - // println!("dep {} range: {:#?} {:#?}", dep.display_name(analyzer).unwrap(), new_range.min, new_range.max); if new_range.unsat(analyzer, arena) { return Ok((false, None)); // panic!("initial range unsat???") @@ -842,43 +781,12 @@ impl SolcSolver for BruteBinSearchSolver { }); new_range.cache_eval(analyzer, arena)?; - // println!("new range: [{}, {}], [{}, {}]", - // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, - // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, - // new_range.min.to_range_string(false, analyzer, arena).s, - // new_range.max.to_range_string(true, analyzer, arena).s, - // ); if new_range.unsat(analyzer, arena) { // figure out *where* we need to increase or decrease // work on the unreplace range for now - let min_is_dependent = !range.min.dependent_on(analyzer, arena).is_empty(); - let max_is_dependent = !range.max.dependent_on(analyzer, arena).is_empty(); - - match (min_is_dependent, max_is_dependent) { - (true, true) => { - // both sides dependent - // println!("both"); - } - (false, true) => { - // just max is dependent - // println!("just max"); - } - (true, false) => { - // just min is dependent - // println!("just min"); - } - (false, false) => { - // panic!("this shouldnt happen"); - } - } - // println!("new unsat range: [{}, {}]", - // new_range.evaled_range_min(analyzer, arena)?.to_range_string(false, analyzer, arena).s, - // new_range.evaled_range_max(analyzer, arena)?.to_range_string(true, analyzer, arena).s, - // ); // compare new range to prev range to see if they moved down or up - // panic!("here"); let min_change = new_range .evaled_range_min(analyzer, arena)? .range_ord(&range.evaled_range_min(analyzer, arena)?, arena); @@ -891,28 +799,18 @@ impl SolcSolver for BruteBinSearchSolver { } (Some(std::cmp::Ordering::Greater), Some(std::cmp::Ordering::Less)) => { // we shrank our range, dont give a hint - // println!("None, dep isnt sat: {}, dep initial range: {}", dep.display_name(analyzer).unwrap(), dep.range_string(analyzer).unwrap().unwrap()); return Ok((false, None)); } (Some(std::cmp::Ordering::Greater), _) => { // both grew, try lowering - // println!("Lower, dep isnt sat: {}, dep initial range: {}", dep.display_name(analyzer).unwrap(), dep.range_string(analyzer).unwrap().unwrap()); return Ok((false, Some(HintOrRanges::Lower))); } (Some(std::cmp::Ordering::Less), _) => { // both grew, try lowering - // println!("Higher, dep isnt sat: {}, dep initial range: {}", dep.display_name(analyzer).unwrap(), dep.range_string(analyzer).unwrap().unwrap()); return Ok((false, Some(HintOrRanges::Higher))); } - // (Some(std::cmp::Ordering::Equal), _) => { - // panic!("here"); - // } - // (_, Some(std::cmp::Ordering::Equal)) => { - // panic!("here"); - // } _ => { - // println!("None empty, dep isnt sat: {}, dep initial range: {}", dep.display_name(analyzer).unwrap(), dep.range_string(analyzer).unwrap().unwrap()); return Ok((false, None)); } } diff --git a/crates/graph/src/var_type.rs b/crates/graph/src/var_type.rs index 72ed4c8d..37487e97 100644 --- a/crates/graph/src/var_type.rs +++ b/crates/graph/src/var_type.rs @@ -248,7 +248,8 @@ impl VarType { | Node::Entry | Node::Context(..) | Node::Msg(_) - | Node::Block(_) => None, + | Node::Block(_) + | Node::YulFunction(..) => None, } } @@ -259,6 +260,55 @@ impl VarType { } } + pub fn implicitly_castable_to( + &self, + other: &Self, + analyzer: &impl GraphBackend, + from_lit: bool, + ) -> Result { + if self.ty_idx() == other.ty_idx() { + return Ok(true); + } + + let res = match (self, other) { + (Self::BuiltIn(from_bn, _), Self::BuiltIn(to_bn, _)) => { + from_bn.implicitly_castable_to(to_bn, analyzer) + } + (Self::Concrete(from), Self::BuiltIn(to, _)) => { + let from = from.underlying(analyzer)?.as_builtin(); + let to = to.underlying(analyzer)?; + Ok(from.implicitly_castable_to(to)) + } + (Self::BuiltIn(from, _), Self::Concrete(to)) => { + let from = from.underlying(analyzer)?; + let to = to.underlying(analyzer)?.as_builtin(); + Ok(from.implicitly_castable_to(&to)) + } + (Self::Concrete(from), Self::Concrete(to)) => { + let from = from.underlying(analyzer)?.as_builtin(); + let to = to.underlying(analyzer)?.as_builtin(); + Ok(from.implicitly_castable_to(&to)) + } + _ => Ok(false), + }; + + let impl_cast = res?; + if !impl_cast && from_lit { + match (self, other) { + (Self::Concrete(from), Self::BuiltIn(to, _)) => { + let froms = from.underlying(analyzer)?.alt_lit_builtins(); + let to = to.underlying(analyzer)?; + + // exact matches only (i.e. uint160 -> address, uint8 -> bytes1, etc) + Ok(froms.iter().any(|from| from == to)) + } + _ => Ok(impl_cast), + } + } else { + Ok(impl_cast) + } + } + pub fn try_cast( self, other: &Self, @@ -286,7 +336,7 @@ impl VarType { } } (Self::BuiltIn(from_bn, sr), Self::BuiltIn(to_bn, _)) => { - if from_bn.implicitly_castable_to(to_bn, analyzer)? { + if from_bn.castable_to(to_bn, analyzer)? { Ok(Some(Self::BuiltIn(*to_bn, sr))) } else { Ok(None) @@ -296,7 +346,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let b = to_bn.underlying(analyzer)?; if let Some(casted) = c.cast(b.clone()) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -306,7 +356,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let to_c = to_c.underlying(analyzer)?; if let Some(casted) = c.cast_from(to_c) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -334,7 +384,7 @@ impl VarType { let Some(conc) = min.val.cast(builtin.clone()) else { return Ok(None); }; - let conc_idx = analyzer.add_node(Node::Concrete(conc)); + let conc_idx = analyzer.add_node(conc); Ok(Some(conc_idx)) } else { Ok(None) @@ -370,7 +420,7 @@ impl VarType { } } (Self::BuiltIn(from_bn, sr), Self::BuiltIn(to_bn, _)) => { - if from_bn.implicitly_castable_to(to_bn, analyzer)? { + if from_bn.castable_to(to_bn, analyzer)? { Ok(Some(Self::BuiltIn(*to_bn, sr))) } else { Ok(None) @@ -380,7 +430,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let b = to_bn.underlying(analyzer)?; if let Some(casted) = c.literal_cast(b.clone()) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -390,7 +440,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let to_c = to_c.underlying(analyzer)?; if let Some(casted) = c.literal_cast_from(to_c) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -400,21 +450,18 @@ impl VarType { } } - pub fn implicitly_castable_to( + pub fn castable_to( &self, other: &Self, analyzer: &impl GraphBackend, ) -> Result { match (self, other) { (Self::BuiltIn(from_bn, _), Self::BuiltIn(to_bn, _)) => { - from_bn.implicitly_castable_to(to_bn, analyzer) + from_bn.castable_to(to_bn, analyzer) } (Self::Concrete(from_c), Self::BuiltIn(to_bn, _)) => { let to = to_bn.underlying(analyzer)?; - Ok(from_c - .underlying(analyzer)? - .as_builtin() - .implicitly_castable_to(to)) + Ok(from_c.underlying(analyzer)?.as_builtin().castable_to(to)) } _ => Ok(false), } @@ -588,7 +635,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = val.0[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -616,7 +663,7 @@ impl VarType { // if let Some(idx) = val.0.node_idx() { // return Ok(idx.into()); // } else if let Some(c) = val.0.concrete() { - // let cnode = analyzer.add_node(Node::Concrete(c)); + // let cnode = analyzer.add_node(c); // return Ok(cnode.into()); // } // } @@ -640,7 +687,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = val.0[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -649,7 +696,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = elems[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -658,7 +705,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = st.as_bytes()[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -748,18 +795,13 @@ impl VarType { } (VarType::BuiltIn(s, _), VarType::BuiltIn(o, _)) => { match (s.underlying(analyzer)?, o.underlying(analyzer)?) { - (Builtin::Array(l), Builtin::Array(r)) => Ok(l - .unresolved_as_resolved(analyzer)? - == r.unresolved_as_resolved(analyzer)?), - (Builtin::SizedArray(l_size, l), Builtin::SizedArray(r_size, r)) => Ok(l - .unresolved_as_resolved(analyzer)? - == r.unresolved_as_resolved(analyzer)? - && l_size == r_size), - (Builtin::Mapping(lk, lv), Builtin::Mapping(rk, rv)) => Ok(lk - .unresolved_as_resolved(analyzer)? - == rk.unresolved_as_resolved(analyzer)? - && lv.unresolved_as_resolved(analyzer)? - == rv.unresolved_as_resolved(analyzer)?), + (Builtin::Array(l), Builtin::Array(r)) => l.ty_eq(r, analyzer), + (Builtin::SizedArray(l_size, l), Builtin::SizedArray(r_size, r)) => { + Ok(l.ty_eq(r, analyzer)? && l_size == r_size) + } + (Builtin::Mapping(lk, lv), Builtin::Mapping(rk, rv)) => { + Ok(lk.ty_eq(rk, analyzer)? && lv.ty_eq(rv, analyzer)?) + } (l, r) => Ok(l == r), } } diff --git a/crates/pyrometer/src/analyzer.rs b/crates/pyrometer/src/analyzer.rs index a1017491..6aeda249 100644 --- a/crates/pyrometer/src/analyzer.rs +++ b/crates/pyrometer/src/analyzer.rs @@ -5,8 +5,8 @@ use graph::{nodes::*, ContextEdge, Edge, Node, VarType}; use reqwest::Client; use serde::{Deserialize, Serialize}; use shared::{AnalyzerLike, ApplyStats, GraphLike, NodeIdx, Search}; -use shared::{ExprErr, IntoExprErr, RangeArena, USE_DEBUG_SITE}; -use solc_expressions::StatementParser; +use shared::{ExprErr, ExprFlag, FlatExpr, IntoExprErr, RangeArena, USE_DEBUG_SITE}; +use solc_expressions::Flatten; use tokio::runtime::Runtime; use tracing::{error, trace, warn}; @@ -146,6 +146,11 @@ pub struct Analyzer { pub handled_funcs: Vec, /// Target Context to debug pub minimize_debug: Option, + + pub flattened: Vec, + pub expr_flag: Option, + pub current_asm_block: usize, + pub debug_stack: bool, } impl Default for Analyzer { @@ -184,13 +189,17 @@ impl Default for Analyzer { }, handled_funcs: Vec::default(), minimize_debug: None, + flattened: vec![], + expr_flag: None, + current_asm_block: 0, + debug_stack: false, }; a.builtin_fn_inputs = builtin_fns::builtin_fns_inputs(&mut a); let msg = Msg::default(); let block = Block::default(); - let msg = a.graph.add_node(Node::Msg(msg)).into(); - let block = a.graph.add_node(Node::Block(block)).into(); + let msg = a.graph.add_node(Node::Msg(Box::new(msg))).into(); + let block = a.graph.add_node(Node::Block(Box::new(block))).into(); a.msg = msg; a.block = block; a.entry = a.add_node(Node::Entry); @@ -359,7 +368,7 @@ impl Analyzer { expr: &Expression, parent: Option, ) -> Option { - tracing::trace!("Parsing required compile-time evaluation"); + tracing::trace!("Parsing required compile-time evaluation: {expr:?}, {parent:?}"); let ctx = if let Some(parent) = parent { let pf = Function { @@ -384,7 +393,8 @@ impl Analyzer { }; let full_stmt = solang_parser::pt::Statement::Return(expr.loc(), Some(expr.clone())); - self.parse_ctx_statement(arena, &full_stmt, false, Some(ctx)); + self.traverse_statement(&full_stmt, None); + self.interpret(ctx, full_stmt.loc(), arena); let edges = self.add_if_err(ctx.successful_edges(self).into_expr_err(expr.loc()))?; if edges.len() == 1 { let res = edges[0].return_nodes(self).into_expr_err(expr.loc()); @@ -573,69 +583,13 @@ impl Analyzer { }); elems.into_iter().for_each(|final_pass_item| { - // final_pass_item - // .funcs - // .iter() - // .for_each(|func| self.analyze_fn_calls(*func)); - // let mut func_mapping = BTreeMap::default(); - // let mut call_dep_graph: StableGraph = StableGraph::default(); - // let fn_calls_fns = std::mem::take(&mut self.fn_calls_fns); - // fn_calls_fns.iter().for_each(|(func, calls)| { - // if !calls.is_empty() { - // let func_idx = if let Some(idx) = func_mapping.get(func) { - // *idx - // } else { - // let idx = call_dep_graph.add_node(*func); - // func_mapping.insert(func, idx); - // idx - // }; - - // calls.iter().for_each(|call| { - // let call_idx = if let Some(idx) = func_mapping.get(call) { - // *idx - // } else { - // let idx = call_dep_graph.add_node(*call); - // func_mapping.insert(call, idx); - // idx - // }; - - // call_dep_graph.add_edge(func_idx, call_idx, 0); - // }); - // } else { - // self.handled_funcs.push(*func); - // if let Some(body) = &func.underlying(self).unwrap().body.clone() { - // self.parse_ctx_statement(arena, body, false, Some(*func)); - // } - // } - // }); - - // let mut res = petgraph::algo::toposort(&call_dep_graph, None); - // while let Err(cycle) = res { - // call_dep_graph.remove_node(cycle.node_id()); - // res = petgraph::algo::toposort(&call_dep_graph, None); - // } - - // let indices = res.unwrap(); - - // indices.iter().for_each(|idx| { - // let func = call_dep_graph.node_weight(*idx).unwrap(); - // if !self.handled_funcs.contains(func) { - // self.handled_funcs.push(*func); - // if let Some(body) = &func.underlying(self).unwrap().body.clone() { - // self.parse_ctx_statement(arena, body, false, Some(*func)); - // } - // } - // }); - final_pass_item.funcs.into_iter().for_each(|func| { - if !self.handled_funcs.contains(&func) { - if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(arena, body, false, Some(func)); - } + if !self.handled_funcs.contains(&func) + && func.underlying(self).unwrap().body.is_some() + { + self.interpret_entry_func(func, arena); } }); - - // self.fn_calls_fns = fn_calls_fns; }); } @@ -1095,8 +1049,8 @@ impl Analyzer { node.into() }; - inherits.iter().for_each(|contract_node| { - self.add_edge(*contract_node, con_node, Edge::InheritedContract); + inherits.into_iter().flatten().for_each(|contract_node| { + self.add_edge(contract_node, con_node, Edge::InheritedContract); }); let mut usings = vec![]; @@ -1194,7 +1148,6 @@ impl Analyzer { }) .copied() .collect(); - if matches!(self.node(scope_node), Node::Contract(_)) { self.add_edge( scope_node, @@ -1460,7 +1413,7 @@ impl Analyzer { FunctionNode::from(node) } FunctionTy::Function => { - let fn_node = self.add_node(func); + let fn_node = self.add_node(func.clone()); if let Some(con_node) = con_node { self.add_edge(fn_node, con_node, Edge::Func); } diff --git a/crates/pyrometer/src/analyzer_backend.rs b/crates/pyrometer/src/analyzer_backend.rs index 1562477c..7249e37a 100644 --- a/crates/pyrometer/src/analyzer_backend.rs +++ b/crates/pyrometer/src/analyzer_backend.rs @@ -9,8 +9,8 @@ use graph::{ AnalyzerBackend, Edge, GraphBackend, Node, RepresentationInvariant, TypeNode, VarType, }; use shared::{ - AnalyzerLike, ApplyStats, ExprErr, GraphError, GraphLike, IntoExprErr, NodeIdx, RangeArena, - RepresentationErr, + AnalyzerLike, ApplyStats, ExprErr, FlatExpr, GraphError, GraphLike, IntoExprErr, NodeIdx, + RangeArena, RepresentationErr, }; use ahash::AHashMap; @@ -30,9 +30,9 @@ impl AnalyzerBackend for Analyzer { concrete: Concrete, loc: Loc, ) -> Result { - let cnode = self.add_node(Node::Concrete(concrete)); + let cnode = self.add_node(concrete); let var = ContextVar::new_from_concrete(loc, ctx, cnode.into(), self); - let cnode = self.add_node(Node::ContextVar(var.into_expr_err(loc)?)); + let cnode = self.add_node(var.into_expr_err(loc)?); Ok(cnode.into()) } } @@ -82,14 +82,6 @@ impl AnalyzerLike for Analyzer { needed_functions.sort_by(|a, b| a.0.cmp(&b.0)); needed_functions.dedup(); - println!( - "needed functions: {:#?}", - needed_functions - .iter() - .map(|i| i.name(self).unwrap()) - .collect::>() - ); - fn recurse_find( contract: ContractNode, target_contract: ContractNode, @@ -123,7 +115,6 @@ impl AnalyzerLike for Analyzer { let mut structs = vec![]; let mut errs = vec![]; needed_functions.into_iter().for_each(|func| { - println!("iterating with func: {}", func.name(self).unwrap()); let maybe_func_contract = func.maybe_associated_contract(self); let reqs = func.reconstruction_requirements(self); reqs.usertypes.iter().for_each(|var| { @@ -174,8 +165,6 @@ impl AnalyzerLike for Analyzer { entry.push((func, reqs)); }); - // println!("{:#?}", contract_to_funcs); - let contracts = contract_to_funcs.keys().collect::>(); let contract_str = contracts .iter() @@ -243,9 +232,7 @@ impl AnalyzerLike for Analyzer { fn add_expr_err(&mut self, err: ExprErr) { if self.debug_panic() { - println!("here1"); if let Some(path) = self.minimize_debug().clone() { - println!("here2"); let reconstruction_edge: ContextNode = self .graph .node_indices() @@ -253,8 +240,6 @@ impl AnalyzerLike for Analyzer { Node::Context(context) if context.killed.is_some() => { match context.killed.unwrap() { (_, KilledKind::ParseError) => { - println!("here3"); - // println!("found context: {}", context.path); let edges = graph::nodes::ContextNode::from(node) .all_edges(self) .unwrap(); @@ -267,17 +252,14 @@ impl AnalyzerLike for Analyzer { Some(reconstruction_edge) } - e => None, + _e => None, } } _ => None, }) .unwrap(); - println!("here5"); let min_str = self.minimize_err(reconstruction_edge); - println!("here6: {min_str}"); - // println!("reconstructed source:\n{} placed in {}", min_str, path); let mut file = std::fs::OpenOptions::new() .write(true) @@ -361,7 +343,6 @@ impl AnalyzerLike for Analyzer { } } Variable(ident) => { - // println!("variable ident: {}", ident.name); if let Some(idxs) = self.user_types.get(&ident.name) { if idxs.len() == 1 { idxs[0] @@ -436,7 +417,7 @@ impl AnalyzerLike for Analyzer { int }; - self.add_node(Node::Concrete(Concrete::Uint(256, val))) + self.add_node(Concrete::Uint(256, val)) } _ => { if let Some(idx) = self.complicated_parse(arena, expr, parent) { @@ -485,6 +466,11 @@ impl AnalyzerLike for Analyzer { self.add_edge(func_node, self.entry(), Edge::Func); + let underlying_mut = FunctionNode::from(func_node).underlying_mut(self).unwrap(); + let name = underlying_mut.name.as_mut().unwrap(); + let full_name = format!("{}({})", name, params_strs.join(", ")); + name.name.clone_from(&full_name); + self.builtin_fn_nodes_mut() .insert(builtin_name.to_string(), func_node); Some(func_node) @@ -556,4 +542,33 @@ impl AnalyzerLike for Analyzer { } Ok(res) } + + type FlatExpr = FlatExpr; + + fn push_expr(&mut self, flat: FlatExpr) { + self.flattened.push(flat); + } + + fn decrement_asm_block(&mut self) { + self.current_asm_block -= 1; + } + fn increment_asm_block(&mut self) { + self.current_asm_block += 1; + } + + fn current_asm_block(&self) -> usize { + self.current_asm_block + } + + fn expr_stack(&self) -> &[FlatExpr] { + &self.flattened + } + + fn expr_stack_mut(&mut self) -> &mut Vec { + &mut self.flattened + } + + fn debug_stack(&self) -> bool { + self.debug_stack + } } diff --git a/crates/pyrometer/src/builtin_fns.rs b/crates/pyrometer/src/builtin_fns.rs index 1c3caeaa..519cc300 100644 --- a/crates/pyrometer/src/builtin_fns.rs +++ b/crates/pyrometer/src/builtin_fns.rs @@ -317,6 +317,15 @@ pub fn builtin_fns() -> AHashMap { Loc::Builtin, )))], ), + builtin_fn!( + name: Some(Identifier { + loc: Loc::Builtin, + name: "codehash".to_string(), + }), + attributes: vec![FunctionAttribute::Visibility(Visibility::External(Some( + Loc::Builtin, + )))], + ), ]; funcs .into_iter() diff --git a/crates/pyrometer/src/graph_backend.rs b/crates/pyrometer/src/graph_backend.rs index 55d65308..d9e79fa1 100644 --- a/crates/pyrometer/src/graph_backend.rs +++ b/crates/pyrometer/src/graph_backend.rs @@ -17,7 +17,6 @@ use tokio::runtime::Runtime; use tracing::{error, trace, warn}; use petgraph::{dot::Dot, graph::EdgeIndex, visit::EdgeRef, Directed, Direction, Graph}; -use std::convert::TryFrom; use std::{ collections::BTreeSet, sync::{Arc, Mutex}, @@ -170,7 +169,6 @@ impl TryFrom<&RangeArena>> for Elems { for elem in &arena.ranges { // Get the map value if let Some(map_value) = arena.map.get(elem).copied() { - // println!("Adding idx {} to elems {}", map_value, elem); inner.push((map_value, elem.clone())); } else { // println!("NONE REF elem: {:?}", elem); @@ -212,11 +210,6 @@ impl TryFrom<&RangeArena>> for Elems { // dedup is needed as there are duplicate indices in the inner vec. TODO @brock is this a bug? arena has duplicate elems inner.dedup(); - // Print out elems - // for (idx, elem) in inner.iter() { - // println!("elem {}: {}", idx, elem); - // } - Ok(Elems { inner }) } } @@ -305,7 +298,6 @@ impl Elems { let lhs_arena = match *range_expr.lhs.clone() { Elem::Arena(lhs) => Some(lhs), Elem::Reference(_lhs) => { - // println!("LHS is a reference: {}", range_expr.lhs); // attempt to add in the ContextVar node that the elem is referencing let context_var_nodes = elem .dependent_on(graph_backend, arena) @@ -333,12 +325,8 @@ impl Elems { _ => None, }; let rhs_arena = match *range_expr.rhs.clone() { - Elem::Arena(rhs) => { - // println!("RHS is an arena index: {}", range_expr.rhs); - Some(rhs) - } + Elem::Arena(rhs) => Some(rhs), Elem::Reference(_rhs) => { - // println!("RHS is a reference: {}", range_expr.rhs); // attempt to add in the ContextVar node that the elem is referencing let context_var_nodes = elem .dependent_on(graph_backend, arena) @@ -399,16 +387,12 @@ impl Elems { // THIRD PASS - iterate over ContextVarNodes // iterate over the dependency map and add edges between the ContextVar nodes and the arena nodes - // println!("dependency map: {:?}", dependency_map); for (cvar_node, &node_idx) in dependency_map.iter() { - // println!("cvar node: {:?}, node idx: {:?}", cvar_node, node_idx); // Find the appropriate arena_idx for range.min and range.max using Elems.inner if let Ok(Some(range_min)) = cvar_node.range_min(graph_backend) { - // println!(" range min: {:?}", range_min); match range_min { Elem::Arena(arena_idx) => { // Make a direct edge to the arena node - // println!(" arena idx: {}", arena_idx); if let Some(&min_node_idx) = arena_idx_to_node_idx.get(&arena_idx) { graph.add_edge(node_idx, min_node_idx, ArenaEdge::MIN); } @@ -422,9 +406,7 @@ impl Elems { .map(|(idx, _)| *idx); // Add edges to the min arena indices if let Some(min_idx) = min_arena_idx { - // println!(" min idx: {:?}", min_idx); if let Some(&min_node_idx) = arena_idx_to_node_idx.get(&min_idx) { - // println!(" min node idx: {:?}", min_node_idx); graph.add_edge(node_idx, min_node_idx, ArenaEdge::MIN); } } @@ -433,11 +415,9 @@ impl Elems { } if let Ok(Some(range_max)) = cvar_node.range_max(graph_backend) { - // println!(" range max: {:?}", range_max); match range_max { Elem::Arena(arena_idx) => { // Make a direct edge to the arena node - // println!(" arena idx: {}", arena_idx); if let Some(&max_node_idx) = arena_idx_to_node_idx.get(&arena_idx) { graph.add_edge(node_idx, max_node_idx, ArenaEdge::MAX); } @@ -451,9 +431,7 @@ impl Elems { .map(|(idx, _)| *idx); // Add edges to the min arena indices if let Some(max_idx) = max_arena_idx { - // println!(" max idx: {:?}", max_idx); if let Some(&max_node_idx) = arena_idx_to_node_idx.get(&max_idx) { - // println!(" max node idx: {:?}", max_node_idx); graph.add_edge(node_idx, max_node_idx, ArenaEdge::MAX); } } @@ -919,7 +897,7 @@ impl GraphDot for Analyzer { } } - fn dot_str(&self, arena: &mut RangeArena>) -> String + fn dot_str(&self, arena: &mut RangeArena<::RangeElem>) -> String where Self: std::marker::Sized, Self: AnalyzerBackend, @@ -1006,7 +984,7 @@ impl GraphDot for Analyzer { dot_str.join("\n") } - fn dot_str_no_tmps(&self, arena: &mut RangeArena>) -> String + fn dot_str_no_tmps(&self, arena: &mut RangeArena<::RangeElem>) -> String where Self: std::marker::Sized, Self: GraphLike + AnalyzerBackend, @@ -1080,7 +1058,7 @@ impl GraphDot for Analyzer { dot_str.join("\n") } - fn mermaid_str(&self, arena: &mut RangeArena>) -> String + fn mermaid_str(&self, arena: &mut RangeArena<::RangeElem>) -> String where Self: std::marker::Sized, Self: AnalyzerBackend, diff --git a/crates/pyrometer/tests/helpers.rs b/crates/pyrometer/tests/helpers.rs index 02f28c97..44fa10a5 100644 --- a/crates/pyrometer/tests/helpers.rs +++ b/crates/pyrometer/tests/helpers.rs @@ -2,6 +2,7 @@ use analyzers::FunctionVarsBoundAnalyzer; use analyzers::ReportConfig; use analyzers::ReportDisplay; use ariadne::sources; +use graph::nodes::KilledKind; use graph::{ elem::Elem, nodes::{Concrete, FunctionNode}, @@ -29,6 +30,15 @@ pub fn assert_no_parse_errors(path_str: String) { ); } +pub fn assert_has_parse_or_panic_errors(path_str: String) { + let panics = std::panic::catch_unwind(|| assert_no_parse_errors(path_str.clone())).is_err(); + assert!( + panics, + "Supposedly broken file did not encounter parse errors or panic in {}", + path_str + ); +} + pub fn assert_no_ctx_killed(path_str: String, sol: &str) { let mut analyzer = Analyzer::default(); let mut arena_base = Default::default(); @@ -91,21 +101,31 @@ pub fn no_ctx_killed( let funcs = analyzer.search_children(entry, &Edge::Func); for func in funcs.into_iter() { if let Some(ctx) = FunctionNode::from(func).maybe_body_ctx(&mut analyzer) { - if ctx.killed_loc(&analyzer).unwrap().is_some() { - analyzer - .bounds_for_all(arena, &file_mapping, ctx, config) - .as_cli_compat(&file_mapping) - .print_reports(&mut source_map, &analyzer, arena); - panic!("Killed context in test"); - } - ctx.all_edges(&analyzer).unwrap().iter().for_each(|subctx| { - if subctx.killed_loc(&analyzer).unwrap().is_some() { + let maybe_killed = ctx.killed_loc(&analyzer).unwrap(); + match maybe_killed { + Some((_, KilledKind::Ended)) => {} + Some(..) => { analyzer - .bounds_for_all(arena, &file_mapping, *subctx, config) + .bounds_for_all(arena, &file_mapping, ctx, config) .as_cli_compat(&file_mapping) .print_reports(&mut source_map, &analyzer, arena); panic!("Killed context in test"); } + _ => {} + } + ctx.all_edges(&analyzer).unwrap().iter().for_each(|subctx| { + let maybe_killed = subctx.killed_loc(&analyzer).unwrap(); + match maybe_killed { + Some((_, KilledKind::Ended)) => {} + Some(..) => { + analyzer + .bounds_for_all(arena, &file_mapping, ctx, config) + .as_cli_compat(&file_mapping) + .print_reports(&mut source_map, &analyzer, arena); + panic!("Killed context in test"); + } + _ => {} + } }); } } diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index b56de34f..5e88f4fa 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -2,6 +2,14 @@ use std::env; mod helpers; use helpers::*; +#[test] +fn test_assign() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/assign.sol"); + let sol = include_str!("./test_data/assign.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_bitwise() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -10,6 +18,14 @@ fn test_bitwise() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_binop() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/bin_op.sol"); + let sol = include_str!("./test_data/bin_op.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_cast() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -18,6 +34,21 @@ fn test_cast() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_condop() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/cond_op.sol"); + assert_no_parse_errors(path_str); +} + +#[test] +fn test_delete() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/delete.sol"); + let sol = include_str!("./test_data/delete.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_dyn_types() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -42,6 +73,14 @@ fn test_function_calls() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_literals() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/literals.sol"); + let sol = include_str!("./test_data/literals.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_logical() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -82,6 +121,13 @@ fn test_require() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_require_with_killed() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/require_with_killed.sol"); + assert_no_parse_errors(path_str); +} + #[test] fn test_using() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -184,3 +230,32 @@ fn test_repros() { assert_no_parse_errors(path_str); } } + +#[test] +fn test_variable() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/variable.sol"); + let sol = include_str!("./test_data/variable.sol"); + assert_no_ctx_killed(path_str, sol); +} + +#[test] +fn test_broken() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/broken/"); + let paths = std::fs::read_dir(path_str).unwrap(); + for path in paths { + let path_str = path.unwrap().path().display().to_string(); + println!("checking parse errors in: {path_str}"); + assert_has_parse_or_panic_errors(path_str); + } +} + +#[test] +#[should_panic] +fn test_todo() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/todo.sol"); + let sol = include_str!("./test_data/todo.sol"); + assert_no_ctx_killed(path_str, sol); +} diff --git a/crates/pyrometer/tests/test_data/abstract.sol b/crates/pyrometer/tests/test_data/abstract.sol index 4837a1a2..0a2a922d 100644 --- a/crates/pyrometer/tests/test_data/abstract.sol +++ b/crates/pyrometer/tests/test_data/abstract.sol @@ -1,19 +1,23 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + abstract contract A { uint a; + function foo(uint b) internal { if (bar() > 1) {} a = b; } - function bar() virtual internal view returns (uint); + function bar() internal view virtual returns (uint); } abstract contract YInterface { - function foo() virtual external returns (uint); + function foo() external virtual returns (uint); } contract Y is YInterface { - function foo() virtual override public returns (uint) { + function foo() public virtual override returns (uint) { return 1; } @@ -23,7 +27,7 @@ contract Y is YInterface { } abstract contract Base { - function foo() virtual public returns (uint){ + function foo() public virtual returns (uint) { return 0; } } @@ -33,7 +37,7 @@ contract Base2 is Base {} contract Base3 is Base2 {} contract Test is Base3 { - function foo() override public returns (uint){ + function foo() public override returns (uint) { return super.foo(); } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/assign.sol b/crates/pyrometer/tests/test_data/assign.sol new file mode 100644 index 00000000..facc63bc --- /dev/null +++ b/crates/pyrometer/tests/test_data/assign.sol @@ -0,0 +1,42 @@ +pragma solidity ^0.8.0; + +contract Assign { + function doAssignment() public { + // Multi-value LHS (tuple) + (uint x, uint y) = (uint16(1), 2); + + // Single value RHS + uint z = 3; + + (x, y) = (z, z); + } + + uint x; + + function array_literals(uint z) public { + uint[][] memory ax = new uint[][](z); + ax[0] = new uint[](3); + ax[0][2] = 2; + + uint[][] memory bx = ax; + uint8[0x2][2] memory a = [[1, 2], [1, 2]]; + a[1]; + uint[2] memory b = [uint(3), x++]; + uint[2][2] memory c = [[uint(3), x++], [uint(2), uint(3)]]; + a; + b; + } + + function array_slices( + uint[] calldata a, + uint x + ) public pure returns (uint[] memory) { + require(a.length >= 4, "Array must have at least 4 elements"); + a[2] = 14; + uint[] memory b = a[2:4]; + uint[] memory c = a[1:]; + uint[] memory d = a[:2]; + uint[] memory e = a[2:4][0:1]; + uint[] memory f = a[2:x]; + } +} diff --git a/crates/pyrometer/tests/test_data/bin_op.sol b/crates/pyrometer/tests/test_data/bin_op.sol new file mode 100644 index 00000000..c78d64d3 --- /dev/null +++ b/crates/pyrometer/tests/test_data/bin_op.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.8.0; + +contract BinOp { + function testBinOp(int y) public { + int x = 1; + int z = 5 + x; + int a = x * y; + } +} diff --git a/crates/pyrometer/tests/test_data/bitwise.sol b/crates/pyrometer/tests/test_data/bitwise.sol index 42207a1d..00781f0c 100644 --- a/crates/pyrometer/tests/test_data/bitwise.sol +++ b/crates/pyrometer/tests/test_data/bitwise.sol @@ -1,9 +1,12 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract BitAnd { - function bit_and(bytes32 x, bytes32 y) public returns (bytes32) { + function bit_and(bytes32 x, bytes32 y) public pure returns (bytes32) { return x & y; } - function bit_and_conc(bytes32 d) public { + function bit_and_conc1() public pure { require(uint(bytes32(type(uint256).max) & bytes32(uint(100))) == 100); require(uint(bytes32(0) & bytes32(uint(100))) == 0); require(uint(bytes32(uint(101)) & bytes32(uint(105))) == 97); @@ -13,11 +16,11 @@ contract BitAnd { require(uint(bit_and(bytes32(uint(50)), bytes32(uint(500)))) == 48); } - function bit_and(uint256 x, uint256 y) public returns (uint256) { + function bit_and(uint256 x, uint256 y) public pure returns (uint256) { return x & y; } - function bit_and_conc(uint256 d) public { + function bit_and_conc() public pure { require(type(uint256).max & 100 == 100); require(0 & uint(100) == 0); require(101 & 105 == 97); @@ -25,11 +28,11 @@ contract BitAnd { require(bit_and(50, 500) == 48); } - function int_bit_and(int256 x, int256 y) public returns (int256) { + function int_bit_and(int256 x, int256 y) public pure returns (int256) { return x & y; } - function int_bit_and_conc(uint256 d) public { + function int_bit_and_conc() public pure { require(type(int256).max & int(100) == 100); require(0 & int(100) == 0); require(101 & 105 == 97); @@ -39,11 +42,11 @@ contract BitAnd { } contract BitOr { - function bit_or(bytes32 x, bytes32 y) public returns (bytes32) { + function bit_or(bytes32 x, bytes32 y) public pure returns (bytes32) { return x | y; } - function bit_or_conc(bytes32 d) public { + function bit_or_conc1() public pure { require( bytes32(type(uint256).max) | bytes32(uint(100)) == bytes32(type(uint256).max) @@ -57,11 +60,11 @@ contract BitOr { require(uint(bit_or(bytes32(uint(50)), bytes32(uint(500)))) == 502); } - function bit_or(uint256 x, uint256 y) public returns (uint256) { + function bit_or(uint256 x, uint256 y) public pure returns (uint256) { return x | y; } - function bit_or_conc(uint256 d) public { + function bit_or_conc() public pure { require(type(uint256).max | uint(100) == type(uint256).max); require(0 | uint(100) == 100); require(101 | 105 == 109); @@ -69,11 +72,11 @@ contract BitOr { require(bit_or(50, 500) == 502); } - function int_bit_or(int256 x, int256 y) public returns (int256) { + function int_bit_or(int256 x, int256 y) public pure returns (int256) { return x | y; } - function int_bit_or_conc(uint256 d) public { + function int_bit_or_conc() public pure { require(type(int256).max | int(100) == type(int256).max); require(0 | int(100) == 100); require(101 | 105 == 109); @@ -83,11 +86,11 @@ contract BitOr { } contract BitXor { - function bit_xor(uint256 x, uint256 y) public returns (uint256) { + function bit_xor(uint256 x, uint256 y) public pure returns (uint256) { return x ^ y; } - function bit_xor_conc(uint256 d) public { + function bit_xor_conc() public pure { require( type(uint256).max ^ uint(100) == 115792089237316195423570985008687907853269984665640564039457584007913129639835 @@ -98,11 +101,11 @@ contract BitXor { require(bit_xor(50, 500) == 454); } - function int_bit_xor(int256 x, int256 y) public returns (int256) { + function int_bit_xor(int256 x, int256 y) public pure returns (int256) { return x ^ y; } - function int_bit_xor_conc(uint256 d) public { + function int_bit_xor_conc() public pure { require( type(int256).max ^ int(100) == 57896044618658097711785492504343953926634992332820282019728792003956564819867 @@ -117,7 +120,7 @@ contract BitXor { } contract BitNot { - function yul_bit_not(bytes32 d) public view returns (bytes32) { + function yul_bit_not() public pure { uint256 x; assembly { x := not(100) @@ -128,7 +131,7 @@ contract BitNot { ); } - function bit_not(bytes32 d) public view returns (bytes32) { + function bit_not() public pure returns (bytes32) { bytes32 x = hex"1111"; require( ~x == @@ -153,11 +156,11 @@ contract BitNot { return ~x; } - function bit_not(uint256 x) public returns (uint256) { + function bit_not(uint256 x) public pure returns (uint256) { return ~x; } - function bit_not_conc(uint256 d) public { + function bit_not_conc() public pure { require(~type(uint256).max == 0); require( ~uint(100) == @@ -178,11 +181,11 @@ contract BitNot { ); } - function int_bit_not(int256 x) public returns (int256) { + function int_bit_not(int256 x) public pure returns (int256) { return ~x; } - function int_bit_not_conc(uint256 d) public returns (int256) { + function int_bit_not_conc() public pure { require(~type(int256).max == type(int256).min); require(~type(int256).min == type(int256).max); require(~int256(100) == -101); @@ -194,7 +197,7 @@ contract BitNot { } contract BitShl { - function yulShl(uint256 x, uint256 y) public returns (uint256) { + function yulShl(uint256 x, uint256 y) public pure returns (uint256) { uint256 ret; assembly { ret := shl(y, x) @@ -202,13 +205,13 @@ contract BitShl { return ret; } - function yulShl_conc() public { + function yulShl_conc() public pure { uint256 ret = yulShl(10, 1); uint256 other_ret = 10 << 1; require(ret == other_ret); } - function yulShr(uint256 x, uint256 y) public returns (uint256) { + function yulShr(uint256 x, uint256 y) public pure returns (uint256) { uint256 ret; assembly { ret := shr(x, y) @@ -216,15 +219,15 @@ contract BitShl { return ret; } - function shl(uint256 x, uint256 y) public returns (uint256) { + function shl(uint256 x, uint256 y) public pure returns (uint256) { return x << y; } - function int_shl(int256 x, uint256 y) public returns (int256) { + function int_shl(int256 x, uint256 y) public pure returns (int256) { return x << y; } - function shl_conc() public returns (uint256) { + function shl_conc() public pure { uint256 a1 = shl(100, 1); require(a1 == 200); uint256 a2 = shl(100, 2); @@ -253,7 +256,7 @@ contract BitShl { ); } - function int_shl_conc() public returns (int256) { + function int_shl_conc() public pure { int256 a1 = int_shl(100, 1); "pyro:variable:a1:range:[200,200]"; int256 a2 = int_shl(100, 2); @@ -313,15 +316,15 @@ contract BitShl { } contract BitShr { - function shr(uint256 x, uint256 y) public returns (uint256) { + function shr(uint256 x, uint256 y) public pure returns (uint256) { return x >> y; } - function int_shr(int256 x, uint256 y) public returns (int256) { + function int_shr(int256 x, uint256 y) public pure returns (int256) { return x >> y; } - function shr_conc() public { + function shr_conc() public pure { uint256 a1 = shr(100, 1); require(a1 == 50); uint256 a2 = shr(100, 2); @@ -347,7 +350,7 @@ contract BitShr { require(a10 == 2); } - function int_shr_conc() public returns (int256) { + function int_shr_conc() public pure { int256 a1 = int_shr(100, 1); require(a1 == 50); int256 a2 = int_shr(100, 2); diff --git a/crates/pyrometer/tests/test_data/broken/delete.sol b/crates/pyrometer/tests/test_data/broken/delete.sol new file mode 100644 index 00000000..811f68c9 --- /dev/null +++ b/crates/pyrometer/tests/test_data/broken/delete.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + +// Merge into delete.sol when fixed +contract ComplexDelete { + struct ContactInfo { + string email; + string phone; + } + + struct Address { + string street; + string city; + string country; + uint256 postalCode; + } + + struct Employment { + string company; + string position; + uint256 startDate; + uint256 endDate; + } + + struct Education { + string institution; + string degree; + uint256 graduationYear; + } + + struct User { + uint256 id; + string name; + ContactInfo contactInfo; + Address[] addresses; + Employment[] employmentHistory; + Education[] educationHistory; + mapping(string => bool) preferences; + } + + mapping(uint256 => User) public users; + uint256[] public userIds; + + function deleteUserAddress(uint256 userId, uint256 addressIndex) public { + require( + addressIndex < users[userId].addresses.length, + "Address index out of bounds" + ); + users[userId].addresses[addressIndex] = users[userId].addresses[ + users[userId].addresses.length - 1 + ]; + users[userId].addresses.pop(); + } + + function deleteEmploymentHistory( + uint256 userId, + uint256 employmentIndex + ) public { + require( + employmentIndex < users[userId].employmentHistory.length, + "Employment index out of bounds" + ); + users[userId].employmentHistory[employmentIndex] = users[userId] + .employmentHistory[users[userId].employmentHistory.length - 1]; + users[userId].employmentHistory.pop(); + } + + function deleteEducationHistory( + uint256 userId, + uint256 educationIndex + ) public { + require( + educationIndex < users[userId].educationHistory.length, + "Education index out of bounds" + ); + users[userId].educationHistory[educationIndex] = users[userId] + .educationHistory[users[userId].educationHistory.length - 1]; + users[userId].educationHistory.pop(); + } +} diff --git a/crates/pyrometer/tests/test_data/broken/require_killed.sol b/crates/pyrometer/tests/test_data/broken/require_killed.sol new file mode 100644 index 00000000..243d4e89 --- /dev/null +++ b/crates/pyrometer/tests/test_data/broken/require_killed.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT or APACHE2 +// Move to require_with_killed.sol when fixed +// Note: I've added broken@brock comments to the issues i know of +pragma solidity ^0.8.0; + +contract RequireWithKilled { + uint public count = 0; + uint storeRange = 0; + + function setStoreRange(uint x) public { + storeRange = x; + } + + function requireLt(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + "pyro::variable::storeRange::range::[6, 99]"; // broken@brock this range appears as [0,99] + // set tighter bounds for x + require(6 < x && x < 99); + "pyro::variable::x::range::[7, 98]"; // broken@brock this range appears as [0,98] + // make x less than storeRange + require(x < storeRange); + } + + function requireLte(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x less than or equal to storeRange + require(x <= storeRange); + } + + function requireGt(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x greater than storeRange + require(x > storeRange); + } + + function requireGte(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x greater than or equal to storeRange + require(x >= storeRange); + } + + function requireEq(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x equal to storeRange + require(x == storeRange); + } + + function requireNeq(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x not equal to storeRange + require(x != storeRange); + } + + function setCount() public { + count = 0; + } + + function andShortCircuit() public { + count = 0; + // ( bump(false) && bump(true) ) || true , this will test that the second bump is not evaluated since the `and` short circuits + require( + (bumpCountIfValueEq1ThenReturn(1, false) && + bumpCountIfValueEq1ThenReturn(1, true)) || true + ); + "pyro::variable::count::range::[1, 1]"; // broken@brock `count` is not found in context. this goes for the other "pyro::" statements with `count` too + } + + function andFullCircuit() public { + count = 0; + // ( bump(true) && bump(true) ) , this will test that the second bump is evaluated since the `and` does not short circuit + // broken@brock `&&` has parse issues here + require( + (bumpCountIfValueEq1ThenReturn(1, true) && + bumpCountIfValueEq1ThenReturn(1, true)) + ); + "pyro::variable::count::range::[2, 2]"; + } + + function orShortCircuit() public { + count = 0; + // ( bump(true) || bump(true) ) , this will test that the second bump is not evaluated since the `or` short circuits + require( + bumpCountIfValueEq1ThenReturn(1, true) || + bumpCountIfValueEq1ThenReturn(1, true) + ); + "pyro::variable::count::range::[1, 1]"; + } + + function orShortCircuitRHS() public { + count = 0; + // ( bump(true) || bump(true) ) , this will test that the second bump is not evaluated since the `or` short circuits + require( + bumpCountIfValueEq1ThenReturn(2, true) || + bumpCountIfValueEq1ThenReturn(1, true) + ); + "pyro::variable::count::range::[0, 0]"; + + count = 0; + require(bumpCountIfValueEq1ThenReturn(1, true) || true); + "pyro::variable::count::range::[1, 1]"; + + count = 0; + require(true || bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[0, 0]"; + } + + function yulAndFullCircuit() public { + count = 0; + assembly { + function bumpCountIfValueEq1ThenReturn(x, returnValue) -> result { + let count_val := sload(0) + // first if needs both x and count to be 0 + if and(eq(count_val, 0), eq(x, 0)) { + // add 1 to count + sstore(0, add(sload(0), 1)) + } + // second if needs both values to be 1 + if and(eq(count_val, 1), eq(x, 1)) { + // add 1 to count + sstore(0, add(sload(0), 1)) + } + result := true + } + + // in yul: rhs is evaluated, then lhs. no short circuiting + if or( + bumpCountIfValueEq1ThenReturn(1, true), + bumpCountIfValueEq1ThenReturn(0, true) + ) { + + } + } + "pyro::variable::count::range::[2, 2]"; + } + + function orFullCircuit() public { + count = 0; + // ( bump(false) || bump(true) ) , this will test that the second bump is evaluated since the `or` does not short circuit + require( + bumpCountIfValueEq1ThenReturn(1, false) || + bumpCountIfValueEq1ThenReturn(1, true) + ); + "pyro::variable::count::range::[2, 2]"; + } + + function bumpCountIfValueEq1ThenReturn( + uint8 x, + bool returnValue + ) internal returns (bool) { + if (x == 1) { + count += 1; + } + return returnValue; + } +} diff --git a/crates/pyrometer/tests/test_data/cmp.sol b/crates/pyrometer/tests/test_data/cmp.sol new file mode 100644 index 00000000..5ca64b57 --- /dev/null +++ b/crates/pyrometer/tests/test_data/cmp.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.8.0; + +contract Cmp { + function testCmp(int x) public { + uint a = 5; + bool b = (5 < 6) || (5 == 6 && 0 < 1); // Correct the tuple comparison + } +} diff --git a/crates/pyrometer/tests/test_data/cond_op.sol b/crates/pyrometer/tests/test_data/cond_op.sol new file mode 100644 index 00000000..5279d074 --- /dev/null +++ b/crates/pyrometer/tests/test_data/cond_op.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.8.0; + +contract CondOp { + + function if_both_possible(uint x) public returns (uint) { + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_first_possible() public returns (uint) { + uint x = 101; + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_second_possible() public returns (uint) { + uint x = 99; + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_neither_possible(uint x) public returns (uint) { + if (x > 100) { + require(x < 100); // not possible + return 1; + } else { + require(x > 100); // not possible + return 2; + } + return 0; + } + + + function ternaryParam(uint x) public returns (uint) { + uint y = x > 2 ? 1 : 2; + return y; + } + + function ternaryLiteral() public returns (uint) { + uint y = 1 > 2 ? 1 : 2; + "pyro::variable::y::range::[2,2]"; + return y; + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/const_var.sol b/crates/pyrometer/tests/test_data/const_var.sol index a30ec42a..98c379e7 100644 --- a/crates/pyrometer/tests/test_data/const_var.sol +++ b/crates/pyrometer/tests/test_data/const_var.sol @@ -1,7 +1,10 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract ConstVar { uint256 internal constant typeVar = type(uint256).max; - uint256 internal constant funcVar = a(); - uint256 internal constant funcVarInput = aInput(100); + uint256 internal immutable funcVar = a(); + uint256 internal immutable funcVarInput = aInput(100); bytes16 private constant bytesString = "0123456789abcdef"; function a() public pure returns (uint256) { @@ -16,11 +19,11 @@ contract ConstVar { } } - function checkA() public pure { + function checkA() public view { require(funcVar == type(uint256).max); } - function checkAInput() public pure { + function checkAInput() public view { require(funcVarInput == 110); } diff --git a/crates/pyrometer/tests/test_data/constructor.sol b/crates/pyrometer/tests/test_data/constructor.sol index cbf4d23e..818702bc 100644 --- a/crates/pyrometer/tests/test_data/constructor.sol +++ b/crates/pyrometer/tests/test_data/constructor.sol @@ -1,5 +1,9 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract A { address a; + constructor(address _a) { a = _a; } @@ -15,6 +19,7 @@ contract C is B { contract X { address x; + constructor(address _x) { x = _x; } @@ -29,9 +34,7 @@ contract D is Y, C { } abstract contract F { - function foo() public virtual { - - } + function foo() public virtual {} } contract G is F { @@ -41,11 +44,12 @@ contract G is F { } abstract contract H { - function foo() virtual external returns (uint) {} + function foo() external virtual returns (uint) {} } abstract contract I is H { H a; + function liquidateBorrowInternal(H _a) internal returns (uint, uint, uint) { uint b = foo(); uint b2 = _a.foo(); @@ -57,7 +61,7 @@ abstract contract I is H { return (b, b2, b3); } - function foo() public virtual override returns (uint){ + function foo() public virtual override returns (uint) { return 1; } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/delete.sol b/crates/pyrometer/tests/test_data/delete.sol new file mode 100644 index 00000000..23c3fc69 --- /dev/null +++ b/crates/pyrometer/tests/test_data/delete.sol @@ -0,0 +1,210 @@ +pragma solidity ^0.8.0; + +contract ComplexDelete { + struct ContactInfo { + string email; + string phone; + } + + struct Address { + string street; + string city; + string country; + uint256 postalCode; + } + + struct Employment { + string company; + string position; + uint256 startDate; + uint256 endDate; + } + + struct Education { + string institution; + string degree; + uint256 graduationYear; + } + + struct User { + uint256 id; + string name; + ContactInfo contactInfo; + Address[] addresses; + Employment[] employmentHistory; + Education[] educationHistory; + mapping(string => bool) preferences; + } + + mapping(uint256 => User) public users; + uint256[] public userIds; + + function addUser( + uint256 id, + string memory name, + string memory email, + string memory phone + ) public { + require(users[id].id == 0, "User already exists"); + User storage newUser = users[id]; + newUser.id = id; + newUser.name = name; + newUser.contactInfo = ContactInfo(email, phone); + userIds.push(id); + } + + function addUserAddress( + uint256 userId, + string memory street, + string memory city, + string memory country, + uint256 postalCode + ) public { + users[userId].addresses.push( + Address(street, city, country, postalCode) + ); + } + + function addEmploymentHistory( + uint256 userId, + string memory company, + string memory position, + uint256 startDate, + uint256 endDate + ) public { + users[userId].employmentHistory.push( + Employment(company, position, startDate, endDate) + ); + } + + function addEducationHistory( + uint256 userId, + string memory institution, + string memory degree, + uint256 graduationYear + ) public { + users[userId].educationHistory.push( + Education(institution, degree, graduationYear) + ); + } + + function setUserPreference( + uint256 userId, + string memory key, + bool value + ) public { + users[userId].preferences[key] = value; + } + + function deleteUser(uint256 userId) public { + require(users[userId].id != 0, "User does not exist"); + delete users[userId]; + for (uint256 i = 0; i < userIds.length; i++) { + if (userIds[i] == userId) { + userIds[i] = userIds[userIds.length - 1]; + userIds.pop(); + } + } + } + + function deleteUserPreference(uint256 userId, string memory key) public { + delete users[userId].preferences[key]; + } + + function updateContactInfo( + uint256 userId, + string memory newEmail, + string memory newPhone + ) public { + users[userId].contactInfo = ContactInfo(newEmail, newPhone); + } + + function clearAllUserAddresses(uint256 userId) public { + delete users[userId].addresses; + } + + function clearAllEmploymentHistory(uint256 userId) public { + delete users[userId].employmentHistory; + } + + function clearAllEducationHistory(uint256 userId) public { + delete users[userId].educationHistory; + } +} + +contract UseComplexDelete { + ComplexDelete t; + + constructor() { + t = new ComplexDelete(); + } + + function useIt() public { + // Add users + t.addUser(1, "Alice", "alice@example.com", "1234567890"); + t.addUser(2, "Bob", "bob@example.com", "0987654321"); + + // Add addresses + t.addUserAddress(1, "123 Main St", "New York", "USA", 10001); + t.addUserAddress(1, "456 Elm St", "Los Angeles", "USA", 90001); + t.addUserAddress(2, "789 Oak St", "Chicago", "USA", 60601); + + // Add employment history + t.addEmploymentHistory( + 1, + "TechCorp", + "Developer", + 1609459200, + 1640995200 + ); + t.addEmploymentHistory(1, "WebSoft", "Senior Developer", 1641081600, 0); + t.addEmploymentHistory(2, "DataFirm", "Analyst", 1577836800, 0); + + // Add education history + t.addEducationHistory( + 1, + "Tech University", + "BSc Computer Science", + 2020 + ); + t.addEducationHistory(2, "Data College", "MSc Data Science", 2019); + + // Set preferences + t.setUserPreference(1, "receiveNewsletter", true); + t.setUserPreference(1, "darkMode", false); + t.setUserPreference(2, "receiveNewsletter", false); + + // Test deletions and updates + + // Delete an address + // t.deleteUserAddress(1, 0); // TODO @brock these need uncommented when the pop is fixed and these functions are back + + // Delete employment history + // t.deleteEmploymentHistory(1, 0); // TODO @brock these need uncommented when the pop is fixed and these functions are back + + // Delete education history + // t.deleteEducationHistory(2, 0); // TODO @brock these need uncommented when the pop is fixed and these functions are back + + // Delete user preference + t.deleteUserPreference(1, "darkMode"); + + // Update contact info + t.updateContactInfo(2, "bob.new@example.com", "1122334455"); + + // Clear all addresses for a user + t.clearAllUserAddresses(1); + + // Clear all employment history for a user + t.clearAllEmploymentHistory(2); + + // Clear all education history for a user + t.clearAllEducationHistory(1); + + // Delete an entire user + t.deleteUser(1); + + // Add a new user to test after deletions + t.addUser(3, "Charlie", "charlie@example.com", "5556667777"); + t.addUserAddress(3, "321 Pine St", "San Francisco", "USA", 94101); + } +} diff --git a/crates/pyrometer/tests/test_data/dyn_types.sol b/crates/pyrometer/tests/test_data/dyn_types.sol index 81d4e01e..c4a6c6ff 100644 --- a/crates/pyrometer/tests/test_data/dyn_types.sol +++ b/crates/pyrometer/tests/test_data/dyn_types.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract DynTypes { uint256[] storeVar; @@ -7,15 +10,16 @@ contract DynTypes { } mapping(address => Strukt) public someMapping; + mapping(address => Strukt[]) public someMapping2; - function bytes_dyn(bytes calldata x) public { + function bytes_dyn(bytes calldata x) public pure { bytes memory y = x; require(x.length < 10); y[8] = 0xff; require(y.length == 9); } - function array_dyn(uint256[] memory x) public { + function array_dyn(uint256[] memory x) public pure { x[0] = 5; require(x.length < 10); uint256[] memory y = x; @@ -23,10 +27,7 @@ contract DynTypes { require(y.length == 9); } - function nested_bytes_dyn( - bytes[] memory x, - uint y - ) public returns (bytes1) { + function nested_bytes_dyn(bytes[] memory x, uint y) public pure { bytes memory a = hex"1337"; x[0] = a; require(x[0][0] == hex"13"); @@ -51,11 +52,11 @@ contract DynTypes { require(y == x); } - function indexInto() public returns (uint256) { + function indexInto() public view returns (uint256) { return storeVar[basicFunc()]; } - function basicFunc() public returns (uint256) { + function basicFunc() public pure returns (uint256) { return 1; } @@ -69,15 +70,58 @@ contract DynTypes { address[] t; - function inLoop(address holder, address[] memory tokens) public { + function inLoop(address holder, address[] memory tokens) public pure { address[] memory h = new address[](1); h[0] = holder; inLoop(h, tokens); } - function inLoop(address[] memory holders, address[] memory tokens) public { + function inLoop(address[] memory holders, address[] memory) public pure { for (uint j = 0; j < holders.length; j++) { address holder = holders[j]; + holder; } } + + struct DontUseMoreThanOnce { + uint256 a; + uint256 b; + } + + function dynUserType() public { + DontUseMoreThanOnce[] memory dont = new DontUseMoreThanOnce[](1); + dont[0].a = 100; + dont[0].b = 100; + require(dont[0].a == 100); + } + + function getReturnedUserType() public pure { + // Strukt[] memory strukt = returnUserType()[0]; + Strukt memory strukt = returnUserType()[0]; + require(strukt.a == 100); + } + + function returnUserType() public pure returns (Strukt[] memory) { + Strukt[] memory strukt = new Strukt[](1); + strukt[0].a = 100; + strukt[0].b = 100; + return strukt; + } + + function multiDimensionalArray() public returns (bool z) { + uint256[][] memory multiArray = new uint256[][](2); + uint256[] memory indices = new uint256[](2); + + indices[0] = 0; + indices[1] = 1; + + for (uint i = 0; i < multiArray.length; i++) { + multiArray[i] = new uint256[](2); + for (uint j = 0; j < multiArray[i].length; j++) { + multiArray[i][j] = 1; + } + } + + z = true; + } } diff --git a/crates/pyrometer/tests/test_data/env.sol b/crates/pyrometer/tests/test_data/env.sol index a11015f2..c8b3065b 100644 --- a/crates/pyrometer/tests/test_data/env.sol +++ b/crates/pyrometer/tests/test_data/env.sol @@ -1,9 +1,51 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Env { - function msg_sender() public returns (address) { + function msg_sender() public view returns (address) { return msg.sender; } - function msg_data() public returns (bytes memory) { + function msg_data() public pure returns (bytes memory) { return msg.data; } + + function testBlock() public payable { + /* + blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks + blobhash(uint index) returns (bytes32): versioned hash of the index-th blob associated with the current transaction. A versioned hash consists of a single byte representing the version (currently 0x01), followed by the last 31 bytes of the SHA256 hash of the KZG commitment (EIP-4844). + block.basefee (uint): current block’s base fee (EIP-3198 and EIP-1559) + block.blobbasefee (uint): current block’s blob base fee (EIP-7516 and EIP-4844) + block.chainid (uint): current chain id + block.coinbase (address payable): current block miner’s address + block.difficulty (uint): current block difficulty (EVM < Paris). For other EVM versions it behaves as a deprecated alias for block.prevrandao that will be removed in the next breaking release + block.gaslimit (uint): current block gaslimit + block.number (uint): current block number + block.prevrandao (uint): random number provided by the beacon chain (EVM >= Paris) (see EIP-4399 ) + block.timestamp (uint): current block timestamp in seconds since Unix epoch + gasleft() returns (uint256): remaining gas + msg.data (bytes): complete calldata + msg.sender (address): sender of the message (current call) + msg.sig (bytes4): first four bytes of the calldata (i.e. function identifier) + msg.value (uint): number of wei sent with the message + tx.gasprice (uint): gas price of the transaction + tx.origin (address): sender of the transaction (full call chain) + */ + bytes32 a = blockhash(1); + uint c = block.basefee; + uint e = block.chainid; + address payable f = block.coinbase; + uint g = block.difficulty; + uint h = block.gaslimit; + uint i = block.number; + uint j = block.prevrandao; + uint k = block.timestamp; + uint l = gasleft(); + bytes memory m = msg.data; + address n = msg.sender; + bytes4 o = msg.sig; + uint p = msg.value; + uint q = tx.gasprice; + address r = tx.origin; + } } diff --git a/crates/pyrometer/tests/test_data/func_override.sol b/crates/pyrometer/tests/test_data/func_override.sol index 7f0c5851..4779e5f3 100644 --- a/crates/pyrometer/tests/test_data/func_override.sol +++ b/crates/pyrometer/tests/test_data/func_override.sol @@ -1,22 +1,24 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract C {} contract B is C { - function a(uint256 x) internal virtual returns (uint256) { - return 200; - } + function a(uint256) internal virtual returns (uint256) { + return 200; + } } +contract A is B { + function a(uint256 x) internal pure override returns (uint256) { + return x + 5; + } -contract A is B, C { - function a(uint256 x) internal override returns (uint256) { - return x + 5; - } - - function b() public returns (uint256) { - uint256 ret = a(5); - require(ret == 10); - ret = super.a(5); - require(ret == 200); - return ret; - } + function b() public returns (uint256) { + uint256 ret = a(5); + require(ret == 10); + ret = super.a(5); + require(ret == 200); + return ret; + } } diff --git a/crates/pyrometer/tests/test_data/function_calls.sol b/crates/pyrometer/tests/test_data/function_calls.sol index e687f402..2a84953a 100644 --- a/crates/pyrometer/tests/test_data/function_calls.sol +++ b/crates/pyrometer/tests/test_data/function_calls.sol @@ -1,53 +1,67 @@ -// contract InternalFuncCalls { -// address _owner; +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; -// function transferOwnership(address newOwner) public virtual { -// innerRequire(newOwner); -// _transferOwnership(newOwner); -// } +contract InternalFuncCalls { + address _owner; -// function innerRequire(address newOwner) public virtual { -// require(newOwner != address(0), "Ownable: new owner is the zero address"); -// } + function transferOwnership(address newOwner) public virtual { + innerRequire(newOwner); + _transferOwnership(newOwner); + } -// function _transferOwnership(address newOwner) internal virtual { -// address oldOwner = _owner; -// _owner = newOwner; -// } -// } + function innerRequire(address newOwner) public virtual { + require( + newOwner != address(0), + "Ownable: new owner is the zero address" + ); + } -// contract B { -// uint256 public a; + function _transferOwnership(address newOwner) internal virtual { + _owner = newOwner; + } +} -// function addToA(uint256 x) public { -// a += x; -// } +contract B { + uint256 public a; -// constructor(uint256 x) { -// a = x; -// } -// } + function addToA(uint256 x) public { + a += x; + } + + function addTo(uint256 y) public { + a += y; + } + + constructor(uint256 x) { + a = x; + } +} contract ExternalFuncCalls { - // function externalCall(uint256 x) public { - // B(address(100)).addToA(x); - // } + function externalCall(uint256 x) public { + B(address(100)).addTo({y: x}); + } - // function externalCall_conc() public { - // B(address(100)).addToA(100); + function externalCall_conc() public { + B(address(100)).addToA(100); - // uint256 ba = B(address(100)).a(); - // } + uint256 ba = B(address(100)).a(); + ba; + } - function multiReturn() public returns (uint256, uint256, uint256, uint256) { + function multiReturn() + public + pure + returns (uint256, uint256, uint256, uint256) + { return (1, 2, 3, 4); } - function partialReturn() public { + function partialReturn() public pure { (uint256 w, , uint256 y, ) = multiReturn(); require(w == 1); require(y == 3); - (uint256 w1, uint256 x1, uint256 y1, ) = multiReturn(); + (uint256 w1, , uint256 y1, ) = multiReturn(); require(w1 == 1); require(y1 == 3); (, uint256 x2, , uint256 z) = multiReturn(); @@ -60,26 +74,77 @@ contract ExternalFuncCalls { } } -// contract K { -// struct L { -// uint b; -// uint c; -// } +contract K { + struct L { + uint b; + uint c; + } + + function foo() internal pure { + L memory l = L(2, 3); + require(l.b == 2); + require(l.c == 3); + } +} + +contract Disambiguation { + function foo(address from, address to, uint256 id) public { + foo(from, to, id, 0); + } -// function foo() internal { -// L memory l = L(2, 3); -// require(l.b == 2); -// require(l.c == 3); -// } -// } + function foo(address from, address to, uint256 id, uint num) internal {} + function foo(address by, address from, address to, uint256 id) internal {} +} -// contract Disambiguation { -// function foo(address from, address to, uint256 id) public { -// foo(from, to, id, 0); -// } +contract S1 { + function a(uint x) internal pure virtual returns (uint) { + return 100; + } +} -// function foo(address from, address to, uint256 id, uint num) internal {} +contract S2 { + function a(uint x) internal pure virtual returns (uint) { + return 10; + } -// function foo(address by, address from, address to, uint256 id) internal {} -// } + function b(uint x) internal pure virtual returns (uint) { + return 10; + } +} + +contract C is S1, S2 { + function supers(uint128 x) public pure returns (uint) { + uint local_a = a(1); + uint super_a = super.a(1); + require(local_a == 50); + require(super_a == 10); + + uint local_super_b = b(x); + uint super_b = super.b(x); + require(local_super_b == super_b); + return 0; + } + + function a(uint256 x) internal pure override(S1, S2) returns (uint) { + return 50; + } +} + +contract D is S2, S1 { + function supers(uint128 x) public pure returns (uint) { + uint local_a = a(1); + uint super_a = super.a(1); + require(local_a == 50); + require(super_a == 100); + + uint local_super_b = b(x); + uint super_b = super.b(x); + require(local_super_b == super_b); + return 0; + } + + function a(uint256 x) internal pure override(S1, S2) returns (uint) { + return 50; + } +} diff --git a/crates/pyrometer/tests/test_data/interface.sol b/crates/pyrometer/tests/test_data/interface.sol index cfca673d..eaf8b784 100644 --- a/crates/pyrometer/tests/test_data/interface.sol +++ b/crates/pyrometer/tests/test_data/interface.sol @@ -1,14 +1,15 @@ -// contract A { -// B b; -// } +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; interface B { function foo(uint bar) external pure returns (uint8); + function baz() external returns (uint); } contract A { B public b; + constructor(B _b) { b = _b; } @@ -17,4 +18,4 @@ contract A { b.baz(); b = _b; } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/intrinsics.sol b/crates/pyrometer/tests/test_data/intrinsics.sol index f9754dc9..d391a045 100644 --- a/crates/pyrometer/tests/test_data/intrinsics.sol +++ b/crates/pyrometer/tests/test_data/intrinsics.sol @@ -1,26 +1,39 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Intrinsics { - function strConcat() public { - string memory a = "aa"; - string memory b = "bb"; - string memory c = string.concat(a, b); - string memory d = string.concat(a, b, c); - } + function strConcat() public pure { + string memory a = "aa"; + string memory b = "bb"; + string memory c = string.concat(a, b); + string memory d = string.concat(a, b, c); + d; + } - function bytesConcat() public { - bytes memory a = hex"aa"; - bytes memory b = hex"bb"; - bytes memory c = bytes.concat(a, b); - require(c[0] == hex"aa"); - require(c[1] == hex"bb"); - bytes memory d = bytes.concat(a, b, c); - require(d[0] == hex"aa"); - require(d[1] == hex"bb"); - require(d[2] == hex"aa"); - require(d[3] == hex"bb"); + function bytesConcat() public pure { + bytes memory a = hex"aa"; + bytes memory b = hex"bb"; + bytes memory c = bytes.concat(a, b); + require(c[0] == hex"aa"); + require(c[1] == hex"bb"); + bytes memory d = bytes.concat(a, b, c); + require(d[0] == hex"aa"); + require(d[1] == hex"bb"); + require(d[2] == hex"aa"); + require(d[3] == hex"bb"); + } + + function selfdestructed() public { + selfdestruct(payable(address(this))); + } - } + function yulSelfdestructed() public { + assembly { + selfdestruct(1) + } + } - function yulIntrinsics() public { + function yulIntrinsics() public view { assembly { let a := timestamp() let b := caller() @@ -29,228 +42,264 @@ contract Intrinsics { } } + function blockData() public view { + uint256 fee = block.basefee; + uint256 chainid = block.chainid; + address coinbase = block.coinbase; + uint256 difficulty = block.difficulty; + uint256 prevrandao = block.prevrandao; + uint256 gaslimit = block.gaslimit; + uint256 number = block.number; + uint256 timestamp = block.timestamp; + bytes32 hash = blockhash(number); - function blockData() public { - uint256 fee = block.basefee; - uint256 chainid = block.chainid; - address coinbase = block.coinbase; - uint256 difficulty = block.difficulty; - uint256 prevrandao = block.prevrandao; - uint256 gaslimit = block.gaslimit; - uint256 number = block.number; - uint256 timestamp = block.timestamp; - bytes32 hash = blockhash(number); - } - - function msgData() public { - bytes memory data = msg.data; - address sender = msg.sender; - bytes4 sig = msg.sig; - uint256 value = msg.value; - } + fee; + chainid; + coinbase; + difficulty; + prevrandao; + gaslimit; + timestamp; + hash; + } - function txData() public { - uint256 gasprice = tx.gasprice; - address origin = tx.origin; - uint256 gasleft = gasleft(); - } + function msgData() public payable { + bytes memory data = msg.data; + address sender = msg.sender; + bytes4 sig = msg.sig; + uint256 value = msg.value; + data; + sender; + sig; + value; + } - function asserting() public { - assert(true); - } + function txData() public view { + uint256 gasprice = tx.gasprice; + address origin = tx.origin; + uint256 gas = gasleft(); + gasprice; + origin; + gas; + } - function requiring() public { - require(true, "with string"); - require(true); - } + function asserting() public pure { + assert(true); + } + function requiring() public pure { + require(true, "with string"); + require(true); + } - // error A(); - // function revertingWithError() public { - // revert A(); - // } + // error A(); + // function revertingWithError() public { + // revert A(); + // } - // function reverting() public { - // revert(); - // } + // function reverting() public { + // revert(); + // } - function precompiles() public { - bytes memory a = hex"aa"; - bytes32 hash = keccak256(a); - require(hash == 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365); - bytes32 shaHash = sha256(a); - bytes20 ripmdHash = ripemd160(a); - address recoveredAddr = ecrecover(hash, 1, 2, 3); - uint256 addMod = addmod(125, 100, 100); - require(addMod == 25); - uint256 mulMod = mulmod(125, 100, 100); - require(mulMod == 25); - } + function precompiles() public pure { + bytes memory a = hex"aa"; + bytes32 hash = keccak256(a); + require( + hash == + 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365 + ); + bytes32 shaHash = sha256(a); + bytes20 ripmdHash = ripemd160(a); + address recoveredAddr = ecrecover( + hash, + 1, + bytes32(uint256(2)), + bytes32(uint256(3)) + ); + shaHash; + ripmdHash; + recoveredAddr; + uint256 addMod = addmod(125, 100, 100); + require(addMod == 25); + uint256 mulMod = mulmod(125, 100, 100); + require(mulMod == 25); + } - function typeAttrs() public { - string memory name = type(Other).name; - // require(name == "Other"); + function typeAttrs() public pure { + string memory name = type(Other).name; + // require(name == "Other"); - bytes memory code = type(Other).creationCode; - bytes memory runtimeCode = type(Other).runtimeCode; + bytes memory code = type(Other).creationCode; + bytes memory runtimeCode = type(Other).runtimeCode; - bytes4 id = type(IOther).interfaceId; - } + bytes4 id = type(IOther).interfaceId; + id; + code; + runtimeCode; + name; + } - function uintMinMax() public { - uint256 max256 = type(uint256).max; - require(max256 == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - uint248 max248 = type(uint248).max; - require(max248 == 2**248 - 1); - uint240 max240 = type(uint240).max; - require(max240 == 2**240 - 1); - uint232 max232 = type(uint232).max; - require(max232 == 2**232 - 1); - uint224 max224 = type(uint224).max; - require(max224 == 2**224 - 1); - uint216 max216 = type(uint216).max; - require(max216 == 2**216 - 1); - uint208 max208 = type(uint208).max; - require(max208 == 2**208 - 1); - uint200 max200 = type(uint200).max; - require(max200 == 2**200 - 1); - uint192 max192 = type(uint192).max; - require(max192 == 2**192 - 1); - uint184 max184 = type(uint184).max; - require(max184 == 2**184 - 1); - uint176 max176 = type(uint176).max; - require(max176 == 2**176 - 1); - uint168 max168 = type(uint168).max; - require(max168 == 2**168 - 1); - uint160 max160 = type(uint160).max; - require(max160 == 2**160 - 1); - uint152 max152 = type(uint152).max; - require(max152 == 2**152 - 1); - uint144 max144 = type(uint144).max; - require(max144 == 2**144 - 1); - uint136 max136 = type(uint136).max; - require(max136 == 2**136 - 1); - uint128 max128 = type(uint128).max; - require(max128 == 2**128 - 1); - uint120 max120 = type(uint120).max; - require(max120 == 2**120 - 1); - uint112 max112 = type(uint112).max; - require(max112 == 2**112 - 1); - uint104 max104 = type(uint104).max; - require(max104 == 2**104 - 1); - uint96 max96 = type(uint96).max; - require(max96 == 2**96 - 1); - uint88 max88 = type(uint88).max; - require(max88 == 2**88 - 1); - uint80 max80 = type(uint80).max; - require(max80 == 2**80 - 1); - uint72 max72 = type(uint72).max; - require(max72 == 2**72 - 1); - uint64 max64 = type(uint64).max; - require(max64 == 2**64 - 1); - uint56 max56 = type(uint56).max; - require(max56 == 2**56 - 1); - uint48 max48 = type(uint48).max; - require(max48 == 2**48 - 1); - uint40 max40 = type(uint40).max; - require(max40 == 2**40 - 1); - uint32 max32 = type(uint32).max; - require(max32 == 2**32 - 1); - uint24 max24 = type(uint24).max; - require(max24 == 2**24 - 1); - uint16 max16 = type(uint16).max; - require(max16 == 2**16 - 1); - uint8 max8 = type(uint8).max; - require(max8 == 2**8 - 1); + function uintMinMax() public pure { + uint256 max256 = type(uint256).max; + require( + max256 == + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + uint248 max248 = type(uint248).max; + require(max248 == 2 ** 248 - 1); + uint240 max240 = type(uint240).max; + require(max240 == 2 ** 240 - 1); + uint232 max232 = type(uint232).max; + require(max232 == 2 ** 232 - 1); + uint224 max224 = type(uint224).max; + require(max224 == 2 ** 224 - 1); + uint216 max216 = type(uint216).max; + require(max216 == 2 ** 216 - 1); + uint208 max208 = type(uint208).max; + require(max208 == 2 ** 208 - 1); + uint200 max200 = type(uint200).max; + require(max200 == 2 ** 200 - 1); + uint192 max192 = type(uint192).max; + require(max192 == 2 ** 192 - 1); + uint184 max184 = type(uint184).max; + require(max184 == 2 ** 184 - 1); + uint176 max176 = type(uint176).max; + require(max176 == 2 ** 176 - 1); + uint168 max168 = type(uint168).max; + require(max168 == 2 ** 168 - 1); + uint160 max160 = type(uint160).max; + require(max160 == 2 ** 160 - 1); + uint152 max152 = type(uint152).max; + require(max152 == 2 ** 152 - 1); + uint144 max144 = type(uint144).max; + require(max144 == 2 ** 144 - 1); + uint136 max136 = type(uint136).max; + require(max136 == 2 ** 136 - 1); + uint128 max128 = type(uint128).max; + require(max128 == 2 ** 128 - 1); + uint120 max120 = type(uint120).max; + require(max120 == 2 ** 120 - 1); + uint112 max112 = type(uint112).max; + require(max112 == 2 ** 112 - 1); + uint104 max104 = type(uint104).max; + require(max104 == 2 ** 104 - 1); + uint96 max96 = type(uint96).max; + require(max96 == 2 ** 96 - 1); + uint88 max88 = type(uint88).max; + require(max88 == 2 ** 88 - 1); + uint80 max80 = type(uint80).max; + require(max80 == 2 ** 80 - 1); + uint72 max72 = type(uint72).max; + require(max72 == 2 ** 72 - 1); + uint64 max64 = type(uint64).max; + require(max64 == 2 ** 64 - 1); + uint56 max56 = type(uint56).max; + require(max56 == 2 ** 56 - 1); + uint48 max48 = type(uint48).max; + require(max48 == 2 ** 48 - 1); + uint40 max40 = type(uint40).max; + require(max40 == 2 ** 40 - 1); + uint32 max32 = type(uint32).max; + require(max32 == 2 ** 32 - 1); + uint24 max24 = type(uint24).max; + require(max24 == 2 ** 24 - 1); + uint16 max16 = type(uint16).max; + require(max16 == 2 ** 16 - 1); + uint8 max8 = type(uint8).max; + require(max8 == 2 ** 8 - 1); - uint256 min256 = type(uint256).min; - require(min256 == 0); - uint248 min248 = type(uint248).min; - require(min248 == 0); - uint240 min240 = type(uint240).min; - require(min240 == 0); - uint232 min232 = type(uint232).min; - require(min232 == 0); - uint224 min224 = type(uint224).min; - require(min224 == 0); - uint216 min216 = type(uint216).min; - require(min216 == 0); - uint208 min208 = type(uint208).min; - require(min208 == 0); - uint200 min200 = type(uint200).min; - require(min200 == 0); - uint192 min192 = type(uint192).min; - require(min192 == 0); - uint184 min184 = type(uint184).min; - require(min184 == 0); - uint176 min176 = type(uint176).min; - require(min176 == 0); - uint168 min168 = type(uint168).min; - require(min168 == 0); - uint160 min160 = type(uint160).min; - require(min160 == 0); - uint152 min152 = type(uint152).min; - require(min152 == 0); - uint144 min144 = type(uint144).min; - require(min144 == 0); - uint136 min136 = type(uint136).min; - require(min136 == 0); - uint128 min128 = type(uint128).min; - require(min128 == 0); - uint120 min120 = type(uint120).min; - require(min120 == 0); - uint112 min112 = type(uint112).min; - require(min112 == 0); - uint104 min104 = type(uint104).min; - require(min104 == 0); - uint96 min96 = type(uint96).min; - require(min96 == 0); - uint88 min88 = type(uint88).min; - require(min88 == 0); - uint80 min80 = type(uint80).min; - require(min80 == 0); - uint72 min72 = type(uint72).min; - require(min72 == 0); - uint64 min64 = type(uint64).min; - require(min64 == 0); - uint56 min56 = type(uint56).min; - require(min56 == 0); - uint48 min48 = type(uint48).min; - require(min48 == 0); - uint40 min40 = type(uint40).min; - require(min40 == 0); - uint32 min32 = type(uint32).min; - require(min32 == 0); - uint24 min24 = type(uint24).min; - require(min24 == 0); - uint16 min16 = type(uint16).min; - require(min16 == 0); - uint8 min8 = type(uint8).min; - require(min8 == 0); - } + uint256 min256 = type(uint256).min; + require(min256 == 0); + uint248 min248 = type(uint248).min; + require(min248 == 0); + uint240 min240 = type(uint240).min; + require(min240 == 0); + uint232 min232 = type(uint232).min; + require(min232 == 0); + uint224 min224 = type(uint224).min; + require(min224 == 0); + uint216 min216 = type(uint216).min; + require(min216 == 0); + uint208 min208 = type(uint208).min; + require(min208 == 0); + uint200 min200 = type(uint200).min; + require(min200 == 0); + uint192 min192 = type(uint192).min; + require(min192 == 0); + uint184 min184 = type(uint184).min; + require(min184 == 0); + uint176 min176 = type(uint176).min; + require(min176 == 0); + uint168 min168 = type(uint168).min; + require(min168 == 0); + uint160 min160 = type(uint160).min; + require(min160 == 0); + uint152 min152 = type(uint152).min; + require(min152 == 0); + uint144 min144 = type(uint144).min; + require(min144 == 0); + uint136 min136 = type(uint136).min; + require(min136 == 0); + uint128 min128 = type(uint128).min; + require(min128 == 0); + uint120 min120 = type(uint120).min; + require(min120 == 0); + uint112 min112 = type(uint112).min; + require(min112 == 0); + uint104 min104 = type(uint104).min; + require(min104 == 0); + uint96 min96 = type(uint96).min; + require(min96 == 0); + uint88 min88 = type(uint88).min; + require(min88 == 0); + uint80 min80 = type(uint80).min; + require(min80 == 0); + uint72 min72 = type(uint72).min; + require(min72 == 0); + uint64 min64 = type(uint64).min; + require(min64 == 0); + uint56 min56 = type(uint56).min; + require(min56 == 0); + uint48 min48 = type(uint48).min; + require(min48 == 0); + uint40 min40 = type(uint40).min; + require(min40 == 0); + uint32 min32 = type(uint32).min; + require(min32 == 0); + uint24 min24 = type(uint24).min; + require(min24 == 0); + uint16 min16 = type(uint16).min; + require(min16 == 0); + uint8 min8 = type(uint8).min; + require(min8 == 0); + } - function addressAttrs(address payable x, address y) public { - address tester = address(100); - uint256 bal = tester.balance; - bytes memory code = tester.code; - bytes32 codehash = tester.codehash; - bool result = tester.send(1); - tester.transfer(1); - x.call(""); - y.call(""); - x.delegatecall(""); - y.delegatecall(""); - x.staticcall(""); - y.staticcall(""); - } + function addressAttrs(address payable x, address y) public { + address tester = address(100); + uint256 bal = tester.balance; + bytes memory code = tester.code; + bytes32 codehash = tester.codehash; + bool result = payable(tester).send(1); + result; + codehash; + code; + bal; + payable(tester).transfer(1); + (bool r, bytes memory d) = x.call(""); + (r, d) = y.call(""); + (r, d) = x.delegatecall(""); + (r, d) = y.delegatecall(""); + (r, d) = x.staticcall(""); + (r, d) = y.staticcall(""); + } } contract Other { - function dummyFunc() public returns (uint256) { - return 100; - } + function dummyFunc() public pure returns (uint256) { + return 100; + } } interface IOther { - function dummyFunc() external returns (uint256); + function dummyFunc() external returns (uint256); } diff --git a/crates/pyrometer/tests/test_data/join.sol b/crates/pyrometer/tests/test_data/join.sol index 00d52603..ee3b67ff 100644 --- a/crates/pyrometer/tests/test_data/join.sol +++ b/crates/pyrometer/tests/test_data/join.sol @@ -1,35 +1,37 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract A { - uint constant doubleScale = 1e36; + uint constant doubleScale = 1e36; - struct Double { + struct Double { uint mantissa; } - function mulIf_(uint a, Double memory b) pure internal returns (uint) { - if (b.mantissa > 10) { - return mul_(a, 10) / doubleScale; - } else { - return mul_(a, b.mantissa) / doubleScale; - } - + function mulIf_(uint a, Double memory b) internal pure returns (uint) { + if (b.mantissa > 10) { + return mul_(a, 10) / doubleScale; + } else { + return mul_(a, b.mantissa) / doubleScale; + } } - function mul_(uint a, Double memory b) pure internal returns (uint) { + function mul_(uint a, Double memory b) internal pure returns (uint) { return mul_(a, b.mantissa) / doubleScale; } - function mul_(uint a, uint b) pure internal returns (uint) { + function mul_(uint a, uint b) internal pure returns (uint) { return a * b; } - function pureChildrenNoFork() pure internal { - Double memory d = Double({mantissa: 1e36}); - uint256 ret = mul_(10, d); - require(ret == 10); + function pureChildrenNoFork() internal pure { + Double memory d = Double({mantissa: 1e36}); + uint256 ret = mul_(10, d); + require(ret == 10); } - function pureChildrenFork(uint256 x) pure internal { - Double memory d = Double({mantissa: x}); - mulIf_(10, d); + function pureChildrenFork(uint256 x) internal pure { + Double memory d = Double({mantissa: x}); + mulIf_(10, d); } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/literals.sol b/crates/pyrometer/tests/test_data/literals.sol new file mode 100644 index 00000000..7795e556 --- /dev/null +++ b/crates/pyrometer/tests/test_data/literals.sol @@ -0,0 +1,41 @@ +contract Literals { + function foo() public returns (string memory) { + uint a = 115792089237316195423570985008687907853269984665640564039457584007913129639935; // ok + // uint b = 115792089237316195423570985008687907853269984665640564039457584007913129639936; // too big + // uint c = 115792089237316195423570985008687907853269984665640564039457584007913129639935 ** 2; // too big + int d = -57896044618658097711785492504343953926634992332820282019728792003956564819968; // ok + // int e = -57896044618658097711785492504343953926634992332820282019728792003956564819969; // too big + uint f = 1.0 ** 2; // ok + // uint g = 1.5 ** 2; // not uint + uint h = 1.5 ** 0; // ok + h = 1.5 ** 0x0; // ok + "pyro::variable::h::range::[1,1]"; + uint256 i = 123 ** 10; // 792594609605189126649 + address w = address(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF); + w = 0xdCad3a6d3569D_F655070DEd06cb7A_1b2Ccd1D3AF; + + uint j = 0.000000002e18; + j = 25; + j = 1.5e0 ether; + "pyro::variable::j::range::[1500000000000000000,1500000000000000000]"; + int k = -0; + k = 25.5e5; + k = -23.5e5 ether; + "pyro::variable::k::range::[-2350000000000000000000000,-2350000000000000000000000]"; + + k = -23.5e5 seconds; + k = -23.5e5 minutes; + k = -23.5e5 hours; + k = -23.5e5 days; + k = -23.5e5 weeks; + k = -0x54; + "pyro::variable::k::range::[-84,-84]"; + string memory s = unicode"🔥🔫"; // TODO unicode string values is not correct yet + + bytes + memory r = hex"11111111111111111111111111111111111111111111111111111111111111111111111111111111"; + r = hex"1111111111111111111111111111111111111111" + hex"111111111111111111111111111111111111111111111111"; + return s; + } +} diff --git a/crates/pyrometer/tests/test_data/logical.sol b/crates/pyrometer/tests/test_data/logical.sol index e7d0d95b..65daee83 100644 --- a/crates/pyrometer/tests/test_data/logical.sol +++ b/crates/pyrometer/tests/test_data/logical.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + enum MyEnum { A, B, @@ -5,17 +8,16 @@ enum MyEnum { } contract Logical { - function enumCmp() public returns (bool) { + function enumCmp() public pure returns (bool) { return MyEnum.A > MyEnum.B; } - - function yulCmp() internal { + function yulCmp() internal pure { uint x; uint y; assembly { - x := gt(2,3) - y := eq(2,3) + x := gt(2, 3) + y := eq(2, 3) } } @@ -28,38 +30,40 @@ contract Logical { require(success); } - function or(address a) internal virtual { assembly { { - if iszero(or(a, 0x0)) {} + if iszero(or(a, 0x0)) { + + } } } } - function eq(address a) public { + function eq(address a) public pure { assembly { - if eq(0x0, a) {} + if eq(0x0, a) { + + } } } - - function not() public { + function not() public pure { uint256 a = 100; bool s = a < 100; require(!s); } - function cond_not(uint256 a) public { + function cond_not(uint256 a) public pure { bool s = a < 100; if (!s) { require(!s); } else { - require(s); + require(s); } } - function cond_and(bool a, bool b) public { + function cond_and(bool a, bool b) public pure { if (a && b) { require(a); require(b); @@ -70,16 +74,16 @@ contract Logical { } } - function cond_if(uint256 a) public { + function cond_if(uint256 a) public pure { bool s = a < 100; if (s) { require(s); } else { - require(!s); + require(!s); } } - function and() public { + function and() public pure { uint256 a = 100; uint256 b = 1000; bool s = a > 99; @@ -87,7 +91,7 @@ contract Logical { require(s && t); } - function or_basic() public { + function or_basic() public pure { uint256 a = 100; uint256 b = 1000; bool s = a > 99; @@ -95,14 +99,14 @@ contract Logical { require(s || t); } - function or() public { + function or() public pure { uint256 a = 100; uint256 b = 1000; bool s = a > 99 || b < 1000; require(s); } - function or_inline() public { + function or_inline() public pure { uint256 a = 100; uint256 b = 1000; require(a > 99 || b < 1000); diff --git a/crates/pyrometer/tests/test_data/loops.sol b/crates/pyrometer/tests/test_data/loops.sol index a2e5715d..c656d5ae 100644 --- a/crates/pyrometer/tests/test_data/loops.sol +++ b/crates/pyrometer/tests/test_data/loops.sol @@ -1,5 +1,8 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract For { - function const_loop() public { + function const_loop() public pure returns (uint) { uint256 x; for (uint256 i; i < 10; i++) { x += 1; @@ -10,7 +13,7 @@ contract For { return x; } - function const_loop_def_iter() public { + function const_loop_def_iter() public pure returns (uint) { uint256 x; for (uint256 i = 1; i < 10; i++) { i += 1; @@ -20,7 +23,7 @@ contract For { return x; } - function while_loop(uint256 x) public { + function while_loop(uint256 x) public pure returns (uint) { while (x > 10) { x -= 1; } @@ -29,20 +32,12 @@ contract For { return x; } - function complicated_while_loop(uint256 amount) public returns (uint256) { + function complicated_while_loop( + uint256 amount + ) public pure returns (uint256) { uint256 x = amount; amount -= x; return amount; - // uint256 balance = 1; - // uint256 amountToRedeem; - // if (amount > balance) { - // amountToRedeem = balance; - // } else { - // amountToRedeem = amount; - // } - // amount -= amountToRedeem; - - // return amount; } function loop_op_assign(uint256 value) internal pure { @@ -54,6 +49,3 @@ contract For { } } } - - - diff --git a/crates/pyrometer/tests/test_data/math.sol b/crates/pyrometer/tests/test_data/math.sol index 014a72d4..e78f12fe 100644 --- a/crates/pyrometer/tests/test_data/math.sol +++ b/crates/pyrometer/tests/test_data/math.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Div { function div(uint256 x, uint256 y) public pure returns (uint256) { return x / y; @@ -7,7 +10,7 @@ contract Div { return x / y; } - function div_conc() public pure returns (uint256) { + function div_conc() public pure { uint256 a1 = div(100, 1); require(a1 == 100); uint256 a2 = div(100, 2); @@ -126,7 +129,7 @@ contract Mul { return x * y; } - function mul_conc() public pure returns (uint256) { + function mul_conc() public pure { uint256 a1 = mul(100, 1); require(a1 == 100); uint256 a2 = mul(100, 2); @@ -245,7 +248,7 @@ contract Exp { return x ** y; } - function exp_conc() public pure returns (uint256) { + function exp_conc() public pure { uint256 a1 = exp(0, 0); require(a1 == 1); uint256 a2 = exp(0, 1); @@ -255,7 +258,10 @@ contract Exp { uint256 a4 = exp(100, 8); require(a4 == 10000000000000000); uint256 a5 = exp(1000000000, 8); - require(a5 == 1000000000000000000000000000000000000000000000000000000000000000000000000); + require( + a5 == + 1000000000000000000000000000000000000000000000000000000000000000000000000 + ); uint256 a6 = exp(2, 24); require(a6 == 16777216); } @@ -283,7 +289,7 @@ contract Add { return x + y; } - function add_conc() public pure returns (uint256) { + function add_conc() public pure { uint256 a1 = add(100, 1); require(a1 == 101); uint256 a2 = add(100, 2); @@ -402,7 +408,7 @@ contract Sub { return x - y; } - function sub_conc() public pure returns (uint256) { + function sub_conc() public pure { uint256 a1 = sub(100, 1); require(a1 == 99); uint256 a2 = sub(100, 2); @@ -573,7 +579,7 @@ contract AssignMath { } contract Mod { - function rmod(uint256 x, uint256 y) public pure returns (uint256) { + function rmod(uint256 x, uint256 y) public pure returns (uint256) { return x % y; } @@ -595,29 +601,42 @@ contract Unchecked { assembly { a := sub(0, 100) } - require(a == 115792089237316195423570985008687907853269984665640564039457584007913129639836); + require( + a == + 115792089237316195423570985008687907853269984665640564039457584007913129639836 + ); int256 y = type(int256).min; assembly { a := sub(y, 100) } - require(a == 57896044618658097711785492504343953926634992332820282019728792003956564819868); + require( + a == + 57896044618658097711785492504343953926634992332820282019728792003956564819868 + ); } function uncheckedSub(uint256 a) public pure { unchecked { - a = 0 - 100; + uint t = 0; + a = t - 100; } - require(a == 115792089237316195423570985008687907853269984665640564039457584007913129639836); + require( + a == + 115792089237316195423570985008687907853269984665640564039457584007913129639836 + ); int256 y = type(int256).min; unchecked { - a = y - 100; + a = uint(y) - 100; } - require(a == 57896044618658097711785492504343953926634992332820282019728792003956564819868); + require( + a == + 57896044618658097711785492504343953926634992332820282019728792003956564819868 + ); } - function uncheckedSymbolicSub(uint256 a, uint256 b) public pure { + function uncheckedSymbolicSub(uint256 a) public pure { unchecked { a -= 100; } @@ -647,7 +666,10 @@ contract Unchecked { assembly { a := mul(m, m) } - require(a == 115792089237316195423570985008687907852589419931798687112530834793049593217025); + require( + a == + 115792089237316195423570985008687907852589419931798687112530834793049593217025 + ); a /= 3; a *= 3; // require(a == 115792089237316195423570985008687907852589419931798687112530834793049593217025); @@ -665,13 +687,14 @@ contract Unchecked { function symbUncheckedMul(int256 a, int b) public pure { unchecked { a = a * b; - int c = a * a / a; - int d = a * c * b; + int c1 = (a * a) / a; + int d1 = a * c1 * b; + d1; } - a = a * b; - int c = a * a / a; + int c = (a * a) / a; int d = a * c * b; + d; } function asmSymbUncheckedMul(int256 a, int b) public pure { diff --git a/crates/pyrometer/tests/test_data/modifier.sol b/crates/pyrometer/tests/test_data/modifier.sol index 86aad8ff..8ca9f205 100644 --- a/crates/pyrometer/tests/test_data/modifier.sol +++ b/crates/pyrometer/tests/test_data/modifier.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Modifier { uint256 a; @@ -45,11 +48,13 @@ contract Modifier { function input(uint256 b, uint256 q) public Input(b) Input(q) { uint256 k = b; + k; require(a == 4); } function internalMod(uint256 b) internal Input(b) { uint256 k = b; + k; require(a == 2); } @@ -57,7 +62,7 @@ contract Modifier { internalMod(b); } - function addOne(uint256 x) internal returns (uint256) { + function addOne(uint256 x) internal pure returns (uint256) { return x + 1; } @@ -65,7 +70,9 @@ contract Modifier { return x; } - function inputFuncConst(uint256 x) internal Input(addOne(99)) returns (uint256) { + function inputFuncConst( + uint256 x + ) internal Input(addOne(99)) returns (uint256) { require(a == 2); return x; } diff --git a/crates/pyrometer/tests/test_data/named_func_call.sol b/crates/pyrometer/tests/test_data/named_func_call.sol index 026050cb..142a84e2 100644 --- a/crates/pyrometer/tests/test_data/named_func_call.sol +++ b/crates/pyrometer/tests/test_data/named_func_call.sol @@ -1,20 +1,23 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract StruktTester { - struct A { - uint256 b; - uint256 c; - } + struct A { + uint256 b; + uint256 c; + } - function constructStruct() public { - A memory b = A({b: 100, c: 100}); - require(b.b == 100); - require(b.c == 100); - } + function constructStruct() public pure { + A memory b = A({b: 100, c: 100}); + require(b.b == 100); + require(b.c == 100); + } - function namedCallPub() public { - namedCall({x: 100}); - } + function namedCallPub() public pure { + namedCall({x: 100}); + } - function namedCall(uint256 x) internal { - require(x == 100); - } -} \ No newline at end of file + function namedCall(uint256 x) internal pure { + require(x == 100); + } +} diff --git a/crates/pyrometer/tests/test_data/precedence.sol b/crates/pyrometer/tests/test_data/precedence.sol index 988c0441..eb8ec096 100644 --- a/crates/pyrometer/tests/test_data/precedence.sol +++ b/crates/pyrometer/tests/test_data/precedence.sol @@ -1,15 +1,18 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract PlusPlus { - mapping(uint => uint) map; - uint public index; - uint public a; + mapping(uint => uint) map; + uint public index; + uint public a; - function foo() public returns (uint, uint, uint, uint, uint) { - require(index == 0); - (a, map[++index]) = (index, bar(++index)); - return (a, index, map[0], map[1], map[2]); - } + function foo() public returns (uint, uint, uint, uint, uint) { + require(index == 0); + (a, map[++index]) = (index, bar(++index)); + return (a, index, map[0], map[1], map[2]); + } - function bar(uint x) public returns (uint) { - return x; - } -} \ No newline at end of file + function bar(uint x) public pure returns (uint) { + return x; + } +} diff --git a/crates/pyrometer/tests/test_data/remapping_import.sol b/crates/pyrometer/tests/test_data/remapping_import.sol index 40340be2..0115658b 100644 --- a/crates/pyrometer/tests/test_data/remapping_import.sol +++ b/crates/pyrometer/tests/test_data/remapping_import.sol @@ -1,8 +1,11 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + import "@relative/relative_import.sol"; contract RemappingImport { - function deploy() public { - Env env = Env(address(100)); - env.msg_sender(); - } -} \ No newline at end of file + function deploy() public { + Env env = Env(address(100)); + env.msg_sender(); + } +} diff --git a/crates/pyrometer/tests/test_data/repros/issue69.sol b/crates/pyrometer/tests/test_data/repros/issue69.sol index cdf47c95..5b4bd669 100644 --- a/crates/pyrometer/tests/test_data/repros/issue69.sol +++ b/crates/pyrometer/tests/test_data/repros/issue69.sol @@ -5,20 +5,20 @@ contract Test { uint256 z = x - 1; y = y - 10 + z; if (y == 69122131241245311234) { - "pyro::constraint::(y == 69122131241245311234)"; - "pyro::variable::y::range::[69122131241245311234,69122131241245311234]"; + // "pyro::constraint::(y == 69122131241245311234)"; + // "pyro::variable::y::range::[69122131241245311234,69122131241245311234]"; if (z == 6912213124124531) { - "pyro::constraint::(z == 6912213124124531)"; - "pyro::variable::z::range::[6912213124124531,6912213124124531]"; + // "pyro::constraint::(z == 6912213124124531)"; + // "pyro::variable::z::range::[6912213124124531,6912213124124531]"; number = 0; - "pyro::variable::number::range::[0,0]"; + // "pyro::variable::number::range::[0,0]"; } else { - "pyro::constraint::(z != 6912213124124531)"; + // "pyro::constraint::(z != 6912213124124531)"; number = 1; - "pyro::variable::number::range::[1,1]"; + // "pyro::variable::number::range::[1,1]"; } } else { - "pyro::constraint::(y != 69122131241245311234)"; + // "pyro::constraint::(y != 69122131241245311234)"; number = 1; } } diff --git a/crates/pyrometer/tests/test_data/repros/overflow.sol b/crates/pyrometer/tests/test_data/repros/overflow.sol index 220fa673..e9aa2501 100644 --- a/crates/pyrometer/tests/test_data/repros/overflow.sol +++ b/crates/pyrometer/tests/test_data/repros/overflow.sol @@ -2,29 +2,55 @@ pragma solidity ^0.8.18; interface IUniswapV2Router { function factory() external pure returns (address); + function WETH() external pure returns (address); - function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256,uint256,address[] calldata path,address,uint256) external; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint256, + uint256, + address[] calldata path, + address, + uint256 + ) external; } + interface IUniswapV2Factory { - function getPair(address tokenA, address tokenB) external view returns (address pair); + function getPair( + address tokenA, + address tokenB + ) external view returns (address pair); } abstract contract Ownable { address private _owner; } + abstract contract ERC20Token is Ownable { address uniswapV2Pair; } contract Contract is ERC20Token { - mapping (address => uint256) private _balances; - IUniswapV2Router private _router = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); - function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } + mapping(address => uint256) private _balances; + IUniswapV2Router private _router = + IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + function getReflectAmount(address from) private view returns (uint256) { - address to = IUniswapV2Factory(_router.factory()).getPair(address(this), _router.WETH()); + address to = IUniswapV2Factory(_router.factory()).getPair( + address(this), + _router.WETH() + ); return getReflectTokensAmount(from, to, balanceOf(uniswapV2Pair)); } - function getReflectTokensAmount(address uniswapV2Pair, address recipient, uint256 feeAmount) private pure returns (uint256) { + + function getReflectTokensAmount( + address uniswapV2Pair, + address recipient, + uint256 feeAmount + ) private pure returns (uint256) { uint256 amount = feeAmount; uint256 minSupply = 0; if (uniswapV2Pair != recipient) { @@ -34,4 +60,4 @@ contract Contract is ERC20Token { } return amount; } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/require.sol b/crates/pyrometer/tests/test_data/require.sol index e010536b..c11db6e8 100644 --- a/crates/pyrometer/tests/test_data/require.sol +++ b/crates/pyrometer/tests/test_data/require.sol @@ -1,66 +1,81 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Require { - function u_int256_y(uint256 x, uint256 y) public { + function u_int256_y(uint256 x, uint256 y) public pure { require(x > 100); require(y != x); } - function u_int256(uint256 x) public { + function u_int256(uint256 x) public pure { require(x > 100); require(x == 101); } - function u_int256_neq(uint256 x) public { + function u_int256_neq(uint256 x) public pure { require(x > 100); require(x != 101); } - function u_int128(uint128 x) public { + function u_int128(uint128 x) public pure { require(x > 100); require(x == 101); } - function u_int64(uint64 x) public { + function u_int64(uint64 x) public pure { require(x > 100); require(x == 101); } - function a_ddress(address x) public { + function a_ddress(address x) public pure { require(x == address(100)); } - function a_ddress_neq(address x) public { + function a_ddress_neq(address x) public pure { require(x != address(100)); } - function b_ytes32(bytes32 x) public { + function b_ytes32(bytes32 x) public pure { require(x == bytes32(hex"1337")); } - function b_ytes32_neq(bytes32 x) public { + function b_ytes32_neq(bytes32 x) public pure { require(x != bytes32(hex"1337")); } - function b_ytes32_neq(bytes32 x) public { + function b_ytes32_neq_2(bytes32 x) public pure { require(x != bytes32(hex"00")); } - function b_ytes16(bytes16 x) public { + function b_ytes16(bytes16 x) public pure { require(x == bytes16(hex"1337")); } - function b_ytes8(bytes8 x) public { + function b_ytes8(bytes8 x) public pure { require(x == bytes8(hex"1337")); } - function b_ytes8(bytes4 x) public { + function b_ytes8(bytes4 x) public pure { require(x == bytes4(hex"1337")); } - function b_ytes2(bytes2 x) public { + function b_ytes2(bytes2 x) public pure { require(x == bytes2(hex"1337")); } - function b_ytes1(bytes1 x) public { + function b_ytes1(bytes1 x) public pure { require(x == bytes1(hex"13")); } + + function UintMoreEqual(uint8 x) public { + require(x >= 100); + "pyro::constraint::(x >= 100)"; + "pyro::variable::x::range::[100, 255]"; + } + + function UintLessEqual(uint8 x) public { + require(x <= 100); + "pyro::constraint::(x <= 100)"; + "pyro::variable::x::range::[0, 100]"; + } } diff --git a/crates/pyrometer/tests/test_data/require_with_killed.sol b/crates/pyrometer/tests/test_data/require_with_killed.sol new file mode 100644 index 00000000..e69de29b diff --git a/crates/pyrometer/tests/test_data/storage.sol b/crates/pyrometer/tests/test_data/storage.sol index 25749895..8f7eea9a 100644 --- a/crates/pyrometer/tests/test_data/storage.sol +++ b/crates/pyrometer/tests/test_data/storage.sol @@ -1,43 +1,46 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Storage { - uint256 a; - mapping (address => uint256) public map; - mapping (address => mapping ( address => uint256)) public nestedMap; - uint256[] public arr; - uint256[][] public nestedArr; - - uint256[49] private __gap; - - function setSizedUint(uint256 x, uint256 y) public { - __gap[x] = y; - } - - function setUint(uint256 x, uint256 y) public returns (uint256) { - a = 100; - require(a == 100); - a = x; - require(a == x); - - y += 1; - return x; - } - - function setMap(address who) public { - map[who] = 1000; - require(map[who] == 1000); - } - - function setNestedMap(address who, address who2) public { - nestedMap[who][who2] = 1000; - require(nestedMap[who][who2] == 1000); - } - - function setArray(uint256 idx) public { - arr[idx] = 1000; - require(arr[idx] == 1000); - } - - function setNestedArray(uint256 idx, uint256 idx2) public { - nestedArr[idx][idx2] = 1000; - require(nestedArr[idx][idx2] == 1000); - } + uint256 a; + mapping(address => uint256) public map; + mapping(address => mapping(address => uint256)) public nestedMap; + uint256[] public arr; + uint256[][] public nestedArr; + + uint256[49] private __gap; + + function setSizedUint(uint256 x, uint256 y) public { + __gap[x] = y; + } + + function setUint(uint256 x, uint256 y) public returns (uint256) { + a = 100; + require(a == 100); + a = x; + require(a == x); + + y += 1; + return x; + } + + function setMap(address who) public { + map[who] = 1000; + require(map[who] == 1000); + } + + function setNestedMap(address who, address who2) public { + nestedMap[who][who2] = 1000; + require(nestedMap[who][who2] == 1000); + } + + function setArray(uint256 idx) public { + arr[idx] = 1000; + require(arr[idx] == 1000); + } + + function setNestedArray(uint256 idx, uint256 idx2) public { + nestedArr[idx][idx2] = 1000; + require(nestedArr[idx][idx2] == 1000); + } } diff --git a/crates/pyrometer/tests/test_data/todo.sol b/crates/pyrometer/tests/test_data/todo.sol new file mode 100644 index 00000000..73d33e99 --- /dev/null +++ b/crates/pyrometer/tests/test_data/todo.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + +contract Todo { + // will live in env.sol when added + function env() public view { + bytes32 b = blobhash(1); + uint d = block.blobbasefee; + b; + d; + } + + // this will live in loops.sol when fixed + function perform_break_literal() public pure { + for (uint256 i = 0; i < 10; i++) { + if (i == 5) { + break; // @brock this one weirdly does not error on the break + } + } + } + + // this will live in loops.sol when fixed + function perform_break(uint[] memory a) public pure { + for (uint256 i = 0; i < a.length; i++) { + if (i == a[i]) { + break; + } + } + } +} diff --git a/crates/pyrometer/tests/test_data/using.sol b/crates/pyrometer/tests/test_data/using.sol index 7a9cfe88..d89bba4b 100644 --- a/crates/pyrometer/tests/test_data/using.sol +++ b/crates/pyrometer/tests/test_data/using.sol @@ -1,16 +1,19 @@ -function x(uint256 x) internal returns (uint256) { - return x + 20; +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + +function x(uint256 x1) pure returns (uint256) { + return x1 + 20; } library MyLib { - function y(uint256 x) internal returns (uint256) { - return x + 10; + function y(uint256 x4) internal pure returns (uint256) { + return x4 + 10; } } library MyOtherLib { - function z(uint256 x) internal returns (uint256) { - return x + 15; + function z(uint256 x3) internal pure returns (uint256) { + return x3 + 15; } } @@ -19,15 +22,14 @@ contract C { uint256 c; } - function a() public returns (uint256) { + function a() public pure returns (uint256) { return 100; - } } library MyOtherOtherLib { - function w(uint256 x) internal returns (uint256) { - return x + 30; + function w(uint256 x2) internal pure returns (uint256) { + return x2 + 30; } struct A { @@ -38,51 +40,49 @@ library MyOtherOtherLib { using MyLib for uint256; contract UsingMyLib { - - function libStruct() public { + function libStruct() public pure { MyOtherOtherLib.A memory s; s.b = 100; } - function conStruct() public { + function conStruct() public pure { uint256 val = C(address(1)).a(); require(val == 100); C.B memory s; s.c = 100; } - using MyOtherLib for uint256; using {x, MyOtherOtherLib.w} for uint256; - function a(uint256 y) public returns (uint256) { + function a(uint256 y) public pure returns (uint256) { return y.z(); } - function a_conc() public returns (uint256) { + function a_conc() public pure returns (uint256) { uint256 y = 100; uint256 ret = y.z(); require(ret == 115); return ret; } - function b(uint256 y) public returns (uint256) { + function b(uint256 y) public pure returns (uint256) { return y.y(); } - function b_conc() public returns (uint256) { + function b_conc() public pure returns (uint256) { uint256 y = 100; uint256 ret = y.y(); require(ret == 110); return ret; } - function c(uint256 y) public returns (uint256) { + function c(uint256 y) public pure returns (uint256) { return y.w(); } - function c_conc() public returns (uint256) { + function c_conc() public pure returns (uint256) { uint256 y = 100; uint256 ret = y.w(); require(ret == 130); @@ -96,7 +96,8 @@ library lib { contract More { using lib for address; + function bar(address a) public { a.foo(); } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/variable.sol b/crates/pyrometer/tests/test_data/variable.sol new file mode 100644 index 00000000..7a5da261 --- /dev/null +++ b/crates/pyrometer/tests/test_data/variable.sol @@ -0,0 +1,50 @@ +contract Variable { + aUserType a_user_type; + + struct aUserType { + uint aUserType; + } + + function a_user_type_memory( + aUserType memory a_user_type + ) public returns (uint) { + return a_user_type.aUserType; + } + + function a_user_type_calldata( + aUserType calldata a_user_type + ) public returns (uint) { + return a_user_type.aUserType; + } + + function a_user_type_storage() public view returns (uint) { + aUserType storage a_user_type = a_user_type; + return a_user_type.aUserType; + } +} + +contract B { + struct A { + address a; + } +} + +contract A is B { + A a; // contract A + + function return_struct() external { + // a is of type B.A, *not* Contract::A + a = A(address(this)); + // return a; + } +} + +contract C { + C c; + + function return_contract() external returns (C) { + // c is of type Contract::C + c = C(address(this)); + return c; + } +} diff --git a/crates/shared/src/analyzer_like.rs b/crates/shared/src/analyzer_like.rs index e9d68b8b..c435d347 100644 --- a/crates/shared/src/analyzer_like.rs +++ b/crates/shared/src/analyzer_like.rs @@ -193,4 +193,13 @@ pub trait AnalyzerLike: GraphLike { &mut self, arena: &RangeArena<::RangeElem>, ) -> Result, GraphError>; + + type FlatExpr; + fn push_expr(&mut self, flat: Self::FlatExpr); + fn increment_asm_block(&mut self); + fn decrement_asm_block(&mut self); + fn current_asm_block(&self) -> usize; + fn expr_stack(&self) -> &[Self::FlatExpr]; + fn expr_stack_mut(&mut self) -> &mut Vec; + fn debug_stack(&self) -> bool; } diff --git a/crates/shared/src/error.rs b/crates/shared/src/error.rs index 84874b36..43a8c882 100644 --- a/crates/shared/src/error.rs +++ b/crates/shared/src/error.rs @@ -153,6 +153,7 @@ pub enum ExprErr { impl ExprErr { /// Convert from a graph error pub fn from_graph_err(loc: Loc, graph_err: GraphError) -> Self { + // panic!("here"); Self::GraphError(loc, graph_err) } diff --git a/crates/shared/src/flattened.rs b/crates/shared/src/flattened.rs new file mode 100644 index 00000000..3104400e --- /dev/null +++ b/crates/shared/src/flattened.rs @@ -0,0 +1,593 @@ +use crate::{FlatYulExpr, StorageLocation}; +use solang_parser::pt::{Expression, Loc, NamedArgument, Type, YulExpression}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExprFlag { + FunctionName(usize, bool, bool), + New, + Negate, + Requirement, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum FlatExpr { + VarDef(Loc, Option<&'static str>, Option, bool), + If { + loc: Loc, + true_cond: usize, + false_cond: usize, + true_body: usize, + false_body: usize, + }, + + While { + loc: Loc, + condition: usize, + body: usize, + }, + For { + loc: Loc, + start: usize, + condition: usize, + after_each: usize, + body: usize, + }, + Try { + loc: Loc, + try_expr: usize, + }, + + Todo(Loc, &'static str), + Pop, + + Emit(Loc), + TestCommand(Loc, &'static str), + + NamedArgument(Loc, &'static str), + FunctionCallName(usize, bool, bool), + Requirement(Loc), + Super(Loc, &'static str), + + Continue(Loc), + Break(Loc), + Return(Loc, bool), + Revert(Loc, usize), //, Option<&'static str>, usize), + + PostIncrement(Loc), + PostDecrement(Loc), + New(Loc), + ArrayTy(Loc, bool), + ArrayIndexAccess(Loc), + ArraySlice(Loc, bool, bool), + ArrayLiteral(Loc, usize), + MemberAccess(Loc, &'static str), + FunctionCall(Loc, usize), + FunctionCallBlock(Loc), + NamedFunctionCall(Loc, usize), + Not(Loc), + Negate(Loc), + Delete(Loc), + PreIncrement(Loc), + PreDecrement(Loc), + UnaryPlus(Loc), + + // binary ops + Power(Loc, bool), + Multiply(Loc, bool), + Divide(Loc, bool), + Modulo(Loc), + Add(Loc, bool), + Subtract(Loc, bool), + AssignAdd(Loc, bool), + AssignSubtract(Loc, bool), + AssignMultiply(Loc, bool), + AssignDivide(Loc, bool), + AssignModulo(Loc), + ShiftLeft(Loc), + ShiftRight(Loc), + BitwiseAnd(Loc), + BitwiseXor(Loc), + BitwiseOr(Loc), + BitwiseNot(Loc), + AssignOr(Loc), + AssignAnd(Loc), + AssignXor(Loc), + AssignShiftLeft(Loc), + AssignShiftRight(Loc), + + // cmp ops + Less(Loc), + More(Loc), + LessEqual(Loc), + MoreEqual(Loc), + Equal(Loc), + NotEqual(Loc), + And(Loc), + Or(Loc), + + Assign(Loc), + Type(Loc, &'static Type), + This(Loc), + List(Loc, usize), + + Parameter(Loc, Option, Option<&'static str>), + Null(Loc), + + BoolLiteral(Loc, bool), + NumberLiteral(Loc, &'static str, &'static str, Option<&'static str>), + RationalNumberLiteral( + Loc, + &'static str, + &'static str, + &'static str, + Option<&'static str>, + ), + HexNumberLiteral(Loc, &'static str, Option<&'static str>), + StringLiteral(Loc, &'static str), + HexLiteral(Loc, &'static str), + AddressLiteral(Loc, &'static str), + Variable(Loc, &'static str), + + YulExpr(FlatYulExpr), +} + +impl std::fmt::Display for FlatExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use FlatExpr::*; + match self { + VarDef(_, maybe_name, maybe_storage, inited) => { + let inited_str = if *inited { " = ..;" } else { ";" }; + let name_str = if let Some(name) = maybe_name { + name + } else { + "" + }; + let storage_str = if let Some(stor) = maybe_storage { + format!("{stor} ") + } else { + "".to_string() + }; + write!(f, "{storage_str}{name_str}{inited_str}") + } + Super(_, name) => write!(f, "super.{name}"), + Continue(..) => write!(f, "continue;"), + Break(..) => write!(f, "break;"), + Return(.., elem) => { + if *elem { + write!(f, "return ..;") + } else { + write!(f, "return;") + } + } + + PostIncrement(..) => write!(f, "(..)++"), + PostDecrement(..) => write!(f, "(..)--"), + PreIncrement(..) => write!(f, "++(..)"), + PreDecrement(..) => write!(f, "--(..)"), + New(..) => write!(f, "new "), + ArrayTy(..) => write!(f, "[]"), + ArrayIndexAccess(..) => write!(f, "[(..)]"), + MemberAccess(_, field) => write!(f, ".{field}"), + FunctionCall(_, n) => write!(f, "({})", "_,".repeat(*n)), + NamedFunctionCall(_, _) => write!(f, "(..)"), + Not(_) => write!(f, "~"), + Negate(_) => write!(f, "-"), + Delete(_) => write!(f, "delete "), + + // binary ops + Power(..) => write!(f, " ** "), + Multiply(..) => write!(f, " * "), + Divide(..) => write!(f, " / "), + Modulo(_) => write!(f, " % "), + Add(..) => write!(f, " + "), + Subtract(..) => write!(f, " - "), + AssignAdd(..) => write!(f, " += "), + AssignSubtract(..) => write!(f, " -= "), + AssignMultiply(..) => write!(f, " *= "), + AssignDivide(..) => write!(f, " /= "), + AssignModulo(_) => write!(f, " %= "), + ShiftLeft(_) => write!(f, " << "), + ShiftRight(_) => write!(f, " >> "), + BitwiseAnd(_) => write!(f, " & "), + BitwiseXor(_) => write!(f, " ^ "), + BitwiseOr(_) => write!(f, " | "), + BitwiseNot(_) => write!(f, "~"), + AssignOr(_) => write!(f, " |= "), + AssignAnd(_) => write!(f, " &= "), + AssignXor(_) => write!(f, " ^= "), + AssignShiftLeft(_) => write!(f, " <<= "), + AssignShiftRight(_) => write!(f, " >>= "), + + // cmp ops + Less(_) => write!(f, " < "), + More(_) => write!(f, " > "), + LessEqual(_) => write!(f, " <= "), + MoreEqual(_) => write!(f, " >= "), + Equal(_) => write!(f, " == "), + NotEqual(_) => write!(f, " != "), + And(_) => write!(f, " && "), + Or(_) => write!(f, " || "), + + Assign(_) => write!(f, " = "), + This(_) => write!(f, "this"), + + BoolLiteral(_, b) => write!(f, "{b}"), + NumberLiteral(_, int, exp, unit) => { + let unit_str = if let Some(unit) = unit { unit } else { "" }; + let e_str = if exp.is_empty() { + "".to_string() + } else { + format!("e{exp}") + }; + write!(f, "{int}{e_str} {unit_str}") + } + RationalNumberLiteral(_, int, frac, exp, unit) => { + let unit_str = if let Some(unit) = unit { unit } else { "" }; + let e_str = if exp.is_empty() { + "".to_string() + } else { + format!("e{exp}") + }; + write!(f, "{int}.{frac}{e_str} {unit_str}") + } + HexNumberLiteral(_, s, _) + | StringLiteral(_, s) + | HexLiteral(_, s) + | AddressLiteral(_, s) + | Variable(_, s) => write!(f, "{s}"), + + YulExpr(yul) => write!(f, "{yul}"), + _ => write!(f, ""), + } + } +} + +impl FlatExpr { + pub fn if_debug_str(&self, start: usize, stack: &[FlatExpr]) -> String { + use FlatExpr::*; + let If { + true_cond, + false_cond, + true_body, + false_body, + .. + } = self + else { + unreachable!("Not if") + }; + let true_range = start..start + true_cond; + let true_cond_str = stack[true_range] + .iter() + .map(|i| format!("{i}")) + .collect::>() + .join(" "); + let false_range = start + true_cond..start + true_cond + false_cond; + let false_cond_str = stack[false_range] + .iter() + .map(|i| format!("{i}")) + .collect::>() + .join(" "); + + let true_body_range = + start + true_cond + false_cond..start + true_cond + false_cond + true_body; + let true_body_str = stack[true_body_range] + .iter() + .enumerate() + .map(|(j, i)| { + if matches!(i, If { .. }) { + let new_start = start + true_cond + false_cond + j; + i.if_debug_str(new_start, stack) + } else { + format!("{i}") + } + }) + .collect::>() + .join(" "); + let false_body_range = start + true_cond + false_cond + true_body + ..start + true_cond + false_cond + true_body + false_body; + let false_body_str = stack[false_body_range] + .iter() + .enumerate() + .map(|(j, i)| { + if matches!(i, If { .. }) { + let new_start = start + true_cond + false_cond + true_body + j; + i.if_debug_str(new_start, stack) + } else { + format!("{i}") + } + }) + .collect::>() + .join(" "); + + format!("if ({true_cond_str}) {{\n\t{true_body_str}\n}} else ({false_cond_str}) {{\n\t{false_body_str}\n}}") + } + pub fn try_inv_cmp(&self) -> Option { + use FlatExpr::*; + match self { + Less(loc) => Some(MoreEqual(*loc)), + More(loc) => Some(LessEqual(*loc)), + LessEqual(loc) => Some(More(*loc)), + MoreEqual(loc) => Some(Less(*loc)), + Equal(loc) => Some(NotEqual(*loc)), + NotEqual(loc) => Some(Equal(*loc)), + _ => None, + } + } + pub fn try_loc(&self) -> Option { + use FlatExpr::*; + match self { + If { loc, .. } + | While { loc, .. } + | For { loc, .. } + | Try { loc, .. } + | VarDef(loc, ..) + | Todo(loc, ..) + | Emit(loc, ..) + | NamedArgument(loc, ..) + | Continue(loc, ..) + | Break(loc, ..) + | Return(loc, ..) + | TestCommand(loc, ..) + | PostIncrement(loc, ..) + | PostDecrement(loc, ..) + | New(loc, ..) + | ArrayTy(loc, ..) + | ArrayIndexAccess(loc, ..) + | ArraySlice(loc, ..) + | MemberAccess(loc, ..) + | FunctionCall(loc, ..) + | FunctionCallBlock(loc, ..) + | NamedFunctionCall(loc, ..) + | Not(loc, ..) + | Negate(loc, ..) + | Delete(loc, ..) + | PreIncrement(loc, ..) + | PreDecrement(loc, ..) + | UnaryPlus(loc, ..) + | Power(loc, ..) + | Multiply(loc, ..) + | Divide(loc, ..) + | Modulo(loc, ..) + | Add(loc, ..) + | Subtract(loc, ..) + | AssignAdd(loc, ..) + | AssignSubtract(loc, ..) + | AssignMultiply(loc, ..) + | AssignDivide(loc, ..) + | AssignModulo(loc, ..) + | ShiftLeft(loc, ..) + | ShiftRight(loc, ..) + | BitwiseAnd(loc, ..) + | BitwiseXor(loc, ..) + | BitwiseOr(loc, ..) + | BitwiseNot(loc, ..) + | AssignOr(loc, ..) + | AssignAnd(loc, ..) + | AssignXor(loc, ..) + | AssignShiftLeft(loc, ..) + | AssignShiftRight(loc, ..) + | Less(loc, ..) + | More(loc, ..) + | LessEqual(loc, ..) + | MoreEqual(loc, ..) + | Equal(loc, ..) + | NotEqual(loc, ..) + | And(loc, ..) + | Or(loc, ..) + | Assign(loc, ..) + | Type(loc, ..) + | This(loc, ..) + | List(loc, ..) + | Parameter(loc, ..) + | Null(loc, ..) + | BoolLiteral(loc, ..) + | NumberLiteral(loc, ..) + | RationalNumberLiteral(loc, ..) + | HexNumberLiteral(loc, ..) + | StringLiteral(loc, ..) + | HexLiteral(loc, ..) + | AddressLiteral(loc, ..) + | Variable(loc, ..) + | Requirement(loc, ..) + | Super(loc, ..) + | Revert(loc, ..) + | YulExpr(FlatYulExpr::YulVariable(loc, ..)) + | YulExpr(FlatYulExpr::YulFuncCall(loc, ..)) + | YulExpr(FlatYulExpr::YulAssign(loc, ..)) + | YulExpr(FlatYulExpr::YulSuffixAccess(loc, ..)) + | YulExpr(FlatYulExpr::YulVarDecl(loc, ..)) + | YulExpr(FlatYulExpr::YulFuncDef(loc, ..)) + | ArrayLiteral(loc, ..) => Some(*loc), + + FunctionCallName(..) + | Pop + | YulExpr(FlatYulExpr::YulStartBlock(_)) + | YulExpr(FlatYulExpr::YulEndBlock(_)) => None, + } + } +} + +pub fn string_to_static(s: impl ToString) -> &'static str { + Box::leak(s.to_string().into_boxed_str()) +} + +impl From<&NamedArgument> for FlatExpr { + fn from(arg: &NamedArgument) -> Self { + FlatExpr::NamedArgument(arg.loc, string_to_static(arg.name.name.clone())) + } +} + +impl TryFrom<&YulExpression> for FlatExpr { + type Error = (); + fn try_from(expr: &YulExpression) -> Result { + use YulExpression::*; + let res = match expr { + BoolLiteral(loc, b, _unimpled_type) => FlatExpr::BoolLiteral(*loc, *b), + NumberLiteral(loc, int, exp, _unimpled_type) => FlatExpr::NumberLiteral( + *loc, + Box::leak(int.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + None, + ), + HexNumberLiteral(loc, b, _unimpled_type) => { + FlatExpr::HexNumberLiteral(*loc, Box::leak(b.clone().into_boxed_str()), None) + } + HexStringLiteral(hexes, _unimpled_type) => { + let final_str = hexes.hex.clone(); + let loc = hexes.loc; + FlatExpr::HexLiteral(loc, string_to_static(final_str)) + } + StringLiteral(lits, _unimpled_type) => { + let final_str = lits.string.clone(); + let loc = lits.loc; + FlatExpr::StringLiteral(loc, string_to_static(final_str)) + } + other => FlatExpr::YulExpr(FlatYulExpr::try_from(other)?), + }; + Ok(res) + } +} + +impl TryFrom<&Expression> for FlatExpr { + type Error = (); + fn try_from(expr: &Expression) -> Result { + use Expression::*; + let res = match expr { + PostIncrement(loc, ..) => FlatExpr::PostIncrement(*loc), + PostDecrement(loc, ..) => FlatExpr::PostDecrement(*loc), + New(loc, ..) => FlatExpr::New(*loc), + ArraySubscript(loc, _, None) => FlatExpr::ArrayTy(*loc, false), + ArraySubscript(loc, _, Some(_)) => FlatExpr::ArrayIndexAccess(*loc), + ArraySlice(loc, _, s, e) => FlatExpr::ArraySlice(*loc, s.is_some(), e.is_some()), + MemberAccess(loc, _, name) => { + FlatExpr::MemberAccess(*loc, string_to_static(name.name.clone())) + } + FunctionCall(loc, _, input_exprs) => FlatExpr::FunctionCall(*loc, input_exprs.len()), + FunctionCallBlock(loc, _, _) => FlatExpr::FunctionCallBlock(*loc), + NamedFunctionCall(loc, _, input_exprs) => { + FlatExpr::NamedFunctionCall(*loc, input_exprs.len()) + } + Not(loc, ..) => FlatExpr::Not(*loc), + Delete(loc, ..) => FlatExpr::Delete(*loc), + PreIncrement(loc, ..) => FlatExpr::PreIncrement(*loc), + PreDecrement(loc, ..) => FlatExpr::PreDecrement(*loc), + UnaryPlus(loc, ..) => FlatExpr::UnaryPlus(*loc), + Parenthesis(_, expr) => FlatExpr::try_from(&**expr)?, + Modulo(loc, _, _) => FlatExpr::Modulo(*loc), + ShiftLeft(loc, ..) => FlatExpr::ShiftLeft(*loc), + ShiftRight(loc, ..) => FlatExpr::ShiftRight(*loc), + BitwiseAnd(loc, ..) => FlatExpr::BitwiseAnd(*loc), + BitwiseXor(loc, ..) => FlatExpr::BitwiseXor(*loc), + BitwiseOr(loc, ..) => FlatExpr::BitwiseOr(*loc), + BitwiseNot(loc, ..) => FlatExpr::BitwiseNot(*loc), + Less(loc, ..) => FlatExpr::Less(*loc), + More(loc, ..) => FlatExpr::More(*loc), + LessEqual(loc, ..) => FlatExpr::LessEqual(*loc), + MoreEqual(loc, ..) => FlatExpr::MoreEqual(*loc), + Equal(loc, ..) => FlatExpr::Equal(*loc), + NotEqual(loc, ..) => FlatExpr::NotEqual(*loc), + And(loc, ..) => FlatExpr::And(*loc), + Or(loc, ..) => FlatExpr::Or(*loc), + Assign(loc, ..) => FlatExpr::Assign(*loc), + AssignOr(loc, ..) => FlatExpr::AssignOr(*loc), + AssignAnd(loc, ..) => FlatExpr::AssignAnd(*loc), + AssignXor(loc, ..) => FlatExpr::AssignXor(*loc), + AssignShiftLeft(loc, ..) => FlatExpr::AssignShiftLeft(*loc), + AssignShiftRight(loc, ..) => FlatExpr::AssignShiftRight(*loc), + AssignModulo(loc, ..) => FlatExpr::AssignModulo(*loc), + Type(loc, ty) => { + let ty_box = Box::new(ty.clone()); + let leaked_ty = Box::leak(ty_box); + FlatExpr::Type(*loc, leaked_ty) + } + Negate(loc, ..) => FlatExpr::Negate(*loc), + NumberLiteral(loc, int, exp, unit) => { + if let Some(unit) = unit { + FlatExpr::NumberLiteral( + *loc, + Box::leak(int.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + Some(Box::leak(unit.name.clone().into_boxed_str())), + ) + } else { + FlatExpr::NumberLiteral( + *loc, + Box::leak(int.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + None, + ) + } + } + AddressLiteral(loc, addr) => { + FlatExpr::AddressLiteral(*loc, Box::leak(addr.clone().into_boxed_str())) + } + StringLiteral(lits) => { + let mut final_str = "".to_string(); + let mut loc = lits[0].loc; + lits.iter().for_each(|s| { + loc.use_end_from(&s.loc); + final_str.push_str(&s.string); + }); + FlatExpr::StringLiteral(loc, string_to_static(final_str)) + } + BoolLiteral(loc, b) => FlatExpr::BoolLiteral(*loc, *b), + HexNumberLiteral(loc, b, unit) => { + if let Some(unit) = unit { + FlatExpr::HexNumberLiteral( + *loc, + Box::leak(b.clone().into_boxed_str()), + Some(Box::leak(unit.name.clone().into_boxed_str())), + ) + } else { + FlatExpr::HexNumberLiteral(*loc, Box::leak(b.clone().into_boxed_str()), None) + } + } + HexLiteral(hexes) => { + let mut final_str = "".to_string(); + let mut loc = hexes[0].loc; + hexes.iter().for_each(|s| { + loc.use_end_from(&s.loc); + final_str.push_str(&s.hex); + }); + FlatExpr::HexLiteral(loc, string_to_static(final_str)) + } + RationalNumberLiteral(loc, integer, fraction, exp, unit) => { + if let Some(unit) = unit { + FlatExpr::RationalNumberLiteral( + *loc, + Box::leak(integer.clone().into_boxed_str()), + Box::leak(fraction.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + Some(Box::leak(unit.name.clone().into_boxed_str())), + ) + } else { + FlatExpr::RationalNumberLiteral( + *loc, + Box::leak(integer.clone().into_boxed_str()), + Box::leak(fraction.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + None, + ) + } + } + ArrayLiteral(loc, args) => FlatExpr::ArrayLiteral(*loc, args.len()), + Variable(var) => { + FlatExpr::Variable(var.loc, Box::leak(var.name.clone().into_boxed_str())) + } + List(loc, params) => FlatExpr::List(*loc, params.len()), + This(loc, ..) => FlatExpr::This(*loc), + + Power(_, _, _) + | Multiply(_, _, _) + | Divide(_, _, _) + | Add(_, _, _) + | Subtract(_, _, _) + | AssignAdd(_, _, _) + | AssignSubtract(_, _, _) + | AssignMultiply(_, _, _) + | ConditionalOperator(..) + | AssignDivide(_, _, _) => return Err(()), + }; + Ok(res) + } +} diff --git a/crates/shared/src/flattened_yul.rs b/crates/shared/src/flattened_yul.rs new file mode 100644 index 00000000..a07cb3ab --- /dev/null +++ b/crates/shared/src/flattened_yul.rs @@ -0,0 +1,150 @@ +use crate::{string_to_static, ExprErr}; + +use solang_parser::pt::{ + Identifier, Loc, YulExpression, YulFunctionCall, YulStatement, YulSwitchOptions, +}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum FlatYulExpr { + YulStartBlock(usize), + YulVariable(Loc, &'static str), + YulFuncCall(Loc, &'static str, usize), + YulSuffixAccess(Loc, &'static str), + YulAssign(Loc, usize), + YulVarDecl(Loc, usize, bool), + YulFuncDef(Loc, &'static str, usize), + YulEndBlock(usize), +} + +impl std::fmt::Display for FlatYulExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use FlatYulExpr::*; + match self { + YulFuncCall(_c, s, n) => write!(f, "{s}({})", "_,".repeat(*n)), + YulVariable(_, s) | YulSuffixAccess(_, s) => write!(f, "{s}"), + _ => write!(f, ""), + } + } +} + +impl TryFrom<&YulExpression> for FlatYulExpr { + type Error = (); + fn try_from(expr: &YulExpression) -> Result { + use YulExpression::*; + let res = match expr { + Variable(ident) => { + FlatYulExpr::YulVariable(ident.loc, string_to_static(ident.name.clone())) + } + FunctionCall(yul_func_call) => FlatYulExpr::YulFuncCall( + yul_func_call.loc, + string_to_static(yul_func_call.id.name.clone()), + yul_func_call.arguments.len(), + ), + SuffixAccess(loc, _, ident) => { + FlatYulExpr::YulSuffixAccess(*loc, string_to_static(ident.name.clone())) + } + _ => return Err(()), + }; + Ok(res) + } +} + +#[derive(Clone, Debug)] +/// A yul-based if-else chain, which represents a switch statement +pub struct IfElseChain { + pub if_expr: YulExpression, + pub true_stmt: YulStatement, + pub next: Option, +} + +#[derive(Clone, Debug)] +/// Wrapper over a switch statement that denotes either another else statement or the default case +pub enum ElseOrDefault { + Else(Box), + Default(YulStatement), +} + +impl From for ElseOrDefault { + fn from(iec: IfElseChain) -> Self { + Self::Else(Box::new(iec)) + } +} + +impl IfElseChain { + pub fn from_child(ed: ElseOrDefault) -> Option { + match ed { + ElseOrDefault::Else(iec) => Some(*iec), + _ => None, + } + } +} + +impl From for ElseOrDefault { + fn from(default: YulSwitchOptions) -> Self { + match default { + YulSwitchOptions::Default(_loc, block) => { + ElseOrDefault::Default(YulStatement::Block(block)) + } + _ => unreachable!("case as default"), + } + } +} + +pub type SwitchInfo = ( + YulExpression, + Vec, + Option, +); + +impl IfElseChain { + pub fn from(loc: Loc, (condition, cases, default): SwitchInfo) -> Result { + let mut child: Option = default.map(|default| default.into()); + + cases.into_iter().rev().for_each(|case| { + let mut chain_part: IfElseChain = From::from((condition.clone(), case)); + if let Some(c) = child.take() { + chain_part.next = c.into(); + } + child = Some(chain_part.into()); + }); + let Some(child) = child else { + return Err(ExprErr::NoRhs( + loc, + "No cases or default found for switch statement".to_string(), + )); + }; + + let Some(iec) = IfElseChain::from_child(child) else { + return Err(ExprErr::NoRhs( + loc, + "No cases or default found for switch statement".to_string(), + )); + }; + Ok(iec) + } +} + +impl From<(YulExpression, YulSwitchOptions)> for IfElseChain { + fn from((condition, case): (YulExpression, YulSwitchOptions)) -> Self { + match case { + YulSwitchOptions::Case(loc, expr, stmt) => { + let if_expr = YulExpression::FunctionCall(Box::new(YulFunctionCall { + loc, + id: Identifier { + loc, + name: "eq".to_string(), + }, + arguments: vec![condition, expr], + })); + IfElseChain { + if_expr, + true_stmt: YulStatement::Block(stmt), + next: None, + } + } + YulSwitchOptions::Default(_loc, _block) => { + unreachable!("We shouldn't have a `default` case in cases - only in the `default` input parameter") + } + } + } +} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 7355e36f..967f75c9 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,11 +1,15 @@ mod analyzer_like; mod error; +mod flattened; +mod flattened_yul; pub mod gas; mod graph_like; mod search; pub use analyzer_like::*; pub use error::*; +pub use flattened::*; +pub use flattened_yul::*; pub use graph_like::*; pub use search::*; diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs index 017e96ec..68b6f4ed 100644 --- a/crates/solc-expressions/src/array.rs +++ b/crates/solc-expressions/src/array.rs @@ -1,79 +1,132 @@ -use crate::{require::Require, variable::Variable, ContextBuilder, ExpressionParser, ListAccess}; +use crate::{require::Require, variable::Variable, ListAccess}; use graph::{ elem::{Elem, RangeDyn, RangeOp}, - nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, TmpConstruction}, + nodes::{ + BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, + TmpConstruction, + }, AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; use shared::{ExprErr, IntoExprErr, RangeArena}; -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Loc}, -}; +use ethers_core::types::U256; +use solang_parser::pt::{Expression, Loc}; impl Array for T where T: AnalyzerBackend + Sized {} /// Handles arrays pub trait Array: AnalyzerBackend + Sized { - /// Gets the array type - #[tracing::instrument(level = "trace", skip_all)] - fn array_ty( + fn slice_inner( &mut self, arena: &mut RangeArena>, - ty_expr: &Expression, ctx: ContextNode, + arr: ExprRet, + start: Option, + end: Option, + loc: Loc, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, ty_expr, ctx)?; - self.apply_to_edges(ctx, ty_expr.loc(), arena, &|analyzer, _arena, ctx, loc| { - if let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_ty(ctx, ty_expr, ret) - } else { - Err(ExprErr::NoLhs( - loc, - "No array specified for getting array type".to_string(), - )) - } - }) + let arr = ContextVarNode::from(arr.expect_single().into_expr_err(loc)?); + + if let (Some(s), Some(e)) = (&start, &end) { + self.handle_require_inner(arena, ctx, e, s, RangeOp::Gte, loc)?; + } + + let start = if let Some(start) = start { + Elem::from(ContextVarNode::from( + start.expect_single().into_expr_err(loc)?, + )) + } else { + Elem::from(Concrete::from(0)) + }; + + let end = if let Some(end) = end { + Elem::from(ContextVarNode::from( + end.expect_single().into_expr_err(loc)?, + )) + } else { + Elem::from(arr).get_length() + }; + + let as_bn = self.builtin_or_add(Builtin::Uint(256)).index(); + let as_var = + ContextVar::new_from_builtin(loc, BuiltInNode(as_bn), self).into_expr_err(loc)?; + let slice_var = ContextVarNode::from(self.add_node(as_var)); + slice_var + .set_range_min(self, arena, start) + .into_expr_err(loc)?; + slice_var + .set_range_max(self, arena, end) + .into_expr_err(loc)?; + + let new_range = Elem::from(arr).slice(Elem::from(slice_var)); + + let mut new_arr = ContextVar { + loc: Some(loc), + name: format!("tmp_arr{}", ctx.new_tmp(self).into_expr_err(loc)?), + display_name: "tmp_arr".to_string(), + storage: None, + is_tmp: true, + is_symbolic: false, + is_return: false, + tmp_of: None, + dep_on: None, + ty: VarType::try_from_idx(self, arr.0.into()).unwrap(), + }; + new_arr.set_range(From::from(new_range)); + + let new_arr = ContextVarNode::from(self.add_node(new_arr)); + ctx.add_var(new_arr, self).into_expr_err(loc)?; + self.add_edge(new_arr, ctx, Edge::Context(ContextEdge::Variable)); + + let _ = self.create_length(arena, ctx, new_arr, true, loc)?; + + ctx.push_expr(ExprRet::Single(new_arr.0.into()), self) + .into_expr_err(loc) } + /// Gets the array type fn match_ty( &mut self, ctx: ContextNode, - ty_expr: &Expression, + loc: Loc, ret: ExprRet, + sized: Option, ) -> Result<(), ExprErr> { match ret { ExprRet::Single(inner_ty) | ExprRet::SingleLiteral(inner_ty) => { + // ie: uint[] + // ie: uint[][] if let Some(var_type) = VarType::try_from_idx(self, inner_ty) { - let dyn_b = Builtin::Array(var_type); + let dyn_b = if let Some(sized) = sized { + Builtin::SizedArray(sized, var_type) + } else { + Builtin::Array(var_type) + }; + if let Some(idx) = self.builtins().get(&dyn_b) { ctx.push_expr(ExprRet::Single(*idx), self) - .into_expr_err(ty_expr.loc())?; + .into_expr_err(loc)?; } else { let idx = self.add_node(Node::Builtin(dyn_b.clone())); self.builtins_mut().insert(dyn_b, idx); ctx.push_expr(ExprRet::Single(idx), self) - .into_expr_err(ty_expr.loc())?; + .into_expr_err(loc)?; } Ok(()) } else { - Err(ExprErr::ArrayTy(ty_expr.loc(), "Expected to be able to convert to a var type from an index to determine array type. This is a bug. Please report it at github.com/nascentxyz/pyrometer.".to_string())) + Err(ExprErr::ArrayTy(loc, "Expected to be able to convert to a var type from an index to determine array type. This is a bug. Please report it at github.com/nascentxyz/pyrometer.".to_string())) } } ExprRet::Multi(inner) => { + // ie: unsure of syntax needed to get here. (not possible?) inner .into_iter() - .map(|i| self.match_ty(ctx, ty_expr, i)) + .map(|i| self.match_ty(ctx, loc, i, sized)) .collect::, ExprErr>>()?; Ok(()) } ExprRet::CtxKilled(kind) => { - ctx.kill(self, ty_expr.loc(), kind) - .into_expr_err(ty_expr.loc())?; + ctx.kill(self, loc, kind).into_expr_err(loc)?; Ok(()) } ExprRet::Null => Ok(()), @@ -81,63 +134,18 @@ pub trait Array: AnalyzerBackend + Sized { } /// Indexes into an array - #[tracing::instrument(level = "trace", skip_all)] - fn index_into_array( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - ty_expr: &Expression, - index_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - tracing::trace!("Indexing into array"); - self.parse_ctx_expr(arena, index_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(index_tys) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Could not find the index variable".to_string(), - )); - }; - if matches!(index_tys, ExprRet::CtxKilled(_)) { - ctx.push_expr(index_tys, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.parse_ctx_expr(arena, ty_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(inner_tys) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs(loc, "Could not find the array".to_string())); - }; - if matches!(inner_tys, ExprRet::CtxKilled(_)) { - ctx.push_expr(inner_tys, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.index_into_array_inner( - arena, - ctx, - loc, - inner_tys.flatten(), - index_tys.clone().flatten(), - ) - }) - }) - } - #[tracing::instrument(level = "trace", skip_all)] fn index_into_array_inner( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, inner_paths: ExprRet, index_paths: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { match (inner_paths, index_paths) { (_, ExprRet::Null) | (ExprRet::Null, _) => Ok(()), - (_, ExprRet::CtxKilled(kind)) => { - ctx.kill(self, loc, kind).into_expr_err(loc) - } - (ExprRet::CtxKilled(kind), _) => { + (_, ExprRet::CtxKilled(kind)) | (ExprRet::CtxKilled(kind), _) => { ctx.kill(self, loc, kind).into_expr_err(loc) } (ExprRet::Single(parent), ExprRet::Single(index)) | (ExprRet::Single(parent), ExprRet::SingleLiteral(index)) => { @@ -166,25 +174,22 @@ pub trait Array: AnalyzerBackend + Sized { && parent.is_indexable(self).into_expr_err(loc)? { let len_var = self - .get_length(arena, ctx, loc, parent, true)? + .get_length(arena, ctx, parent, true, loc)? .unwrap() .latest_version_or_inherited_in_ctx(ctx, self); self.require( arena, + ctx, len_var.latest_version_or_inherited_in_ctx(ctx, self), idx.latest_version_or_inherited_in_ctx(ctx, self), - ctx, - loc, RangeOp::Gt, - RangeOp::Lt, - (RangeOp::Lte, RangeOp::Gte), + loc, )?; } - let name = format!( "{}[{}]", parent.name(self).into_expr_err(loc)?, - index.name(self).into_expr_err(loc)? + index.as_controllable_name(self, arena).into_expr_err(loc)? ); if let Some(index_var) = ctx.var_by_name_or_recurse(self, &name).into_expr_err(loc)? { let index_var = index_var.latest_version_or_inherited_in_ctx(ctx, self); @@ -228,7 +233,7 @@ pub trait Array: AnalyzerBackend + Sized { ty, }; - let idx_access_node = self.add_node(Node::ContextVar(index_access_var)); + let idx_access_node = self.add_node(index_access_var); self.add_edge( idx_access_node, parent, @@ -253,7 +258,6 @@ pub trait Array: AnalyzerBackend + Sized { let max = Elem::from(parent) .get_index(index.into()) .min(ContextVarNode::from(idx_access_node).into()); //.range_max(self).unwrap().unwrap()); - let idx_access_cvar = self.advance_var_in_ctx(ContextVarNode::from(idx_access_node), loc, ctx)?; @@ -273,7 +277,7 @@ pub trait Array: AnalyzerBackend + Sized { { // if the index access is also an array, produce a length variable // we specify to return the variable because we dont want it on the stack - let _ = self.get_length(arena, ctx, loc, idx_access_node.into(), true)?; + let _ = self.get_length(arena, ctx, idx_access_cvar, true, loc)?; } idx_access_cvar } else { @@ -357,30 +361,6 @@ pub trait Array: AnalyzerBackend + Sized { Ok(()) } - fn update_array_if_length_var( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - maybe_length: ContextVarNode, - ) -> Result<(), ExprErr> { - if let Some(backing_arr) = maybe_length.len_var_to_array(self).into_expr_err(loc)? { - let next_arr = self.advance_var_in_ctx( - backing_arr.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; - let new_len = Elem::from(backing_arr).set_length(maybe_length.into()); - next_arr - .set_range_min(self, arena, new_len.clone()) - .into_expr_err(loc)?; - next_arr - .set_range_max(self, arena, new_len) - .into_expr_err(loc)?; - } - Ok(()) - } - fn set_var_as_length( &mut self, arena: &mut RangeArena>, @@ -470,52 +450,4 @@ pub trait Array: AnalyzerBackend + Sized { Ok(()) } } - - fn update_array_min_if_length( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - maybe_length: ContextVarNode, - ) -> Result<(), ExprErr> { - if let Some(backing_arr) = maybe_length.len_var_to_array(self).into_expr_err(loc)? { - let next_arr = self.advance_var_in_ctx( - backing_arr.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; - let new_len = Elem::from(backing_arr) - .get_length() - .max(maybe_length.into()); - let min = Elem::from(backing_arr).set_length(new_len); - next_arr - .set_range_min(self, arena, min) - .into_expr_err(loc)?; - } - Ok(()) - } - - fn update_array_max_if_length( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - maybe_length: ContextVarNode, - ) -> Result<(), ExprErr> { - if let Some(backing_arr) = maybe_length.len_var_to_array(self).into_expr_err(loc)? { - let next_arr = self.advance_var_in_ctx( - backing_arr.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; - let new_len = Elem::from(backing_arr) - .get_length() - .min(maybe_length.into()); - let max = Elem::from(backing_arr).set_length(new_len); - next_arr - .set_range_max(self, arena, max) - .into_expr_err(loc)?; - } - Ok(()) - } } diff --git a/crates/solc-expressions/src/assign.rs b/crates/solc-expressions/src/assign.rs index bf6787fc..aa8bcf05 100644 --- a/crates/solc-expressions/src/assign.rs +++ b/crates/solc-expressions/src/assign.rs @@ -1,7 +1,7 @@ -use crate::{array::Array, variable::Variable, ContextBuilder, ExpressionParser, ListAccess}; +use crate::variable::Variable; use graph::{ - elem::{Elem, RangeElem}, + elem::{Elem, RangeDyn, RangeElem}, nodes::{Concrete, ContextNode, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, }; @@ -12,55 +12,6 @@ use solang_parser::pt::{Expression, Loc}; impl Assign for T where T: AnalyzerBackend + Sized {} /// Handles assignments pub trait Assign: AnalyzerBackend + Sized { - #[tracing::instrument(level = "trace", skip_all)] - /// Parse an assignment expression - fn assign_exprs( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - lhs_expr: &Expression, - rhs_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, lhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Assign operation had no left hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - analyzer.parse_ctx_expr(arena, rhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Assign operation had no right hand side".to_string(), - )); - }; - let lhs_paths = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap() - .flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_assign_sides(arena, ctx, loc, &lhs_paths, &rhs_paths)?; - Ok(()) - }) - }) - } - /// Match on the [`ExprRet`]s of an assignment expression fn match_assign_sides( &mut self, @@ -76,7 +27,9 @@ pub trait Assign: AnalyzerBackend + Sized ctx.kill(self, loc, *kind).into_expr_err(loc)?; Ok(()) } + (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: uint x = 5; let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -86,6 +39,7 @@ pub trait Assign: AnalyzerBackend + Sized Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: uint x = y; let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -94,23 +48,31 @@ pub trait Assign: AnalyzerBackend + Sized .into_expr_err(loc)?; Ok(()) } - (l @ ExprRet::Single(_), ExprRet::Multi(rhs_sides)) => rhs_sides - .iter() - .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, l, expr_ret)), + (l @ ExprRet::Single(_), ExprRet::Multi(rhs_sides)) => { + // ie: uint x = (a, b), not possible? + rhs_sides + .iter() + .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, l, expr_ret)) + } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { + // ie: (uint x, uint y) = a, not possible? lhs_sides .iter() .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, expr_ret, r)) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { // try to zip sides if they are the same length + // (x, y) = (a, b) + // ie: (x, y) = (a, b, c), not possible? if lhs_sides.len() == rhs_sides.len() { + // (x, y) = (a, b) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.match_assign_sides(arena, ctx, loc, lhs_expr_ret, rhs_expr_ret) }, ) } else { + // ie: (x, y) = (a, b, c), not possible? rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.match_assign_sides(arena, ctx, loc, lhs_paths, rhs_expr_ret) }) @@ -121,6 +83,7 @@ pub trait Assign: AnalyzerBackend + Sized } /// Perform an assignment + #[tracing::instrument(level = "trace", skip_all)] fn assign( &mut self, arena: &mut RangeArena>, @@ -135,6 +98,12 @@ pub trait Assign: AnalyzerBackend + Sized lhs_cvar.display_name(self).unwrap(), ); + if lhs_cvar.is_struct(self).into_expr_err(loc)? + && rhs_cvar.is_struct(self).into_expr_err(loc)? + { + return self.assign_struct_to_struct(arena, loc, lhs_cvar, rhs_cvar, ctx); + } + rhs_cvar .cast_from(&lhs_cvar, self, arena) .into_expr_err(loc)?; @@ -179,13 +148,15 @@ pub trait Assign: AnalyzerBackend + Sized self.add_edge(new_lhs, rhs_cvar, Edge::Context(ContextEdge::StorageWrite)); } + self.add_edge(new_lhs, rhs_cvar, Edge::Context(ContextEdge::Assign)); + if rhs_cvar.underlying(self).into_expr_err(loc)?.is_return { if let Some(rhs_ctx) = rhs_cvar.maybe_ctx(self) { self.add_edge( rhs_cvar, new_lhs, Edge::Context(ContextEdge::ReturnAssign( - rhs_ctx.underlying(self).unwrap().ext_fn_call.is_some(), + rhs_ctx.underlying(self).unwrap().is_ext_fn_call(), )), ); } else { @@ -202,41 +173,11 @@ pub trait Assign: AnalyzerBackend + Sized } } - if !lhs_cvar.ty_eq(&rhs_cvar, self).into_expr_err(loc)? { - let cast_to_min = match lhs_cvar.range_min(self).into_expr_err(loc)? { - Some(v) => v, - None => { - return Err(ExprErr::BadRange( - loc, - format!( - "No range during cast? {:?}, {:?}", - lhs_cvar.underlying(self).unwrap(), - rhs_cvar.underlying(self).unwrap(), - ), - )) - } - }; - - let cast_to_max = match lhs_cvar.range_max(self).into_expr_err(loc)? { - Some(v) => v, - None => { - return Err(ExprErr::BadRange( - loc, - format!( - "No range during cast? {:?}, {:?}", - lhs_cvar.underlying(self).unwrap(), - rhs_cvar.underlying(self).unwrap(), - ), - )) - } - }; + // we use try_set_* because some types like functions dont have a range. + let _ = new_lhs.try_set_range_min(self, arena, new_lower_bound); + let _ = new_lhs.try_set_range_max(self, arena, new_upper_bound); + self.maybe_assign_to_parent_array(arena, loc, lhs_cvar, rhs_cvar, ctx)?; - let _ = new_lhs.try_set_range_min(self, arena, new_lower_bound.cast(cast_to_min)); - let _ = new_lhs.try_set_range_max(self, arena, new_upper_bound.cast(cast_to_max)); - } else { - let _ = new_lhs.try_set_range_min(self, arena, new_lower_bound); - let _ = new_lhs.try_set_range_max(self, arena, new_upper_bound); - } if let Some(rhs_range) = rhs_cvar.ref_range(self).into_expr_err(loc)? { let res = new_lhs .try_set_range_exclusions(self, rhs_range.exclusions.clone()) @@ -244,91 +185,6 @@ pub trait Assign: AnalyzerBackend + Sized let _ = self.add_if_err(res); } - if rhs_cvar.is_indexable(self).into_expr_err(loc)? { - // rhs is indexable. get the length attribute, create a new length for the lhs, - // and perform assign - let rhs_len_cvar = self.get_length(arena, ctx, loc, rhs_cvar, true)?.unwrap(); - let lhs_len_cvar = self.get_length(arena, ctx, loc, lhs_cvar, true)?.unwrap(); - self.assign(arena, loc, lhs_len_cvar, rhs_len_cvar, ctx)?; - // update the range - self.update_array_if_length_var( - arena, - ctx, - loc, - lhs_len_cvar.latest_version_or_inherited_in_ctx(ctx, self), - )?; - } - - self.update_array_if_index_access(arena, ctx, loc, lhs_cvar, rhs_cvar)?; - - // handle struct assignment - if let (Ok(lhs_fields), Ok(rhs_fields)) = ( - lhs_cvar - .latest_version_or_inherited_in_ctx(ctx, self) - .struct_to_fields(self), - rhs_cvar - .latest_version_or_inherited_in_ctx(ctx, self) - .struct_to_fields(self), - ) { - // assert_eq!(lhs_fields.len(), rhs_fields.len()); - lhs_fields.iter().try_for_each(|field| { - let full_name = field.name(self).unwrap(); - let field_name = full_name - .split('.') - .collect::>() - .last() - .cloned() - .unwrap(); - if let Some(matching_field) = rhs_fields.iter().find(|r_field| { - let r_full_name = r_field.name(self).unwrap(); - let r_field_name = r_full_name - .split('.') - .collect::>() - .last() - .cloned() - .unwrap(); - field_name == r_field_name - }) { - let _ = self.assign( - arena, - loc, - field.latest_version_or_inherited_in_ctx(ctx, self), - matching_field.latest_version_or_inherited_in_ctx(ctx, self), - ctx, - )?; - Ok(()) - } else { - Err(ExprErr::ParseError( - loc, - "Missing fields for struct assignment".to_string(), - )) - } - })?; - - // if !fields.is_empty() { - // fields.into_iter().for_each(|field| { - // lhs_cvar.struc - // let mut new_var = field.underlying(self).unwrap().clone(); - // let field_name = field.name(self).unwrap(); - // let field_name = field_name - // .split('.') - // .collect::>() - // .last() - // .cloned() - // .unwrap(); - // let new_name = format!("{}.{field_name}", lhs_cvar.name(self).unwrap()); - // new_var.name.clone_from(&new_name); - // new_var.display_name = new_name; - // let new_field = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); - // self.add_edge( - // new_field, - // lhs_cvar.first_version(self), - // Edge::Context(ContextEdge::AttrAccess("field")), - // ); - // }) - // } - } - // advance the rhs variable to avoid recursion issues self.advance_var_in_ctx_forcible( rhs_cvar.latest_version_or_inherited_in_ctx(ctx, self), @@ -338,4 +194,95 @@ pub trait Assign: AnalyzerBackend + Sized )?; Ok(ExprRet::Single(new_lhs.into())) } + + fn maybe_assign_to_parent_array( + &mut self, + arena: &mut RangeArena>, + loc: Loc, + maybe_arr_attr: ContextVarNode, + rhs: ContextVarNode, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + if let Some(index) = maybe_arr_attr.index_access_to_index(self) { + let array = maybe_arr_attr.index_access_to_array(self).unwrap(); + let latest_arr = array.latest_version_or_inherited_in_ctx(ctx, self); + let new_arr = self.advance_var_in_ctx_forcible(latest_arr, loc, ctx, true)?; + let new_elem = Elem::from(latest_arr).set_indices(RangeDyn::new_for_indices( + vec![(Elem::from(index), Elem::from(rhs))], + loc, + )); + new_arr + .set_range_min(self, arena, new_elem.clone()) + .into_expr_err(loc)?; + new_arr + .set_range_max(self, arena, new_elem) + .into_expr_err(loc)?; + + self.maybe_assign_to_parent_array(arena, loc, latest_arr, new_arr, ctx)?; + } + + if let Some(array) = maybe_arr_attr.len_var_to_array(self) { + let latest_arr = array.latest_version_or_inherited_in_ctx(ctx, self); + let new_arr = self.advance_var_in_ctx_forcible(latest_arr, loc, ctx, true)?; + let new_elem = Elem::from(latest_arr).set_length(Elem::from(rhs)); + new_arr + .set_range_min(self, arena, new_elem.clone()) + .into_expr_err(loc)?; + new_arr + .set_range_max(self, arena, new_elem) + .into_expr_err(loc)?; + + self.maybe_assign_to_parent_array(arena, loc, latest_arr, new_arr, ctx)?; + } + + Ok(()) + } + + fn assign_struct_to_struct( + &mut self, + arena: &mut RangeArena>, + loc: Loc, + lhs_cvar: ContextVarNode, + rhs_cvar: ContextVarNode, + ctx: ContextNode, + ) -> Result { + let lhs_fields = lhs_cvar.struct_to_fields(self).into_expr_err(loc)?; + let rhs_fields = rhs_cvar.struct_to_fields(self).into_expr_err(loc)?; + lhs_fields.iter().try_for_each(|lhs_field| { + let lhs_full_name = lhs_field.name(self).into_expr_err(loc)?; + let split = lhs_full_name.split('.').collect::>(); + let Some(lhs_field_name) = split.last() else { + return Err(ExprErr::ParseError( + lhs_field.loc(self).unwrap(), + format!("Incorrectly named field: {lhs_full_name} - no '.' delimiter"), + )); + }; + + let mut found = false; + for rhs_field in rhs_fields.iter() { + let rhs_full_name = rhs_field.name(self).into_expr_err(loc)?; + let split = rhs_full_name.split('.').collect::>(); + let Some(rhs_field_name) = split.last() else { + return Err(ExprErr::ParseError( + rhs_field.loc(self).unwrap(), + format!("Incorrectly named field: {rhs_full_name} - no '.' delimiter"), + )); + }; + if lhs_field_name == rhs_field_name { + found = true; + let _ = self.assign(arena, loc, *lhs_field, *rhs_field, ctx)?; + break; + } + } + if found { + Ok(()) + } else { + Err(ExprErr::ParseError( + loc, + format!("Struct types mismatched - could not find field: {lhs_field_name}"), + )) + } + })?; + Ok(ExprRet::Single(lhs_cvar.0.into())) + } } diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index 01c02663..dc1e6e7c 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -1,11 +1,11 @@ -use crate::{require::Require, variable::Variable, ContextBuilder, ExpressionParser}; +use crate::{require::Require, variable::Variable}; use graph::{ elem::*, nodes::{ Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, KilledKind, TmpConstruction, }, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; use shared::{ExprErr, IntoExprErr, RangeArena}; @@ -16,45 +16,6 @@ impl BinOp for T where T: AnalyzerBackend + Sized { /// Evaluate and execute a binary operation expression - #[tracing::instrument(level = "trace", skip_all)] - fn op_expr( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - lhs_expr: &Expression, - rhs_expr: &Expression, - ctx: ContextNode, - op: RangeOp, - assign: bool, - ) -> Result<(), ExprErr> { - ctx.add_gas_cost(self, shared::gas::BIN_OP_GAS) - .into_expr_err(loc)?; - self.parse_ctx_expr(arena, rhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Binary operation had no right hand side".to_string())) - }; - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let rhs_paths = rhs_paths.flatten(); - let rhs_ctx = ctx; - analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs(loc, format!("Binary operation had no left hand side, Expr: {lhs_expr:#?}, rhs ctx: {}, curr ctx: {}", rhs_ctx.path(analyzer), ctx.path(analyzer)))) - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let lhs_paths = lhs_paths.flatten(); - analyzer.op_match(arena, ctx, loc, &lhs_paths, &rhs_paths, op, assign) - }) - }) - } - fn op_match( &mut self, arena: &mut RangeArena>, @@ -75,6 +36,7 @@ pub trait BinOp: AnalyzerBackend + Sized { "No right hand side provided for binary operation".to_string(), )), (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: 5 + 5 let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -89,6 +51,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: 5 + x ContextVarNode::from(*lhs) .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; @@ -104,6 +67,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: x + 5 ContextVarNode::from(*rhs) .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; @@ -119,6 +83,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: x + y let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -131,6 +96,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (lhs @ ExprRet::Single(..), ExprRet::Multi(rhs_sides)) => { + // ie: x + (y, z), (not possible?) rhs_sides .iter() .map(|expr_ret| self.op_match(arena, ctx, loc, lhs, expr_ret, op, assign)) @@ -138,6 +104,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Multi(lhs_sides), rhs @ ExprRet::Single(..)) => { + // ie: (x, y) + z, (not possible?) lhs_sides .iter() .map(|expr_ret| self.op_match(arena, ctx, loc, expr_ret, rhs, op, assign)) @@ -147,6 +114,7 @@ pub trait BinOp: AnalyzerBackend + Sized { (_, ExprRet::CtxKilled(kind)) => ctx.kill(self, loc, *kind).into_expr_err(loc), (ExprRet::CtxKilled(kind), _) => ctx.kill(self, loc, *kind).into_expr_err(loc), (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => Err(ExprErr::UnhandledCombo( + // ie: (x, y) + (a, b), (not possible?) loc, format!("Unhandled combination in binop: {lhs_sides:?} {rhs_sides:?}"), )), @@ -170,9 +138,8 @@ pub trait BinOp: AnalyzerBackend + Sized { assign: bool, ) -> Result { tracing::trace!( - "binary op: {} {} {}, assign: {}", + "binary op: {} {op} {}, assign: {}", lhs_cvar.display_name(self).into_expr_err(loc)?, - op.to_string(), rhs_cvar.display_name(self).into_expr_err(loc)?, assign ); @@ -210,7 +177,7 @@ pub trait BinOp: AnalyzerBackend + Sized { .concrete_to_builtin(self) .into_expr_err(loc)?; - let new_var = self.add_node(Node::ContextVar(new_lhs_underlying)); + let new_var = self.add_node(new_lhs_underlying); ctx.add_var(new_var.into(), self).into_expr_err(loc)?; self.add_edge(new_var, ctx, Edge::Context(ContextEdge::Variable)); ContextVarNode::from(new_var) @@ -278,7 +245,7 @@ pub trait BinOp: AnalyzerBackend + Sized { return Ok(killed); } } - RangeOp::Exp => { + RangeOp::Exp(..) => { if let Some(killed) = self.checked_require_exp(arena, lhs_cvar, new_lhs, new_rhs, loc, ctx)? { @@ -294,38 +261,14 @@ pub trait BinOp: AnalyzerBackend + Sized { )) } - #[tracing::instrument(level = "trace", skip_all)] - fn bit_not( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - lhs_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, lhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Not operation had no element".to_string(), - )); - }; - - if matches!(lhs, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.bit_not_inner(arena, ctx, loc, lhs.flatten()) - }) - } - + /// Perform bitwise not #[tracing::instrument(level = "trace", skip_all)] fn bit_not_inner( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, lhs_expr: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { match lhs_expr { ExprRet::CtxKilled(kind) => { @@ -339,7 +282,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ContextVarNode::from(lhs) .try_increase_size(self, arena) .into_expr_err(loc)?; - self.bit_not_inner(arena, ctx, loc, ExprRet::Single(lhs))?; + self.bit_not_inner(arena, ctx, ExprRet::Single(lhs), loc)?; Ok(()) } ExprRet::Single(lhs) => { @@ -375,7 +318,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Elem::Null, )); - let out_var = ContextVarNode::from(self.add_node(Node::ContextVar(out_var))); + let out_var = ContextVarNode::from(self.add_node(out_var)); out_var .set_range_min(self, arena, expr.clone()) @@ -428,16 +371,7 @@ pub trait BinOp: AnalyzerBackend + Sized { let zero_node = self.add_concrete_var(ctx, Concrete::from(U256::zero()), loc)?; if self - .require( - arena, - tmp_rhs, - zero_node, - ctx, - loc, - RangeOp::Neq, - RangeOp::Neq, - (RangeOp::Eq, RangeOp::Neq), - )? + .require(arena, ctx, tmp_rhs, zero_node, RangeOp::Neq, loc)? .is_none() { return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); @@ -467,13 +401,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, - ctx, - loc, RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), + loc, )? .is_none() { @@ -498,13 +430,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, - ctx, - loc, RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), + loc, )? .is_none() { @@ -536,13 +466,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, - ctx, - loc, RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), + loc, )? .is_none() { @@ -569,13 +497,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, - ctx, - loc, RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), + loc, )? .is_none() { @@ -608,13 +534,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, - ctx, - loc, RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), + loc, )? .is_none() { @@ -662,13 +586,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, - ctx, - loc, RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), + loc, )? .is_none() { @@ -693,16 +615,7 @@ pub trait BinOp: AnalyzerBackend + Sized { let zero = rhs.ty_zero_concrete(self).into_expr_err(loc)?.unwrap(); let zero = self.add_concrete_var(ctx, zero, loc)?; if self - .require( - arena, - rhs, - zero, - ctx, - loc, - RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), - )? + .require(arena, ctx, rhs, zero, RangeOp::Gte, loc)? .is_none() { return Ok(Some(ExprRet::CtxKilled(KilledKind::Revert))); @@ -720,13 +633,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, - ctx, - loc, RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), + loc, )? .is_none() { @@ -752,13 +663,11 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, + ctx, new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, - ctx, - loc, RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), + loc, )? .is_none() { diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs index 59f81700..d5333f10 100644 --- a/crates/solc-expressions/src/cmp.rs +++ b/crates/solc-expressions/src/cmp.rs @@ -1,46 +1,19 @@ -use crate::{ContextBuilder, ExpressionParser}; - use graph::{ elem::*, nodes::{ BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, TmpConstruction, }, - AnalyzerBackend, Node, Range, SolcRange, VarType, + AnalyzerBackend, SolcRange, VarType, }; -use shared::{ExprErr, GraphError, IntoExprErr, RangeArena}; +use shared::{ExprErr, IntoExprErr, RangeArena}; use solang_parser::pt::{Expression, Loc}; -use std::cmp::Ordering; impl Cmp for T where T: AnalyzerBackend + Sized {} /// Handles comparator operations, i.e: `!` pub trait Cmp: AnalyzerBackend + Sized { - #[tracing::instrument(level = "trace", skip_all)] - fn not( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - lhs_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, lhs_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Not operation had no element".to_string(), - )); - }; - - if matches!(lhs, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.not_inner(arena, ctx, loc, lhs.flatten()) - }) - } - + /// Perform logical not operation #[tracing::instrument(level = "trace", skip_all)] fn not_inner( &mut self, @@ -56,18 +29,20 @@ pub trait Cmp: AnalyzerBackend + Sized { Ok(()) } ExprRet::Single(lhs) | ExprRet::SingleLiteral(lhs) => { - let lhs_cvar = ContextVarNode::from(lhs); + let lhs_cvar = + ContextVarNode::from(lhs).latest_version_or_inherited_in_ctx(ctx, self); tracing::trace!("not: {}", lhs_cvar.display_name(self).into_expr_err(loc)?); - let mut elem = Elem::Expr(RangeExpr::new( + let elem = Elem::Expr(RangeExpr::new( Elem::from(lhs_cvar), - RangeOp::Not, - Elem::Null, + RangeOp::Eq, + Elem::from(false), )); - let _ = elem.arenaize(self, arena); - let mut range = SolcRange::new(elem.clone(), elem, vec![]); - range.cache_eval(self, arena).into_expr_err(loc)?; + let bool_idx = self.builtin_or_add(Builtin::Bool); + let ty = VarType::try_from_idx(self, bool_idx).unwrap(); + + let false_node = self.add_concrete_var(ctx, Concrete::from(false), loc)?; let out_var = ContextVar { loc: Some(loc), name: format!( @@ -78,21 +53,24 @@ pub trait Cmp: AnalyzerBackend + Sized { display_name: format!("!{}", lhs_cvar.display_name(self).into_expr_err(loc)?,), storage: None, is_tmp: true, - tmp_of: Some(TmpConstruction::new(lhs_cvar, RangeOp::Not, None)), + tmp_of: Some(TmpConstruction::new( + lhs_cvar, + RangeOp::Eq, + Some(false_node), + )), dep_on: Some(lhs_cvar.dependent_on(self, true).into_expr_err(loc)?), is_symbolic: lhs_cvar.is_symbolic(self).into_expr_err(loc)?, is_return: false, - ty: VarType::BuiltIn( - BuiltInNode::from(self.builtin_or_add(Builtin::Bool)), - Some(range), - ), + ty, }; + let cvar = ContextVarNode::from(self.add_node(out_var)); + cvar.set_range_min(self, arena, elem.clone()) + .into_expr_err(loc)?; + cvar.set_range_max(self, arena, elem.clone()) + .into_expr_err(loc)?; - ctx.push_expr( - ExprRet::Single(self.add_node(Node::ContextVar(out_var))), - self, - ) - .into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(cvar.0.into()), self) + .into_expr_err(loc)?; Ok(()) } ExprRet::Multi(f) => Err(ExprErr::MultiNot( @@ -106,52 +84,7 @@ pub trait Cmp: AnalyzerBackend + Sized { } } - #[tracing::instrument(level = "trace", skip_all)] - fn cmp( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - lhs_expr: &Expression, - op: RangeOp, - rhs_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - analyzer.parse_ctx_expr(arena, rhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Cmp operation had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Cmp operation had no left hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.cmp_inner(arena, ctx, loc, &lhs_paths.flatten(), op, &rhs_paths) - }) - }) - }) - } - + /// Performs a comparison operation #[tracing::instrument(level = "trace", skip_all)] fn cmp_inner( &mut self, @@ -165,12 +98,14 @@ pub trait Cmp: AnalyzerBackend + Sized { match (lhs_paths, rhs_paths) { (_, ExprRet::Null) | (ExprRet::Null, _) => Ok(()), (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: 5 == x ContextVarNode::from(*lhs) .literal_cast_from(&ContextVarNode::from(*rhs), self) .into_expr_err(loc)?; self.cmp_inner(arena, ctx, loc, &ExprRet::Single(*rhs), op, rhs_paths) } (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: 5 == 5 let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -187,18 +122,19 @@ pub trait Cmp: AnalyzerBackend + Sized { ) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: x == 5 ContextVarNode::from(*rhs) .literal_cast_from(&ContextVarNode::from(*lhs), self) .into_expr_err(loc)?; self.cmp_inner(arena, ctx, loc, lhs_paths, op, &ExprRet::Single(*rhs)) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: x == y let lhs_cvar = ContextVarNode::from(*lhs); let rhs_cvar = ContextVarNode::from(*rhs); tracing::trace!( - "cmp: {} {} {}", + "cmp: {} {op} {}", lhs_cvar.display_name(self).unwrap(), - op.to_string(), rhs_cvar.display_name(self).unwrap() ); let range = { @@ -223,13 +159,12 @@ pub trait Cmp: AnalyzerBackend + Sized { "tmp{}({} {} {})", ctx.new_tmp(self).into_expr_err(loc)?, lhs_cvar.name(self).into_expr_err(loc)?, - op.to_string(), + op, rhs_cvar.name(self).into_expr_err(loc)?, ), display_name: format!( - "{} {} {}", + "{} {op} {}", lhs_cvar.display_name(self).into_expr_err(loc)?, - op.to_string(), rhs_cvar.display_name(self).into_expr_err(loc)?, ), storage: None, @@ -253,19 +188,18 @@ pub trait Cmp: AnalyzerBackend + Sized { ), }; - ctx.push_expr( - ExprRet::Single(self.add_node(Node::ContextVar(out_var))), - self, - ) - .into_expr_err(loc) + ctx.push_expr(ExprRet::Single(self.add_node(out_var)), self) + .into_expr_err(loc) } (l @ ExprRet::Single(_lhs), ExprRet::Multi(rhs_sides)) => { + // ie: x == [y, z] (not possible?) rhs_sides .iter() .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, l, op, expr_ret))?; Ok(()) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_)) => { + // ie: (x, y) == z (not possible?) lhs_sides .iter() .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, expr_ret, op, r))?; @@ -273,7 +207,10 @@ pub trait Cmp: AnalyzerBackend + Sized { } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { // try to zip sides if they are the same length + // ie: (x, y) == (a, b) (not possible?) + // ie: (x, y, z) == (a, b) (not possible?) if lhs_sides.len() == rhs_sides.len() { + // ie: (x, y) == (a, b) (not possible?) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.cmp_inner(arena, ctx, loc, lhs_expr_ret, op, rhs_expr_ret) @@ -281,6 +218,7 @@ pub trait Cmp: AnalyzerBackend + Sized { )?; Ok(()) } else { + // ie: (x, y, z) == (a, b) (not possible?) rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.cmp_inner(arena, ctx, loc, lhs_paths, op, rhs_expr_ret) })?; @@ -293,200 +231,4 @@ pub trait Cmp: AnalyzerBackend + Sized { )), } } - - // fn not_eval( - // &mut self, - // _ctx: ContextNode, - // loc: Loc, - // lhs_cvar: ContextVarNode, - // ) -> Result { - // if let Some(lhs_range) = lhs_cvar.ref_range(self).into_expr_err(loc)? { - // let lhs_min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; - - // // invert - // if lhs_min.range_eq(&lhs_range.minimize(self, arena).into_expr_err(loc)?, self) { - // let val = Elem::Expr(RangeExpr::new( - // lhs_range.range_min().into_owned(), - // RangeOp::Not, - // Elem::Null, - // )); - - // return Ok(SolcRange::new(val.clone(), val, lhs_range.exclusions.clone())); - // } - // } - - // let min = Elem::Concrete(RangeConcrete { - // val: Concrete::Bool(false), - // loc, - // }).arenaize(self); - - // let max = Elem::Concrete(RangeConcrete { - // val: Concrete::Bool(true), - // loc, - // }).arenaize(self); - // Ok(SolcRange::new( - // min, - // max, - // vec![], - // )) - // } - - fn range_eval( - &self, - arena: &mut RangeArena>, - _ctx: ContextNode, - lhs_cvar: ContextVarNode, - rhs_cvar: ContextVarNode, - op: RangeOp, - ) -> Result { - if let Some(lhs_range) = lhs_cvar.ref_range(self)? { - if let Some(rhs_range) = rhs_cvar.ref_range(self)? { - match op { - RangeOp::Lt => { - // if lhs_max < rhs_min, we know this cmp will evaluate to - // true - - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { - return Ok(true.into()); - } - - // Similarly if lhs_min >= rhs_max, we know this cmp will evaluate to - // false - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - match lhs_min.range_ord(&rhs_max, arena) { - Some(Ordering::Greater) => { - return Ok(false.into()); - } - Some(Ordering::Equal) => { - return Ok(false.into()); - } - _ => {} - } - } - RangeOp::Gt => { - // if lhs_min > rhs_max, we know this cmp will evaluate to - // true - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { - return Ok(true.into()); - } - - // if lhs_max <= rhs_min, we know this cmp will evaluate to - // false - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - match lhs_max.range_ord(&rhs_min, arena) { - Some(Ordering::Less) => { - return Ok(false.into()); - } - Some(Ordering::Equal) => { - return Ok(false.into()); - } - _ => {} - } - } - RangeOp::Lte => { - // if lhs_max <= rhs_min, we know this cmp will evaluate to - // true - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - match lhs_max.range_ord(&rhs_min, arena) { - Some(Ordering::Less) => { - return Ok(true.into()); - } - Some(Ordering::Equal) => { - return Ok(true.into()); - } - _ => {} - } - - // Similarly if lhs_min > rhs_max, we know this cmp will evaluate to - // false - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { - return Ok(false.into()); - } - } - RangeOp::Gte => { - // if lhs_min >= rhs_max, we know this cmp will evaluate to - // true - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - match lhs_min.range_ord(&rhs_max, arena) { - Some(Ordering::Greater) => { - return Ok(true.into()); - } - Some(Ordering::Equal) => { - return Ok(true.into()); - } - _ => {} - } - - // if lhs_max < rhs_min, we know this cmp will evaluate to - // false - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { - return Ok(false.into()); - } - } - RangeOp::Eq => { - // if all elems are equal we know its true - // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let ( - Some(Ordering::Equal), - Some(Ordering::Equal), - Some(Ordering::Equal), - ) = ( - // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, arena), - // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, arena), - // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, arena), - ) { - return Ok(true.into()); - } - } - RangeOp::Neq => { - // if all elems are equal we know its true - // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let ( - Some(Ordering::Equal), - Some(Ordering::Equal), - Some(Ordering::Equal), - ) = ( - // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, arena), - // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, arena), - // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, arena), - ) { - return Ok(false.into()); - } - } - e => unreachable!("Cmp with strange op: {:?}", e), - } - Ok(SolcRange::default_bool()) - } else { - Ok(SolcRange::default_bool()) - } - } else { - Ok(SolcRange::default_bool()) - } - } } diff --git a/crates/solc-expressions/src/cond_op.rs b/crates/solc-expressions/src/cond_op.rs index b1be1688..7b95216b 100644 --- a/crates/solc-expressions/src/cond_op.rs +++ b/crates/solc-expressions/src/cond_op.rs @@ -1,248 +1,11 @@ -use crate::{require::Require, ContextBuilder, ExpressionParser, StatementParser}; +use crate::require::Require; -use graph::{ - elem::Elem, - nodes::{Concrete, Context, ContextNode}, - AnalyzerBackend, ContextEdge, Edge, Node, -}; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; +use graph::AnalyzerBackend; +use shared::ExprErr; -use solang_parser::pt::CodeLocation; -use solang_parser::pt::{Expression, Loc, Statement}; +use solang_parser::pt::Expression; impl CondOp for T where T: AnalyzerBackend + Require + Sized {} /// Handles conditional operations, like `if .. else ..` and ternary operations -pub trait CondOp: AnalyzerBackend + Require + Sized { - #[tracing::instrument(level = "trace", skip_all)] - /// Handles a conditional operation like `if .. else ..` - fn cond_op_stmt( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - if_expr: &Expression, - true_stmt: &Statement, - false_stmt: &Option>, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let tctx = - Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) - .into_expr_err(loc)?; - let true_subctx = ContextNode::from(analyzer.add_node(Node::Context(tctx))); - let fctx = - Context::new_subctx(ctx, None, loc, Some("false"), None, false, analyzer, None) - .into_expr_err(loc)?; - let false_subctx = ContextNode::from(analyzer.add_node(Node::Context(fctx))); - ctx.set_child_fork(true_subctx, false_subctx, analyzer) - .into_expr_err(loc)?; - true_subctx - .set_continuation_ctx(analyzer, ctx, "fork_true") - .into_expr_err(loc)?; - false_subctx - .set_continuation_ctx(analyzer, ctx, "fork_false") - .into_expr_err(loc)?; - let ctx_fork = analyzer.add_node(Node::ContextFork); - analyzer.add_edge(ctx_fork, ctx, Edge::Context(ContextEdge::ContextFork)); - analyzer.add_edge( - NodeIdx::from(true_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - analyzer.add_edge( - NodeIdx::from(false_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - - // we want to check if the true branch is possible to take - analyzer.true_fork_if_cvar(arena, if_expr.clone(), true_subctx)?; - let mut true_killed = false; - if true_subctx.is_killed(analyzer).into_expr_err(loc)? - || true_subctx - .unreachable(analyzer, arena) - .into_expr_err(loc)? - { - // it was killed, therefore true branch is unreachable. - // since it is unreachable, we want to not create - // unnecessary subcontexts - true_killed = true; - } - - // we want to check if the false branch is possible to take - analyzer.false_fork_if_cvar(arena, if_expr.clone(), false_subctx)?; - let mut false_killed = false; - if false_subctx.is_killed(analyzer).into_expr_err(loc)? - || false_subctx - .unreachable(analyzer, arena) - .into_expr_err(loc)? - { - // it was killed, therefore true branch is unreachable. - // since it is unreachable, we want to not create - // unnecessary subcontexts - false_killed = true; - } - - match (true_killed, false_killed) { - (true, true) => { - // both have been killed, delete the child and dont process the bodies - // println!("BOTH KILLED"); - ctx.delete_child(analyzer).into_expr_err(loc)?; - } - (true, false) => { - // println!("TRUE KILLED"); - // the true context has been killed, delete child, process the false fork expression - // in the parent context and parse the false body - ctx.delete_child(analyzer).into_expr_err(loc)?; - analyzer.false_fork_if_cvar(arena, if_expr.clone(), ctx)?; - if let Some(false_stmt) = false_stmt { - return analyzer.apply_to_edges( - ctx, - loc, - arena, - &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_statement(arena, false_stmt, false, Some(ctx)); - Ok(()) - }, - ); - } - } - (false, true) => { - // println!("FALSE KILLED"); - // the false context has been killed, delete child, process the true fork expression - // in the parent context and parse the true body - ctx.delete_child(analyzer).into_expr_err(loc)?; - analyzer.true_fork_if_cvar(arena, if_expr.clone(), ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_statement( - arena, - true_stmt, - ctx.unchecked(analyzer).into_expr_err(loc)?, - Some(ctx), - ); - Ok(()) - })?; - } - (false, false) => { - // println!("NEITHER KILLED"); - // both branches are reachable. process each body - analyzer.apply_to_edges( - true_subctx, - loc, - arena, - &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_statement( - arena, - true_stmt, - ctx.unchecked(analyzer).into_expr_err(loc)?, - Some(ctx), - ); - Ok(()) - }, - )?; - if let Some(false_stmt) = false_stmt { - return analyzer.apply_to_edges( - false_subctx, - loc, - arena, - &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_statement(arena, false_stmt, false, Some(ctx)); - Ok(()) - }, - ); - } - } - } - Ok(()) - }) - } - - /// Handles a conditional expression like `if .. else ..` - /// When we have a conditional operator, we create a fork in the context. One side of the fork is - /// if the expression is true, the other is if it is false. - #[tracing::instrument(level = "trace", skip_all)] - fn cond_op_expr( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - if_expr: &Expression, - true_expr: &Expression, - false_expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - tracing::trace!("conditional operator"); - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let tctx = - Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) - .into_expr_err(loc)?; - let true_subctx = ContextNode::from(analyzer.add_node(Node::Context(tctx))); - let fctx = - Context::new_subctx(ctx, None, loc, Some("false"), None, false, analyzer, None) - .into_expr_err(loc)?; - let false_subctx = ContextNode::from(analyzer.add_node(Node::Context(fctx))); - ctx.set_child_fork(true_subctx, false_subctx, analyzer) - .into_expr_err(loc)?; - true_subctx - .set_continuation_ctx(analyzer, ctx, "fork_true") - .into_expr_err(loc)?; - false_subctx - .set_continuation_ctx(analyzer, ctx, "fork_false") - .into_expr_err(loc)?; - let ctx_fork = analyzer.add_node(Node::ContextFork); - analyzer.add_edge(ctx_fork, ctx, Edge::Context(ContextEdge::ContextFork)); - analyzer.add_edge( - NodeIdx::from(true_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - analyzer.add_edge( - NodeIdx::from(false_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - - analyzer.true_fork_if_cvar(arena, if_expr.clone(), true_subctx)?; - analyzer.apply_to_edges(true_subctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_expr(arena, true_expr, ctx) - })?; - - analyzer.false_fork_if_cvar(arena, if_expr.clone(), false_subctx)?; - analyzer.apply_to_edges(false_subctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_expr(arena, false_expr, ctx) - }) - }) - } - - /// Creates the true_fork cvar (updates bounds assuming its true) - fn true_fork_if_cvar( - &mut self, - arena: &mut RangeArena>, - if_expr: Expression, - true_fork_ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.apply_to_edges( - true_fork_ctx, - if_expr.loc(), - arena, - &|analyzer, arena, ctx, _loc| { - analyzer.handle_require(arena, &[if_expr.clone()], ctx)?; - Ok(()) - }, - ) - } - - /// Creates the false_fork cvar (inverts the expression and sets the bounds assuming its false) - fn false_fork_if_cvar( - &mut self, - arena: &mut RangeArena>, - if_expr: Expression, - false_fork_ctx: ContextNode, - ) -> Result<(), ExprErr> { - let loc = if_expr.loc(); - let inv_if_expr = self.inverse_expr(if_expr); - self.apply_to_edges(false_fork_ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.handle_require(arena, &[inv_if_expr.clone()], ctx)?; - Ok(()) - }) - } -} +pub trait CondOp: AnalyzerBackend + Require + Sized {} diff --git a/crates/solc-expressions/src/context_builder/expr.rs b/crates/solc-expressions/src/context_builder/expr.rs deleted file mode 100644 index e86e5cdf..00000000 --- a/crates/solc-expressions/src/context_builder/expr.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crate::{ - context_builder::ContextBuilder, - func_call::{func_caller::FuncCaller, intrinsic_call::IntrinsicFuncCaller}, - variable::Variable, - ExprTyParser, -}; - -use graph::{ - elem::*, - nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, KilledKind}, - AnalyzerBackend, ContextEdge, Edge, Node, -}; -use shared::{post_to_site, ExprErr, IntoExprErr, RangeArena, USE_DEBUG_SITE}; - -use ethers_core::types::I256; -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Loc}, -}; - -impl ExpressionParser for T where - T: AnalyzerBackend + Sized + ExprTyParser -{ -} - -/// Solidity expression parser -pub trait ExpressionParser: - AnalyzerBackend + Sized + ExprTyParser -{ - /// Perform setup for parsing an expression - fn parse_ctx_expr( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - let res = if !ctx.killed_or_ret(self).unwrap() { - let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; - if edges.is_empty() { - self.parse_ctx_expr_inner(arena, expr, ctx) - } else { - edges - .iter() - .try_for_each(|fork_ctx| self.parse_ctx_expr(arena, expr, *fork_ctx))?; - Ok(()) - } - } else { - Ok(()) - }; - if unsafe { USE_DEBUG_SITE } { - post_to_site(&*self, arena); - } - - if ctx - .underlying(self) - .into_expr_err(expr.loc())? - .expr_ret_stack - .is_empty() - { - let res = self.is_representation_ok(arena).into_expr_err(expr.loc()); - if let Some(errs) = self.add_if_err(res) { - if !errs.is_empty() { - ctx.kill(self, expr.loc(), KilledKind::ParseError).unwrap(); - errs.into_iter().for_each(|err| { - self.add_expr_err(ExprErr::from_repr_err(expr.loc(), err)); - }); - } - } - } - - res - } - - #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self).replace('.', "\n\t.")))] - /// Perform parsing of an expression - fn parse_ctx_expr_inner( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - use Expression::*; - // tracing::trace!( - // "ctx: {}, current stack: {:?}, \nexpr: {:?}\n", - // ctx.underlying(self).unwrap().path, - // ctx.underlying(self) - // .unwrap() - // .expr_ret_stack - // .iter() - // .map(|i| i.debug_str(self)) - // .collect::>(), - // expr - // ); - match expr { - // literals - NumberLiteral(loc, int, exp, unit) => { - self.number_literal(ctx, *loc, int, exp, false, unit) - } - AddressLiteral(loc, addr) => self.address_literal(ctx, *loc, addr), - StringLiteral(lits) => lits - .iter() - .try_for_each(|lit| self.string_literal(ctx, lit.loc, &lit.string)), - BoolLiteral(loc, b) => self.bool_literal(ctx, *loc, *b), - HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), - HexLiteral(hexes) => self.hex_literals(ctx, hexes), - RationalNumberLiteral(loc, integer, fraction, exp, unit) => { - self.rational_number_literal(arena, ctx, *loc, integer, fraction, exp, unit, false) - } - Negate(_loc, expr) => match &**expr { - NumberLiteral(loc, int, exp, unit) => { - self.number_literal(ctx, *loc, int, exp, true, unit) - } - HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, true), - RationalNumberLiteral(loc, integer, fraction, exp, unit) => self - .rational_number_literal(arena, ctx, *loc, integer, fraction, exp, unit, true), - e => { - self.parse_ctx_expr(arena, e, ctx)?; - self.apply_to_edges(ctx, e.loc(), arena, &|analyzer, arena, ctx, loc| { - tracing::trace!("Negate variable pop"); - let Some(rhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No variable present to negate".to_string(), - )); - }; - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - // Solidity is dumb and used to allow negation of unsigned integers. - // That means we have to cast this as a int256. - let var = rhs_paths.expect_single().into_expr_err(loc)?; - - let zero = - analyzer.add_node(Node::Concrete(Concrete::from(I256::from(0i32)))); - let zero = ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - zero.into(), - analyzer, - ) - .into_expr_err(loc)?; - let zero = analyzer.add_node(Node::ContextVar(zero)); - let new_underlying = ContextVarNode::from(var) - .underlying(analyzer) - .into_expr_err(loc)? - .clone() - .as_cast_tmp(loc, ctx, Builtin::Int(256), analyzer) - .into_expr_err(loc)?; - let node = analyzer.add_node(Node::ContextVar(new_underlying)); - ctx.add_var(node.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - - ContextVarNode::from(node) - .cast_from(&ContextVarNode::from(zero), analyzer, arena) - .into_expr_err(loc)?; - - let lhs_paths = ExprRet::Single(zero); - analyzer.op_match( - arena, - ctx, - loc, - &lhs_paths, - &ExprRet::Single( - ContextVarNode::from(node).latest_version(analyzer).into(), - ), - RangeOp::Sub(true), - false, - ) - }) - } // e => todo!("UnaryMinus unexpected rhs: {e:?}"), - }, - UnaryPlus(_loc, e) => todo!("UnaryPlus unexpected rhs: {e:?}"), - - // Binary ops - Power(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Exp, false) - } - Add(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Add(ctx.unchecked(self).into_expr_err(*loc)?), - false, - ), - AssignAdd(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Add(ctx.unchecked(self).into_expr_err(*loc)?), - true, - ), - Subtract(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Sub(ctx.unchecked(self).into_expr_err(*loc)?), - false, - ), - AssignSubtract(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Sub(ctx.unchecked(self).into_expr_err(*loc)?), - true, - ), - Multiply(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Mul(ctx.unchecked(self).into_expr_err(*loc)?), - false, - ), - AssignMultiply(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Mul(ctx.unchecked(self).into_expr_err(*loc)?), - true, - ), - Divide(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Div(false), - false, - ), - AssignDivide(loc, lhs_expr, rhs_expr) => self.op_expr( - arena, - *loc, - lhs_expr, - rhs_expr, - ctx, - RangeOp::Div(false), - true, - ), - Modulo(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, false) - } - AssignModulo(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Mod, true) - } - ShiftLeft(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, false) - } - AssignShiftLeft(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shl, true) - } - ShiftRight(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, false) - } - AssignShiftRight(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::Shr, true) - } - ConditionalOperator(loc, if_expr, true_expr, false_expr) => { - self.cond_op_expr(arena, *loc, if_expr, true_expr, false_expr, ctx) - } - - // Bitwise ops - BitwiseAnd(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, false) - } - AssignAnd(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitAnd, true) - } - BitwiseXor(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, false) - } - AssignXor(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitXor, true) - } - BitwiseOr(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, false) - } - AssignOr(loc, lhs_expr, rhs_expr) => { - self.op_expr(arena, *loc, lhs_expr, rhs_expr, ctx, RangeOp::BitOr, true) - } - BitwiseNot(loc, lhs_expr) => self.bit_not(arena, *loc, lhs_expr, ctx), - - // assign - Assign(loc, lhs_expr, rhs_expr) => { - self.assign_exprs(arena, *loc, lhs_expr, rhs_expr, ctx) - } - List(loc, params) => self.list(arena, ctx, *loc, params), - // array - ArraySubscript(_loc, ty_expr, None) => self.array_ty(arena, ty_expr, ctx), - ArraySubscript(loc, ty_expr, Some(index_expr)) => { - self.index_into_array(arena, *loc, ty_expr, index_expr, ctx) - } - ArraySlice(loc, _lhs_expr, _maybe_middle_expr, _maybe_rhs) => Err(ExprErr::Todo( - *loc, - "Array slicing not currently supported".to_string(), - )), - ArrayLiteral(loc, _) => Err(ExprErr::Todo( - *loc, - "Array literal not currently supported".to_string(), - )), - - // Comparator - Equal(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Eq, rhs, ctx), - NotEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Neq, rhs, ctx), - Less(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Lt, rhs, ctx), - More(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Gt, rhs, ctx), - LessEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Lte, rhs, ctx), - MoreEqual(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Gte, rhs, ctx), - - // Logical - Not(loc, expr) => self.not(arena, *loc, expr, ctx), - And(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::And, rhs, ctx), - Or(loc, lhs, rhs) => self.cmp(arena, *loc, lhs, RangeOp::Or, rhs, ctx), - - // Function calls - FunctionCallBlock(loc, _func_expr, _input_exprs) => { - // TODO: update msg node - Err(ExprErr::Todo( - *loc, - "Function call block is unsupported. We shouldn't have hit this code path" - .to_string(), - )) - } - NamedFunctionCall(loc, func_expr, input_args) => { - self.named_fn_call_expr(arena, ctx, loc, func_expr, input_args) - } - FunctionCall(loc, func_expr, input_exprs) => { - let updated_func_expr = match **func_expr { - FunctionCallBlock(_loc, ref inner_func_expr, ref _call_block) => { - // we dont currently handle the `{value: .. gas: ..}` msg updating - // println!("call block: {call_block:#?}"); - - // let mut tmp_msg = Msg { - - // } - // self.add_expr_err(ExprErr::FunctionCallBlockTodo(call_block.loc(), "Function call block is currently unsupported. Relevant changes on `msg` will not take effect".to_string())); - inner_func_expr.clone() - } - _ => func_expr.clone(), - }; - - self.fn_call_expr(arena, ctx, loc, &updated_func_expr, input_exprs) - } - // member - New(loc, expr) => { - match &**expr { - Expression::FunctionCall(_loc, func, inputs) => { - // parse the type - self.new_call(arena, loc, func, inputs, ctx) - } - _ => panic!("Bad new call"), - } - } - This(loc) => { - let var = ContextVar::new_from_contract( - *loc, - ctx.associated_contract(self).into_expr_err(*loc)?, - self, - ) - .into_expr_err(*loc)?; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).into_expr_err(*loc)?; - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(cvar), self) - .into_expr_err(*loc)?; - Ok(()) - } - MemberAccess(loc, member_expr, ident) => { - self.member_access(arena, *loc, member_expr, ident, ctx) - } - - Delete(loc, expr) => { - fn delete_match( - ctx: ContextNode, - loc: &Loc, - analyzer: &mut impl AnalyzerBackend, - ret: ExprRet, - ) { - match ret { - ExprRet::CtxKilled(kind) => { - let _ = ctx.kill(analyzer, *loc, kind); - } - ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { - let mut new_var = - analyzer.advance_var_in_ctx(cvar.into(), *loc, ctx).unwrap(); - let res = new_var.sol_delete_range(analyzer).into_expr_err(*loc); - let _ = analyzer.add_if_err(res); - } - ExprRet::Multi(inner) => { - inner - .iter() - .for_each(|i| delete_match(ctx, loc, analyzer, i.clone())); - } - ExprRet::Null => {} - } - } - - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - tracing::trace!("Delete variable pop"); - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Delete operation had no right hand side".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - delete_match(ctx, &loc, analyzer, ret); - Ok(()) - }) - } - - // de/increment stuff - PreIncrement(loc, expr) => self.pre_increment(arena, expr, *loc, ctx), - PostIncrement(loc, expr) => self.post_increment(arena, expr, *loc, ctx), - PreDecrement(loc, expr) => self.pre_decrement(arena, expr, *loc, ctx), - PostDecrement(loc, expr) => self.post_decrement(arena, expr, *loc, ctx), - - // Misc. - Variable(ident) => self.variable(arena, ident, ctx, None, None), - Type(loc, ty) => { - if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self, arena) { - if let Some(idx) = self.builtins().get(&builtin) { - ctx.push_expr(ExprRet::Single(*idx), self) - .into_expr_err(*loc)?; - Ok(()) - } else { - let idx = self.add_node(Node::Builtin(builtin.clone())); - self.builtins_mut().insert(builtin, idx); - ctx.push_expr(ExprRet::Single(idx), self) - .into_expr_err(*loc)?; - Ok(()) - } - } else { - ctx.push_expr(ExprRet::Null, self).into_expr_err(*loc)?; - Ok(()) - } - } - Parenthesis(_loc, expr) => self.parse_ctx_expr(arena, expr, ctx), - } - } -} diff --git a/crates/solc-expressions/src/context_builder/flattened.rs b/crates/solc-expressions/src/context_builder/flattened.rs new file mode 100644 index 00000000..79611126 --- /dev/null +++ b/crates/solc-expressions/src/context_builder/flattened.rs @@ -0,0 +1,2578 @@ +use graph::elem::RangeDyn; +use std::collections::BTreeMap; + +use crate::{ + context_builder::{test_command_runner::TestCommandRunner, ContextBuilder}, + func_call::{ + func_caller::FuncCaller, + internal_call::{FindFunc, InternalFuncCaller}, + intrinsic_call::*, + }, + loops::Looper, + yul::YulFuncCaller, + ExprTyParser, +}; +use graph::{ + elem::{Elem, RangeConcrete, RangeExpr, RangeOp}, + nodes::{ + BuiltInNode, Builtin, Concrete, ConcreteNode, Context, ContextNode, ContextVar, + ContextVarNode, ContractNode, ExprRet, FunctionNode, KilledKind, StructNode, + TmpConstruction, YulFunction, + }, + AnalyzerBackend, ContextEdge, Edge, Node, SolcRange, TypeNode, VarType, +}; + +use ethers_core::types::U256; +use shared::{ + post_to_site, string_to_static, ElseOrDefault, ExprErr, ExprFlag, FlatExpr, FlatYulExpr, + GraphError, IfElseChain, IntoExprErr, RangeArena, USE_DEBUG_SITE, +}; +use solang_parser::pt::{ + CodeLocation, Expression, Identifier, Loc, Statement, YulExpression, YulStatement, +}; + +impl Flatten for T where + T: AnalyzerBackend + Sized + ExprTyParser +{ +} + +#[derive(Debug, Copy, Clone)] +pub enum FuncOrCtx { + Func(FunctionNode), + Ctx(ContextNode), +} + +impl From for FuncOrCtx { + fn from(f: FunctionNode) -> Self { + FuncOrCtx::Func(f) + } +} + +impl From for FuncOrCtx { + fn from(ctx: ContextNode) -> Self { + FuncOrCtx::Ctx(ctx) + } +} + +pub trait Flatten: + AnalyzerBackend + Sized + ExprTyParser +{ + fn traverse_statement(&mut self, stmt: &Statement, unchecked: Option) { + use Statement::*; + match stmt { + Block { + loc: _, + unchecked, + statements, + } => { + for statement in statements { + self.traverse_statement(statement, Some(*unchecked)); + } + } + VariableDefinition(loc, var_decl, maybe_expr) => { + let lhs = var_decl.ty.clone(); + if let Some(rhs) = maybe_expr { + self.traverse_expression(rhs, unchecked); + } + self.traverse_expression(&lhs, unchecked); + if let Some(name) = var_decl.name.clone() { + self.push_expr(FlatExpr::VarDef( + *loc, + Some(string_to_static(name.name)), + var_decl.storage.clone().map(Into::into), + maybe_expr.is_some(), + )); + } else { + self.push_expr(FlatExpr::VarDef( + *loc, + None, + var_decl.storage.clone().map(Into::into), + maybe_expr.is_some(), + )); + } + } + Args(loc, _args) => { + self.push_expr(FlatExpr::Todo( + *loc, + "Args statements are currently unsupported", + )); + } + If(loc, if_expr, true_body, maybe_false_body) => { + // 1. Add conditional expressions + // 2. Remove added conditional expressions + // 3. Clone and negate for false side + // 4. Add true body expressions + // 5. Remove true body expressions + // 6. Add false body expressions + // 7. Remove false body expressions + // 8. construct an `If` that lets the intepreter jump to each + // based on their size + let start_len = self.expr_stack_mut().len(); + self.traverse_expression(if_expr, unchecked); + let mut false_cond = self.expr_stack()[start_len..].to_vec(); + let cmp = self.expr_stack_mut().pop().unwrap(); + // have it be a require statement + self.traverse_requirement(cmp, if_expr.loc()); + let true_cond = self.expr_stack_mut().drain(start_len..).collect::>(); + + // the false condition is the same as the true, but with the comparator inverted + if let Some(last) = false_cond.pop() { + match last { + FlatExpr::And(loc, ..) => { + let lhs = false_cond.pop().unwrap(); + let rhs = false_cond.pop().unwrap(); + false_cond.push(lhs); + false_cond.push(FlatExpr::Not(loc)); + false_cond.push(rhs); + false_cond.push(FlatExpr::Not(loc)); + false_cond.push(FlatExpr::Requirement(loc)); + false_cond.push(FlatExpr::Or(loc)); + } + _ => { + if let Some(inv) = last.try_inv_cmp() { + false_cond.push(inv) + } else { + false_cond.push(last); + false_cond.push(FlatExpr::Requirement(*loc)); + false_cond.push(FlatExpr::Not(*loc)); + } + } + } + } + + let true_cond_delta = true_cond.len(); + let false_cond_delta = false_cond.len(); + + self.traverse_statement(true_body, unchecked); + let true_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let true_body_delta = true_body.len(); + + let (if_expr, false_body) = if let Some(false_body) = maybe_false_body { + self.traverse_statement(false_body, unchecked); + let false_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let false_body_delta = false_body.len(); + ( + FlatExpr::If { + loc: *loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: false_body_delta, + }, + false_body, + ) + } else { + ( + FlatExpr::If { + loc: *loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: 0, + }, + vec![], + ) + }; + + self.push_expr(if_expr); + let stack = self.expr_stack_mut(); + stack.extend(true_cond); + stack.extend(false_cond); + stack.extend(true_body); + stack.extend(false_body); + } + While(loc, if_expr, body) => { + let start_len = self.expr_stack_mut().len(); + self.traverse_expression(if_expr, unchecked); + let cmp = self.expr_stack_mut().pop().unwrap(); + self.traverse_requirement(cmp, if_expr.loc()); + let cond_exprs = self.expr_stack_mut().drain(start_len..).collect::>(); + let condition = cond_exprs.len(); + + self.traverse_statement(body, unchecked); + let body_exprs = self.expr_stack_mut().drain(start_len..).collect::>(); + let body = body_exprs.len(); + + self.push_expr(FlatExpr::While { + loc: *loc, + condition, + body, + }); + let stack = self.expr_stack_mut(); + stack.extend(cond_exprs); + stack.extend(body_exprs); + } + For(loc, maybe_for_start, maybe_for_cond, maybe_for_after_each, maybe_for_body) => { + let start_len = self.expr_stack_mut().len(); + + let for_start_exprs = if let Some(start) = maybe_for_start { + self.traverse_statement(start, unchecked); + self.expr_stack_mut().drain(start_len..).collect::>() + } else { + vec![] + }; + let start = for_start_exprs.len(); + + let for_cond_exprs = if let Some(cond) = maybe_for_cond { + self.traverse_expression(cond, unchecked); + let cmp = self.expr_stack_mut().pop().unwrap(); + self.traverse_requirement(cmp, cond.loc()); + self.expr_stack_mut().drain(start_len..).collect::>() + } else { + vec![] + }; + let condition = for_cond_exprs.len(); + + let for_body_exprs = if let Some(body) = maybe_for_body { + self.traverse_statement(body, unchecked); + self.expr_stack_mut().drain(start_len..).collect::>() + } else { + vec![] + }; + let body = for_body_exprs.len(); + + let for_after_each_exprs = if let Some(after_each) = maybe_for_after_each { + self.traverse_statement(after_each, unchecked); + self.expr_stack_mut().drain(start_len..).collect::>() + } else { + vec![] + }; + let after_each = for_after_each_exprs.len(); + + self.push_expr(FlatExpr::For { + loc: *loc, + start, + condition, + after_each, + body, + }); + let stack = self.expr_stack_mut(); + stack.extend(for_start_exprs); + stack.extend(for_cond_exprs); + stack.extend(for_body_exprs); + stack.extend(for_after_each_exprs); + } + DoWhile(loc, _while_stmt, _while_expr) => { + self.push_expr(FlatExpr::Todo( + *loc, + "Do While statements are currently unsupported", + )); + } + Expression(_, expr) => { + match expr { + solang_parser::pt::Expression::StringLiteral(lits) if lits.len() == 1 => { + if lits[0].string.starts_with("pyro::") { + self.push_expr(FlatExpr::TestCommand( + lits[0].loc, + string_to_static(lits[0].string.clone()), + )); + return; + } + } + _ => {} + } + self.traverse_expression(expr, unchecked); + } + Continue(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "continue statements are currently unsupported", + )); + // self.push_expr(FlatExpr::Continue(*loc)); + } + Break(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "break statements are currently unsupported", + )); + // self.push_expr(FlatExpr::Break(*loc)); + } + Assembly { + loc: _, + dialect: _, + flags: _, + block: yul_block, + } => { + self.increment_asm_block(); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulStartBlock( + self.current_asm_block(), + ))); + self.traverse_yul_statement(&YulStatement::Block(yul_block.clone())); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulEndBlock( + self.current_asm_block(), + ))); + } + Return(loc, maybe_ret_expr) => { + if let Some(ret_expr) = maybe_ret_expr { + self.traverse_expression(ret_expr, unchecked); + } + + self.push_expr(FlatExpr::Return(*loc, maybe_ret_expr.is_some())); + } + Revert(loc, _maybe_err_path, exprs) => { + exprs.iter().rev().for_each(|expr| { + self.traverse_expression(expr, unchecked); + }); + self.push_expr(FlatExpr::Revert(*loc, exprs.len())) + } + RevertNamedArgs(loc, _maybe_err_path, named_args) => { + named_args.iter().rev().for_each(|arg| { + self.traverse_expression(&arg.expr, unchecked); + }); + self.push_expr(FlatExpr::Revert(*loc, named_args.len())); + } + Emit(loc, emit_expr) => { + // self.traverse_expression(emit_expr, unchecked); + // self.push_expr(FlatExpr::Emit(*loc)); + } + Try(loc, _try_expr, _maybe_returns, _clauses) => { + self.push_expr(FlatExpr::Todo( + *loc, + "Try-Catch statements are currently unsupported", + )); + } + Error(_loc) => {} + } + } + + fn traverse_requirement(&mut self, cmp: FlatExpr, loc: Loc) { + match cmp { + FlatExpr::And(..) => { + // Its better to just break up And into its component + // parts now as opposed to trying to do it later + // i.e.: + // require(x && y) ==> + // require(x); + // require(y); + let rhs = self.expr_stack_mut().pop().unwrap(); + let lhs = self.expr_stack_mut().pop().unwrap(); + self.push_expr(FlatExpr::Requirement(loc)); + self.push_expr(rhs); + self.push_expr(FlatExpr::Requirement(loc)); + self.push_expr(lhs); + } + _ => { + self.push_expr(FlatExpr::Requirement(loc)); + self.push_expr(cmp); + } + } + } + + fn traverse_yul_statement(&mut self, stmt: &YulStatement) { + use YulStatement::*; + match stmt { + Assign(loc, lhs, rhs) => { + self.traverse_yul_expression(rhs); + lhs.iter().for_each(|l| { + self.traverse_yul_expression(l); + }); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulAssign(*loc, lhs.len()))); + } + VariableDeclaration(loc, idents, maybe_assignment) => { + if let Some(rhs) = maybe_assignment { + self.traverse_yul_expression(rhs); + } + let uint: &'static solang_parser::pt::Type = + Box::leak(Box::new(solang_parser::pt::Type::Uint(256))); + + idents.iter().for_each(|ident| { + // NOTE: for now yul does not support + // user types. but they may in the future + self.push_expr(FlatExpr::Type(ident.loc, uint)); + self.push_expr(FlatExpr::VarDef( + *loc, + Some(string_to_static(ident.id.name.clone())), + None, + false, + )); + }); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulVarDecl( + *loc, + idents.len(), + maybe_assignment.is_some(), + ))); + } + If(loc, if_expr, true_stmt) => { + let iec = IfElseChain { + if_expr: if_expr.clone(), + true_stmt: YulStatement::Block(true_stmt.clone()), + next: None, + }; + self.traverse_yul_if_else(*loc, iec); + } + For(yul_for) => { + self.push_expr(FlatExpr::Todo( + yul_for.loc, + "Yul for statements are currently unsupported", + )); + } + Switch(solang_parser::pt::YulSwitch { + loc, + condition, + cases, + default, + }) => { + if let Some(iec) = self.add_if_err(IfElseChain::from( + *loc, + (condition.clone(), cases.clone(), default.clone()), + )) { + self.traverse_yul_if_else(*loc, iec); + } + } + Leave(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "yul 'leave' statements are currently unsupported", + )); + // self.push_expr(FlatExpr::Break(*loc)); + } + Break(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "yul 'break' statements are currently unsupported", + )); + // self.push_expr(FlatExpr::Break(*loc)); + } + Continue(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "yul 'continue' statements are currently unsupported", + )); + // self.push_expr(FlatExpr::Continue(*loc)); + } + Block(block) => { + for statement in block.statements.iter() { + self.traverse_yul_statement(statement); + } + } + FunctionDefinition(def) => { + let start_len = self.expr_stack().len(); + let inputs_as_var_decl = + YulStatement::VariableDeclaration(def.loc, def.params.clone(), None); + self.traverse_yul_statement(&inputs_as_var_decl); + + def.params.iter().for_each(|param| { + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulVariable( + param.loc, + string_to_static(param.id.name.clone()), + ))) + }); + + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulAssign( + def.loc, + def.params.len(), + ))); + let rets_as_var_decl = + YulStatement::VariableDeclaration(def.loc, def.returns.clone(), None); + self.traverse_yul_statement(&rets_as_var_decl); + + self.increment_asm_block(); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulStartBlock( + self.current_asm_block(), + ))); + for stmt in def.body.statements.iter() { + self.traverse_yul_statement(stmt); + } + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulEndBlock( + self.current_asm_block(), + ))); + + let func = self.expr_stack_mut().drain(start_len..).collect::>(); + + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulFuncDef( + def.loc, + string_to_static(def.id.name.clone()), + func.len(), + ))); + self.expr_stack_mut().extend(func); + } + FunctionCall(call) => { + self.traverse_yul_expression(&YulExpression::FunctionCall(call.clone())); + } + Error(loc) => { + self.push_expr(FlatExpr::Todo( + *loc, + "yul 'error' statements are currently unsupported", + )); + } + } + } + + fn traverse_yul_if_else(&mut self, loc: Loc, iec: IfElseChain) { + let true_body = &iec.true_stmt; + let start_len = self.expr_stack_mut().len(); + self.push_expr(FlatExpr::NumberLiteral(loc, "0", "", None)); + self.traverse_yul_expression(&iec.if_expr); + + // have it be a require statement + self.push_expr(FlatExpr::Requirement(loc)); + self.push_expr(FlatExpr::More(loc)); + + let true_cond = self.expr_stack_mut().drain(start_len..).collect::>(); + + // the false condition is the same as the true, but with the comparator inverted + let mut false_cond = true_cond.clone(); + let _ = false_cond.pop(); + false_cond.push(FlatExpr::Equal(loc)); + + let true_cond_delta = true_cond.len(); + let false_cond_delta = false_cond.len(); + + self.traverse_yul_statement(true_body); + let true_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let true_body_delta = true_body.len(); + + let (if_expr, false_body) = if let Some(next) = iec.next { + match next { + ElseOrDefault::Else(curr) => { + self.traverse_yul_if_else(loc, *curr); + } + ElseOrDefault::Default(false_body) => self.traverse_yul_statement(&false_body), + } + let false_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let false_body_delta = false_body.len(); + ( + FlatExpr::If { + loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: false_body_delta, + }, + false_body, + ) + } else { + ( + FlatExpr::If { + loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: 0, + }, + vec![], + ) + }; + + self.push_expr(if_expr); + let stack = self.expr_stack_mut(); + stack.extend(true_cond); + stack.extend(false_cond); + stack.extend(true_body); + stack.extend(false_body); + } + + fn traverse_yul_expression(&mut self, expr: &YulExpression) { + use YulExpression::*; + match expr { + FunctionCall(func_call) => { + func_call.arguments.iter().rev().for_each(|expr| { + self.traverse_yul_expression(expr); + }); + + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulFuncCall( + expr.loc(), + string_to_static(func_call.id.name.clone()), + func_call.arguments.len(), + ))); + } + SuffixAccess(_, member, _) => { + self.traverse_yul_expression(member); + self.push_expr(FlatExpr::try_from(expr).unwrap()); + } + _ => self.push_expr(FlatExpr::try_from(expr).unwrap()), + } + } + + fn traverse_expression(&mut self, parent_expr: &Expression, unchecked: Option) { + use Expression::*; + match parent_expr { + // literals + NumberLiteral(..) + | AddressLiteral(..) + | StringLiteral(..) + | BoolLiteral(..) + | HexNumberLiteral(..) + | HexLiteral(..) + | RationalNumberLiteral(..) + | Variable(..) => { + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + + Negate(_loc, expr) => { + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + self.traverse_expression(expr, unchecked); + } + + Parenthesis(_loc, expr) => self.traverse_expression(expr, unchecked), + + UnaryPlus(_loc, expr) + | BitwiseNot(_loc, expr) + | Not(_loc, expr) + | Delete(_loc, expr) + | PreIncrement(_loc, expr) + | PostIncrement(_loc, expr) + | PreDecrement(_loc, expr) + | PostDecrement(_loc, expr) => { + self.traverse_expression(expr, unchecked); + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + + New(new_loc, expr) => { + match &**expr { + FunctionCall(func_loc, func_expr, input_exprs) => { + input_exprs.iter().rev().for_each(|expr| { + self.traverse_expression(expr, unchecked); + }); + + self.traverse_expression(func_expr, unchecked); + self.push_expr(FlatExpr::New(*new_loc)); + self.push_expr(FlatExpr::FunctionCall(*func_loc, input_exprs.len())); + } + NamedFunctionCall(loc, func_expr, input_args) => { + input_args.iter().rev().for_each(|arg| { + self.traverse_expression(&arg.expr, unchecked); + }); + + self.traverse_expression(func_expr, unchecked); + self.push_expr(FlatExpr::New(*new_loc)); + input_args.iter().for_each(|arg| { + self.push_expr(FlatExpr::from(arg)); + }); + self.push_expr(FlatExpr::NamedFunctionCall(*loc, input_args.len())); + } + _ => { + // add error + todo!() + } + } + } + // Binary ops + Power(loc, lhs, rhs) => { + self.traverse_expression(rhs, unchecked); + let rhs = self.expr_stack_mut().pop().unwrap(); + let zero_exp = match rhs { + FlatExpr::NumberLiteral(loc, int, exp, unit) + if int == "0" && unit.is_none() && exp.is_empty() => + { + self.push_expr(FlatExpr::NumberLiteral(loc, "1", "", None)); + true + } + FlatExpr::HexNumberLiteral(loc, int, _) => { + let all_zero = int + .strip_prefix("0x") + .unwrap_or(int) + .chars() + .all(|char| char == '0'); + if all_zero { + self.push_expr(FlatExpr::NumberLiteral(loc, "1", "", None)); + true + } else { + false + } + } + _ => false, + }; + + if !zero_exp { + self.push_expr(rhs); + self.traverse_expression(lhs, unchecked); + self.push_expr(FlatExpr::Power(*loc, unchecked.unwrap_or(false))); + } + } + + Add(_, lhs, rhs) + | AssignAdd(_, lhs, rhs) + | Subtract(_, lhs, rhs) + | AssignSubtract(_, lhs, rhs) + | Multiply(_, lhs, rhs) + | AssignMultiply(_, lhs, rhs) + | Divide(_, lhs, rhs) + | AssignDivide(_, lhs, rhs) => { + self.traverse_expression(rhs, unchecked); + self.traverse_expression(lhs, unchecked); + let parent = match parent_expr { + Power(loc, ..) => FlatExpr::Power(*loc, unchecked.unwrap_or(false)), + Add(loc, ..) => FlatExpr::Add(*loc, unchecked.unwrap_or(false)), + AssignAdd(loc, ..) => FlatExpr::AssignAdd(*loc, unchecked.unwrap_or(false)), + Subtract(loc, ..) => FlatExpr::Subtract(*loc, unchecked.unwrap_or(false)), + AssignSubtract(loc, ..) => { + FlatExpr::AssignSubtract(*loc, unchecked.unwrap_or(false)) + } + Multiply(loc, ..) => FlatExpr::Multiply(*loc, unchecked.unwrap_or(false)), + AssignMultiply(loc, ..) => { + FlatExpr::AssignMultiply(*loc, unchecked.unwrap_or(false)) + } + Divide(loc, ..) => FlatExpr::Divide(*loc, unchecked.unwrap_or(false)), + AssignDivide(loc, ..) => { + FlatExpr::AssignDivide(*loc, unchecked.unwrap_or(false)) + } + _ => unreachable!(), + }; + + self.push_expr(parent); + } + + Assign(_, lhs, rhs) => { + self.traverse_expression(lhs, unchecked); + self.traverse_expression(rhs, unchecked); + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + + Modulo(_, lhs, rhs) + | AssignModulo(_, lhs, rhs) + | ShiftLeft(_, lhs, rhs) + | AssignShiftLeft(_, lhs, rhs) + | ShiftRight(_, lhs, rhs) + | AssignShiftRight(_, lhs, rhs) + | BitwiseAnd(_, lhs, rhs) + | AssignAnd(_, lhs, rhs) + | BitwiseXor(_, lhs, rhs) + | AssignXor(_, lhs, rhs) + | BitwiseOr(_, lhs, rhs) + | AssignOr(_, lhs, rhs) + | Equal(_, lhs, rhs) + | NotEqual(_, lhs, rhs) + | Less(_, lhs, rhs) + | More(_, lhs, rhs) + | LessEqual(_, lhs, rhs) + | MoreEqual(_, lhs, rhs) + | And(_, lhs, rhs) + | Or(_, lhs, rhs) => { + self.traverse_expression(rhs, unchecked); + self.traverse_expression(lhs, unchecked); + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + + List(_, params) => { + params.iter().for_each(|(loc, maybe_param)| { + if let Some(param) = maybe_param { + self.traverse_expression(¶m.ty, unchecked); + } else { + self.push_expr(FlatExpr::Null(*loc)); + } + }); + params.iter().for_each(|(loc, maybe_param)| { + if let Some(param) = maybe_param { + if let Some(name) = ¶m.name { + self.push_expr(FlatExpr::Parameter( + param.loc, + param.storage.clone().map(Into::into), + Some(Box::leak(name.name.clone().into_boxed_str())), + )); + } else { + self.push_expr(FlatExpr::Parameter( + param.loc, + param.storage.clone().map(Into::into), + None, + )); + } + } else { + self.push_expr(FlatExpr::Parameter(*loc, None, None)); + } + }); + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + // array + ArraySubscript(loc, ty_expr, None) => { + self.traverse_expression(ty_expr, unchecked); + self.push_expr(FlatExpr::ArrayTy(*loc, false)); + } + ArraySubscript(loc, ty_expr, Some(index_expr)) => { + let start_len = self.expr_stack().len(); + self.traverse_expression(ty_expr, unchecked); + let ty_exprs: Vec<_> = self.expr_stack_mut().drain(start_len..).collect(); + match ty_exprs.last().unwrap() { + FlatExpr::Type(..) | FlatExpr::ArrayTy(..) => { + // sized array defintion + self.traverse_expression(index_expr, unchecked); + self.expr_stack_mut().extend(ty_exprs); + self.push_expr(FlatExpr::ArrayTy(*loc, true)); + } + _ => { + self.traverse_expression(index_expr, unchecked); + self.expr_stack_mut().extend(ty_exprs); + self.push_expr(FlatExpr::ArrayIndexAccess(*loc)); + } + } + } + ConditionalOperator(loc, if_expr, true_expr, false_expr) => { + // convert into statement if + let as_stmt: Statement = Statement::If( + *loc, + *if_expr.clone(), + Box::new(Statement::Expression(true_expr.loc(), *true_expr.clone())), + Some(Box::new(Statement::Expression( + false_expr.loc(), + *false_expr.clone(), + ))), + ); + self.traverse_statement(&as_stmt, unchecked); + } + ArraySlice(loc, lhs_expr, maybe_range_start, maybe_range_exclusive_end) => { + let mut has_start = false; + let mut has_end = false; + if let Some(range_exclusive_end) = maybe_range_exclusive_end { + self.traverse_expression(range_exclusive_end, unchecked); + has_end = true; + } + + if let Some(range_start) = maybe_range_start { + self.traverse_expression(range_start, unchecked); + has_start = true; + } + + self.traverse_expression(lhs_expr, unchecked); + + self.push_expr(FlatExpr::ArraySlice(*loc, has_start, has_end)); + } + ArrayLiteral(loc, val_exprs) => { + val_exprs + .iter() + .rev() + .for_each(|val| self.traverse_expression(val, unchecked)); + self.push_expr(FlatExpr::ArrayLiteral(*loc, val_exprs.len())); + } + // Function calls + FunctionCallBlock(loc, func_expr, call_block) => { + self.traverse_statement(call_block, unchecked); + self.traverse_expression(func_expr, unchecked); + self.push_expr(FlatExpr::FunctionCallBlock(*loc)); + } + NamedFunctionCall(loc, func_expr, input_args) => { + input_args.iter().rev().for_each(|arg| { + self.traverse_expression(&arg.expr, unchecked); + }); + + self.traverse_expression(func_expr, unchecked); + match self.expr_stack_mut().pop().unwrap() { + FlatExpr::Super(loc, name) => { + self.push_expr(FlatExpr::FunctionCallName(input_args.len(), true, true)); + self.push_expr(FlatExpr::Variable(loc, name)); + } + other => { + self.push_expr(FlatExpr::FunctionCallName(input_args.len(), false, true)); + self.push_expr(other); + } + } + + input_args.iter().for_each(|arg| { + self.push_expr(FlatExpr::from(arg)); + }); + self.push_expr(FlatExpr::NamedFunctionCall(*loc, input_args.len())); + } + FunctionCall(loc, func_expr, input_exprs) => match &**func_expr { + Variable(Identifier { name, .. }) if matches!(&**name, "require" | "assert") => { + // require(inputs) | assert(inputs) + input_exprs.iter().rev().for_each(|expr| { + self.traverse_expression(expr, unchecked); + }); + let cmp = self.expr_stack_mut().pop().unwrap(); + self.traverse_requirement(cmp, *loc); + if input_exprs.len() > 1 { + self.push_expr(FlatExpr::Pop); + } + } + _ => { + // func(inputs) + input_exprs.iter().rev().for_each(|expr| { + self.traverse_expression(expr, unchecked); + }); + + self.traverse_expression(func_expr, unchecked); + + // For clarity we make these variables + let mut is_super = false; + let named_args = false; + let num_inputs = input_exprs.len(); + match self.expr_stack_mut().pop().unwrap() { + FlatExpr::Super(loc, name) => { + is_super = true; + self.push_expr(FlatExpr::FunctionCallName( + num_inputs, is_super, named_args, + )); + self.push_expr(FlatExpr::Variable(loc, name)); + } + // mem @ FlatExpr::MemberAccess(..) => { + // // member.name(inputs) -> name(member, inputs) so we need + // // to make sure the member is passed as an input + // num_inputs += 1; + // self.push_expr(FlatExpr::FunctionCallName( + // num_inputs, is_super, named_args, + // )); + // self.push_expr(mem); + // } + other => { + self.push_expr(FlatExpr::FunctionCallName( + num_inputs, is_super, named_args, + )); + self.push_expr(other); + } + } + + self.push_expr(FlatExpr::FunctionCall(*loc, num_inputs)); + } + }, + // member + This(loc) => self.push_expr(FlatExpr::This(*loc)), + MemberAccess(loc, member_expr, ident) => match &**member_expr { + Variable(Identifier { name, .. }) if name == "super" => { + self.push_expr(FlatExpr::Super(*loc, string_to_static(ident))); + } + _ => { + self.traverse_expression(member_expr, unchecked); + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + } + }, + + // Misc. + Type(..) => self.push_expr(FlatExpr::try_from(parent_expr).unwrap()), + } + } + + fn interpret_entry_func(&mut self, func: FunctionNode, arena: &mut RangeArena>) { + let loc = func + .body_loc(self) + .unwrap() + .unwrap_or_else(|| func.definition_loc(self).unwrap()); + let raw_ctx = Context::new( + func, + self.add_if_err(func.name(self).into_expr_err(loc)).unwrap(), + loc, + ); + let ctx = ContextNode::from(self.add_node(Node::Context(raw_ctx))); + self.add_edge(ctx, func, Edge::Context(ContextEdge::Context)); + + let res = func.add_params_to_ctx(ctx, self).into_expr_err(loc); + self.add_if_err(res); + + let res = self.func_call_inner( + arena, + true, // entry_call + ctx, + func, + func.definition_loc(self).unwrap(), + &[], + &[], + None, // alt function name + &None, // mod state + ); + let _ = self.add_if_err(res); + } + + fn interpret( + &mut self, + func_or_ctx: impl Into, + body_loc: Loc, + arena: &mut RangeArena>, + ) { + let mut stack = std::mem::take(self.expr_stack_mut()); + tracing::trace!("stack: {stack:#?}"); + let foc: FuncOrCtx = func_or_ctx.into(); + + let ctx = match foc { + FuncOrCtx::Func(func) => { + let raw_ctx = Context::new( + func, + self.add_if_err(func.name(self).into_expr_err(body_loc)) + .unwrap(), + body_loc, + ); + let ctx = ContextNode::from(self.add_node(Node::Context(raw_ctx))); + self.add_edge(ctx, func, Edge::Context(ContextEdge::Context)); + + let res = func.add_params_to_ctx(ctx, self).into_expr_err(body_loc); + self.add_if_err(res); + + ctx + } + FuncOrCtx::Ctx(ctx) => ctx, + }; + + while (!ctx.is_ended(self).unwrap() || !ctx.live_edges(self).unwrap().is_empty()) + && ctx.parse_idx(self) < stack.len() + { + let res = self.interpret_step(arena, ctx, body_loc, &mut stack); + if unsafe { USE_DEBUG_SITE } { + post_to_site(&*self, arena); + } + self.add_if_err(res); + } + } + + fn interpret_step( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + body_loc: Loc, + stack: &mut Vec, + ) -> Result<(), ExprErr> { + let res = self.flat_apply_to_edges( + ctx, + body_loc, + arena, + stack, + &|analyzer: &mut Self, + arena: &mut RangeArena>, + ctx: ContextNode, + _: Loc, + stack: &mut Vec| { + analyzer.interpret_expr(arena, ctx, stack) + }, + ); + + if let Err(e) = res { + ctx.kill(self, e.loc(), KilledKind::ParseError).unwrap(); + Err(e) + } else { + Ok(()) + } + } + + fn interpret_expr( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + ) -> Result<(), ExprErr> { + use FlatExpr::*; + if ctx.is_killed(self).unwrap() { + return Ok(()); + } + + tracing::trace!("getting parse idx: {}", ctx.path(self)); + let parse_idx = ctx.increment_parse_idx(self); + let Some(next) = stack.get(parse_idx) else { + let mut loc = None; + let mut stack_rev_iter = stack.iter().rev(); + let mut loccer = stack_rev_iter.next(); + while loc.is_none() && loccer.is_some() { + loc = loccer.unwrap().try_loc(); + loccer = stack_rev_iter.next(); + } + let loc = loc.unwrap_or(Loc::Implicit); + self.return_match(arena, ctx, loc, ExprRet::CtxKilled(KilledKind::Ended), 0); + return Ok(()); + }; + let next = *next; + + tracing::trace!( + "parsing (idx: {parse_idx}) {:?} in context {} - flag: {:?}", + next, + ctx.path(self), + ctx.peek_expr_flag(self) + ); + + if self.debug_stack() { + tracing::trace!("return stack: {}", ctx.debug_expr_stack_str(self).unwrap()); + } + + match next { + Todo(loc, err_str) => Err(ExprErr::Todo(loc, err_str.to_string())), + // Flag expressions + FunctionCallName(n, is_super, named_args) => { + ctx.set_expr_flag(self, ExprFlag::FunctionName(n, is_super, named_args)); + Ok(()) + } + Negate(_) => { + ctx.set_expr_flag(self, ExprFlag::Negate); + Ok(()) + } + New(_) => { + ctx.set_expr_flag(self, ExprFlag::New); + Ok(()) + } + + // Literals + AddressLiteral(loc, lit) => self.address_literal(ctx, loc, lit), + StringLiteral(loc, lit) => self.string_literal(ctx, loc, lit), + BoolLiteral(loc, b) => self.bool_literal(ctx, loc, b), + HexLiteral(loc, hex) => self.hex_literals(ctx, loc, hex), + NumberLiteral(..) | HexNumberLiteral(..) | RationalNumberLiteral(..) => { + self.interp_negatable_literal(arena, ctx, next) + } + + // Variable + VarDef(..) => self.interp_var_def(arena, ctx, next), + Type(..) => self.interp_type(arena, ctx, next), + Variable(..) => self.interp_var(arena, ctx, stack, next, parse_idx), + Assign(..) => self.interp_assign(arena, ctx, next), + List(_, _) => self.interp_list(ctx, stack, next, parse_idx), + This(_) => self.interp_this(ctx, next), + Delete(_) => self.interp_delete(ctx, next), + + // Conditional + If { .. } => self.interp_if(arena, ctx, stack, next), + Requirement(..) => { + ctx.set_expr_flag(self, ExprFlag::Requirement); + Ok(()) + } + + TestCommand(..) => self.interp_test_command(arena, ctx, next), + + // Looping + While { .. } => self.interp_while(arena, ctx, stack, next), + For { .. } => self.interp_for(arena, ctx, stack, next), + Continue(loc) | Break(loc) => Err(ExprErr::Todo( + loc, + "Control flow expressions like break and continue are not currently supported" + .to_string(), + )), + + // Pre and post increment/decrement + PostIncrement(loc, ..) + | PreIncrement(loc, ..) + | PostDecrement(loc, ..) + | PreDecrement(loc, ..) => self.interp_xxcrement(arena, ctx, next, loc), + + // Array + ArrayTy(..) => self.interp_array_ty(arena, ctx, next), + ArrayIndexAccess(_) => self.interp_array_idx(arena, ctx, next), + ArraySlice(..) => self.interp_array_slice(arena, ctx, next), + ArrayLiteral(..) => self.interp_array_lit(ctx, next), + + // Binary operators + Power(loc, ..) + | Multiply(loc, ..) + | Divide(loc, ..) + | Modulo(loc, ..) + | Add(loc, ..) + | Subtract(loc, ..) + | ShiftLeft(loc, ..) + | ShiftRight(loc, ..) + | BitwiseAnd(loc, ..) + | BitwiseXor(loc, ..) + | BitwiseOr(loc, ..) => self.interp_op(arena, ctx, next, loc, false), + AssignAdd(loc, ..) + | AssignSubtract(loc, ..) + | AssignMultiply(loc, ..) + | AssignDivide(loc, ..) + | AssignModulo(loc, ..) + | AssignOr(loc, ..) + | AssignAnd(loc, ..) + | AssignXor(loc, ..) + | AssignShiftLeft(loc, ..) + | AssignShiftRight(loc, ..) => self.interp_op(arena, ctx, next, loc, true), + BitwiseNot(..) => self.interp_bit_not(arena, ctx, next), + // Comparator + Not(..) => self.interp_not(arena, ctx, next), + Equal(loc) | NotEqual(loc) | Less(loc) | More(loc) | LessEqual(loc) + | MoreEqual(loc) | And(loc) | Or(loc) => self.interp_cmp(arena, ctx, loc, next), + + // Function calling + MemberAccess(..) => self.interp_member_access(arena, ctx, stack, next, parse_idx), + FunctionCall(..) => self.interp_func_call(arena, ctx, next, None), + FunctionCallBlock(_) => todo!(), + NamedArgument(..) => Ok(()), + NamedFunctionCall(..) => { + self.interp_named_func_call(arena, ctx, stack, next, parse_idx) + } + Return(..) => self.interp_return(arena, ctx, next), + Revert(loc, n) => { + let _ = ctx.pop_n_latest_exprs(n, loc, self).into_expr_err(loc)?; + ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc) + } + + // Semi useless + Super(..) => unreachable!(), + Parameter(_, _, _) => Ok(()), + Pop => { + let _ = ctx + .pop_n_latest_exprs(1, Loc::Implicit, self) + .into_expr_err(Loc::Implicit)?; + Ok(()) + } + Emit(loc) => { + let _ = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + Ok(()) + } + Null(loc) => ctx.push_expr(ExprRet::Null, self).into_expr_err(loc), + + // Todo + UnaryPlus(_) => todo!(), + Try { .. } => todo!(), + + // Yul + YulExpr(FlatYulExpr::YulStartBlock(s)) => { + if self.current_asm_block() < s { + self.increment_asm_block(); + } + Ok(()) + } + YulExpr(yul @ FlatYulExpr::YulVariable(..)) => self.interp_yul_var(arena, ctx, yul), + YulExpr(yul @ FlatYulExpr::YulFuncCall(..)) => { + self.interp_yul_func_call(arena, ctx, stack, yul) + } + YulExpr(FlatYulExpr::YulSuffixAccess(..)) => Ok(()), + YulExpr(yul @ FlatYulExpr::YulAssign(..)) => self.interp_yul_assign(arena, ctx, yul), + YulExpr(yul @ FlatYulExpr::YulFuncDef(..)) => { + self.interp_yul_func_def(ctx, stack, yul, parse_idx) + } + YulExpr(yul @ FlatYulExpr::YulVarDecl(..)) => { + self.interp_yul_var_decl(arena, ctx, stack, yul, parse_idx) + } + YulExpr(FlatYulExpr::YulEndBlock(s)) => { + if self.current_asm_block() > s { + self.decrement_asm_block(); + } + Ok(()) + } + }?; + + if let Some(loc) = next.try_loc() { + if ctx.kill_if_ret_killed(self, loc).into_expr_err(loc)? { + return Ok(()); + } + } + + if matches!(ctx.peek_expr_flag(self), Some(ExprFlag::Requirement)) + && !matches!(next, Requirement(..)) + { + let _ = ctx.take_expr_flag(self); + let loc = next.try_loc().unwrap(); + let mut lhs = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + let lhs = lhs.swap_remove(0); + let cnode = ConcreteNode::from(self.add_node(Concrete::Bool(true))); + let tmp_true = ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, self) + .into_expr_err(loc)?; + let rhs = ExprRet::Single(self.add_node(tmp_true)); + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Eq, loc) + } else { + Ok(()) + } + } + + fn interp_delete(&mut self, ctx: ContextNode, next: FlatExpr) -> Result<(), ExprErr> { + let FlatExpr::Delete(loc) = next else { + unreachable!() + }; + + let to_delete = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); + + self.delete_match(ctx, to_delete, loc) + } + + fn delete_match( + &mut self, + ctx: ContextNode, + to_delete: ExprRet, + loc: Loc, + ) -> Result<(), ExprErr> { + match to_delete { + ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), + ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { + let mut new_var = self.advance_var_in_ctx(cvar.into(), loc, ctx).unwrap(); + new_var.sol_delete_range(self).into_expr_err(loc) + } + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|i| self.delete_match(ctx, i, loc)), + ExprRet::Null => Ok(()), + } + } + + fn interp_this(&mut self, ctx: ContextNode, next: FlatExpr) -> Result<(), ExprErr> { + let FlatExpr::This(loc) = next else { + unreachable!() + }; + + let mut var = ContextVar::new_from_contract( + loc, + ctx.associated_contract(self).into_expr_err(loc)?, + self, + ) + .into_expr_err(loc)?; + var.name = "this".to_string(); + var.display_name = "this".to_string(); + let cvar = self.add_node(Node::ContextVar(var)); + ctx.add_var(cvar.into(), self).into_expr_err(loc)?; + self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(cvar), self) + .into_expr_err(loc) + } + + fn interp_test_command( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::TestCommand(loc, cmd_str) = next else { + unreachable!() + }; + if let Some(cmd) = self.test_string_literal(cmd_str) { + self.run_test_command(arena, ctx, cmd, loc); + } + Ok(()) + } + + fn interp_bit_not( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::BitwiseNot(loc) = next else { + unreachable!() + }; + + let to_not = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); + + self.bit_not_inner(arena, ctx, to_not, loc) + } + + fn interp_list( + &mut self, + ctx: ContextNode, + stack: &mut Vec, + list: FlatExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatExpr::List(loc, n) = list else { + unreachable!() + }; + + let param_names_start = parse_idx.saturating_sub(n); + let params = &stack[param_names_start..parse_idx]; + let mut values = ctx.pop_n_latest_exprs(n, loc, self).into_expr_err(loc)?; + values.reverse(); + values + .into_iter() + .zip(params) + .try_for_each(|(ret, param)| { + let res = self.list_inner(ctx, *param, ret, loc)?; + ctx.push_expr(res, self).into_expr_err(loc) + })?; + + let mut new_values = ctx.pop_n_latest_exprs(n, loc, self).into_expr_err(loc)?; + new_values.reverse(); + ctx.push_expr(ExprRet::Multi(new_values), self) + .into_expr_err(loc)?; + Ok(()) + } + + fn interp_member_access( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + next: FlatExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatExpr::MemberAccess(loc, name) = next else { + unreachable!() + }; + + let member = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); + + // If the member access points to a library function, we need to keep the + // member on the stack + match ctx.take_expr_flag(self) { + Some(ExprFlag::FunctionName(n, super_call, named_args)) => { + let maybe_names = if named_args { + let start = parse_idx + 1; + Some(self.get_named_args(stack, start, n)) + } else { + None + }; + let member_idx = member.expect_single().into_expr_err(loc)?; + + let mut found_funcs = self + .find_func(ctx, name, n, &maybe_names, super_call, Some(member_idx)) + .into_expr_err(loc)?; + match found_funcs.len() { + 0 => Err(ExprErr::FunctionNotFound( + loc, + "Member Function not found".to_string(), + )), + 1 => { + let FindFunc { + func, + reordering, + was_lib_func, + } = found_funcs.swap_remove(0); + + self.order_fn_inputs(ctx, n, reordering, loc)?; + + if was_lib_func { + ctx.push_expr(member, self).into_expr_err(loc)?; + match stack.get_mut(ctx.parse_idx(self)) { + Some(FlatExpr::FunctionCall(_, ref mut n)) => { + *n += 1; + } + Some(FlatExpr::NamedFunctionCall(_, ref mut n)) => { + *n += 1; + } + Some(_) | None => {} + } + } + + let as_var = + ContextVar::maybe_from_user_ty(self, loc, func.into()).unwrap(); + let fn_var = ContextVarNode::from(self.add_node(as_var)); + ctx.add_var(fn_var, self).into_expr_err(loc)?; + self.add_edge(fn_var, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(fn_var.into()), self) + .into_expr_err(loc) + } + _ => { + let err_str = format!( + "Unable to disambiguate member function call, possible functions: {:?}", + found_funcs + .iter() + .map(|i| i.func.name(self).unwrap()) + .collect::>() + ); + Err(ExprErr::FunctionNotFound(loc, err_str)) + } + } + } + _ => { + let was_lib_func = self.member_access(arena, ctx, member.clone(), name, loc)?; + if was_lib_func { + todo!("Got a library function without a function call?"); + } + + Ok(()) + } + } + } + + fn interp_xxcrement( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatExpr, + loc: Loc, + ) -> Result<(), ExprErr> { + let (pre, increment) = match next { + FlatExpr::PreIncrement(_) => (true, true), + FlatExpr::PostIncrement(_) => (false, true), + FlatExpr::PreDecrement(_) => (true, false), + FlatExpr::PostDecrement(_) => (false, false), + _ => unreachable!(), + }; + + let res = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + let [var] = into_sized(res); + self.match_in_de_crement(arena, ctx, pre, increment, loc, &var) + } + + fn interp_op( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatExpr, + loc: Loc, + assign: bool, + ) -> Result<(), ExprErr> { + let op = RangeOp::try_from(next).unwrap(); + let res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let [lhs, rhs] = into_sized(res); + self.op_match(arena, ctx, loc, &lhs, &rhs, op, assign) + } + + fn interp_while( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + while_expr: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::While { + loc, + condition, + body, + } = while_expr + else { + unreachable!() + }; + + let loop_ctx = Context::add_loop_subctx(ctx, loc, self).into_expr_err(loc)?; + + // run the condition + if condition > 0 { + for _ in 0..condition { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + // run the body + if body > 0 { + for _ in 0..body { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + self.flat_apply_to_edges( + loop_ctx, + loc, + arena, + stack, + &|analyzer: &mut Self, + arena: &mut RangeArena>, + loop_ctx: ContextNode, + loc: Loc, + _: &mut Vec| { + analyzer.reset_vars(arena, ctx, loop_ctx, loc) + }, + )?; + + let end = ctx.parse_idx(self) + condition + body; + + self.modify_edges(ctx, loc, &|analyzer, ctx| { + ctx.underlying_mut(analyzer).unwrap().parse_idx = end; + Ok(()) + }) + } + + fn interp_for( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + for_expr: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::For { + loc, + start, + condition, + after_each, + body, + } = for_expr + else { + unreachable!() + }; + + let loop_ctx = Context::add_loop_subctx(ctx, loc, self).into_expr_err(loc)?; + + // initiate the loop variable + if start > 0 { + for _ in 0..start { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + // run the condition + if condition > 0 { + for _ in 0..condition { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + // run the body + if body > 0 { + for _ in 0..body { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + // run the after each + if after_each > 0 { + for _ in 0..after_each { + self.interpret_step(arena, loop_ctx, loc, stack)?; + } + } + + self.flat_apply_to_edges( + loop_ctx, + loc, + arena, + stack, + &|analyzer: &mut Self, + arena: &mut RangeArena>, + loop_ctx: ContextNode, + loc: Loc, + _: &mut Vec| { + analyzer.reset_vars(arena, ctx, loop_ctx, loc) + }, + )?; + + let end = ctx.parse_idx(self) + start + condition + body + after_each; + + self.modify_edges(ctx, loc, &|analyzer, ctx| { + ctx.underlying_mut(analyzer).unwrap().parse_idx = end; + Ok(()) + }) + } + + fn interp_if( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + if_expr: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::If { + loc, + true_cond, + false_cond, + true_body, + false_body, + } = if_expr + else { + unreachable!() + }; + + let (true_subctx, false_subctx) = + Context::add_fork_subctxs(self, ctx, loc).into_expr_err(loc)?; + + // Parse the true condition expressions then skip the + // false condition expressions, thus resulting in the + // true_subctx parse_idx being the start of true body + for _ in 0..true_cond { + self.interpret_step(arena, true_subctx, loc, stack)?; + } + self.modify_edges(true_subctx, loc, &|analyzer, true_subctx| { + true_subctx.skip_n_exprs(false_cond, analyzer); + Ok(()) + })?; + + // Skip the true condition expressions then parse the false + // condition expressions + false_subctx.skip_n_exprs(true_cond, self); + for _ in 0..false_cond { + self.interpret_step(arena, false_subctx, loc, stack)?; + } + + let true_killed = true_subctx.is_killed(self).into_expr_err(loc)? + || true_subctx.unreachable(self, arena).into_expr_err(loc)?; + let false_killed = false_subctx.is_killed(self).into_expr_err(loc)? + || false_subctx.unreachable(self, arena).into_expr_err(loc)?; + + match (true_killed, false_killed) { + (true, true) => { + // both have been killed, delete the child and dont process the bodies + ctx.delete_child(self).into_expr_err(loc)?; + } + (true, false) => { + // the true context has been killed, delete child, process the false fork expression + // in the parent context and parse the false body + ctx.delete_child(self).into_expr_err(loc)?; + + // point the parse index of the parent ctx to the false body + ctx.underlying_mut(self).unwrap().parse_idx = + ctx.parse_idx(self) + true_cond + false_cond + true_body; + for _ in 0..false_body { + self.interpret_step(arena, ctx, loc, stack)?; + } + } + (false, true) => { + // the false context has been killed, delete child, process the true fork expression + // in the parent context and parse the true body + ctx.delete_child(self).into_expr_err(loc)?; + + // point the parse index of the parent ctx to the true body + ctx.underlying_mut(self).unwrap().parse_idx = + ctx.parse_idx(self) + true_cond + false_cond; + for _ in 0..true_body { + self.interpret_step(arena, ctx, loc, stack)?; + } + + // skip false body + self.modify_edges(ctx, loc, &|analyzer, ctx| { + ctx.skip_n_exprs(false_body, analyzer); + Ok(()) + })?; + } + (false, false) => { + // both branches are reachable. process each body + for _ in 0..true_body { + self.interpret_step(arena, true_subctx, loc, stack)?; + } + // skip the false body expressions + self.modify_edges(true_subctx, loc, &|analyzer, true_subctx| { + true_subctx.skip_n_exprs(false_body, analyzer); + Ok(()) + })?; + + // skip the true body expressions + self.modify_edges(false_subctx, loc, &|analyzer, false_subctx| { + false_subctx.skip_n_exprs(true_body, analyzer); + Ok(()) + })?; + // parse the false body expressions + for _ in 0..false_body { + self.interpret_step(arena, false_subctx, loc, stack)?; + } + } + } + Ok(()) + } + + fn interp_cmp( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + loc: Loc, + cmp: FlatExpr, + ) -> Result<(), ExprErr> { + let res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let [lhs, rhs] = into_sized::(res); + + if matches!(ctx.peek_expr_flag(self), Some(ExprFlag::Requirement)) { + ctx.take_expr_flag(self); + match cmp { + FlatExpr::Equal(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Eq, loc) + } + FlatExpr::NotEqual(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Neq, loc) + } + FlatExpr::Less(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Lt, loc) + } + FlatExpr::More(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Gt, loc) + } + FlatExpr::LessEqual(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Lte, loc) + } + FlatExpr::MoreEqual(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::Gte, loc) + } + FlatExpr::Or(..) => { + let lhs = ContextVarNode::from(lhs.expect_single().into_expr_err(loc)?); + let rhs = ContextVarNode::from(rhs.expect_single().into_expr_err(loc)?); + let elem = Elem::Expr(RangeExpr::new(lhs.into(), RangeOp::Or, rhs.into())); + let range = SolcRange::new(elem.clone(), elem, vec![]); + let new_lhs_underlying = ContextVar { + loc: Some(loc), + name: format!( + "tmp{}({} {} {})", + ctx.new_tmp(self).into_expr_err(loc)?, + lhs.name(self).into_expr_err(loc)?, + RangeOp::Or, + rhs.name(self).into_expr_err(loc)? + ), + display_name: format!( + "({} {} {})", + lhs.display_name(self).into_expr_err(loc)?, + RangeOp::Or, + rhs.display_name(self).into_expr_err(loc)? + ), + storage: None, + is_tmp: true, + is_symbolic: lhs.is_symbolic(self).into_expr_err(loc)? + || rhs.is_symbolic(self).into_expr_err(loc)?, + is_return: false, + tmp_of: Some(TmpConstruction::new(lhs, RangeOp::Or, Some(rhs))), + dep_on: { + let mut deps = lhs.dependent_on(self, true).into_expr_err(loc)?; + deps.extend(rhs.dependent_on(self, true).into_expr_err(loc)?); + Some(deps) + }, + ty: VarType::BuiltIn( + self.builtin_or_add(Builtin::Bool).into(), + Some(range), + ), + }; + let or_var = ContextVarNode::from(self.add_node(new_lhs_underlying)); + let node = self.add_concrete_var(ctx, Concrete::Bool(true), loc)?; + ctx.add_var(node, self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + self.handle_require_inner( + arena, + ctx, + &ExprRet::Single(or_var.into()), + &ExprRet::Single(node.into()), + RangeOp::Eq, + loc, + ) + } + FlatExpr::And(..) => { + self.handle_require_inner(arena, ctx, &lhs, &rhs, RangeOp::And, loc) + } + _ => unreachable!(), + } + } else { + match cmp { + FlatExpr::Equal(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Eq, &rhs), + FlatExpr::NotEqual(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Neq, &rhs), + FlatExpr::Less(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Lt, &rhs), + FlatExpr::More(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Gt, &rhs), + FlatExpr::LessEqual(..) => { + self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Lte, &rhs) + } + FlatExpr::MoreEqual(..) => { + self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Gte, &rhs) + } + FlatExpr::And(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::And, &rhs), + FlatExpr::Or(..) => self.cmp_inner(arena, ctx, loc, &lhs, RangeOp::Or, &rhs), + _ => unreachable!(), + } + } + } + + fn interp_not( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + not: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::Not(loc) = not else { + unreachable!() + }; + + let res = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + let [inner] = into_sized::(res); + + self.not_inner(arena, ctx, loc, inner.flatten()) + } + + fn interp_var_def( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + var_def: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::VarDef(loc, maybe_name, maybe_storage, inited) = var_def else { + unreachable!() + }; + + let lhs_ty; + let mut rhs = None; + + if inited { + let res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let [lhs_res, rhs_res] = into_sized::(res); + lhs_ty = Some(lhs_res); + rhs = Some(rhs_res); + } else { + let res = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + let [lhs_res] = into_sized::(res); + lhs_ty = Some(lhs_res); + } + if let Some(lhs_ty) = lhs_ty { + let _ = self.match_var_def( + arena, + ctx, + (maybe_name, maybe_storage), + loc, + &lhs_ty, + rhs.as_ref(), + )?; + Ok(()) + } else { + Err(ExprErr::NoLhs( + loc, + "Variable defintion had no left hand side".to_string(), + )) + } + } + + fn interp_type( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + ty: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::Type(loc, ty) = ty else { + unreachable!() + }; + + if matches!(ctx.peek_expr_flag(self), Some(ExprFlag::FunctionName(..))) { + ctx.take_expr_flag(self); + } + + if let Some(builtin) = Builtin::try_from_ty(ty.clone(), self, arena) { + if let Some(idx) = self.builtins().get(&builtin) { + ctx.push_expr(ExprRet::Single(*idx), self) + .into_expr_err(loc) + } else { + let idx = self.add_node(Node::Builtin(builtin.clone())); + self.builtins_mut().insert(builtin, idx); + ctx.push_expr(ExprRet::Single(idx), self).into_expr_err(loc) + } + } else { + ctx.push_expr(ExprRet::Null, self).into_expr_err(loc) + } + } + + fn interp_return( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + ret: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::Return(loc, nonempty) = ret else { + unreachable!() + }; + if nonempty { + let mut ret = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + self.return_match(arena, ctx, loc, ret.swap_remove(0), 0); + Ok(()) + } else { + self.return_match(arena, ctx, loc, ExprRet::CtxKilled(KilledKind::Ended), 0); + Ok(()) + } + } + + fn interp_var( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + var: FlatExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatExpr::Variable(loc, name) = var else { + unreachable!() + }; + + match ctx.take_expr_flag(self) { + Some(ExprFlag::FunctionName(n, super_call, named_args)) => { + let maybe_names = if named_args { + let start = parse_idx + 1; + Some(self.get_named_args(stack, start, n)) + } else { + None + }; + + let mut found_funcs = self + .find_func(ctx, name, n, &maybe_names, super_call, None) + .into_expr_err(loc)?; + match found_funcs.len() { + 0 => { + // try a regular `variable` lookup + } + 1 => { + let FindFunc { + func, + reordering, + was_lib_func: _, + } = found_funcs.swap_remove(0); + self.order_fn_inputs(ctx, n, reordering, loc)?; + let as_var = + ContextVar::maybe_from_user_ty(self, loc, func.into()).unwrap(); + let fn_var = ContextVarNode::from(self.add_node(as_var)); + ctx.add_var(fn_var, self).into_expr_err(loc)?; + self.add_edge(fn_var, ctx, Edge::Context(ContextEdge::Variable)); + return ctx + .push_expr(ExprRet::Single(fn_var.into()), self) + .into_expr_err(loc); + } + _ => { + let err_str = format!( + "Unable to disambiguate member function call, possible functions: {:?}", + found_funcs + .iter() + .map(|i| i.func.name(self).unwrap()) + .collect::>() + ); + return Err(ExprErr::FunctionNotFound(loc, err_str)); + } + } + } + Some(other) => { + ctx.set_expr_flag(self, other); + } + _ => {} + } + self.variable( + arena, + &solang_parser::pt::Identifier { + loc, + name: name.to_string(), + }, + ctx, + None, + None, + ) + } + + fn order_fn_inputs( + &mut self, + ctx: ContextNode, + n: usize, + reordering: BTreeMap, + loc: Loc, + ) -> Result<(), ExprErr> { + // reorder the inputs now that we have the function + let inputs = ctx.pop_n_latest_exprs(n, loc, self).into_expr_err(loc)?; + let mut tmp_inputs = vec![]; + tmp_inputs.resize(n, ExprRet::Null); + inputs.into_iter().enumerate().for_each(|(i, ret)| { + let target_idx = reordering[&i]; + tmp_inputs[target_idx] = ret; + }); + + // we reverse it because of how they are popped off the stack in the actual + // function call + tmp_inputs + .into_iter() + .rev() + .try_for_each(|i| ctx.push_expr(i, self)) + .into_expr_err(loc) + } + + fn interp_assign( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + assign: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::Assign(loc) = assign else { + unreachable!() + }; + + let res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let [rhs, lhs] = into_sized(res); + self.match_assign_sides(arena, ctx, loc, &lhs, &rhs) + } + + fn interp_array_ty( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + arr_ty: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::ArrayTy(loc, sized) = arr_ty else { + unreachable!() + }; + if sized { + let mut res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let arr_ty = res.swap_remove(0); + let size_var = + ContextVarNode::from(res.swap_remove(0).expect_single().into_expr_err(loc)?); + assert!(size_var.is_const(self, arena).into_expr_err(loc)?); + let size_elem = size_var + .evaled_range_max(self, arena) + .into_expr_err(loc)? + .unwrap(); + let size = size_elem.maybe_concrete().unwrap().val.uint_val().unwrap(); + self.match_ty(ctx, loc, arr_ty, Some(size)) + } else { + let res = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + let [arr_ty] = into_sized(res); + self.match_ty(ctx, loc, arr_ty, None) + } + } + + fn interp_array_slice( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + arr_slice: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::ArraySlice(loc, has_start, has_end) = arr_slice else { + unreachable!() + }; + + let to_pop = 1 + has_start as usize + has_end as usize; + let mut res = ctx + .pop_n_latest_exprs(to_pop, loc, self) + .into_expr_err(loc)?; + + let arr = res.swap_remove(0); + let end = if has_end { + Some(res.swap_remove(0)) + } else { + None + }; + let start = if has_start { + Some(res.swap_remove(0)) + } else { + None + }; + + self.slice_inner(arena, ctx, arr, start, end, loc) + } + + fn interp_array_idx( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + arr_idx: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::ArrayIndexAccess(loc) = arr_idx else { + unreachable!() + }; + let res = ctx.pop_n_latest_exprs(2, loc, self).into_expr_err(loc)?; + let [arr_ty, arr_idx] = into_sized(res); + + self.index_into_array_inner(arena, ctx, arr_ty.flatten(), arr_idx.flatten(), loc) + } + + fn interp_array_lit(&mut self, ctx: ContextNode, arr_lit: FlatExpr) -> Result<(), ExprErr> { + let FlatExpr::ArrayLiteral(loc, n) = arr_lit else { + unreachable!() + }; + + let res = ctx.pop_n_latest_exprs(n, loc, self).into_expr_err(loc)?; + let ty = VarType::try_from_idx(self, res[0].expect_single().into_expr_err(loc)?).unwrap(); + + let ty = Builtin::SizedArray(U256::from(n), ty); + let bn_node = BuiltInNode::from(self.builtin_or_add(ty)); + + let var = ContextVar::new_from_builtin(loc, bn_node, self).into_expr_err(loc)?; + let arr = ContextVarNode::from(self.add_node(var)); + + let kv_pairs = res + .iter() + .enumerate() + .map(|(i, input)| { + let i_var = ContextVarNode::from(input.expect_single().unwrap()); + let idx = self.add_concrete_var(ctx, Concrete::Uint(256, U256::from(i)), loc)?; + self.add_edge(idx, ctx, Edge::Context(ContextEdge::Variable)); + Ok((Elem::from(idx), Elem::from(i_var))) + }) + .collect::, _>>()?; + + let len = RangeConcrete { + val: Concrete::from(U256::from(n)), + loc, + }; + let range_elem: Elem = + Elem::ConcreteDyn(RangeDyn::new(Elem::from(len), kv_pairs, loc)); + + arr.ty_mut(self) + .into_expr_err(loc)? + .set_range(range_elem.into()) + .into_expr_err(loc)?; + ctx.push_expr( + ExprRet::SingleLiteral(arr.latest_version(self).into()), + self, + ) + .into_expr_err(loc) + } + + fn interp_named_func_call( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + func_call: FlatExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatExpr::NamedFunctionCall(_, n) = func_call else { + unreachable!() + }; + + let names_start = parse_idx.saturating_sub(n); + let names = self.get_named_args(stack, names_start, n); + + self.interp_func_call(arena, ctx, func_call, Some(names)) + } + + fn get_named_args( + &self, + stack: &mut Vec, + start: usize, + n: usize, + ) -> Vec<&'static str> { + stack[start..start + n] + .iter() + .map(|named_arg| { + let FlatExpr::NamedArgument(_, name) = named_arg else { + unreachable!() + }; + *name + }) + .collect::>() + } + + fn interp_func_call( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + func_call: FlatExpr, + input_names: Option>, + ) -> Result<(), ExprErr> { + let (loc, n) = match func_call { + FlatExpr::FunctionCall(loc, n) => (loc, n), + FlatExpr::NamedFunctionCall(loc, n) => (loc, n), + _ => unreachable!(), + }; + + let func_and_inputs = ctx + .pop_n_latest_exprs(n + 1, loc, self) + .into_expr_err(loc)?; + + let func = func_and_inputs + .first() + .unwrap() + .expect_single() + .into_expr_err(loc)?; + + let is_new_call = match ctx.peek_expr_flag(self) { + Some(ExprFlag::New) => { + let _ = ctx.take_expr_flag(self); + true + } + _ => false, + }; + + // order the named inputs + let inputs = if n > 0 { + let res = if let Some(input_names) = input_names { + let mut ret = Ok(None); + let ordered_names = match self.node(func) { + Node::ContextVar(..) => match ContextVarNode::from(func).ty(self).unwrap() { + VarType::User(TypeNode::Func(func), _) => func.ordered_param_names(self), + VarType::User(TypeNode::Struct(strukt), _) => { + strukt.ordered_new_param_names(self) + } + VarType::User(TypeNode::Contract(con), _) => { + con.ordered_new_param_names(self) + } + other => todo!("Unhandled named arguments parent: {other:?}"), + }, + Node::Function(..) => FunctionNode::from(func).ordered_param_names(self), + Node::Struct(..) => StructNode::from(func).ordered_new_param_names(self), + Node::Contract(..) => ContractNode::from(func).ordered_new_param_names(self), + other => todo!("Unhandled named arguments parent: {other:?}"), + }; + + if ordered_names != input_names { + let mapping = ordered_names + .iter() + .enumerate() + .filter_map(|(i, n)| Some((input_names.iter().position(|k| k == n)?, i))) + .collect::>(); + if mapping.len() != ordered_names.len() { + ret = Err(ExprErr::ParseError( + loc, + "Named arguments are incorrect".to_string(), + )); + } else { + let mut tmp_inputs = vec![]; + tmp_inputs.resize(n, ExprRet::Null); + func_and_inputs[1..] + .iter() + .enumerate() + .for_each(|(i, ret)| { + let target_idx = mapping[&i]; + tmp_inputs[target_idx] = ret.clone(); + }); + ret = Ok(Some(tmp_inputs)); + } + } + ret + } else { + Ok(None) + }; + res?.unwrap_or(func_and_inputs[1..].to_vec()) + } else { + vec![] + }; + + let inputs = ExprRet::Multi(inputs); + + if self.debug_stack() { + tracing::trace!("inputs: {}", inputs.debug_str(self)); + } + + if is_new_call { + return self.new_call_inner(arena, ctx, func, inputs, loc); + } + + let ty = match self.node(func) { + Node::ContextVar(..) => { + let ty = ContextVarNode::from(func).ty(self).unwrap(); + ty.clone() + } + _ => VarType::try_from_idx(self, func).unwrap(), + }; + + match ty { + VarType::User(TypeNode::Struct(s), _) => { + self.construct_struct_inner(arena, ctx, s, inputs, loc) + } + VarType::User(TypeNode::Contract(c), _) => { + self.construct_contract_inner(arena, ctx, c, inputs, loc) + } + VarType::User(TypeNode::Func(s), _) => { + if self + .builtin_fn_nodes() + .iter() + .any(|(_, v)| *v == s.0.into()) + { + // its a builtin function call + self.call_builtin(arena, ctx, &s.name(self).into_expr_err(loc)?, inputs, loc) + } else { + self.func_call(arena, ctx, loc, &inputs, s, None, None) + } + } + VarType::BuiltIn(bn, _) => { + // cast to type + let Node::Builtin(builtin) = self.node(bn).clone() else { + unreachable!() + }; + self.cast_inner(arena, ctx, ty, &builtin, inputs, loc) + } + VarType::User(TypeNode::Unresolved(idx), _) => Err(ExprErr::ParseError( + loc, + format!( + "Could not call function: {:?}. The inputs may be wrong, or the this is a bug.", + self.node(idx) + ), + )), + e => todo!("Unhandled ty: {e:?}"), + } + } + + fn interp_negatable_literal( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + lit: FlatExpr, + ) -> Result<(), ExprErr> { + let mut negate = false; + match ctx.take_expr_flag(self) { + Some(ExprFlag::Negate) => { + negate = true; + } + Some(other) => ctx.set_expr_flag(self, other), + _ => {} + }; + + match lit { + FlatExpr::NumberLiteral(loc, int, exp, unit) => { + self.number_literal(ctx, loc, int, exp, negate, unit) + } + FlatExpr::HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, loc, b, negate), + FlatExpr::RationalNumberLiteral(loc, integer, fraction, exp, unit) => { + self.rational_number_literal(arena, ctx, loc, integer, fraction, exp, unit, negate) + } + _ => unreachable!(), + } + } + + fn interp_yul_func_call( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + next: FlatYulExpr, + ) -> Result<(), ExprErr> { + let FlatYulExpr::YulFuncCall(loc, name, num_inputs) = next else { + unreachable!() + }; + let inputs = ExprRet::Multi( + ctx.pop_n_latest_exprs(num_inputs, loc, self) + .into_expr_err(loc)?, + ); + self.yul_func_call( + arena, + ctx, + stack, + name, + inputs, + self.current_asm_block(), + loc, + ) + } + + fn interp_yul_var( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatYulExpr, + ) -> Result<(), ExprErr> { + let FlatYulExpr::YulVariable(loc, name) = next else { + unreachable!() + }; + + self.variable( + arena, + &solang_parser::pt::Identifier { + loc, + name: name.to_string(), + }, + ctx, + None, + None, + )?; + + let ret = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); + if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?) + .is_memory(self) + .into_expr_err(loc)? + { + // its a memory based variable, push a uint instead + let b = Builtin::Uint(256); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc) + } else { + ctx.push_expr(ret, self).into_expr_err(loc) + } + } + + fn interp_yul_func_def( + &mut self, + ctx: ContextNode, + stack: &mut Vec, + next: FlatYulExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatYulExpr::YulFuncDef(loc, name, num) = next else { + unreachable!() + }; + + let end = parse_idx + 1 + num; + let exprs = stack[parse_idx + 1..end].to_vec(); + let fn_node = ctx.associated_fn(self).into_expr_err(loc)?; + let yul_fn = self.add_node(YulFunction::new(exprs, name, loc)); + self.add_edge(yul_fn, fn_node, Edge::YulFunction(self.current_asm_block())); + ctx.underlying_mut(self).into_expr_err(loc)?.parse_idx = end; + Ok(()) + } + + fn interp_yul_assign( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatYulExpr, + ) -> Result<(), ExprErr> { + let FlatYulExpr::YulAssign(loc, num) = next else { + unreachable!() + }; + + let to_assign = ctx + .pop_n_latest_exprs(num * 2, loc, self) + .into_expr_err(loc)?; + let (to_assign_to, assignments) = to_assign.split_at(to_assign.len() / 2); + assert!(assignments.len() == to_assign_to.len()); + to_assign_to + .iter() + .zip(assignments) + .try_for_each(|(lhs, rhs)| self.match_assign_sides(arena, ctx, loc, lhs, rhs)) + } + + fn interp_yul_var_decl( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + stack: &mut Vec, + next: FlatYulExpr, + parse_idx: usize, + ) -> Result<(), ExprErr> { + let FlatYulExpr::YulVarDecl(loc, num, assign) = next else { + unreachable!() + }; + + if assign { + let names = stack[parse_idx - num * 2..parse_idx] + .iter() + .filter_map(|s| match s { + FlatExpr::VarDef(_, Some(name), _, _) => Some(name), + _ => None, + }) + .collect::>(); + + names.iter().try_for_each(|name| { + self.variable( + arena, + &Identifier { + loc, + name: name.to_string(), + }, + ctx, + None, + None, + ) + })?; + let to_assign = ctx + .pop_n_latest_exprs(num * 2, loc, self) + .into_expr_err(loc)?; + let (to_assign_to, assignments) = to_assign.split_at(to_assign.len() / 2); + assert!(assignments.len() == to_assign_to.len()); + to_assign_to + .iter() + .zip(assignments) + .try_for_each(|(lhs, rhs)| self.match_assign_sides(arena, ctx, loc, lhs, rhs)) + } else { + Ok(()) + } + } + + fn modify_edges( + &mut self, + ctx: ContextNode, + loc: Loc, + closure: &impl Fn(&mut Self, ContextNode) -> Result<(), GraphError>, + ) -> Result<(), ExprErr> { + let live_edges = ctx.live_edges(self).into_expr_err(loc)?; + if !ctx.killed_or_ret(self).into_expr_err(loc)? { + if ctx.underlying(self).into_expr_err(loc)?.child.is_some() { + if live_edges.is_empty() { + Ok(()) + } else { + live_edges + .iter() + .try_for_each(|ctx| closure(self, *ctx)) + .into_expr_err(loc) + } + } else if live_edges.is_empty() { + closure(self, ctx).into_expr_err(loc) + } else { + live_edges + .iter() + .try_for_each(|ctx| closure(self, *ctx)) + .into_expr_err(loc) + } + } else { + Ok(()) + } + } + + /// Apply an expression or statement to all *live* edges of a context. This is used everywhere + /// to ensure we only ever update *live* contexts. If a context has a subcontext, we *never* + /// want to update the original context. We only ever want to operate on the latest edges. + fn flat_apply_to_edges( + &mut self, + ctx: ContextNode, + loc: Loc, + arena: &mut RangeArena>, + stack: &mut Vec, + closure: &impl Fn( + &mut Self, + &mut RangeArena>, + ContextNode, + Loc, + &mut Vec, + ) -> Result<(), ExprErr>, + ) -> Result<(), ExprErr> { + let live_edges = ctx.live_edges(self).into_expr_err(loc)?; + if !ctx.killed_or_ret(self).into_expr_err(loc)? { + if ctx.underlying(self).into_expr_err(loc)?.child.is_some() { + if live_edges.is_empty() { + Ok(()) + } else { + live_edges + .iter() + .try_for_each(|ctx| closure(self, arena, *ctx, loc, stack)) + } + } else if live_edges.is_empty() { + closure(self, arena, ctx, loc, stack) + } else { + live_edges + .iter() + .try_for_each(|ctx| closure(self, arena, *ctx, loc, stack)) + } + } else { + Ok(()) + } + } +} + +fn into_sized(v: Vec) -> [T; N] { + v.try_into() + .unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())) +} diff --git a/crates/solc-expressions/src/context_builder/fn_calls.rs b/crates/solc-expressions/src/context_builder/fn_calls.rs deleted file mode 100644 index d07f2b0a..00000000 --- a/crates/solc-expressions/src/context_builder/fn_calls.rs +++ /dev/null @@ -1,324 +0,0 @@ -use crate::{ExpressionParser, StatementParser}; -use solang_parser::helpers::CodeLocation; - -use graph::{ - nodes::{Context, ContextNode, FunctionNode}, - AnalyzerBackend, Node, -}; - -use shared::ExprErr; - -use solang_parser::pt::{Expression, Statement}; - -impl FnCallBuilder for T where - T: AnalyzerBackend - + Sized - + StatementParser - + ExpressionParser -{ -} - -/// Dispatcher for building up a context of a function -pub trait FnCallBuilder: - AnalyzerBackend + Sized + StatementParser + ExpressionParser -{ - fn analyze_fn_calls(&mut self, caller: FunctionNode) { - self.fn_calls_fns_mut().entry(caller).or_default(); - if let Some(body) = caller.underlying(self).unwrap().body.clone() { - self.analyze_fn_calls_stmt(caller, body); - } - } - - fn analyze_fn_calls_stmt(&mut self, caller: FunctionNode, stmt: Statement) { - use Statement::*; - match stmt { - Block { statements, .. } => { - statements.iter().for_each(|stmt| { - self.analyze_fn_calls_stmt(caller, stmt.clone()); - }); - } - Assembly { .. } => {} - Args(_, args) => { - args.iter().for_each(|arg| { - self.analyze_fn_calls_expr(caller, arg.expr.clone()); - }); - } - If(_, expr, stmt_true, maybe_stmt_false) => { - self.analyze_fn_calls_expr(caller, expr); - self.analyze_fn_calls_stmt(caller, *stmt_true); - if let Some(stmt_false) = maybe_stmt_false { - self.analyze_fn_calls_stmt(caller, *stmt_false); - } - } - While(_, expr, stmt) => { - self.analyze_fn_calls_expr(caller, expr); - self.analyze_fn_calls_stmt(caller, *stmt); - } - Expression(_, expr) => self.analyze_fn_calls_expr(caller, expr), - VariableDefinition(_, var_decl, maybe_expr) => { - self.analyze_fn_calls_expr(caller, var_decl.ty); - if let Some(expr) = maybe_expr { - self.analyze_fn_calls_expr(caller, expr); - } - } - For(_, maybe_stmt, maybe_expr, maybe_stmt_1, maybe_stmt_2) => { - if let Some(stmt) = maybe_stmt { - self.analyze_fn_calls_stmt(caller, *stmt); - } - - if let Some(expr) = maybe_expr { - self.analyze_fn_calls_expr(caller, *expr); - } - - if let Some(stmt1) = maybe_stmt_1 { - self.analyze_fn_calls_stmt(caller, *stmt1); - } - - if let Some(stmt2) = maybe_stmt_2 { - self.analyze_fn_calls_stmt(caller, *stmt2); - } - } - DoWhile(_, stmt, expr) => { - self.analyze_fn_calls_stmt(caller, *stmt); - self.analyze_fn_calls_expr(caller, expr); - } - Continue(_) => {} - Break(_) => {} - Return(_, maybe_expr) => { - if let Some(expr) = maybe_expr { - self.analyze_fn_calls_expr(caller, expr); - } - } - Revert(_, _, exprs) => { - exprs.iter().for_each(|expr| { - self.analyze_fn_calls_expr(caller, expr.clone()); - }); - } - RevertNamedArgs(_, _, args) => { - args.iter().for_each(|arg| { - self.analyze_fn_calls_expr(caller, arg.expr.clone()); - }); - } - Emit(_, expr) => { - self.analyze_fn_calls_expr(caller, expr); - } - Try(_, expr, maybe_tuple, catch_clauses) => { - self.analyze_fn_calls_expr(caller, expr); - // Option<(ParameterList, Box)> - if let Some((param_list, stmt)) = maybe_tuple { - param_list.iter().for_each(|(_, maybe_param)| { - if let Some(param) = maybe_param { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - } - }); - self.analyze_fn_calls_stmt(caller, *stmt); - } - - catch_clauses - .iter() - .for_each(|catch_clause| match catch_clause { - solang_parser::pt::CatchClause::Simple(_, maybe_param, stmt) => { - if let Some(param) = maybe_param { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - } - self.analyze_fn_calls_stmt(caller, stmt.clone()); - } - solang_parser::pt::CatchClause::Named(_, _, param, stmt) => { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - self.analyze_fn_calls_stmt(caller, stmt.clone()); - } - }) - } - Error(_) => {} - } - } - - fn analyze_fn_calls_expr(&mut self, caller: FunctionNode, expr: Expression) { - use Expression::*; - match expr { - BoolLiteral(_, _) - | NumberLiteral(_, _, _, _) - | RationalNumberLiteral(_, _, _, _, _) - | HexNumberLiteral(_, _, _) - | StringLiteral(_) - | HexLiteral(_) - | AddressLiteral(_, _) - | Variable(_) - | This(_) => {} - - PostIncrement(_, expr) - | PostDecrement(_, expr) - | New(_, expr) - | Parenthesis(_, expr) - | MemberAccess(_, expr, _) - | Not(_, expr) - | Delete(_, expr) - | PreIncrement(_, expr) - | PreDecrement(_, expr) - | BitwiseNot(_, expr) - | Negate(_, expr) - | UnaryPlus(_, expr) => { - self.analyze_fn_calls_expr(caller, *expr); - } - - Power(_, expr, expr1) - | Multiply(_, expr, expr1) - | Divide(_, expr, expr1) - | Modulo(_, expr, expr1) - | Add(_, expr, expr1) - | Subtract(_, expr, expr1) - | ShiftLeft(_, expr, expr1) - | ShiftRight(_, expr, expr1) - | BitwiseAnd(_, expr, expr1) - | BitwiseXor(_, expr, expr1) - | BitwiseOr(_, expr, expr1) - | Less(_, expr, expr1) - | More(_, expr, expr1) - | LessEqual(_, expr, expr1) - | MoreEqual(_, expr, expr1) - | Equal(_, expr, expr1) - | NotEqual(_, expr, expr1) - | And(_, expr, expr1) - | Or(_, expr, expr1) - | Assign(_, expr, expr1) - | AssignOr(_, expr, expr1) - | AssignAnd(_, expr, expr1) - | AssignXor(_, expr, expr1) - | AssignShiftLeft(_, expr, expr1) - | AssignShiftRight(_, expr, expr1) - | AssignAdd(_, expr, expr1) - | AssignSubtract(_, expr, expr1) - | AssignMultiply(_, expr, expr1) - | AssignDivide(_, expr, expr1) - | AssignModulo(_, expr, expr1) => { - self.analyze_fn_calls_expr(caller, *expr); - self.analyze_fn_calls_expr(caller, *expr1); - } - - ArraySubscript(_, expr, maybe_expr) => { - self.analyze_fn_calls_expr(caller, *expr); - if let Some(expr1) = maybe_expr { - self.analyze_fn_calls_expr(caller, *expr1); - } - } - ArraySlice(_, expr, maybe_expr, maybe_expr1) => { - self.analyze_fn_calls_expr(caller, *expr); - if let Some(expr1) = maybe_expr { - self.analyze_fn_calls_expr(caller, *expr1); - } - - if let Some(expr2) = maybe_expr1 { - self.analyze_fn_calls_expr(caller, *expr2); - } - } - ConditionalOperator(_, expr, expr1, expr2) => { - self.analyze_fn_calls_expr(caller, *expr); - self.analyze_fn_calls_expr(caller, *expr1); - self.analyze_fn_calls_expr(caller, *expr2); - } - List(_, param_list) => { - param_list.iter().for_each(|(_, maybe_param)| { - if let Some(param) = maybe_param { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - } - }); - } - ArrayLiteral(_, exprs) => { - exprs.into_iter().for_each(|expr| { - self.analyze_fn_calls_expr(caller, expr); - }); - } - - Type(_, ty) => match ty { - solang_parser::pt::Type::Mapping { key, value, .. } => { - self.analyze_fn_calls_expr(caller, *key); - self.analyze_fn_calls_expr(caller, *value); - } - solang_parser::pt::Type::Function { - params, returns, .. - } => { - params.iter().for_each(|(_, maybe_param)| { - if let Some(param) = maybe_param { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - } - }); - if let Some((param_list, _)) = returns { - param_list.iter().for_each(|(_, maybe_param)| { - if let Some(param) = maybe_param { - self.analyze_fn_calls_expr(caller, param.ty.clone()); - } - }); - } - } - _ => {} - }, - - FunctionCallBlock(_, func_expr, _input_exprs) => { - if let Variable(ref ident) = *func_expr { - let loc = func_expr.loc(); - let ctx = Context::new( - caller, - format!("<{}_parser_fn>", caller.name(self).unwrap()), - loc, - ); - let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); - let visible_funcs = ctx.visible_funcs(self).unwrap(); - let possible_funcs: Vec<_> = visible_funcs - .into_iter() - .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) - .collect(); - if possible_funcs.len() == 1 { - let func = possible_funcs[0]; - self.add_fn_call(caller, func); - } - } - } - NamedFunctionCall(_, func_expr, input_args) => { - if let Variable(ref ident) = *func_expr { - let loc = func_expr.loc(); - let ctx = Context::new( - caller, - format!("<{}_parser_fn>", caller.name(self).unwrap()), - loc, - ); - let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); - let visible_funcs = ctx.visible_funcs(self).unwrap(); - let mut possible_funcs: Vec<_> = visible_funcs - .into_iter() - .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) - .collect(); - possible_funcs.retain(|func| func.params(self).len() == input_args.len()); - if possible_funcs.len() == 1 { - let func = possible_funcs[0]; - self.add_fn_call(caller, func); - } - } - } - FunctionCall(_, func_expr, input_exprs) => { - if let Variable(ref ident) = *func_expr { - let loc = func_expr.loc(); - let ctx = Context::new( - caller, - format!("<{}_parser_fn>", caller.name(self).unwrap()), - loc, - ); - let ctx = ContextNode::from(self.add_node(Node::Context(ctx))); - let visible_funcs = ctx.visible_funcs(self).unwrap(); - let mut possible_funcs: Vec<_> = visible_funcs - .into_iter() - .filter(|f| f.name(self).unwrap().starts_with(&ident.name)) - .collect(); - possible_funcs.retain(|func| func.params(self).len() == input_exprs.len()); - if possible_funcs.len() == 1 { - let func = possible_funcs[0]; - self.add_fn_call(caller, func); - } - } - - input_exprs.iter().for_each(|expr| { - self.analyze_fn_calls_expr(caller, expr.clone()); - }) - } - } - } -} diff --git a/crates/solc-expressions/src/context_builder/mod.rs b/crates/solc-expressions/src/context_builder/mod.rs index 9b40e224..5912b47d 100644 --- a/crates/solc-expressions/src/context_builder/mod.rs +++ b/crates/solc-expressions/src/context_builder/mod.rs @@ -1,32 +1,24 @@ //! Trait and blanket implementation for the core parsing loop +use crate::variable::Variable; use graph::{ elem::Elem, nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, KilledKind}, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; use shared::{ExprErr, GraphError, IntoExprErr, RangeArena}; use solang_parser::pt::{Expression, Loc}; -impl ContextBuilder for T where - T: AnalyzerBackend + Sized + StatementParser -{ -} +impl ContextBuilder for T where T: AnalyzerBackend + Sized {} -mod expr; -mod fn_calls; -mod stmt; +mod flattened; mod test_command_runner; -pub use expr::*; -pub use fn_calls::*; -pub use stmt::*; +pub use flattened::*; pub use test_command_runner::*; /// Dispatcher for building up a context of a function -pub trait ContextBuilder: - AnalyzerBackend + Sized + StatementParser -{ +pub trait ContextBuilder: AnalyzerBackend + Sized { /// TODO: rename this. Sometimes we dont want to kill a context if we hit an error fn widen_if_limit_hit(&mut self, ctx: ContextNode, maybe_err: Result<(), ExprErr>) -> bool { match maybe_err { @@ -57,13 +49,13 @@ pub trait ContextBuilder: &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: &Loc, - paths: &ExprRet, + loc: Loc, + paths: ExprRet, idx: usize, ) { match paths { ExprRet::CtxKilled(kind) => { - let _ = ctx.kill(self, *loc, *kind); + let _ = ctx.kill(self, loc, kind); } ExprRet::Single(expr) | ExprRet::SingleLiteral(expr) => { // construct a variable from the return type @@ -80,7 +72,7 @@ pub trait ContextBuilder: .map(|underlying| { ContextVar::new_from_func_ret(ctx, self, underlying).map(|var| { var.map(|var| { - ContextVarNode::from(self.add_node(Node::ContextVar(var))) + ContextVarNode::from(self.add_node(var)) }).ok_or(GraphError::NodeConfusion("Could not construct a context variable from function return".to_string())) .map(Some) }).and_then(|i| i) @@ -88,24 +80,24 @@ pub trait ContextBuilder: .and_then(|i| i) }) .and_then(|i| i) - .into_expr_err(*loc); + .into_expr_err(loc); let latest = - ContextVarNode::from(*expr).latest_version_or_inherited_in_ctx(ctx, self); + ContextVarNode::from(expr).latest_version_or_inherited_in_ctx(ctx, self); match target_var { Ok(Some(target_var)) => { // perform a cast tracing::trace!( - "{}: casting {:?} to {:?}", + "casting {} to {} in {}", + latest.ty(self).unwrap().as_string(self).unwrap(), + target_var.ty(self).unwrap().as_string(self).unwrap(), ctx.path(self), - latest.ty(self).unwrap(), - target_var.ty(self).unwrap(), ); let next = self - .advance_var_in_ctx_forcible(latest, *loc, ctx, true) + .advance_var_in_ctx_forcible(latest, loc, ctx, true) .unwrap(); - let res = next.cast_from(&target_var, self, arena).into_expr_err(*loc); + let res = next.cast_from(&target_var, self, arena).into_expr_err(loc); self.add_if_err(res); } Ok(None) => {} @@ -114,7 +106,7 @@ pub trait ContextBuilder: // let ret = self.advance_var_in_ctx(latest, *loc, *ctx); let path = ctx.path(self); - let res = latest.underlying_mut(self).into_expr_err(*loc); + let res = latest.underlying_mut(self).into_expr_err(loc); match res { Ok(var) => { tracing::trace!("Returning: {}, {}", path, var.display_name); @@ -122,7 +114,7 @@ pub trait ContextBuilder: self.add_edge(latest, ctx, Edge::Context(ContextEdge::Return)); - let res = ctx.add_return_node(*loc, latest, self).into_expr_err(*loc); + let res = ctx.add_return_node(loc, latest, self).into_expr_err(loc); // ctx.kill(self, *loc, KilledKind::Ended); let _ = self.add_if_err(res); } @@ -130,7 +122,7 @@ pub trait ContextBuilder: } } ExprRet::Multi(rets) => { - rets.iter().enumerate().for_each(|(i, expr_ret)| { + rets.into_iter().enumerate().for_each(|(i, expr_ret)| { self.return_match(arena, ctx, loc, expr_ret, i); }); } @@ -154,11 +146,11 @@ pub trait ContextBuilder: ) -> Result<(), ExprErr>, ) -> Result<(), ExprErr> { let live_edges = ctx.live_edges(self).into_expr_err(loc)?; - tracing::trace!( - "Applying to live edges of: {}. edges: {:#?}", - ctx.path(self), - live_edges.iter().map(|i| i.path(self)).collect::>(), - ); + // tracing::trace!( + // "Applying to live edges of: {}. edges: {:#?}", + // ctx.path(self), + // live_edges.iter().map(|i| i.path(self)).collect::>(), + // ); if !ctx.killed_or_ret(self).into_expr_err(loc)? { if ctx.underlying(self).into_expr_err(loc)?.child.is_some() { if live_edges.is_empty() { diff --git a/crates/solc-expressions/src/context_builder/stmt.rs b/crates/solc-expressions/src/context_builder/stmt.rs deleted file mode 100644 index 143e31c2..00000000 --- a/crates/solc-expressions/src/context_builder/stmt.rs +++ /dev/null @@ -1,649 +0,0 @@ -use crate::{ - context_builder::ContextBuilder, - func_call::{func_caller::FuncCaller, modifier::ModifierCaller}, - loops::Looper, - yul::YulBuilder, - ExpressionParser, TestCommandRunner, -}; - -use graph::{ - elem::Elem, - nodes::{ - Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, - FunctionParamNode, FunctionReturnNode, KilledKind, - }, - AnalyzerBackend, ContextEdge, Edge, Node, -}; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; - -use petgraph::{visit::EdgeRef, Direction}; -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Statement, YulStatement}, -}; - -impl StatementParser for T where - T: AnalyzerBackend + Sized + ExpressionParser -{ -} - -/// Solidity statement parser -pub trait StatementParser: - AnalyzerBackend + Sized + ExpressionParser + TestCommandRunner -{ - /// Performs setup for parsing a solidity statement - fn parse_ctx_statement( - &mut self, - arena: &mut RangeArena>, - stmt: &Statement, - unchecked: bool, - parent_ctx: Option + Copy>, - ) where - Self: Sized, - { - if let Some(parent) = parent_ctx { - match self.node(parent) { - Node::Context(_) => { - let ctx = ContextNode::from(parent.into()); - if !ctx.killed_or_ret(self).unwrap() { - if let Some(live_edges) = - self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) - { - if live_edges.is_empty() { - self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx) - } else { - live_edges.iter().for_each(|fork_ctx| { - self.parse_ctx_stmt_inner( - arena, - stmt, - unchecked, - Some(*fork_ctx), - ); - }); - } - } - } - } - _ => self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx), - } - } else { - self.parse_ctx_stmt_inner(arena, stmt, unchecked, parent_ctx) - } - } - - #[tracing::instrument(level = "trace", skip_all)] - /// Performs parsing of a solidity statement - fn parse_ctx_stmt_inner( - &mut self, - arena: &mut RangeArena>, - stmt: &Statement, - unchecked: bool, - parent_ctx: Option + Copy>, - ) where - Self: Sized, - { - use Statement::*; - // tracing::trace!( - // "stmt: {:#?}, node: {:#?}", - // stmt, - // if let Some(node) = parent_ctx { - // Some(self.node(node.into())) - // } else { - // None - // } - // ); - - // at the end of a statement we shouldn't have anything in the stack? - if let Some(ctx) = parent_ctx { - if let Node::Context(_) = self.node(ctx) { - let c = ContextNode::from(ctx.into()); - let res = self.is_representation_ok(arena).into_expr_err(stmt.loc()); - if let Some(errs) = self.add_if_err(res) { - if !errs.is_empty() { - let Some(is_killed) = - self.add_if_err(c.is_killed(self).into_expr_err(stmt.loc())) - else { - return; - }; - if !is_killed { - c.kill(self, stmt.loc(), KilledKind::ParseError).unwrap(); - errs.into_iter().for_each(|err| { - self.add_expr_err(ExprErr::from_repr_err(stmt.loc(), err)); - }); - } - } - } - - let _ = c.pop_expr_latest(stmt.loc(), self); - if unchecked { - let _ = c.set_unchecked(self); - } else { - let _ = c.unset_unchecked(self); - } - - if c.killed_or_ret(self).unwrap() { - return; - } - } - } - - match stmt { - Block { - loc, - unchecked, - statements, - } => { - tracing::trace!("parsing block"); - let parent = parent_ctx.expect("Free floating contexts shouldn't happen"); - let mut entry_loc = None; - let mut mods_set = false; - let ctx_node = match self.node(parent) { - Node::Function(fn_node) => { - mods_set = fn_node.modifiers_set; - entry_loc = Some(fn_node.loc); - tracing::trace!("creating genesis context for function"); - let ctx = Context::new( - FunctionNode::from(parent.into()), - self.add_if_err( - FunctionNode::from(parent.into()) - .name(self) - .into_expr_err(stmt.loc()), - ) - .unwrap(), - *loc, - ); - let ctx_node = self.add_node(Node::Context(ctx)); - self.add_edge(ctx_node, parent, Edge::Context(ContextEdge::Context)); - - ctx_node - } - Node::Context(_) => { - // let ctx = Context::new_subctx( - // ContextNode::from(parent.into()), - // *loc, - // false, - // self, - // ); - // let ctx_node = self.add_node(Node::Context(ctx)); - // self.add_edge(ctx_node, parent, Edge::Context(ContextEdge::Subcontext)); - // ctx_node - parent.into() - } - e => todo!( - "Expected a context to be created by a function or context but got: {:?}", - e - ), - }; - - // optionally add named input and named outputs into context - let (params, inputs): (Vec<_>, Vec<_>) = self - .graph() - .edges_directed(parent.into(), Direction::Incoming) - .filter(|edge| *edge.weight() == Edge::FunctionParam) - .map(|edge| FunctionParamNode::from(edge.source())) - .collect::>() - .into_iter() - .filter_map(|param_node| { - let res = param_node - .underlying(self) - .into_expr_err(stmt.loc()) - .cloned(); - let func_param = self.add_if_err(res)?; - if let Some(cvar) = ContextVar::maybe_new_from_func_param(self, func_param) - { - let cvar_node = self.add_node(Node::ContextVar(cvar)); - ContextNode::from(ctx_node) - .add_var(cvar_node.into(), self) - .unwrap(); - self.add_edge( - cvar_node, - ctx_node, - Edge::Context(ContextEdge::Variable), - ); - - self.add_edge( - cvar_node, - ctx_node, - Edge::Context(ContextEdge::CalldataVariable), - ); - - let ty = ContextVarNode::from(cvar_node).ty(self).unwrap(); - if let Some(strukt) = ty.maybe_struct() { - strukt - .add_fields_to_cvar(self, *loc, ContextVarNode::from(cvar_node)) - .unwrap(); - } - - Some((param_node, ContextVarNode::from(cvar_node))) - } else { - None - } - }) - .unzip(); - - self.graph() - .edges_directed(parent.into(), Direction::Incoming) - .filter(|edge| *edge.weight() == Edge::FunctionReturn) - .map(|edge| FunctionReturnNode::from(edge.source())) - .collect::>() - .iter() - .for_each(|ret_node| { - let res = ret_node.underlying(self).into_expr_err(stmt.loc()).cloned(); - let func_ret = self.add_if_err(res).unwrap(); - if let Some(cvar) = ContextVar::maybe_new_from_func_ret(self, func_ret) { - let cvar_node = self.add_node(Node::ContextVar(cvar)); - ContextNode::from(ctx_node) - .add_var(cvar_node.into(), self) - .unwrap(); - self.add_edge( - cvar_node, - ctx_node, - Edge::Context(ContextEdge::Variable), - ); - } - }); - - if let Some(fn_loc) = entry_loc { - if !mods_set { - let parent = FunctionNode::from(parent.into()); - let _ = self - .set_modifiers(arena, parent, ctx_node.into()) - .map_err(|e| self.add_expr_err(e)); - } - - let res = self.func_call_inner( - arena, - true, - ctx_node.into(), - parent.into().into(), - fn_loc, - &inputs, - ¶ms, - None, - &None, - ); - if self.widen_if_limit_hit(ctx_node.into(), res) { - return; - } - let res = self.apply_to_edges( - ctx_node.into(), - *loc, - arena, - &|analyzer, _arena, ctx, loc| { - if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - tracing::trace!("killing due to bad funciton call"); - let res = ContextNode::from(ctx_node) - .kill( - analyzer, - fn_loc, - ctx.underlying(analyzer).unwrap().killed.unwrap().1, - ) - .into_expr_err(fn_loc); - let _ = analyzer.add_if_err(res); - } - Ok(()) - }, - ); - - if self.widen_if_limit_hit(ctx_node.into(), res) { - return; - } - - return; - } - - let res = self.apply_to_edges( - ctx_node.into(), - *loc, - arena, - &|analyzer, arena, ctx, _loc| { - statements.iter().for_each(|stmt| { - analyzer.parse_ctx_statement(arena, stmt, *unchecked, Some(ctx)) - }); - Ok(()) - }, - ); - if self.widen_if_limit_hit(ctx_node.into(), res) {} - } - VariableDefinition(loc, var_decl, maybe_expr) => { - let ctx = ContextNode::from( - parent_ctx - .expect("No context for variable definition?") - .into(), - ); - tracing::trace!( - "parsing variable definition, {:?} {var_decl:?}", - ctx.path(self) - ); - - if let Some(rhs) = maybe_expr { - match self.parse_ctx_expr(arena, rhs, ctx) { - Ok(()) => { - let res = self.apply_to_edges( - ctx, - *loc, - arena, - &|analyzer, arena, ctx, loc| { - if !ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - let Some(rhs_paths) = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - format!( - "Variable definition had no right hand side, {}", - ctx.path(analyzer) - ), - )); - }; - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer) - .into_expr_err(loc)?; - return Ok(()); - } - - if let solang_parser::pt::Expression::Variable(ident) = - &var_decl.ty - { - analyzer.apply_to_edges( - ctx, - ident.loc, - arena, - &|analyzer, arena, ctx, _| { - analyzer.variable( - arena, - ident, - ctx, - var_decl.storage.clone(), - None, - ) - }, - )?; - } else { - analyzer.parse_ctx_expr(arena, &var_decl.ty, ctx)?; - } - - analyzer.apply_to_edges( - ctx, - loc, - arena, - &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Variable definition had no left hand side" - .to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer) - .into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_var_def( - arena, - ctx, - var_decl, - loc, - &lhs_paths, - Some(&rhs_paths), - )?; - Ok(()) - }, - ) - } else { - Ok(()) - } - }, - ); - let _ = self.widen_if_limit_hit(ctx, res); - } - ret => { - let _ = self.widen_if_limit_hit(ctx, ret); - } - } - } else { - let res = if let solang_parser::pt::Expression::Variable(ident) = &var_decl.ty { - self.apply_to_edges(ctx, ident.loc, arena, &|analyzer, arena, ctx, _| { - analyzer.variable(arena, ident, ctx, var_decl.storage.clone(), None) - }) - } else { - self.parse_ctx_expr(arena, &var_decl.ty, ctx) - }; - - if self.widen_if_limit_hit(ctx, res) { - return; - } - let res = - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Variable definition had no left hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_var_def(arena, ctx, var_decl, loc, &lhs_paths, None)?; - Ok(()) - }); - let _ = self.widen_if_limit_hit(ctx, res); - } - } - Args(_loc, _args) => { - tracing::trace!("parsing args, {_args:?}"); - } - If(loc, if_expr, true_expr, maybe_false_expr) => { - tracing::trace!("parsing if, {if_expr:?}"); - let ctx = ContextNode::from(parent_ctx.expect("Dangling if statement").into()); - let res = self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - analyzer.cond_op_stmt(arena, loc, if_expr, true_expr, maybe_false_expr, ctx) - }); - let _ = self.widen_if_limit_hit(ctx, res); - } - While(loc, cond, body) => { - tracing::trace!("parsing while, {cond:?}"); - if let Some(parent) = parent_ctx { - let res = self.apply_to_edges( - ContextNode::from(parent.into()), - *loc, - arena, - &|analyzer, arena, ctx, loc| { - analyzer.while_loop(arena, loc, ctx, cond, body) - }, - ); - let _ = self.widen_if_limit_hit(parent.into().into(), res); - } - } - Expression(loc, expr) => { - tracing::trace!("parsing expr, {expr:?}"); - if let Some(parent) = parent_ctx { - let ctx = parent.into().into(); - if let solang_parser::pt::Expression::StringLiteral(lits) = expr { - if lits.len() == 1 { - if let Some(command) = self.test_string_literal(&lits[0].string) { - let _ = self.apply_to_edges( - ctx, - *loc, - arena, - &|analyzer, arena, ctx, loc| { - analyzer.run_test_command(arena, ctx, loc, command.clone()); - Ok(()) - }, - ); - } - } - } - - match self.parse_ctx_expr(arena, expr, ctx) { - Ok(()) => { - let res = self.apply_to_edges( - ctx, - *loc, - arena, - &|analyzer, _arena, ctx, loc| { - if ctx.killed_or_ret(analyzer).into_expr_err(loc)? { - tracing::trace!("killing due to bad expr"); - ContextNode::from(parent.into()) - .kill( - analyzer, - loc, - ctx.underlying(analyzer).unwrap().killed.unwrap().1, - ) - .into_expr_err(loc)?; - } - Ok(()) - }, - ); - let _ = self.widen_if_limit_hit(ctx, res); - } - e => { - let _ = self.widen_if_limit_hit(ctx, e); - } - } - } - } - For(loc, maybe_for_start, maybe_for_middle, maybe_for_end, maybe_for_body) => { - tracing::trace!("parsing for loop"); - if let Some(parent) = parent_ctx { - let res = self.apply_to_edges( - parent.into().into(), - *loc, - arena, - &|analyzer, arena, ctx, loc| { - analyzer.for_loop( - arena, - loc, - ctx, - maybe_for_start, - maybe_for_middle, - maybe_for_end, - maybe_for_body, - ) - }, - ); - let _ = self.widen_if_limit_hit(parent.into().into(), res); - } - } - DoWhile(loc, while_stmt, while_expr) => { - tracing::trace!("parsing `do while`, {while_expr:?}"); - if let Some(parent) = parent_ctx { - let res = self.apply_to_edges( - ContextNode::from(parent.into()), - *loc, - arena, - &|analyzer, arena, ctx, loc| { - analyzer.while_loop(arena, loc, ctx, while_expr, while_stmt) - }, - ); - let _ = self.widen_if_limit_hit(parent.into().into(), res); - } - } - Continue(_loc) => { - tracing::trace!("parsing continue"); - // TODO: We cheat in loops by just widening so continues dont matter yet - } - Break(_loc) => { - tracing::trace!("parsing break"); - // TODO: We cheat in loops by just widening so breaks dont matter yet - } - Assembly { - loc, - dialect: _, - flags: _, - block: yul_block, - } => { - tracing::trace!("parsing assembly"); - let ctx = ContextNode::from( - parent_ctx - .expect("No context for variable definition?") - .into(), - ); - let res = self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_yul_statement( - arena, - &YulStatement::Block(yul_block.clone()), - ctx, - ); - Ok(()) - }); - let _ = self.widen_if_limit_hit(ctx, res); - } - Return(loc, maybe_ret_expr) => { - tracing::trace!("parsing return"); - if let Some(ret_expr) = maybe_ret_expr { - if let Some(parent) = parent_ctx { - let res = self.parse_ctx_expr(arena, ret_expr, parent.into().into()); - if self.widen_if_limit_hit(parent.into().into(), res) { - return; - } - let res = self.apply_to_edges( - parent.into().into(), - *loc, - arena, - &|analyzer, arena, ctx, loc| { - let Ok(Some(ret)) = ctx.pop_expr_latest(loc, analyzer) else { - return Err(ExprErr::NoLhs( - loc, - "Return did not have a associated expression".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let paths = ret.flatten(); - if paths.is_killed() { - tracing::trace!("killing due to bad return"); - let res = ContextNode::from(parent.into()) - .kill(analyzer, loc, paths.killed_kind().unwrap()) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - return Ok(()); - } - analyzer.return_match(arena, ctx, &loc, &paths, 0); - Ok(()) - }, - ); - let _ = self.widen_if_limit_hit(parent.into().into(), res); - } - } - } - Revert(loc, _maybe_err_path, _exprs) => { - tracing::trace!("parsing revert"); - if let Some(parent) = parent_ctx { - let parent = ContextNode::from(parent.into()); - let res = - self.apply_to_edges(parent, *loc, arena, &|analyzer, _arena, ctx, loc| { - let res = ctx - .kill(analyzer, loc, KilledKind::Revert) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - Ok(()) - }); - let _ = self.add_if_err(res); - } - } - RevertNamedArgs(_loc, _maybe_err_path, _named_args) => { - tracing::trace!("parsing named revert"); - todo!("revert named args") - } - Emit(_loc, _emit_expr) => {} - Try(_loc, _try_expr, _maybe_returns, _clauses) => {} - Error(_loc) => {} - } - } -} diff --git a/crates/solc-expressions/src/context_builder/test_command_runner.rs b/crates/solc-expressions/src/context_builder/test_command_runner.rs index 84c70b62..df449ea7 100644 --- a/crates/solc-expressions/src/context_builder/test_command_runner.rs +++ b/crates/solc-expressions/src/context_builder/test_command_runner.rs @@ -1,5 +1,3 @@ -use crate::ExpressionParser; - use graph::{ elem::{Elem, RangeElem}, nodes::{Concrete, ContextNode}, @@ -10,20 +8,18 @@ use shared::{ExprErr, IntoExprErr, RangeArena}; use solang_parser::pt::{Expression, Loc}; impl TestCommandRunner for T where - T: AnalyzerBackend + Sized + ExpressionParser + T: AnalyzerBackend + Sized { } /// Solidity statement parser -pub trait TestCommandRunner: - AnalyzerBackend + Sized + ExpressionParser -{ +pub trait TestCommandRunner: AnalyzerBackend + Sized { fn run_test_command( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, test_command: TestCommand, + loc: Loc, ) -> Option<()> { match test_command { TestCommand::Variable(var_name, VariableCommand::RangeAssert { min, max }) => { @@ -75,7 +71,7 @@ pub trait TestCommandRunner: } } TestCommand::Coverage(CoverageCommand::OnlyPath) => { - if let Some(parent) = ctx.underlying(self).unwrap().parent_ctx { + if let Some(parent) = ctx.underlying(self).unwrap().parent_ctx() { if parent.underlying(self).unwrap().child.is_some() { self.add_expr_err(ExprErr::TestError( loc, diff --git a/crates/solc-expressions/src/env.rs b/crates/solc-expressions/src/env.rs index f5c9436a..6c77f12c 100644 --- a/crates/solc-expressions/src/env.rs +++ b/crates/solc-expressions/src/env.rs @@ -3,7 +3,7 @@ use crate::{func_call::helper::CallerHelper, func_call::modifier::ModifierCaller use graph::{ elem::Elem, nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; use shared::{ExprErr, IntoExprErr, RangeArena, StorageLocation}; @@ -57,9 +57,9 @@ pub trait Env: AnalyzerBackend + Sized { fn block_access( &mut self, - loc: Loc, ctx: ContextNode, ident_name: &str, + loc: Loc, ) -> Result { let name = format!("block.{}", ident_name); tracing::trace!("Block Env member access: {}", name); @@ -74,10 +74,7 @@ pub trait Env: AnalyzerBackend + Sized { "hash" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.hash { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.blockhash".to_string(), - ) + (self.add_node(c).into(), "block.blockhash".to_string()) } else { let node = self.builtin_or_add(Builtin::Bytes(32)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -87,7 +84,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -96,10 +93,7 @@ pub trait Env: AnalyzerBackend + Sized { "basefee" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.basefee { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.basefee".to_string(), - ) + (self.add_node(c).into(), "block.basefee".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -109,7 +103,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -118,10 +112,7 @@ pub trait Env: AnalyzerBackend + Sized { "chainid" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.chainid { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.chainid".to_string(), - ) + (self.add_node(c).into(), "block.chainid".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -131,7 +122,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -140,10 +131,7 @@ pub trait Env: AnalyzerBackend + Sized { "coinbase" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.coinbase { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.coinbase".to_string(), - ) + (self.add_node(c).into(), "block.coinbase".to_string()) } else { let node = self.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -153,7 +141,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -162,10 +150,7 @@ pub trait Env: AnalyzerBackend + Sized { "difficulty" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.difficulty { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.difficulty".to_string(), - ) + (self.add_node(c).into(), "block.difficulty".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -175,7 +160,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -184,10 +169,7 @@ pub trait Env: AnalyzerBackend + Sized { "gaslimit" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.gaslimit { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.gaslimit".to_string(), - ) + (self.add_node(c).into(), "block.gaslimit".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -197,7 +179,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -206,10 +188,7 @@ pub trait Env: AnalyzerBackend + Sized { "number" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.number { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.number".to_string(), - ) + (self.add_node(c).into(), "block.number".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -219,7 +198,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -228,10 +207,7 @@ pub trait Env: AnalyzerBackend + Sized { "prevrandao" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.prevrandao { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.prevrandao".to_string(), - ) + (self.add_node(c).into(), "block.prevrandao".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -241,7 +217,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -250,10 +226,7 @@ pub trait Env: AnalyzerBackend + Sized { "timestamp" => { if let Some(d) = self.block().underlying(self).into_expr_err(loc)?.timestamp { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "block.timestamp".to_string(), - ) + (self.add_node(c).into(), "block.timestamp".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -263,7 +236,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -282,7 +255,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Block(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); Ok(ExprRet::Single(cvar)) @@ -291,9 +264,9 @@ pub trait Env: AnalyzerBackend + Sized { fn msg_access( &mut self, - loc: Loc, ctx: ContextNode, ident_name: &str, + loc: Loc, ) -> Result { let name = format!("msg.{}", ident_name); tracing::trace!("Msg Env member access: {}", name); @@ -309,10 +282,7 @@ pub trait Env: AnalyzerBackend + Sized { "data" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.data.clone() { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "msg.data".to_string(), - ) + (self.add_node(c).into(), "msg.data".to_string()) } else { let b = Builtin::DynamicBytes; let node = self.builtin_or_add(b); @@ -323,7 +293,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -332,10 +302,7 @@ pub trait Env: AnalyzerBackend + Sized { "sender" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.sender { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "msg.sender".to_string(), - ) + (self.add_node(c).into(), "msg.sender".to_string()) } else { let node = self.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -345,7 +312,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -354,10 +321,7 @@ pub trait Env: AnalyzerBackend + Sized { "sig" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.sig { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "msg.sig".to_string(), - ) + (self.add_node(c).into(), "msg.sig".to_string()) } else { let node = self.builtin_or_add(Builtin::Bytes(4)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -367,7 +331,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -376,10 +340,7 @@ pub trait Env: AnalyzerBackend + Sized { "value" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.value { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "msg.value".to_string(), - ) + (self.add_node(c).into(), "msg.value".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -389,7 +350,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -398,10 +359,7 @@ pub trait Env: AnalyzerBackend + Sized { "origin" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.origin { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "tx.origin".to_string(), - ) + (self.add_node(c).into(), "tx.origin".to_string()) } else { let node = self.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -411,7 +369,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -420,10 +378,7 @@ pub trait Env: AnalyzerBackend + Sized { "gasprice" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.gasprice { let c = Concrete::from(d); - ( - self.add_node(Node::Concrete(c)).into(), - "tx.gasprice".to_string(), - ) + (self.add_node(c).into(), "tx.gasprice".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -433,7 +388,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -442,7 +397,7 @@ pub trait Env: AnalyzerBackend + Sized { "gaslimit" => { if let Some(d) = self.msg().underlying(self).into_expr_err(loc)?.gaslimit { let c = Concrete::from(d); - (self.add_node(Node::Concrete(c)).into(), "".to_string()) + (self.add_node(c).into(), "".to_string()) } else { let node = self.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), self) @@ -450,7 +405,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); return Ok(ExprRet::Single(cvar)); @@ -470,7 +425,7 @@ pub trait Env: AnalyzerBackend + Sized { var.is_tmp = false; var.is_symbolic = true; var.storage = Some(StorageLocation::Msg(loc)); - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); Ok(ExprRet::Single(cvar)) diff --git a/crates/solc-expressions/src/func_call/apply.rs b/crates/solc-expressions/src/func_call/apply.rs index a191e609..bd67ebc8 100644 --- a/crates/solc-expressions/src/func_call/apply.rs +++ b/crates/solc-expressions/src/func_call/apply.rs @@ -1,7 +1,8 @@ -use crate::context_builder::StatementParser; use crate::helper::CallerHelper; use crate::member_access::ListAccess; use crate::variable::Variable; +use crate::Flatten; +use solang_parser::helpers::CodeLocation; use graph::{ elem::{Elem, RangeElem, RangeExpr, RangeOp}, @@ -9,7 +10,7 @@ use graph::{ Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, FuncVis, FunctionNode, FunctionParamNode, KilledKind, }, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, SolcRange, VarType, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Range, SolcRange, VarType, }; use shared::{AnalyzerLike, ExprErr, IntoExprErr, NodeIdx, RangeArena, StorageLocation}; @@ -69,7 +70,9 @@ pub trait FuncApplier: ); let edges = apply_ctx.successful_edges(self).into_expr_err(loc)?; match edges.len() { - 0 => {} + 0 => { + ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc)?; + } 1 => { if !self.apply_pure( arena, @@ -153,7 +156,8 @@ pub trait FuncApplier: self.handled_funcs_mut().push(func); if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(arena, body, false, Some(func)); + self.traverse_statement(body, None); + self.interpret(func, body.loc(), arena) } seen.push(func); @@ -201,7 +205,8 @@ pub trait FuncApplier: self.handled_funcs_mut().push(func); if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(arena, body, false, Some(func)); + self.traverse_statement(body, None); + self.interpret(func, body.loc(), arena) } seen.push(func); @@ -248,7 +253,8 @@ pub trait FuncApplier: self.handled_funcs_mut().push(func); if let Some(body) = &func.underlying(self).unwrap().body.clone() { - self.parse_ctx_statement(arena, body, false, Some(func)); + self.traverse_statement(body, None); + self.interpret(func, body.loc(), arena) } seen.push(func); @@ -271,8 +277,26 @@ pub trait FuncApplier: target_ctx: ContextNode, forks: bool, ) -> Result { - let replacement_map = - self.basic_inputs_replacement_map(arena, apply_ctx, loc, params, func_inputs)?; + // construct remappings for inputs + // if applying func a(uint x), map will be = resulting_edge .return_nodes(self) @@ -304,7 +328,7 @@ pub trait FuncApplier: }); } - let mut new_cvar = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + let mut new_cvar = ContextVarNode::from(self.add_node(new_var)); self.add_edge(new_cvar, target_ctx, Edge::Context(ContextEdge::Variable)); target_ctx.add_var(new_cvar, self).unwrap(); @@ -339,8 +363,7 @@ pub trait FuncApplier: } }); } - let new_field = - ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + let new_field = ContextVarNode::from(self.add_node(new_var)); self.add_edge( new_field, new_cvar, @@ -397,7 +420,7 @@ pub trait FuncApplier: } }); } - let new_cvar = ContextVarNode::from(self.add_node(Node::ContextVar(new_var))); + let new_cvar = ContextVarNode::from(self.add_node(new_var)); if new_cvar.is_const(self, arena)? && new_cvar.evaled_range_min(self, arena)? @@ -420,7 +443,7 @@ pub trait FuncApplier: if let Some(var) = ContextVar::maybe_new_from_func_ret(self, ret.underlying(self).unwrap().clone()) { - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); target_ctx.add_var(cvar.into(), self).unwrap(); self.add_edge(cvar, target_ctx, Edge::Context(ContextEdge::Variable)); rets.push(ExprRet::Single(cvar)); @@ -456,6 +479,7 @@ pub trait FuncApplier: &mut self, arena: &mut RangeArena>, apply_ctx: ContextNode, + target_ctx: ContextNode, loc: Loc, params: &[FunctionParamNode], func_inputs: &[ContextVarNode], @@ -483,8 +507,14 @@ pub trait FuncApplier: None }; - let replacement = - ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar))); + let replacement = ContextVarNode::from(self.add_node(new_cvar)); + + self.add_edge( + replacement, + target_ctx, + Edge::Context(ContextEdge::Variable), + ); + target_ctx.add_var(replacement, self).unwrap(); if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) { if !replacement.ty_eq_ty(¶m_ty, self).into_expr_err(loc)? { @@ -496,7 +526,7 @@ pub trait FuncApplier: if let Some(_len_var) = replacement.array_to_len_var(self) { // bring the length variable along as well - self.get_length(arena, apply_ctx, loc, *func_input, false) + self.get_length(arena, apply_ctx, *func_input, false, loc) .unwrap(); } diff --git a/crates/solc-expressions/src/func_call/func_caller.rs b/crates/solc-expressions/src/func_call/func_caller.rs index 248de5c5..b6396d09 100644 --- a/crates/solc-expressions/src/func_call/func_caller.rs +++ b/crates/solc-expressions/src/func_call/func_caller.rs @@ -1,322 +1,31 @@ //! Traits & blanket implementations that facilitate performing various forms of function calls. use crate::{ - func_call::apply::FuncApplier, func_call::modifier::ModifierCaller, helper::CallerHelper, - internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller, - namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExpressionParser, StatementParser, + func_call::{apply::FuncApplier, modifier::ModifierCaller}, + helper::CallerHelper, + ContextBuilder, Flatten, }; -use std::cell::RefCell; -use std::rc::Rc; use graph::{ elem::Elem, nodes::{ Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, - FunctionParamNode, ModifierState, + FunctionParamNode, ModifierState, SubContextKind, }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, }; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; +use shared::{ExprErr, IntoExprErr, RangeArena}; -use solang_parser::pt::{Expression, Loc, NamedArgument}; - -use std::collections::BTreeMap; - -#[derive(Debug)] -pub enum NamedOrUnnamedArgs<'a> { - Named(&'a [NamedArgument]), - Unnamed(&'a [Expression]), -} - -impl<'a> NamedOrUnnamedArgs<'a> { - pub fn named_args(&self) -> Option<&'a [NamedArgument]> { - match self { - NamedOrUnnamedArgs::Named(inner) => Some(inner), - _ => None, - } - } - - pub fn unnamed_args(&self) -> Option<&'a [Expression]> { - match self { - NamedOrUnnamedArgs::Unnamed(inner) => Some(inner), - _ => None, - } - } - - pub fn len(&self) -> usize { - match self { - NamedOrUnnamedArgs::Unnamed(inner) => inner.len(), - NamedOrUnnamedArgs::Named(inner) => inner.len(), - } - } - - pub fn is_empty(&self) -> bool { - match self { - NamedOrUnnamedArgs::Unnamed(inner) => inner.len() == 0, - NamedOrUnnamedArgs::Named(inner) => inner.len() == 0, - } - } - - pub fn exprs(&self) -> Vec { - match self { - NamedOrUnnamedArgs::Unnamed(inner) => inner.to_vec(), - NamedOrUnnamedArgs::Named(inner) => inner.iter().map(|i| i.expr.clone()).collect(), - } - } - - pub fn parse( - &self, - arena: &mut RangeArena>, - analyzer: &mut (impl AnalyzerBackend + Sized), - ctx: ContextNode, - loc: Loc, - ) -> Result<(), ExprErr> { - match self { - NamedOrUnnamedArgs::Unnamed(inner) => analyzer.parse_inputs(arena, ctx, loc, inner), - NamedOrUnnamedArgs::Named(inner) => { - let append = Rc::new(RefCell::new(false)); - inner.iter().try_for_each(|arg| { - analyzer.parse_input(arena, ctx, loc, &arg.expr, &append)?; - Ok(()) - })?; - if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - Ok(()) - } - } - } - } - - pub fn parse_n( - &self, - arena: &mut RangeArena>, - n: usize, - analyzer: &mut (impl AnalyzerBackend + Sized), - ctx: ContextNode, - loc: Loc, - ) -> Result<(), ExprErr> { - let append = Rc::new(RefCell::new(false)); - match self { - NamedOrUnnamedArgs::Unnamed(inner) => { - inner.iter().take(n).try_for_each(|arg| { - analyzer.parse_input(arena, ctx, loc, arg, &append)?; - Ok(()) - })?; - if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - Ok(()) - } - } - NamedOrUnnamedArgs::Named(inner) => { - inner.iter().take(n).try_for_each(|arg| { - analyzer.parse_input(arena, ctx, loc, &arg.expr, &append)?; - Ok(()) - })?; - if !inner.is_empty() { - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - Ok(()) - } - } - } - } - - pub fn order(&self, inputs: ExprRet, ordered_params: Vec) -> ExprRet { - if inputs.len() < 2 { - inputs - } else { - match self { - NamedOrUnnamedArgs::Unnamed(_inner) => inputs, - NamedOrUnnamedArgs::Named(inner) => ExprRet::Multi( - ordered_params - .iter() - .map(|param| { - let index = inner - .iter() - .enumerate() - .find(|(_i, arg)| &arg.name.name == param) - .unwrap() - .0; - match &inputs { - ExprRet::Multi(inner) => inner[index].clone(), - _ => panic!("Mismatched ExprRet type"), - } - }) - .collect(), - ), - } - } - } -} +use solang_parser::pt::{CodeLocation, Expression, Loc}; impl FuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + CallerHelper + T: AnalyzerBackend + Sized + GraphBackend { } /// A trait for calling a function pub trait FuncCaller: GraphBackend + AnalyzerBackend + Sized { - #[tracing::instrument(level = "trace", skip_all)] - /// Perform a function call with named inputs - fn named_fn_call_expr( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: &Loc, - func_expr: &Expression, - input_exprs: &[NamedArgument], - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - match func_expr { - MemberAccess(loc, member_expr, ident) => self.call_name_spaced_func( - arena, - ctx, - loc, - member_expr, - ident, - NamedOrUnnamedArgs::Named(input_exprs), - ), - Variable(ident) => self.call_internal_named_func(arena, ctx, loc, ident, input_exprs), - e => Err(ExprErr::IntrinsicNamedArgs( - *loc, - format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"), - )), - } - } - #[tracing::instrument(level = "trace", skip_all)] - /// Perform a function call - fn fn_call_expr( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: &Loc, - func_expr: &Expression, - input_exprs: &[Expression], - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - match func_expr { - MemberAccess(loc, member_expr, ident) => self.call_name_spaced_func( - arena, - ctx, - loc, - member_expr, - ident, - NamedOrUnnamedArgs::Unnamed(input_exprs), - ), - Variable(ident) => self.call_internal_func( - arena, - ctx, - loc, - ident, - func_expr, - NamedOrUnnamedArgs::Unnamed(input_exprs), - ), - _ => { - self.parse_ctx_expr(arena, func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Function call to nonexistent function".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_intrinsic_fallback( - arena, - ctx, - &loc, - &NamedOrUnnamedArgs::Unnamed(input_exprs), - ret, - ) - }) - } - } - } - - /// Perform an intrinsic function call - fn match_intrinsic_fallback( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: &Loc, - input_exprs: &NamedOrUnnamedArgs, - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => { - self.intrinsic_func_call(arena, loc, input_exprs, func_idx, ctx) - } - ExprRet::Multi(inner) => inner.into_iter().try_for_each(|ret| { - self.match_intrinsic_fallback(arena, ctx, loc, input_exprs, ret) - }), - ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc), - ExprRet::Null => Ok(()), - } - } - - /// Setups up storage variables for a function call and calls it - fn setup_fn_call( - &mut self, - arena: &mut RangeArena>, - loc: &Loc, - inputs: &ExprRet, - func_idx: NodeIdx, - ctx: ContextNode, - func_call_str: Option<&str>, - ) -> Result<(), ExprErr> { - // if we have a single match thats our function - let var = match ContextVar::maybe_from_user_ty(self, *loc, func_idx) { - Some(v) => v, - None => panic!( - "Could not create context variable from user type: {:?}", - self.node(func_idx) - ), - }; - - let new_cvarnode = self.add_node(Node::ContextVar(var)); - ctx.add_var(new_cvarnode.into(), self).into_expr_err(*loc)?; - self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable)); - if let Some(func_node) = ContextVarNode::from(new_cvarnode) - .ty(self) - .into_expr_err(*loc)? - .func_node(self) - { - self.func_call(arena, ctx, *loc, inputs, func_node, func_call_str, None) - } else { - unreachable!() - } - } - /// Matches the input kinds and performs the call fn func_call( &mut self, @@ -432,6 +141,11 @@ pub trait FuncCaller: func_call_str: Option<&str>, modifier_state: &Option, ) -> Result<(), ExprErr> { + tracing::trace!( + "Calling function: {} in context: {}", + func_node.name(self).unwrap(), + ctx.path(self) + ); if !entry_call { if let Ok(true) = self.apply(arena, ctx, loc, func_node, params, inputs, &mut vec![]) { return Ok(()); @@ -456,17 +170,67 @@ pub trait FuncCaller: // begin modifier handling by making sure modifiers were set if !func_node.modifiers_set(self).into_expr_err(loc)? { - self.set_modifiers(arena, func_node, ctx)?; + self.set_modifiers(func_node, ctx)?; } // get modifiers let mods = func_node.modifiers(self); + let modifiers_as_base = func_node + .underlying(self) + .into_expr_err(loc)? + .modifiers_as_base() + .into_iter() + .cloned() + .collect::>(); + modifiers_as_base.iter().rev().for_each(|modifier| { + // We need to get the inputs for the modifier call, but + // the expressions are not part of the function body so + // we need to reset the parse index in the function context + // after we parse the inputs + let Some(args) = &modifier.args else { + return; + }; + let curr_parse_idx = callee_ctx.parse_idx(self); + args.iter() + .for_each(|expr| self.traverse_expression(expr, Some(false))); + self.interpret(callee_ctx, loc, arena); + callee_ctx.underlying_mut(self).unwrap().parse_idx = curr_parse_idx; + }); + let is_constructor = func_node.is_constructor(self).into_expr_err(loc)?; self.apply_to_edges( callee_ctx, loc, arena, &|analyzer, arena, callee_ctx, loc| { - if let Some(mod_state) = + if is_constructor { + let mut state = ModifierState::new( + 0, + loc, + func_node, + callee_ctx, + ctx, + renamed_inputs.clone(), + ); + for _ in 0..mods.len() { + analyzer.call_modifier_for_fn( + arena, + loc, + callee_ctx, + func_node, + state.clone(), + )?; + state.num += 1; + } + + analyzer.execute_call_inner( + arena, + loc, + ctx, + callee_ctx, + func_node, + func_call_str, + ) + } else if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state { // we are iterating through modifiers @@ -483,7 +247,6 @@ pub trait FuncCaller: ctx, callee_ctx, func_node, - &renamed_inputs, func_call_str, ) } @@ -506,7 +269,6 @@ pub trait FuncCaller: ctx, callee_ctx, func_node, - &renamed_inputs, func_call_str, ) } @@ -523,7 +285,6 @@ pub trait FuncCaller: caller_ctx: ContextNode, callee_ctx: ContextNode, func_node: FunctionNode, - _renamed_inputs: &BTreeMap, func_call_str: Option<&str>, ) -> Result<(), ExprErr> { tracing::trace!("executing: {}", func_node.name(self).into_expr_err(loc)?); @@ -534,14 +295,15 @@ pub trait FuncCaller: if let Some(var) = ContextVar::maybe_new_from_func_ret(self, ret.underlying(self).unwrap().clone()) { - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); callee_ctx.add_var(cvar.into(), self).unwrap(); self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); } }); // parse the function body - self.parse_ctx_statement(arena, &body, false, Some(callee_ctx)); + self.traverse_statement(&body, None); + self.interpret(callee_ctx, body.loc(), arena); if let Some(mod_state) = &callee_ctx .underlying(self) .into_expr_err(loc)? @@ -559,13 +321,10 @@ pub trait FuncCaller: Ok(()) } } else { - let ret_ctx = Context::new_subctx( - callee_ctx, - Some(caller_ctx), + let subctx_kind = SubContextKind::new_fn_ret(callee_ctx, caller_ctx); + let ret_subctx = Context::add_subctx( + subctx_kind, loc, - None, - None, - false, self, caller_ctx .underlying(self) @@ -574,10 +333,6 @@ pub trait FuncCaller: .clone(), ) .unwrap(); - let ret_subctx = ContextNode::from(self.add_node(Node::Context(ret_ctx))); - ret_subctx - .set_continuation_ctx(self, caller_ctx, "execute_call_inner") - .into_expr_err(loc)?; let res = callee_ctx .set_child_call(ret_subctx, self) diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs index c3d4b554..66bf9670 100644 --- a/crates/solc-expressions/src/func_call/helper.rs +++ b/crates/solc-expressions/src/func_call/helper.rs @@ -1,19 +1,19 @@ //! Helper traits & blanket implementations that help facilitate performing function calls. -use crate::{member_access::ListAccess, variable::Variable, ContextBuilder, ExpressionParser}; +use crate::{member_access::ListAccess, variable::Variable}; use graph::{ elem::Elem, nodes::{ CallFork, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, - FunctionNode, FunctionParamNode, ModifierState, + FunctionNode, FunctionParamNode, ModifierState, SubContextKind, }, AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, }; use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena, StorageLocation}; -use solang_parser::pt::{CodeLocation, Expression, Loc}; +use solang_parser::pt::{Expression, Loc}; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; +use std::collections::BTreeMap; impl CallerHelper for T where T: AnalyzerBackend + Sized {} /// Helper trait for performing function calls @@ -55,7 +55,7 @@ pub trait CallerHelper: AnalyzerBackend + None }; - let node = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar))); + let node = ContextVarNode::from(self.add_node(new_cvar)); self.add_edge( node, @@ -72,7 +72,7 @@ pub trait CallerHelper: AnalyzerBackend + if let Some(_len_var) = input.array_to_len_var(self) { // bring the length variable along as well - self.get_length(arena, callee_ctx, loc, node, false) + self.get_length(arena, callee_ctx, node, false, loc) .unwrap(); } @@ -106,9 +106,7 @@ pub trait CallerHelper: AnalyzerBackend + None }; - let field_node = ContextVarNode::from( - self.add_node(Node::ContextVar(new_field)), - ); + let field_node = ContextVarNode::from(self.add_node(new_field)); self.add_edge( field_node, @@ -159,69 +157,6 @@ pub trait CallerHelper: AnalyzerBackend + .collect::>()) } - #[tracing::instrument(level = "trace", skip_all)] - /// Parses input expressions into [`ExprRet`]s and adds them to the expr ret stack - fn parse_inputs( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - inputs: &[Expression], - ) -> Result<(), ExprErr> { - let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() { - Rc::new(RefCell::new(true)) - } else { - Rc::new(RefCell::new(false)) - }; - - inputs - .iter() - .try_for_each(|input| self.parse_input(arena, ctx, loc, input, &append))?; - - if !inputs.is_empty() { - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - ctx.push_expr(ExprRet::Null, self).into_expr_err(loc) - } - } - - fn parse_input( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - _loc: Loc, - input: &Expression, - append: &Rc>, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, input, ctx)?; - self.apply_to_edges(ctx, input.loc(), arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - if *append.borrow() { - ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc) - } else { - *append.borrow_mut() = true; - ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc) - } - }) - } - /// Creates a new context for a call fn create_call_ctx( &mut self, @@ -240,18 +175,10 @@ pub trait CallerHelper: AnalyzerBackend + .add_gas_cost(self, shared::gas::FUNC_CALL_GAS) .into_expr_err(loc)?; } - let ctx = Context::new_subctx( - curr_ctx, - None, - loc, - None, - Some(func_node), - fn_ext, - self, - modifier_state, - ) - .into_expr_err(loc)?; - let callee_ctx = ContextNode::from(self.add_node(Node::Context(ctx))); + + let subctx_kind = SubContextKind::new_fn_call(curr_ctx, None, func_node, fn_ext); + let callee_ctx = + Context::add_subctx(subctx_kind, loc, self, modifier_state).into_expr_err(loc)?; curr_ctx .set_child_call(callee_ctx, self) .into_expr_err(loc)?; @@ -274,18 +201,21 @@ pub trait CallerHelper: AnalyzerBackend + literals: Vec, input_paths: &ExprRet, funcs: &[FunctionNode], + maybe_names: Option>, ) -> Option { let input_paths = input_paths.clone().flatten(); // try to find the function based on naive signature // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could // be a function that takes an int256 but we evaled as uint256) - let fn_sig = format!( - "{}{}", - fn_name, - input_paths.try_as_func_input_str(self, arena) - ); - if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) { - return Some(*func); + if maybe_names.is_none() { + let fn_sig = format!( + "{}{}", + fn_name, + input_paths.try_as_func_input_str(self, arena) + ); + if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) { + return Some(*func); + } } // filter by input len @@ -306,16 +236,45 @@ pub trait CallerHelper: AnalyzerBackend + .iter() .filter(|func| { let params = func.params(self); + let ordered_names = func.ordered_param_names(self); + + let mut tmp_inputs: Vec = vec![]; + let mut tmp_literals = vec![]; + tmp_literals.resize(literals.len(), false); + tmp_inputs.resize(inputs.len(), 0.into()); + if let Some(ref input_names) = maybe_names { + if &ordered_names[..] != input_names { + let mapping = ordered_names + .iter() + .enumerate() + .filter_map(|(i, n)| { + Some((input_names.iter().position(|k| k == n)?, i)) + }) + .collect::>(); + inputs.iter().enumerate().for_each(|(i, ret)| { + let target_idx = mapping[&i]; + tmp_inputs[target_idx] = *ret; + tmp_literals[target_idx] = literals[i]; + }); + } else { + tmp_inputs.clone_from(&inputs); + tmp_literals.clone_from(&literals); + } + } else { + tmp_inputs.clone_from(&inputs); + tmp_literals.clone_from(&literals); + } + params .iter() - .zip(&inputs) + .zip(&tmp_inputs) .enumerate() .all(|(i, (param, input))| { let param_ty = VarType::try_from_idx(self, (*param).into()).unwrap(); let input_ty = ContextVarNode::from(*input).ty(self).unwrap(); if param_ty.ty_eq(input_ty, self).unwrap() { true - } else if literals[i] { + } else if tmp_literals[i] { let possibilities = ContextVarNode::from(*input) .ty(self) .unwrap() @@ -362,7 +321,7 @@ pub trait CallerHelper: AnalyzerBackend + .modifier_state .is_some() { - if let Some(ret_ctx) = callee_ctx.underlying(self).into_expr_err(loc)?.parent_ctx { + if let Some(ret_ctx) = callee_ctx.underlying(self).into_expr_err(loc)?.parent_ctx() { let ret = ret_ctx.underlying(self).into_expr_err(loc)?.ret.clone(); ret.iter().try_for_each(|(loc, ret)| { let cvar = self.advance_var_in_forced_ctx(*ret, *loc, callee_ctx)?; @@ -408,13 +367,10 @@ pub trait CallerHelper: AnalyzerBackend + return Ok(()); } - let ctx = Context::new_subctx( - callee_ctx, - Some(caller_ctx), + let subctx_kind = SubContextKind::new_fn_ret(callee_ctx, caller_ctx); + let ret_subctx = Context::add_subctx( + subctx_kind, loc, - None, - None, - false, self, caller_ctx .underlying(self) @@ -423,11 +379,6 @@ pub trait CallerHelper: AnalyzerBackend + .clone(), ) .into_expr_err(loc)?; - let ret_subctx = ContextNode::from(self.add_node(Node::Context(ctx))); - ret_subctx - .set_continuation_ctx(self, caller_ctx, "ctx_rets") - .into_expr_err(loc)?; - let res = callee_ctx .set_child_call(ret_subctx, self) .into_expr_err(loc); @@ -479,7 +430,7 @@ pub trait CallerHelper: AnalyzerBackend + ) .into_expr_err(loc)? .unwrap(); - let cvar = ContextVarNode::from(self.add_node(Node::ContextVar(cvar))); + let cvar = ContextVarNode::from(self.add_node(cvar)); callee_ctx.add_var(cvar, self).into_expr_err(loc)?; self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); callee_ctx @@ -544,143 +495,9 @@ pub trait CallerHelper: AnalyzerBackend + } } - /// Inherit the input changes from a function call - fn inherit_input_changes( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - to_ctx: ContextNode, - from_ctx: ContextNode, - renamed_inputs: &BTreeMap, - ) -> Result<(), ExprErr> { - if to_ctx != from_ctx { - self.apply_to_edges(to_ctx, loc, arena, &|analyzer, arena, to_ctx, loc| { - renamed_inputs - .iter() - .try_for_each(|(input_var, updated_var)| { - let new_input = analyzer.advance_var_in_ctx( - input_var.latest_version(analyzer), - loc, - to_ctx, - )?; - let latest_updated = updated_var.latest_version(analyzer); - if let Some(updated_var_range) = - latest_updated.range(analyzer).into_expr_err(loc)? - { - let res = new_input - .set_range_min( - analyzer, - arena, - updated_var_range.range_min().into_owned(), - ) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_input - .set_range_max( - analyzer, - arena, - updated_var_range.range_max().into_owned(), - ) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_input - .set_range_exclusions( - analyzer, - updated_var_range.exclusions.clone(), - ) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } - Ok(()) - }) - })?; - } - Ok(()) - } - /// Inherit the input changes from a function call fn modifier_inherit_return(&mut self, mod_ctx: ContextNode, fn_ctx: ContextNode) { let ret = fn_ctx.underlying(self).unwrap().ret.clone(); mod_ctx.underlying_mut(self).unwrap().ret = ret; } - - /// Inherit the storage changes from a function call - fn inherit_storage_changes( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - inheritor_ctx: ContextNode, - grantor_ctx: ContextNode, - ) -> Result<(), ExprErr> { - if inheritor_ctx != grantor_ctx { - return self.apply_to_edges( - inheritor_ctx, - loc, - arena, - &|analyzer, _arena, inheritor_ctx, loc| { - let vars = grantor_ctx.local_vars(analyzer).clone(); - vars.iter().try_for_each(|(name, old_var)| { - let var = old_var.latest_version(analyzer); - let underlying = var.underlying(analyzer).into_expr_err(loc)?; - if var.is_storage(analyzer).into_expr_err(loc)? { - if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - analyzer - .advance_var_in_ctx( - inheritor_var, - underlying.loc.expect("No loc for val change"), - inheritor_ctx, - ) - .unwrap(); - } else { - let new_in_inheritor = - analyzer.add_node(Node::ContextVar(underlying.clone())); - inheritor_ctx - .add_var(new_in_inheritor.into(), analyzer) - .into_expr_err(loc)?; - analyzer.add_edge( - new_in_inheritor, - inheritor_ctx, - Edge::Context(ContextEdge::Variable), - ); - analyzer.add_edge( - new_in_inheritor, - var, - Edge::Context(ContextEdge::InheritedVariable), - ); - let from_fields = - var.struct_to_fields(analyzer).into_expr_err(loc)?; - let mut struct_stack = from_fields - .into_iter() - .map(|i| (i, new_in_inheritor)) - .collect::>(); - while let Some((field, parent)) = struct_stack.pop() { - let underlying = - field.underlying(analyzer).into_expr_err(loc)?; - let new_field_in_inheritor = - analyzer.add_node(Node::ContextVar(underlying.clone())); - analyzer.add_edge( - new_field_in_inheritor, - parent, - Edge::Context(ContextEdge::AttrAccess("field")), - ); - - let sub_fields = - field.struct_to_fields(analyzer).into_expr_err(loc)?; - struct_stack.extend( - sub_fields - .into_iter() - .map(|i| (i, new_field_in_inheritor)) - .collect::>(), - ); - } - } - } - Ok(()) - }) - }, - ); - } - Ok(()) - } } diff --git a/crates/solc-expressions/src/func_call/internal_call.rs b/crates/solc-expressions/src/func_call/internal_call.rs index d969f3fb..cc1eeb1c 100644 --- a/crates/solc-expressions/src/func_call/internal_call.rs +++ b/crates/solc-expressions/src/func_call/internal_call.rs @@ -1,19 +1,25 @@ //! Traits & blanket implementations that facilitate performing locally scoped function calls. -use crate::func_caller::NamedOrUnnamedArgs; use crate::{ - assign::Assign, func_call::func_caller::FuncCaller, helper::CallerHelper, ContextBuilder, - ExpressionParser, + helper::CallerHelper, + member_access::{BuiltinAccess, LibraryAccess}, }; use graph::{ - elem::Elem, - nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, VarType, + nodes::{BuiltInNode, ContextNode, ContextVarNode, ContractNode, FunctionNode, StructNode}, + AnalyzerBackend, GraphBackend, Node, TypeNode, VarType, }; -use shared::{ExprErr, IntoExprErr, RangeArena}; +use shared::{ExprErr, GraphError, NodeIdx}; -use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument}; +use solang_parser::pt::{Expression, FunctionTy}; + +use std::collections::BTreeMap; + +pub struct FindFunc { + pub func: FunctionNode, + pub reordering: BTreeMap, + pub was_lib_func: bool, +} impl InternalFuncCaller for T where T: AnalyzerBackend + Sized + GraphBackend + CallerHelper @@ -23,313 +29,339 @@ impl InternalFuncCaller for T where pub trait InternalFuncCaller: AnalyzerBackend + Sized + GraphBackend + CallerHelper { - #[tracing::instrument(level = "trace", skip_all)] - /// Perform a named function call - fn call_internal_named_func( + fn ordered_fn_inputs(&self, func: NodeIdx) -> Option> { + match self.node(func) { + Node::ContextVar(..) => match ContextVarNode::from(func).ty(self).unwrap() { + VarType::User(TypeNode::Func(func), _) => Some(func.ordered_param_names(self)), + VarType::User(TypeNode::Struct(strukt), _) => { + Some(strukt.ordered_new_param_names(self)) + } + VarType::User(TypeNode::Contract(con), _) => { + Some(con.ordered_new_param_names(self)) + } + _ => None, + }, + Node::Function(..) => Some(FunctionNode::from(func).ordered_param_names(self)), + Node::Struct(..) => Some(StructNode::from(func).ordered_new_param_names(self)), + Node::Contract(..) => Some(ContractNode::from(func).ordered_new_param_names(self)), + _ => None, + } + } + + fn potential_lib_funcs( &mut self, - arena: &mut RangeArena>, ctx: ContextNode, - loc: &Loc, - ident: &Identifier, - input_args: &[NamedArgument], - ) -> Result<(), ExprErr> { - // It is a function call, check if we have the ident in scope - let funcs = ctx.visible_funcs(self).into_expr_err(*loc)?; - // filter down all funcs to those that match - let possible_funcs = funcs + name: &str, + num_inputs: usize, + maybe_named: &Option>, + maybe_member: Option, + ) -> Result, GraphError> { + let Some(member) = maybe_member else { + return Ok(vec![]); + }; + + let (var_ty, is_storage) = match self.node(member) { + Node::ContextVar(..) => { + let var = ContextVarNode::from(member); + (var.ty(self).unwrap().clone(), var.is_storage(self)?) + } + _ => (VarType::try_from_idx(self, member).unwrap(), false), + }; + + let mut funcs = self.possible_library_funcs(ctx, var_ty.ty_idx()); + match var_ty { + VarType::BuiltIn(_, _) => { + if let Some((ret, is_lib)) = self.builtin_builtin_fn( + BuiltInNode::from(var_ty.ty_idx()), + name, + num_inputs, + is_storage, + )? { + if is_lib { + funcs.push(ret); + } + } + } + VarType::User(TypeNode::Ty(ty), _) => { + if let Some(func) = self.specialize_ty_fn(ty, name)? { + funcs.push(func) + } + } + _ => {} + } + + let mut possible_funcs = funcs .iter() - .filter(|func| { - let named_correctly = func - .name(self) - .unwrap() - .starts_with(&format!("{}(", ident.name)); - if !named_correctly { - false + .filter(|func| func.name(self).unwrap().starts_with(&format!("{name}("))) + .map(|func| (func, func.params(self))) + .filter(|(_, params)| { + // filter by param length, add 1 to inputs due to passing the member + params.len() == (num_inputs + 1) + }) + .filter(|(_, params)| { + if let Some(ref named) = maybe_named { + // we skip the first because its not named when used a library function + params + .iter() + .skip(1) + .all(|param| named.contains(&&*param.name(self).unwrap())) } else { - // filter by params - let params = func.params(self); - if params.len() != input_args.len() { - false - } else { - params.iter().all(|param| { - input_args - .iter() - .any(|input| input.name.name == param.name(self).unwrap()) - }) - } + true } }) - .copied() + .map(|(func, _)| (*func, true)) .collect::>(); + possible_funcs.sort(); + possible_funcs.dedup(); + Ok(possible_funcs) + } - if possible_funcs.is_empty() { - // check structs - let structs = ctx.visible_structs(self).into_expr_err(*loc)?; - let possible_structs = structs - .iter() - .filter(|strukt| { - let named_correctly = strukt - .name(self) - .unwrap() - .starts_with(&ident.name.to_string()); - if !named_correctly { - false - } else { - // filter by params - let fields = strukt.fields(self); - if fields.len() != input_args.len() { - false - } else { - fields.iter().all(|field| { - input_args - .iter() - .any(|input| input.name.name == field.name(self).unwrap()) - }) - } + fn potential_member_funcs( + &mut self, + name: &str, + member: NodeIdx, + num_inputs: usize, + ) -> Result<(Vec, bool), GraphError> { + match self.node(member) { + // Only instantiated contracts and bytes & strings have non-library functions + Node::ContextVar(..) => { + let var = ContextVarNode::from(member); + match var.ty(self)? { + VarType::User(TypeNode::Contract(con_node), _) => { + let c = *con_node; + let func_mapping = c.linearized_functions(self, false)?; + Ok((func_mapping.values().copied().collect(), false)) } - }) - .copied() - .collect::>(); - if possible_structs.is_empty() { - Err(ExprErr::FunctionNotFound( - *loc, - format!( - "No functions or structs found for named function call: {:?}", - ident.name - ), - )) - } else if possible_structs.len() == 1 { - let strukt = possible_structs[0]; - let var = - ContextVar::new_from_struct(*loc, strukt, ctx, self).into_expr_err(*loc)?; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).into_expr_err(*loc)?; - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + _ => Ok((vec![], false)), + } + } + Node::Builtin(_) => { + if let Some((ret, lib)) = + self.builtin_builtin_fn(BuiltInNode::from(member), name, num_inputs, false)? + { + if !lib { + return Ok((vec![ret], true)); + } + } + Ok((vec![], false)) + } + _ => Ok((vec![], false)), + } + } - strukt.fields(self).iter().try_for_each(|field| { - let field_cvar = ContextVar::maybe_new_from_field( - self, - *loc, - ContextVarNode::from(cvar) - .underlying(self) - .into_expr_err(*loc)?, - field.underlying(self).unwrap().clone(), - ) - .expect("Invalid struct field"); + fn potential_funcs( + &mut self, + ctx: ContextNode, + name: &str, + num_inputs: usize, + maybe_named: &Option>, + maybe_member: Option, + ) -> Result<(Vec<(FunctionNode, bool)>, bool), GraphError> { + let funcs = if let Some(member) = maybe_member { + let (mut funcs, early_end) = self.potential_member_funcs(name, member, num_inputs)?; + if early_end && funcs.len() == 1 { + return Ok((vec![(funcs.swap_remove(0), false)], true)); + } else { + funcs + } + } else { + ctx.visible_funcs(self)? + }; - let fc_node = self.add_node(Node::ContextVar(field_cvar)); - self.add_edge( - fc_node, - cvar, - Edge::Context(ContextEdge::AttrAccess("field")), - ); - self.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(fc_node.into(), self).into_expr_err(*loc)?; - let field_as_ret = ExprRet::Single(fc_node); - let input = input_args + let mut possible_funcs = funcs + .iter() + .filter(|func| func.name(self).unwrap().starts_with(&format!("{name}("))) + .map(|func| (func, func.params(self))) + .filter(|(_, params)| { + // filter by params + params.len() == num_inputs + }) + .filter(|(_, params)| { + if let Some(ref named) = maybe_named { + params .iter() - .find(|arg| arg.name.name == field.name(self).unwrap()) - .expect("No field in struct in struct construction"); - self.parse_ctx_expr(arena, &input.expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(assignment) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs(loc, "Array creation failed".to_string())); - }; + .all(|param| named.contains(&&*param.name(self).unwrap())) + } else { + true + } + }) + .map(|(func, _)| (*func, false)) + .collect::>(); + possible_funcs.extend(self.potential_lib_funcs( + ctx, + name, + num_inputs, + maybe_named, + maybe_member, + )?); + possible_funcs.sort(); + possible_funcs.dedup(); + Ok((possible_funcs, false)) + } - if matches!(assignment, ExprRet::CtxKilled(_)) { - ctx.push_expr(assignment, analyzer).into_expr_err(loc)?; - return Ok(()); - } + fn potential_super_funcs( + &mut self, + ctx: ContextNode, + name: &str, + num_inputs: usize, + maybe_named: &Option>, + ) -> Result, GraphError> { + if let Some(contract) = ctx.maybe_associated_contract(self)? { + let mut possible_funcs: Vec<_> = contract + .linearized_functions(self, true)? + .into_iter() + .filter(|(func_name, func_node)| { + if !func_name.starts_with(name) { + return false; + } - analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &assignment)?; - let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - Ok(()) - }) - })?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, _loc| { - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(*loc)?; - Ok(()) - })?; - Ok(()) - } else { - Err(ExprErr::Todo( - *loc, - "Disambiguation of struct construction not currently supported".to_string(), - )) - } - } else if possible_funcs.len() == 1 { - let func = possible_funcs[0]; - let params = func.params(self); - let inputs: Vec<_> = params - .iter() - .map(|param| { - let input = input_args - .iter() - .find(|arg| arg.name.name == param.name(self).unwrap()) - .expect( - "No parameter with named provided in named parameter function call", - ); - input.expr.clone() + let params = func_node.params(self); + + if params.len() != num_inputs { + return false; + } + + if let Some(ref named) = maybe_named { + params + .iter() + .all(|param| named.contains(&&*param.name(self).unwrap())) + } else { + true + } }) + .map(|(_, node)| node) .collect(); - self.parse_inputs(arena, ctx, *loc, &inputs[..])?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let inputs = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - analyzer.setup_fn_call(arena, &ident.loc, &inputs, func.into(), ctx, None) - }) + possible_funcs.sort(); + possible_funcs.dedup(); + Ok(possible_funcs.into_iter().map(|f| (f, false)).collect()) } else { - todo!("Disambiguate named function call"); + Ok(vec![]) } } - #[tracing::instrument(level = "trace", skip_all)] - fn call_internal_func( + fn find_modifier( &mut self, - arena: &mut RangeArena>, ctx: ContextNode, - loc: &Loc, - ident: &Identifier, - func_expr: &Expression, - input_exprs: NamedOrUnnamedArgs, - ) -> Result<(), ExprErr> { - tracing::trace!("function call: {}(..)", ident.name); - // It is a function call, check if we have the ident in scope - let funcs = ctx.visible_funcs(self).into_expr_err(*loc)?; + name: &str, + constructor: bool, + ) -> Result, GraphError> { + let mut potential_mods = if constructor { + let cons = ctx.visible_constructors(self)?; + cons.into_iter() + .filter(|func| { + let res = matches!(func.ty(self), Ok(FunctionTy::Constructor)); + let contract = func.maybe_associated_contract(self).unwrap(); + res && contract.name(self).unwrap().starts_with(&name.to_string()) + }) + .collect::>() + } else { + let mods = ctx.visible_modifiers(self)?; + mods.into_iter() + .filter(|func| matches!(func.ty(self), Ok(FunctionTy::Modifier))) + .filter(|func| func.name(self).unwrap().starts_with(&format!("{name}("))) + .collect::>() + }; - // filter down all funcs to those that match - let possible_funcs = funcs + potential_mods.sort(); + potential_mods.dedup(); + if potential_mods.len() == 1 { + Ok(vec![potential_mods.swap_remove(0)]) + } else { + Ok(potential_mods) + } + } + + fn find_func( + &mut self, + ctx: ContextNode, + name: &str, + num_inputs: usize, + maybe_named: &Option>, + is_super: bool, + member_access: Option, + ) -> Result, GraphError> { + let possible_funcs = if is_super { + self.potential_super_funcs(ctx, name, num_inputs, maybe_named)? + } else { + let (mut funcs, early_end) = + self.potential_funcs(ctx, name, num_inputs, maybe_named, member_access)?; + if early_end { + return Ok(vec![FindFunc { + func: funcs.swap_remove(0).0, + reordering: (0..num_inputs).map(|i| (i, i)).collect(), + was_lib_func: false, + }]); + } else { + funcs + } + }; + + let stack = &ctx.underlying(self)?.expr_ret_stack; + let len = stack.len(); + let mut inputs = stack[len.saturating_sub(num_inputs)..].to_vec(); + inputs.reverse(); + + let matched_funcs = possible_funcs .iter() - .filter(|func| { - let named_correctly = func - .name(self) - .unwrap() - .starts_with(&format!("{}(", ident.name)); - if !named_correctly { - false - } else { - // filter by params - let params = func.params(self); - if params.len() != input_exprs.len() { - false - } else if matches!(input_exprs, NamedOrUnnamedArgs::Named(_)) { - params.iter().all(|param| { - input_exprs - .named_args() - .unwrap() + .filter_map(|(func, lib_func)| { + let ordered_pos_to_input_pos: BTreeMap<_, _> = + if let Some(input_names) = &maybe_named { + let mut ordered_names = self.ordered_fn_inputs(func.0.into())?; + ordered_names = if *lib_func { + // remove the first input for a lib function + ordered_names.remove(0); + ordered_names + } else { + ordered_names + }; + + if ordered_names[..] != input_names[..] { + ordered_names .iter() - .any(|input| input.name.name == param.name(self).unwrap()) - }) + .enumerate() + .filter_map(|(i, n)| { + Some((i, input_names.iter().position(|k| k == n)?)) + }) + .collect() + } else { + (0..input_names.len()).map(|i| (i, i)).collect() + } } else { - true - } + (0..num_inputs).map(|i| (i, i)).collect() + }; + + let checked_params = if *lib_func { + // remove the first element because its already typechecked in a lib func + let mut params = func.params(self); + params.remove(0); + params + } else { + func.params(self) + }; + + let tys = checked_params + .iter() + .map(|param| VarType::try_from_idx(self, param.ty(self).unwrap()).unwrap()) + .collect::>(); + + let all = tys.iter().enumerate().all(|(true_idx, ty)| { + let input_pos = ordered_pos_to_input_pos.get(&true_idx).unwrap(); + inputs[*input_pos] + .implicitly_castable_to(self, ty) + .unwrap_or(false) + }); + + if all { + Some(FindFunc { + func: *func, + reordering: ordered_pos_to_input_pos, + was_lib_func: *lib_func, + }) + } else { + None } }) - .copied() .collect::>(); - - match possible_funcs.len() { - 0 => { - // this is a builtin, cast, or unknown function - self.parse_ctx_expr(arena, func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let ret = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - let ret = ret.flatten(); - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_intrinsic_fallback(arena, ctx, &loc, &input_exprs, ret) - }) - } - 1 => { - // there is only a single possible function - input_exprs.parse(arena, self, ctx, *loc)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let mut inputs = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - inputs = if let Some(ordered_param_names) = - possible_funcs[0].maybe_ordered_param_names(analyzer) - { - input_exprs.order(inputs, ordered_param_names) - } else { - inputs - }; - let inputs = inputs.flatten(); - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.setup_fn_call( - arena, - &ident.loc, - &inputs, - (possible_funcs[0]).into(), - ctx, - None, - ) - }) - } - _ => { - // this is the annoying case due to function overloading & type inference on number literals - input_exprs.parse(arena, self, ctx, *loc)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let inputs = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - let inputs = inputs.flatten(); - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let resizeables: Vec<_> = inputs.as_flat_vec() - .iter() - .map(|idx| { - match VarType::try_from_idx(analyzer, *idx) { - Some(VarType::BuiltIn(bn, _)) => { - matches!(analyzer.node(bn), Node::Builtin(Builtin::Uint(_)) | Node::Builtin(Builtin::Int(_)) | Node::Builtin(Builtin::Bytes(_))) - } - Some(VarType::Concrete(c)) => { - matches!(analyzer.node(c), Node::Concrete(Concrete::Uint(_, _)) | Node::Concrete(Concrete::Int(_, _)) | Node::Concrete(Concrete::Bytes(_, _))) - } - _ => false - } - }) - .collect(); - if let Some(func) = analyzer.disambiguate_fn_call( - arena, - &ident.name, - resizeables, - &inputs, - &possible_funcs, - ) { - analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) - } else { - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not disambiguate function, default input types: {}, possible functions: {:#?}", - inputs.try_as_func_input_str(analyzer, arena), - possible_funcs - .iter() - .map(|i| i.name(analyzer).unwrap()) - .collect::>() - ), - )) - } - }) - } - } + Ok(matched_funcs) } } diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs index aa543077..4419d511 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs @@ -1,12 +1,8 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{ContextBuilder, ExpressionParser}; - use graph::{ - elem::Elem, - nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, + nodes::{Builtin, ContextNode, ContextVar, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::{ExprErr, IntoExprErr, RangeArena}; +use shared::{ExprErr, IntoExprErr}; use solang_parser::pt::{Expression, Loc}; @@ -14,99 +10,74 @@ impl AbiCaller for T where T: AnalyzerBackend + Sized { - /// Perform an `abi.<..>` function call - fn abi_call( + fn abi_decode(&mut self, ctx: ContextNode, inputs: ExprRet, loc: Loc) -> Result<(), ExprErr> { + match inputs { + ExprRet::Single(ty) => match self.node(ty) { + Node::Builtin(_) => { + let var = + ContextVar::new_from_builtin(loc, ty.into(), self).into_expr_err(loc)?; + let node = self.add_node(var); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + Node::ContextVar(cvar) => { + let bn = self + .builtin_or_add(cvar.ty.as_builtin(self).into_expr_err(loc)?) + .into(); + let var = ContextVar::new_from_builtin(loc, bn, self).into_expr_err(loc)?; + let node = self.add_node(var); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + Node::Struct(_) => { + let var = ContextVar::new_from_struct(loc, ty.into(), ctx, self) + .into_expr_err(loc)?; + let node = self.add_node(var); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + Node::Contract(_) => { + let var = + ContextVar::new_from_contract(loc, ty.into(), self).into_expr_err(loc)?; + let node = self.add_node(var); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc)?; + Ok(()) + } + e => todo!("Unhandled type in abi.decode: {e:?}"), + }, + ExprRet::Multi(inner) => inner + .iter() + .try_for_each(|i| self.abi_decode(ctx, i.clone(), loc)), + ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), + e => panic!("This is invalid solidity: {:?}", e), + } + } + + fn abi_call_inner( &mut self, - arena: &mut RangeArena>, - func_name: String, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { + match func_name { "abi.decode" => { // we skip the first because that is what is being decoded. // TODO: check if we have a concrete bytes value - fn match_decode( - ctx: ContextNode, - loc: &Loc, - ret: ExprRet, - analyzer: &mut impl AnalyzerBackend, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::Single(ty) => match analyzer.node(ty) { - Node::Builtin(_) => { - let var = ContextVar::new_from_builtin(*loc, ty.into(), analyzer) - .into_expr_err(*loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(node.into(), analyzer).into_expr_err(*loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - Node::ContextVar(cvar) => { - let bn = analyzer - .builtin_or_add( - cvar.ty.as_builtin(analyzer).into_expr_err(*loc)?, - ) - .into(); - let var = ContextVar::new_from_builtin(*loc, bn, analyzer) - .into_expr_err(*loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(node.into(), analyzer).into_expr_err(*loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - Node::Struct(_) => { - let var = - ContextVar::new_from_struct(*loc, ty.into(), ctx, analyzer) - .into_expr_err(*loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(node.into(), analyzer).into_expr_err(*loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - Node::Contract(_) => { - let var = ContextVar::new_from_contract(*loc, ty.into(), analyzer) - .into_expr_err(*loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(node.into(), analyzer).into_expr_err(*loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(*loc)?; - Ok(()) - } - e => todo!("Unhandled type in abi.decode: {e:?}"), - }, - ExprRet::Multi(inner) => inner - .iter() - .try_for_each(|i| match_decode(ctx, loc, i.clone(), analyzer)), - ExprRet::CtxKilled(kind) => { - ctx.kill(analyzer, *loc, kind).into_expr_err(*loc) - } - e => panic!("This is invalid solidity: {:?}", e), - } - } - let input_exprs = input_exprs.unnamed_args().unwrap(); - self.parse_ctx_expr(arena, &input_exprs[1], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "abi.decode was not given the types for decoding".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - match_decode(ctx, &loc, ret, analyzer) - }) + let decode_as = ExprRet::Multi(inputs.as_vec()[1..].to_vec()); + self.abi_decode(ctx, decode_as, loc) } "abi.encode" | "abi.encodePacked" @@ -116,7 +87,7 @@ pub trait AbiCaller: AnalyzerBackend + Siz // TODO: Support concrete abi encoding let bn = self.builtin_or_add(Builtin::DynamicBytes); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); ctx.push_expr(ExprRet::Single(node), self) diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs index c0c42475..7c4492d3 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs @@ -1,8 +1,6 @@ -use crate::func_caller::NamedOrUnnamedArgs; - use graph::{ nodes::{Builtin, ContextNode, ContextVar, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; use shared::{ExprErr, IntoExprErr}; @@ -13,35 +11,21 @@ impl AddressCaller for T where T: AnalyzerBackend + Sized { /// Perform an `address.<..>` function call - fn address_call( - &mut self, - func_name: String, - _input_exprs: &NamedOrUnnamedArgs, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - match &*func_name { - "delegatecall" | "staticcall" | "call" => self.external_call(&func_name, loc, ctx), - "code" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::DynamicBytes); + fn address_call(&mut self, ctx: ContextNode, func_name: &str, loc: Loc) -> Result<(), ExprErr> { + match func_name { + "delegatecall" | "staticcall" | "call" => self.external_call(ctx, func_name, loc), + "send" => { + let bn = self.builtin_or_add(Builtin::Bool); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(loc)?; - Ok(()) + .into_expr_err(loc) } - "balance" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::Uint(256)); - let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(loc)?; + "transfer" => { + // TODO: handle balance stuff. but otherwise, transfer does not + // produce a return value. Ok(()) } _ => Err(ExprErr::FunctionNotFound( @@ -54,20 +38,20 @@ pub trait AddressCaller: AnalyzerBackend + } } - fn external_call(&mut self, _ty: &str, loc: Loc, ctx: ContextNode) -> Result<(), ExprErr> { + #[tracing::instrument(level = "trace", skip_all)] + fn external_call(&mut self, ctx: ContextNode, _ty: &str, loc: Loc) -> Result<(), ExprErr> { // TODO: Check if we have the code for the address // if we dont, model it as a unrestricted call that can make other calls - ctx.pop_expr_latest(loc, self).into_expr_err(loc)?; // TODO: try to be smarter based on the address input let booln = self.builtin_or_add(Builtin::Bool); let bool_cvar = ContextVar::new_from_builtin(loc, booln.into(), self).into_expr_err(loc)?; - let bool_node = self.add_node(Node::ContextVar(bool_cvar)); + let bool_node = self.add_node(bool_cvar); ctx.add_var(bool_node.into(), self).into_expr_err(loc)?; self.add_edge(bool_node, ctx, Edge::Context(ContextEdge::Variable)); let bn = self.builtin_or_add(Builtin::DynamicBytes); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); ctx.push_expr( diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs index 3bf86b5c..14b02b73 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs @@ -1,10 +1,10 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{array::Array, bin_op::BinOp, ContextBuilder, ExpressionParser, ListAccess}; +use crate::assign::Assign; +use crate::{array::Array, bin_op::BinOp, ListAccess}; use graph::{ elem::*, - nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, Node, + nodes::{Concrete, ContextNode, ContextVarNode, ExprRet}, + AnalyzerBackend, }; use shared::{ExprErr, IntoExprErr, RangeArena}; @@ -16,332 +16,108 @@ impl ArrayCaller for T where T: AnalyzerBackend + Sized { /// Perform an `array.<..>` function call - fn array_call( + #[tracing::instrument(level = "trace", skip_all)] + fn array_call_inner( &mut self, arena: &mut RangeArena>, - func_name: String, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { + match func_name { "push" => { - if input_exprs.len() == 1 { - // array.push() is valid syntax. It pushes a new - // empty element onto the expr ret stack - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "array[].push(..) was not given an element to push".to_string(), - )); - }; - - if matches!(array, ExprRet::CtxKilled(_)) { - ctx.push_expr(array, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - // get length - let arr = array.expect_single().into_expr_err(loc)?; - let arr = ContextVarNode::from(arr).latest_version(analyzer); - - // get length - let len = analyzer - .get_length(arena, ctx, loc, arr, true)? - .unwrap() - .latest_version(analyzer); - - // get the index access and add it to the stack - let _ = analyzer - .index_into_array_raw(arena, ctx, loc, len, arr, false, false)?; - - // create a temporary 1 variable - let cnode = - analyzer.add_node(Node::Concrete(Concrete::from(U256::from(1)))); - let tmp_one = Node::ContextVar( - ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - cnode.into(), - analyzer, - ) - .into_expr_err(loc)?, - ); - let one = ContextVarNode::from(analyzer.add_node(tmp_one)); - - // add 1 to the length - let tmp_len = - analyzer.op(arena, loc, len, one, ctx, RangeOp::Add(false), false)?; - - let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); - tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; - - analyzer.set_var_as_length( - arena, - ctx, - loc, - tmp_len, - arr.latest_version(analyzer), - )?; - - Ok(()) - }) - } else if input_exprs.len() == 2 { - // array.push(value) - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "array[].push(..) was not an array to push to".to_string(), - )); - }; - if matches!(array, ExprRet::CtxKilled(_)) { - ctx.push_expr(array, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.parse_ctx_expr( - arena, - &input_exprs.unnamed_args().unwrap()[1], - ctx, - )?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(new_elem) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "array[].push(..) was not given an element to push".to_string(), - )); - }; + let inputs_vec = inputs.as_vec(); + let arr = inputs_vec[0].expect_single().into_expr_err(loc)?; + let arr = ContextVarNode::from(arr).latest_version(self); - if matches!(new_elem, ExprRet::CtxKilled(_)) { - ctx.push_expr(new_elem, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let pushed_value = - ContextVarNode::from(new_elem.expect_single().unwrap()); + // get length + let len = self + .get_length(arena, ctx, arr, true, loc)? + .unwrap() + .latest_version(self); - // get length - let arr = array.expect_single().into_expr_err(loc)?; - let arr = ContextVarNode::from(arr).latest_version(analyzer); + // get the index access for the *previous* length + let index_access = self + .index_into_array_raw(arena, ctx, loc, len, arr, false, true)? + .unwrap(); - // get length - let len = analyzer - .get_length(arena, ctx, loc, arr, true)? - .unwrap() - .latest_version(analyzer); + // create a temporary 1 variable + let tmp_one = self.add_concrete_var(ctx, Concrete::from(U256::from(1)), loc)?; - // get the index access for the *previous* length - let index_access = analyzer - .index_into_array_raw(arena, ctx, loc, len, arr, false, true)? - .unwrap(); - // create a temporary 1 variable - let cnode = - analyzer.add_node(Node::Concrete(Concrete::from(U256::from(1)))); - let tmp_one = Node::ContextVar( - ContextVar::new_from_concrete( - Loc::Implicit, - ctx, - cnode.into(), - analyzer, - ) - .into_expr_err(loc)?, - ); - let one = ContextVarNode::from(analyzer.add_node(tmp_one)); + // add 1 to the length + let tmp_len = self.op(arena, loc, len, tmp_one, ctx, RangeOp::Add(false), false)?; - // add 1 to the length - let tmp_len = analyzer.op( - arena, - loc, - len, - one, - ctx, - RangeOp::Add(false), - false, - )?; + let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); + tmp_len.underlying_mut(self).unwrap().is_tmp = false; - let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); - tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; + // set the new length + self.set_var_as_length(arena, ctx, loc, tmp_len, arr.latest_version(self))?; - // set the new length - analyzer.set_var_as_length( - arena, - ctx, - loc, - tmp_len, - arr.latest_version(analyzer), - )?; - - // update the index access's range - let elem = Elem::from(pushed_value); - index_access - .set_range_min(analyzer, arena, elem.clone()) - .into_expr_err(loc)?; - index_access - .set_range_max(analyzer, arena, elem.clone()) - .into_expr_err(loc)?; - - // update the array using the index access - analyzer.update_array_from_index_access( - arena, - ctx, - loc, - len, - index_access.latest_version(analyzer), - arr.latest_version(analyzer), - ) - }) - }) - } else { - return Err(ExprErr::InvalidFunctionInput( + if let Some(push_elem) = inputs_vec.get(1) { + self.match_assign_sides( + arena, + ctx, loc, - format!( - "array[].push(..) expected 0 or 1 inputs, got: {}", - input_exprs.len() - ), - )); + &ExprRet::Single(index_access.0.into()), + push_elem, + ) + } else { + ctx.push_expr(ExprRet::Single(index_access.0.into()), self) + .into_expr_err(loc) } } "pop" => { - if input_exprs.len() != 1 { + if inputs.len() != 1 { return Err(ExprErr::InvalidFunctionInput( loc, format!( "array[].pop() expected 0 inputs, got: {}", - input_exprs.len() + inputs.len().saturating_sub(1) ), )); } - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "array[].pop() was not given an array".to_string(), - )); - }; - - if matches!(array, ExprRet::CtxKilled(_)) { - ctx.push_expr(array, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - // get length - let arr = array.expect_single().into_expr_err(loc)?; - let arr = ContextVarNode::from(arr).latest_version(analyzer); - - // get length - let len = analyzer - .get_length(arena, ctx, loc, arr, true)? - .unwrap() - .latest_version(analyzer); - - // create a temporary 1 variable - let cnode = analyzer.add_node(Node::Concrete(Concrete::from(U256::from(1)))); - let tmp_one = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode.into(), analyzer) - .into_expr_err(loc)?, - ); - let one = ContextVarNode::from(analyzer.add_node(tmp_one)); - - // subtract 1 from the length - let tmp_len = - analyzer.op(arena, loc, len, one, ctx, RangeOp::Sub(false), false)?; - - let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); - tmp_len.underlying_mut(analyzer).unwrap().is_tmp = false; - - // get the index access - let index_access = analyzer - .index_into_array_raw(arena, ctx, loc, tmp_len, arr, false, true)? - .unwrap(); - - analyzer.set_var_as_length( - arena, - ctx, - loc, - tmp_len, - arr.latest_version(analyzer), - )?; - index_access - .set_range_min(analyzer, arena, Elem::Null) - .into_expr_err(loc)?; - index_access - .set_range_max(analyzer, arena, Elem::Null) - .into_expr_err(loc)?; - - analyzer.update_array_from_index_access( - arena, - ctx, - loc, - tmp_len, - index_access.latest_version(analyzer), - arr.latest_version(analyzer), - ) - // let Some(array) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - // return Err(ExprErr::NoLhs( - // loc, - // "array[].pop() was not an array to pop from".to_string(), - // )); - // }; - // if matches!(array, ExprRet::CtxKilled(_)) { - // ctx.push_expr(array, analyzer).into_expr_err(loc)?; - // return Ok(()); - // } - - // let arr = array.expect_single().into_expr_err(loc)?; - // let arr = ContextVarNode::from(arr).latest_version(analyzer); - // // get length - // let len = analyzer.get_length(ctx, loc, arr, true)?.unwrap().latest_version(analyzer); - - // // Subtract one from it - // let cnode = analyzer.add_node(Node::Concrete(Concrete::from(U256::from(1)))); - // let tmp_one = Node::ContextVar( - // ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode.into(), analyzer) - // .into_expr_err(loc)?, - // ); - // let one = ContextVarNode::from(analyzer.add_node(tmp_one.clone())); - // let new_len_expr = analyzer.op( - // loc, - // len, - // one, - // ctx, - // RangeOp::Sub(false), - // false, - // )?; - - // if matches!(new_len_expr, ExprRet::CtxKilled(_)) { - // ctx.push_expr(new_len_expr, analyzer).into_expr_err(loc)?; - // return Ok(()); - // } - - // // connect the new length - // let new_len = ContextVarNode::from(new_len_expr.expect_single().unwrap()).latest_version(analyzer); - // let next_arr = analyzer.advance_var_in_ctx(arr.latest_version(analyzer), loc, ctx)?; - // analyzer.add_edge(new_len.latest_version(analyzer), next_arr, Edge::Context(ContextEdge::AttrAccess("length"))); - - // let min = Elem::from(arr).set_indices(RangeDyn::new_for_indices(vec![(new_len.into(), Elem::Null)], loc)); //.set_length(new_len.into()); - // let max = Elem::from(arr).set_indices(RangeDyn::new_for_indices(vec![(new_len.into(), Elem::Null)], loc)); //.set_length(new_len.into()); - // let cnode = analyzer.add_node(Node::Concrete(Concrete::from(U256::zero()))); - // let tmp_zero = Node::ContextVar( - // ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode.into(), analyzer) - // .into_expr_err(loc)?, - // ); - // let zero = ContextVarNode::from(analyzer.add_node(tmp_one)); - // analyzer.add_edge(zero, next_arr.latest_version(analyzer), Edge::Context(ContextEdge::StorageWrite)); - // next_arr - // .set_range_min(analyzer, min) - // .into_expr_err(loc)?; - // next_arr - // .set_range_max(analyzer, max) - // .into_expr_err(loc) - }) + let [arr] = inputs.into_sized(); + let arr = ContextVarNode::from(arr.expect_single().into_expr_err(loc)?) + .latest_version(self); + + // get length + let len = self + .get_length(arena, ctx, arr, true, loc)? + .unwrap() + .latest_version(self); + + // create a temporary 1 variable + let one = self.add_concrete_var(ctx, Concrete::from(U256::from(1)), loc)?; + + // subtract 1 from the length + let tmp_len = self.op(arena, loc, len, one, ctx, RangeOp::Sub(false), false)?; + + let tmp_len = ContextVarNode::from(tmp_len.expect_single().unwrap()); + tmp_len.underlying_mut(self).unwrap().is_tmp = false; + + // get the index access + let index_access = self + .index_into_array_raw(arena, ctx, loc, tmp_len, arr, false, true)? + .unwrap(); + + self.set_var_as_length(arena, ctx, loc, tmp_len, arr.latest_version(self))?; + index_access + .set_range_min(self, arena, Elem::Null) + .into_expr_err(loc)?; + index_access + .set_range_max(self, arena, Elem::Null) + .into_expr_err(loc)?; + + self.update_array_from_index_access( + arena, + ctx, + loc, + tmp_len, + index_access.latest_version(self), + arr.latest_version(self), + ) } _ => Err(ExprErr::FunctionNotFound( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs index 7e09c935..d92ed744 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs @@ -1,12 +1,8 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::ContextBuilder; - use graph::{ - elem::Elem, - nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, - AnalyzerBackend, Node, + nodes::{Builtin, ContextNode, ContextVar, ExprRet}, + AnalyzerBackend, }; -use shared::{ExprErr, IntoExprErr, RangeArena}; +use shared::{ExprErr, IntoExprErr}; use solang_parser::pt::{Expression, Loc}; @@ -17,37 +13,24 @@ pub trait BlockCaller: AnalyzerBackend + S /// Perform a `block` function call fn block_call( &mut self, - arena: &mut RangeArena>, - func_name: String, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { + match func_name { "blockhash" => { - input_exprs.parse_n(arena, 1, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "blockhash function was not provided a block number".to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) + let _input = inputs.expect_single().into_expr_err(loc)?; + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Bytes(32)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + Ok(()) } _ => Err(ExprErr::FunctionNotFound( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs index 20ae536a..7726eb28 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs @@ -1,10 +1,10 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{assign::Assign, func_call::helper::CallerHelper, ContextBuilder, ExpressionParser}; +use crate::{assign::Assign, func_call::helper::CallerHelper}; +use graph::nodes::Builtin; use graph::{ elem::*, - nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, StructNode}, - AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, + nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ContractNode, ExprRet, StructNode}, + AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; @@ -20,203 +20,135 @@ pub trait ConstructorCaller: AnalyzerBackend + Sized + CallerHelper { /// Construct an array - fn construct_array( + fn construct_array_inner( &mut self, arena: &mut RangeArena>, func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, + len_var: ExprRet, loc: Loc, ctx: ContextNode, ) -> Result<(), ExprErr> { - // create a new list - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(len_var) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Array creation failed".to_string())); - }; - - if matches!(len_var, ExprRet::CtxKilled(_)) { - ctx.push_expr(len_var, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let len_cvar = len_var.expect_single().into_expr_err(loc)?; - - let ty = VarType::try_from_idx(analyzer, func_idx); - - let new_arr = ContextVar { - loc: Some(loc), - name: format!("tmp_arr{}", ctx.new_tmp(analyzer).into_expr_err(loc)?), - display_name: "arr".to_string(), - storage: None, - is_tmp: true, - is_symbolic: false, - is_return: false, - tmp_of: None, - dep_on: None, - ty: ty.expect("No type for node"), - }; - - let arr = ContextVarNode::from(analyzer.add_node(Node::ContextVar(new_arr))); - - let len_var = ContextVar { - loc: Some(loc), - name: arr.name(analyzer).into_expr_err(loc)? + ".length", - display_name: arr.display_name(analyzer).unwrap() + ".length", - storage: None, - is_tmp: true, - tmp_of: None, - dep_on: None, - is_symbolic: true, - is_return: false, - ty: ContextVarNode::from(len_cvar) - .underlying(analyzer) - .into_expr_err(loc)? - .ty - .clone(), - }; - - let len_cvar = analyzer.add_node(Node::ContextVar(len_var)); - analyzer.add_edge(arr, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(arr, analyzer).into_expr_err(loc)?; - analyzer.add_edge(len_cvar, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(len_cvar.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge( - len_cvar, - arr, - Edge::Context(ContextEdge::AttrAccess("length")), - ); - - // update the length - if let Some(r) = arr.ref_range(analyzer).into_expr_err(loc)? { - let min = r.evaled_range_min(analyzer, arena).into_expr_err(loc)?; - let max = r.evaled_range_max(analyzer, arena).into_expr_err(loc)?; - - if let Some(mut rd) = min.maybe_range_dyn() { - rd.len = Box::new(Elem::from(len_cvar)); - arr.set_range_min(analyzer, arena, Elem::ConcreteDyn(rd)) - .into_expr_err(loc)?; - } - - if let Some(mut rd) = max.maybe_range_dyn() { - rd.len = Box::new(Elem::from(len_cvar)); - arr.set_range_min(analyzer, arena, Elem::ConcreteDyn(rd)) - .into_expr_err(loc)?; - } - } - - ctx.push_expr(ExprRet::Single(arr.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + let len_cvar = len_var.expect_single().into_expr_err(loc)?; + + let ty = VarType::try_from_idx(self, func_idx); + + let new_arr = ContextVar { + loc: Some(loc), + name: format!("tmp_arr{}", ctx.new_tmp(self).into_expr_err(loc)?), + display_name: "tmp_arr".to_string(), + storage: None, + is_tmp: true, + is_symbolic: false, + is_return: false, + tmp_of: None, + dep_on: None, + ty: ty.expect("No type for node"), + }; + + let arr = ContextVarNode::from(self.add_node(new_arr)); + + let u256 = self.builtin_or_add(Builtin::Uint(256)); + let new_len_var = ContextVar { + loc: Some(loc), + name: arr.name(self).into_expr_err(loc)? + ".length", + display_name: arr.display_name(self).unwrap() + ".length", + storage: None, + is_tmp: true, + tmp_of: None, + dep_on: None, + is_symbolic: true, + is_return: false, + ty: VarType::try_from_idx(self, u256).unwrap(), + }; + + let new_len_cvar = ContextVarNode::from(self.add_node(new_len_var)); + self.add_edge(arr, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(arr, self).into_expr_err(loc)?; + self.add_edge(new_len_cvar, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(new_len_cvar, self).into_expr_err(loc)?; + self.add_edge( + new_len_cvar, + arr, + Edge::Context(ContextEdge::AttrAccess("length")), + ); + + self.assign(arena, loc, new_len_cvar, len_cvar.into(), ctx)?; + + ctx.push_expr(ExprRet::Single(arr.into()), self) + .into_expr_err(loc) } /// Construct a contract - fn construct_contract( + fn construct_contract_inner( &mut self, - arena: &mut RangeArena>, - func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, + _arena: &mut RangeArena>, ctx: ContextNode, + con_node: ContractNode, + _input: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { // construct a new contract - if !input_exprs.is_empty() { - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - } - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - if !input_exprs.is_empty() { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Contract creation failed".to_string())); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } + let var = match ContextVar::maybe_from_user_ty(self, loc, con_node.0.into()) { + Some(v) => v, + None => { + return Err(ExprErr::VarBadType( + loc, + format!( + "Could not create context variable from user type: {:?}", + self.node(con_node) + ), + )) } - - let var = match ContextVar::maybe_from_user_ty(analyzer, loc, func_idx) { - Some(v) => v, - None => { - return Err(ExprErr::VarBadType( - loc, - format!( - "Could not create context variable from user type: {:?}", - analyzer.node(func_idx) - ), - )) - } - }; - // let idx = ret.expect_single().into_expr_err(loc)?; - let contract_cvar = ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - // contract_cvar - // .set_range_min(analyzer, Elem::from(idx)) - // .into_expr_err(loc)?; - // contract_cvar - // .set_range_max(analyzer, Elem::from(idx)) - // .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) - .into_expr_err(loc) - }) + }; + let contract_cvar = ContextVarNode::from(self.add_node(Node::ContextVar(var))); + ctx.push_expr(ExprRet::Single(contract_cvar.into()), self) + .into_expr_err(loc) } - /// Construct a struct - fn construct_struct( + fn construct_struct_inner( &mut self, arena: &mut RangeArena>, - func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + strukt: StructNode, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - // struct construction - let strukt = StructNode::from(func_idx); let var = ContextVar::new_from_struct(loc, strukt, ctx, self).into_expr_err(loc)?; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( + let inputs = inputs.as_vec(); + // set struct fields + strukt + .fields(self) + .iter() + .zip(inputs) + .try_for_each(|(field, input)| { + let field_cvar = ContextVar::maybe_new_from_field( + self, loc, - "Struct Function call failed".to_string(), - )); - }; - - let inputs = inputs.as_vec(); - // set struct fields - strukt - .fields(analyzer) - .iter() - .zip(inputs) - .try_for_each(|(field, input)| { - let field_cvar = ContextVar::maybe_new_from_field( - analyzer, - loc, - ContextVarNode::from(cvar) - .underlying(analyzer) - .into_expr_err(loc)?, - field.underlying(analyzer).unwrap().clone(), - ) - .expect("Invalid struct field"); - - let fc_node = analyzer.add_node(Node::ContextVar(field_cvar)); - analyzer.add_edge( - fc_node, - cvar, - Edge::Context(ContextEdge::AttrAccess("field")), - ); - analyzer.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(fc_node.into(), analyzer).into_expr_err(loc)?; - let field_as_ret = ExprRet::Single(fc_node); - analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &input)?; - let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - Ok(()) - })?; - - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc) - }) + ContextVarNode::from(cvar) + .underlying(self) + .into_expr_err(loc)?, + field.underlying(self).unwrap().clone(), + ) + .expect("Invalid struct field"); + + let fc_node = self.add_node(field_cvar); + self.add_edge( + fc_node, + cvar, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + self.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); + ctx.add_var(fc_node.into(), self).into_expr_err(loc)?; + let field_as_ret = ExprRet::Single(fc_node); + self.match_assign_sides(arena, ctx, loc, &field_as_ret, &input)?; + let _ = ctx.pop_n_latest_exprs(1, loc, self).into_expr_err(loc)?; + Ok(()) + })?; + + ctx.push_expr(ExprRet::Single(cvar), self) + .into_expr_err(loc) } } diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs index 232e2adc..bdeef735 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/dyn_builtin.rs @@ -1,10 +1,9 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{variable::Variable, ContextBuilder, ExpressionParser, ListAccess}; +use crate::{variable::Variable, ListAccess}; use graph::{ elem::{Elem, RangeElem}, nodes::{Builtin, Concrete, ContextNode, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, SolcRange, VarType, + AnalyzerBackend, ContextEdge, Edge, SolcRange, VarType, }; use shared::{ExprErr, IntoExprErr, RangeArena}; @@ -19,13 +18,13 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, - func_name: String, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { - "concat" => self.concat(arena, &loc, input_exprs, ctx), + match func_name { + "concat" => self.concat(arena, ctx, inputs, loc), _ => Err(ExprErr::FunctionNotFound( loc, format!( @@ -41,45 +40,23 @@ pub trait DynBuiltinCaller: AnalyzerBackend>, - loc: &Loc, - input_exprs: &NamedOrUnnamedArgs, ctx: ContextNode, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - input_exprs.unnamed_args().unwrap()[1..] - .iter() - .try_for_each(|expr| { - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let input = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or(ExprRet::Null); - ctx.append_tmp_expr(input, analyzer).into_expr_err(loc) - }) - })?; - - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(inputs) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Concatenation failed".to_string())); - }; - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let inputs = inputs.as_vec(); - if inputs.is_empty() { - ctx.push_expr(ExprRet::Multi(vec![]), analyzer) - .into_expr_err(loc)?; - Ok(()) + let inputs = inputs.as_vec(); + if inputs.is_empty() { + ctx.push_expr(ExprRet::Multi(vec![]), self) + .into_expr_err(loc)?; + Ok(()) + } else { + let start = &inputs[0]; + if inputs.len() > 1 { + self.match_concat(arena, ctx, loc, start.clone(), &inputs[1..], false) } else { - let start = &inputs[0]; - if inputs.len() > 1 { - analyzer.match_concat(arena, ctx, loc, start.clone(), &inputs[1..], false) - } else { - analyzer.match_concat(arena, ctx, loc, start.clone(), &[], false) - } + self.match_concat(arena, ctx, loc, start.clone(), &[], false) } - }) + } } /// Match on the expression returns @@ -97,8 +74,9 @@ pub trait DynBuiltinCaller: AnalyzerBackend { // pop the accumulation node off the stack let accum_node = ctx - .pop_expr_latest(loc, self) + .pop_n_latest_exprs(1, loc, self) .into_expr_err(loc)? + .first() .unwrap() .expect_single() .unwrap(); @@ -185,12 +163,12 @@ pub trait DynBuiltinCaller: AnalyzerBackend { let new_val = accum_node.clone().concat(right_node).unwrap(); - let new_cnode = self.add_node(Node::Concrete(new_val)); + let new_cnode = self.add_node(new_val); VarType::Concrete(new_cnode.into()) } (accum_node @ Concrete::DynBytes(..), right_node @ Concrete::DynBytes(..)) => { let new_val = accum_node.clone().concat(right_node).unwrap(); - let new_cnode = self.add_node(Node::Concrete(new_val)); + let new_cnode = self.add_node(new_val); VarType::Concrete(new_cnode.into()) } (a, b) => { diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs index 0a5d6b85..0d8f7763 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/intrinsic_caller.rs @@ -1,21 +1,14 @@ -use crate::context_builder::ExpressionParser; -use crate::func_call::func_caller::FuncCaller; -use crate::func_caller::NamedOrUnnamedArgs; use crate::{ - func_call::helper::CallerHelper, + func_call::{func_caller::FuncCaller, helper::CallerHelper}, intrinsic_call::{ AbiCaller, AddressCaller, ArrayCaller, BlockCaller, ConstructorCaller, DynBuiltinCaller, MsgCaller, PrecompileCaller, SolidityCaller, TypesCaller, }, ContextBuilder, }; -use graph::nodes::ContextVar; -use graph::nodes::ContextVarNode; -use graph::nodes::ContractNode; - use graph::{ elem::Elem, - nodes::{Builtin, Concrete, ContextNode, ExprRet}, + nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ContractNode, ExprRet}, AnalyzerBackend, Node, }; use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; @@ -62,37 +55,57 @@ impl IntrinsicFuncCaller for T where pub trait IntrinsicFuncCaller: AnalyzerBackend + Sized + CallerParts { - fn new_call( + fn new_call_inner( &mut self, arena: &mut RangeArena>, - loc: &Loc, - ty_expr: &Expression, - inputs: &[Expression], ctx: ContextNode, + ty_idx: NodeIdx, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, ty_expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ty) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "No type given for call to `new`".to_string(), - )); - }; - let ty_idx = ty.expect_single().into_expr_err(loc)?; - match analyzer.node(ty_idx) { - Node::Builtin(Builtin::Array(_)) | Node::Builtin(Builtin::DynamicBytes) => { - // construct a new list - analyzer.construct_array(arena,ty_idx, &NamedOrUnnamedArgs::Unnamed(inputs), loc, ctx) - } - Node::Contract(_c) => { - let cnode = ContractNode::from(ty_idx); - if let Some(constructor) = cnode.constructor(analyzer) { - let params = constructor.params(analyzer); - if params.is_empty() { + match self.node(ty_idx) { + Node::Builtin(_) => { + assert!(inputs.len() == 1); + self.construct_array_inner(arena, ty_idx, inputs, loc, ctx) + } + Node::Contract(_) => { + let cnode = ContractNode::from(ty_idx); + if let Some(constructor) = cnode.constructor(self) { + let params = constructor.params(self); + if params.is_empty() { + // call the constructor + let inputs = ExprRet::Multi(vec![]); + self.func_call( + arena, + ctx, + loc, + &inputs, + constructor, + None, + None, + )?; + self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { + let var = match ContextVar::maybe_from_user_ty(analyzer, loc, ty_idx) { + Some(v) => v, + None => { + return Err(ExprErr::VarBadType( + loc, + format!( + "Could not create context variable from user type: {:?}", + analyzer.node(ty_idx) + ), + )) + } + }; + let contract_cvar = + ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); + ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) + .into_expr_err(loc) + }) + } else { + + self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { // call the constructor - let inputs = ExprRet::Multi(vec![]); analyzer.func_call( arena, ctx, @@ -120,200 +133,66 @@ pub trait IntrinsicFuncCaller: ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) .into_expr_err(loc) }) - } else { - analyzer.parse_inputs(arena,ctx, loc, inputs)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(input_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No inputs for constructor and expected some".to_string(), - )); - }; - // call the constructor - analyzer.func_call( - arena, - ctx, - loc, - &input_paths, - constructor, - None, - None, - )?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let var = match ContextVar::maybe_from_user_ty(analyzer, loc, ty_idx) { - Some(v) => v, - None => { - return Err(ExprErr::VarBadType( - loc, - format!( - "Could not create context variable from user type: {:?}", - analyzer.node(ty_idx) - ), - )) - } - }; - let contract_cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) - .into_expr_err(loc) - }) - }) - } - } else { - let var = match ContextVar::maybe_from_user_ty(analyzer, loc, ty_idx) { - Some(v) => v, - None => { - return Err(ExprErr::VarBadType( - loc, - format!( - "Could not create context variable from user type: {:?}", - analyzer.node(ty_idx) - ), - )) - } - }; - let contract_cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - ctx.push_expr(ExprRet::Single(contract_cvar.into()), analyzer) - .into_expr_err(loc) + }) } + } else { + let var = match ContextVar::maybe_from_user_ty(self, loc, ty_idx) { + Some(v) => v, + None => { + return Err(ExprErr::VarBadType( + loc, + format!( + "Could not create context variable from user type: {:?}", + self.node(ty_idx) + ), + )) + } + }; + let contract_cvar = + ContextVarNode::from(self.add_node(var)); + ctx.push_expr(ExprRet::Single(contract_cvar.into()), self) + .into_expr_err(loc) } - e => Err(ExprErr::ParseError(loc, format!("Tried to construct a new element of a type ({e:?}) that doesn't support the `new` keyword"))) } - }) + e => Err(ExprErr::ParseError(loc, format!("Tried to construct a new element of a type ({e:?}) that doesn't support the `new` keyword"))) + } } - /// Calls an intrinsic/builtin function call (casts, require, etc.) - #[tracing::instrument(level = "trace", skip_all)] - fn intrinsic_func_call( + fn call_builtin( &mut self, arena: &mut RangeArena>, - loc: &Loc, - input_exprs: &NamedOrUnnamedArgs, - func_idx: NodeIdx, ctx: ContextNode, + name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match self.node(func_idx) { - Node::Function(underlying) => { - if let Some(func_name) = &underlying.name { - match &*func_name.name { - // abi - _ if func_name.name.starts_with("abi.") => { - self.abi_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) - } - // address - "delegatecall" | "staticcall" | "call" | "code" | "balance" => { - self.address_call(func_name.name.clone(), input_exprs, *loc, ctx) - } - // array - "push" | "pop" => { - self.array_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) - } - // block - "blockhash" => { - self.block_call(arena, func_name.name.clone(), input_exprs, *loc, ctx) - } - // dynamic sized builtins - "concat" => self.dyn_builtin_call( - arena, - func_name.name.clone(), - input_exprs, - *loc, - ctx, - ), - // msg - "gasleft" => self.msg_call(func_name.name.clone(), input_exprs, *loc, ctx), - // precompiles - "sha256" | "ripemd160" | "ecrecover" => self.precompile_call( - arena, - func_name.name.clone(), - func_idx, - input_exprs, - *loc, - ctx, - ), - // solidity - "keccak256" | "addmod" | "mulmod" | "require" | "assert" => self - .solidity_call(arena, func_name.name.clone(), input_exprs, *loc, ctx), - // typing - "type" | "wrap" | "unwrap" => self.types_call( - arena, - func_name.name.clone(), - func_idx, - input_exprs, - *loc, - ctx, - ), - e => Err(ExprErr::Todo( - *loc, - format!("builtin function: {e:?} doesn't exist or isn't implemented"), - )), - } - } else { - panic!("unnamed builtin?") - } - } - Node::Builtin(Builtin::Array(_)) => { - // construct a new array - self.construct_array(arena, func_idx, input_exprs, *loc, ctx) - } - Node::Contract(_) => { - // construct a new contract - self.construct_contract(arena, func_idx, input_exprs, *loc, ctx) + let name = name.split('(').collect::>()[0]; + match name { + // abi + _ if name.starts_with("abi.") => self.abi_call_inner(ctx, name, inputs, loc), + // address + "delegatecall" | "staticcall" | "call" | "send" | "transfer" => { + self.address_call(ctx, name, loc) } - Node::Struct(_) => { - // construct a struct - self.construct_struct(arena, func_idx, input_exprs, *loc, ctx) - } - Node::Builtin(ty) => { - // cast to type - self.cast(arena, ty.clone(), func_idx, input_exprs, *loc, ctx) - } - Node::ContextVar(_c) => { - // its a user type, just push it onto the stack - ctx.push_expr(ExprRet::Single(func_idx), self) - .into_expr_err(*loc)?; - Ok(()) - } - Node::Unresolved(_) => { - // Try to give a nice error - input_exprs.parse(arena, self, ctx, *loc)?; - - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Function call failed".to_string())) - }; - - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let visible_funcs = ctx.visible_funcs(analyzer).into_expr_err(loc)? - .iter() - .map(|func| func.name(analyzer).unwrap()) - .collect::>(); - - if let Node::Unresolved(ident) = analyzer.node(func_idx) { - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not find function: \"{}{}\", context: {}, visible functions: {:#?}", - ident.name, - inputs.try_as_func_input_str(analyzer, arena), - ctx.path(analyzer), - visible_funcs - ) - )) - } else { - unreachable!() - } - }) + // array + "push" | "pop" => self.array_call_inner(arena, ctx, name, inputs, loc), + // block + "blockhash" => self.block_call(ctx, name, inputs, loc), + // dynamic sized builtins + "concat" => self.dyn_builtin_call(arena, ctx, name, inputs, loc), + // msg + "gasleft" => self.msg_call(ctx, name, loc), + // precompiles + "sha256" | "ripemd160" | "ecrecover" => self.precompile_call(ctx, name, inputs, loc), + // solidity + "keccak256" | "addmod" | "mulmod" | "require" | "assert" | "selfdestruct" => { + self.solidity_call(arena, ctx, name, inputs, loc) } - e => Err(ExprErr::FunctionNotFound( - *loc, - format!("Unhandled function call type: {e:?}"), + // typing + "type" | "wrap" | "unwrap" => self.types_call(arena, ctx, name, inputs, loc), + e => Err(ExprErr::Todo( + loc, + format!("builtin function: {e:?} doesn't exist or isn't implemented"), )), } } diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs index ad45f72f..1d5b58af 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs @@ -1,8 +1,6 @@ -use crate::func_caller::NamedOrUnnamedArgs; - use graph::{ nodes::{Builtin, ContextNode, ContextVar, ExprRet}, - AnalyzerBackend, Node, + AnalyzerBackend, }; use shared::{ExprErr, IntoExprErr}; @@ -13,14 +11,8 @@ impl MsgCaller for T where T: AnalyzerBackend + Sized { /// Perform a msg's builtin function call, like `gasleft()` - fn msg_call( - &mut self, - func_name: String, - _input_exprs: &NamedOrUnnamedArgs, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - match &*func_name { + fn msg_call(&mut self, ctx: ContextNode, func_name: &str, loc: Loc) -> Result<(), ExprErr> { + match func_name { "gasleft" => { let var = ContextVar::new_from_builtin( loc, @@ -28,7 +20,7 @@ pub trait MsgCaller: AnalyzerBackend + Siz self, ) .into_expr_err(loc)?; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; Ok(()) diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs index 81021a55..4f8e37b1 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs @@ -1,13 +1,11 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{func_call::helper::CallerHelper, ContextBuilder, ExpressionParser}; -use graph::nodes::FunctionNode; +use crate::func_call::helper::CallerHelper; +use graph::nodes::SubContextKind; use graph::{ - elem::Elem, - nodes::{Builtin, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, + nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; +use shared::{ExprErr, IntoExprErr}; use solang_parser::pt::{Expression, Loc}; @@ -23,174 +21,92 @@ pub trait PrecompileCaller: /// Perform a precompile's function call, like `ecrecover` fn precompile_call( &mut self, - arena: &mut RangeArena>, - func_name: String, - func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { + match func_name { "sha256" => { - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "sha256 call was not given input".to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) + // TODO: Compile time calculate the hash if we have concretes. + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Bytes(32)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + Ok(()) } "ripemd160" => { - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "ripemd160 was not given input".to_string(), - )); - }; - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) + // TODO: Compile time calculate the hash if we have concretes. + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Bytes(32)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + Ok(()) } "ecrecover" => { - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let cctx = Context::new_subctx( - ctx, - None, - loc, - None, - Some(func_idx.into()), - true, - analyzer, - None, - ) - .into_expr_err(loc)?; - let call_ctx = analyzer.add_node(Node::Context(cctx)); - ctx.set_child_call(call_ctx.into(), analyzer) - .into_expr_err(loc)?; - let call_node = analyzer.add_node(Node::FunctionCall); - analyzer.add_edge(call_node, func_idx, Edge::Context(ContextEdge::Call)); - analyzer.add_edge(call_node, ctx, Edge::Context(ContextEdge::Subcontext)); - analyzer.add_edge(call_ctx, call_node, Edge::Context(ContextEdge::Subcontext)); - - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "ecrecover did not receive inputs".to_string(), - )); - }; + let func_idx = *(self.builtin_fn_nodes().get("ecrecover").unwrap()); + let subctx_kind = SubContextKind::new_fn_call(ctx, None, func_idx.into(), true); + let call_ctx = + Context::add_subctx(subctx_kind, loc, self, None).into_expr_err(loc)?; + ctx.set_child_call(call_ctx, self).into_expr_err(loc)?; + let call_node = self.add_node(Node::FunctionCall); + self.add_edge(call_node, func_idx, Edge::Context(ContextEdge::Call)); + self.add_edge(call_node, ctx, Edge::Context(ContextEdge::Subcontext)); + self.add_edge(call_ctx, call_node, Edge::Context(ContextEdge::Subcontext)); - let input = if let Some(ordered_param_names) = - FunctionNode::from(func_idx).maybe_ordered_param_names(analyzer) - { - input_exprs.order(input, ordered_param_names) - } else { - input - }; - - if matches!(input, ExprRet::CtxKilled(_)) { - ctx.push_expr(input, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let mut inner_vals = vec![]; - match input { - ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { - inner_vals - .push(ContextVarNode::from(var).display_name(analyzer).unwrap()); - } - _ => inner_vals.push("".to_string()), + let mut inner_vals = vec![]; + match inputs { + ExprRet::Single(var) | ExprRet::SingleLiteral(var) => { + inner_vals.push(ContextVarNode::from(var).display_name(self).unwrap()); } - let inner_name = inner_vals.into_iter().collect::>().join(", "); - let mut var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Address).into(), - analyzer, - ) + _ => inner_vals.push("".to_string()), + } + let inner_name = inner_vals.into_iter().collect::>().join(", "); + let mut var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Address).into(), + self, + ) + .into_expr_err(loc)?; + var.display_name = format!("ecrecover({})", inner_name); + var.is_symbolic = true; + var.is_return = true; + let cvar = self.add_node(var); + ctx.add_var(cvar.into(), self).into_expr_err(loc)?; + self.add_edge(cvar, call_ctx, Edge::Context(ContextEdge::Variable)); + self.add_edge(cvar, call_ctx, Edge::Context(ContextEdge::Return)); + call_ctx + .add_return_node(loc, cvar.into(), self) .into_expr_err(loc)?; - var.display_name = format!("ecrecover({})", inner_name); - var.is_symbolic = true; - var.is_return = true; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(cvar, call_ctx, Edge::Context(ContextEdge::Variable)); - analyzer.add_edge(cvar, call_ctx, Edge::Context(ContextEdge::Return)); - ContextNode::from(call_ctx) - .add_return_node(loc, cvar.into(), analyzer) - .into_expr_err(loc)?; - let rctx = Context::new_subctx( - call_ctx.into(), - Some(ctx), - loc, - None, - None, - true, - analyzer, - None, - ) - .into_expr_err(loc)?; - let ret_ctx = analyzer.add_node(Node::Context(rctx)); - ContextNode::from(call_ctx) - .set_child_call(ret_ctx.into(), analyzer) - .into_expr_err(loc)?; - - // the return is a continuation of the ctx not the ecrecover ctx - ContextNode::from(ret_ctx) - .set_continuation_ctx(analyzer, ctx, "ecrecover") - .into_expr_err(loc)?; + let subctx_kind = SubContextKind::new_fn_ret(call_ctx, ctx); + let ret_ctx = + Context::add_subctx(subctx_kind, loc, self, None).into_expr_err(loc)?; + call_ctx.set_child_call(ret_ctx, self).into_expr_err(loc)?; - let tmp_ret = ContextVarNode::from(cvar) - .as_tmp( - ContextNode::from(call_ctx) - .underlying(analyzer) - .unwrap() - .loc, - ret_ctx.into(), - analyzer, - ) - .unwrap(); - tmp_ret.underlying_mut(analyzer).unwrap().is_return = true; - tmp_ret.underlying_mut(analyzer).unwrap().display_name = - format!("ecrecover({}).return", inner_name); - ctx.add_var(tmp_ret, analyzer).into_expr_err(loc)?; - analyzer.add_edge(tmp_ret, ret_ctx, Edge::Context(ContextEdge::Variable)); + let tmp_ret = ContextVarNode::from(cvar) + .as_tmp(call_ctx.underlying(self).unwrap().loc, ret_ctx, self) + .unwrap(); + tmp_ret.underlying_mut(self).unwrap().is_return = true; + tmp_ret.underlying_mut(self).unwrap().display_name = + format!("ecrecover({}).return", inner_name); + ctx.add_var(tmp_ret, self).into_expr_err(loc)?; + self.add_edge(tmp_ret, ret_ctx, Edge::Context(ContextEdge::Variable)); - ContextNode::from(ret_ctx) - .push_expr(ExprRet::Single(tmp_ret.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + ret_ctx + .push_expr(ExprRet::Single(tmp_ret.into()), self) + .into_expr_err(loc)?; + Ok(()) } _ => Err(ExprErr::FunctionNotFound( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs index a394767d..b2403ace 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs @@ -1,10 +1,12 @@ -use crate::func_caller::NamedOrUnnamedArgs; -use crate::{func_call::helper::CallerHelper, require::Require, ContextBuilder, ExpressionParser}; +use crate::func_call::helper::CallerHelper; use graph::{ elem::Elem, - nodes::{Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, Node, + nodes::{ + Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContextVarNode, ExprRet, + KilledKind, + }, + AnalyzerBackend, }; use shared::{ExprErr, IntoExprErr, RangeArena}; @@ -24,96 +26,84 @@ pub trait SolidityCaller: fn solidity_call( &mut self, arena: &mut RangeArena>, - func_name: String, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { + match func_name { "keccak256" => { - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "No input into keccak256".to_string())); - }; - - let cvar = if let Ok(var) = input.expect_single() { - ContextVarNode::from(var) - } else { - return Err(ExprErr::NoRhs(loc, "No input into keccak256".to_string())); - }; + let cvar = if let Ok(var) = inputs.expect_single() { + ContextVarNode::from(var) + } else { + return Err(ExprErr::NoRhs(loc, "No input into keccak256".to_string())); + }; - if cvar.is_const(analyzer, arena).into_expr_err(loc)? { - let bytes = cvar - .evaled_range_min(analyzer, arena) - .unwrap() - .unwrap() - .as_bytes(analyzer, true, arena) - .unwrap(); - let mut out = [0; 32]; - keccak_hash::keccak_256(&bytes, &mut out); + if cvar.is_const(self, arena).into_expr_err(loc)? { + let bytes = cvar + .evaled_range_min(self, arena) + .unwrap() + .unwrap() + .as_bytes(self, true, arena) + .unwrap(); + let mut out = [0; 32]; + keccak_hash::keccak_256(&bytes, &mut out); - let hash = Node::Concrete(Concrete::from(H256(out))); - let hash_node = ConcreteNode::from(analyzer.add_node(hash)); - let var = ContextVar::new_from_concrete(loc, ctx, hash_node, analyzer) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - } else { - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Bytes(32)).into(), - analyzer, - ) + let hash_node = ConcreteNode::from(self.add_node(Concrete::from(H256(out)))); + let var = ContextVar::new_from_concrete(loc, ctx, hash_node, self) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - } - - Ok(()) - }) - } - "addmod" => { - // TODO: actually calcuate this if possible - input_exprs.parse(arena, self, ctx, loc)?; - - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) + .into_expr_err(loc)?; + } else { let var = ContextVar::new_from_builtin( loc, - analyzer.builtin_or_add(Builtin::Uint(256)).into(), - analyzer, + self.builtin_or_add(Builtin::Bytes(32)).into(), + self, ) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; - Ok(()) - }) + } + + Ok(()) + } + "addmod" => { + // TODO: actually calcuate this if possible + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Uint(256)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) + .into_expr_err(loc)?; + Ok(()) } "mulmod" => { // TODO: actually calcuate this if possible - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(Builtin::Uint(256)).into(), - analyzer, - ) + let var = ContextVar::new_from_builtin( + loc, + self.builtin_or_add(Builtin::Uint(256)).into(), + self, + ) + .into_expr_err(loc)?; + let cvar = self.add_node(var); + ctx.push_expr(ExprRet::Single(cvar), self) .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) + Ok(()) + } + "selfdestruct" => { + // TODO: affect address.balance + ctx.kill(self,loc, KilledKind::Ended).into_expr_err(loc) } "require" | "assert" => { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.handle_require(arena, input_exprs.unnamed_args().unwrap(), ctx) - }) + Err(ExprErr::ParseError( + loc, + "require(..) and assert(..) should have been handled in the parsing step. This is a bug".to_string(), + )) } _ => Err(ExprErr::FunctionNotFound( loc, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs index 823d8809..1e9b32c4 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs @@ -1,16 +1,14 @@ -use crate::func_caller::NamedOrUnnamedArgs; +use crate::variable::Variable; use crate::ListAccess; -use crate::{variable::Variable, ContextBuilder, ExpressionParser}; -use graph::nodes::FunctionNode; use graph::{ elem::*, nodes::{ BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, TyNode, }, - AnalyzerBackend, Node, VarType, + AnalyzerBackend, VarType, }; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; +use shared::{ExprErr, IntoExprErr, RangeArena}; use solang_parser::pt::{Expression, Loc}; @@ -22,119 +20,77 @@ pub trait TypesCaller: AnalyzerBackend + S fn types_call( &mut self, arena: &mut RangeArena>, - func_name: String, - func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + func_name: &str, + inputs: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - match &*func_name { - "type" => self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx), + match func_name { + "type" => { + let mut inputs = inputs.as_vec(); + ctx.push_expr(inputs.swap_remove(0), self) + .into_expr_err(loc) + } "wrap" => { - if input_exprs.len() != 2 { - return Err(ExprErr::InvalidFunctionInput(loc, format!("Expected a member type and an input to the wrap function, but got: {:?}", input_exprs))); - } - - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - ".wrap(..) did not receive an input".to_string(), - )); - }; - - let input = if let Some(ordered_param_names) = - FunctionNode::from(func_idx).maybe_ordered_param_names(analyzer) - { - input_exprs.order(input, ordered_param_names) - } else { - input - }; - - input.expect_length(2).into_expr_err(loc)?; - let ret = input.as_vec(); - let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; - let var = - ContextVar::new_from_ty(loc, TyNode::from(wrapping_ty), ctx, analyzer) - .into_expr_err(loc)?; - let to_be_wrapped = ret[1].expect_single().into_expr_err(loc)?; - let cvar = ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; - let expr = Elem::Expr(RangeExpr::new( - Elem::from(to_be_wrapped), - RangeOp::Cast, - Elem::from(cvar), - )); - next.set_range_min(analyzer, arena, expr.clone()) - .into_expr_err(loc)?; - next.set_range_max(analyzer, arena, expr) - .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) - .into_expr_err(loc) - }) + inputs.expect_length(2).into_expr_err(loc)?; + let ret = inputs.as_vec(); + let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; + let var = ContextVar::new_from_ty(loc, TyNode::from(wrapping_ty), ctx, self) + .into_expr_err(loc)?; + let to_be_wrapped = ret[1].expect_single().into_expr_err(loc)?; + let cvar = ContextVarNode::from(self.add_node(var)); + let next = self.advance_var_in_ctx(cvar, loc, ctx)?; + let expr = Elem::Expr(RangeExpr::new( + Elem::from(to_be_wrapped), + RangeOp::Cast, + Elem::from(cvar), + )); + next.set_range_min(self, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(self, arena, expr).into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(cvar.into()), self) + .into_expr_err(loc) } "unwrap" => { - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(input) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - ".unwrap(..) did not receive an input".to_string(), - )); - }; - - let input = if let Some(ordered_param_names) = - FunctionNode::from(func_idx).maybe_ordered_param_names(analyzer) - { - input_exprs.order(input, ordered_param_names) - } else { - input - }; - - input.expect_length(2).into_expr_err(loc)?; - let ret = input.as_vec(); - let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; - let mut var = ContextVar::new_from_builtin( - loc, - BuiltInNode::from( - TyNode::from(wrapping_ty) - .underlying(analyzer) - .into_expr_err(loc)? - .ty, - ), - analyzer, - ) - .into_expr_err(loc)?; - let to_be_unwrapped = ret[1].expect_single().into_expr_err(loc)?; - var.display_name = format!( - "{}.unwrap({})", + inputs.expect_length(2).into_expr_err(loc)?; + let ret = inputs.as_vec(); + let wrapping_ty = ret[0].expect_single().into_expr_err(loc)?; + let mut var = ContextVar::new_from_builtin( + loc, + BuiltInNode::from( TyNode::from(wrapping_ty) - .name(analyzer) - .into_expr_err(loc)?, - ContextVarNode::from(to_be_unwrapped) - .display_name(analyzer) + .underlying(self) .into_expr_err(loc)? - ); - - let cvar = ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - cvar.set_range_min(analyzer, arena, Elem::from(to_be_unwrapped)) - .into_expr_err(loc)?; - cvar.set_range_max(analyzer, arena, Elem::from(to_be_unwrapped)) - .into_expr_err(loc)?; - let next = analyzer.advance_var_in_ctx(cvar, loc, ctx)?; - let expr = Elem::Expr(RangeExpr::new( - Elem::from(to_be_unwrapped), - RangeOp::Cast, - Elem::from(cvar), - )); - next.set_range_min(analyzer, arena, expr.clone()) - .into_expr_err(loc)?; - next.set_range_max(analyzer, arena, expr) - .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(cvar.into()), analyzer) - .into_expr_err(loc) - }) + .ty, + ), + self, + ) + .into_expr_err(loc)?; + let to_be_unwrapped = ret[1].expect_single().into_expr_err(loc)?; + var.display_name = format!( + "{}.unwrap({})", + TyNode::from(wrapping_ty).name(self).into_expr_err(loc)?, + ContextVarNode::from(to_be_unwrapped) + .display_name(self) + .into_expr_err(loc)? + ); + + let cvar = ContextVarNode::from(self.add_node(var)); + cvar.set_range_min(self, arena, Elem::from(to_be_unwrapped)) + .into_expr_err(loc)?; + cvar.set_range_max(self, arena, Elem::from(to_be_unwrapped)) + .into_expr_err(loc)?; + let next = self.advance_var_in_ctx(cvar, loc, ctx)?; + let expr = Elem::Expr(RangeExpr::new( + Elem::from(to_be_unwrapped), + RangeOp::Cast, + Elem::from(cvar), + )); + next.set_range_min(self, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(self, arena, expr).into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(cvar.into()), self) + .into_expr_err(loc) } _ => Err(ExprErr::FunctionNotFound( loc, @@ -147,82 +103,48 @@ pub trait TypesCaller: AnalyzerBackend + S } /// Perform a cast of a type - fn cast( + fn cast_inner( &mut self, arena: &mut RangeArena>, - ty: Builtin, - func_idx: NodeIdx, - input_exprs: &NamedOrUnnamedArgs, - loc: Loc, ctx: ContextNode, + var_ty: VarType, + ty: &Builtin, + ret: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - // it is a cast - fn cast_match( - ctx: ContextNode, - loc: Loc, - analyzer: &mut impl ListAccess, - arena: &mut RangeArena>, - ty: &Builtin, - ret: ExprRet, - func_idx: NodeIdx, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::CtxKilled(kind) => ctx.kill(analyzer, loc, kind).into_expr_err(loc), - ExprRet::Null => Ok(()), - ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { - let cvar = ContextVarNode::from(cvar); - let new_var = cvar - .as_cast_tmp(loc, ctx, ty.clone(), analyzer) - .into_expr_err(loc)?; - - let v_ty = VarType::try_from_idx(analyzer, func_idx).expect(""); - let maybe_new_range = - cvar.cast_exprs(&v_ty, analyzer, arena).into_expr_err(loc)?; - new_var.underlying_mut(analyzer).into_expr_err(loc)?.ty = v_ty; - - if let Some((new_min, new_max)) = maybe_new_range { - new_var - .set_range_min(analyzer, arena, new_min) - .into_expr_err(loc)?; - new_var - .set_range_max(analyzer, arena, new_max) - .into_expr_err(loc)?; - } + match ret { + ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), + ExprRet::Null => Ok(()), + ExprRet::Single(cvar) | ExprRet::SingleLiteral(cvar) => { + let cvar = ContextVarNode::from(cvar); + let new_var = cvar + .as_cast_tmp(loc, ctx, ty.clone(), self) + .into_expr_err(loc)?; - if cvar.needs_length(analyzer).into_expr_err(loc)? { - // input is indexable. get the length attribute, create a new length for the casted type - let _ = analyzer.create_length( - arena, - ctx, - loc, - new_var, - new_var.latest_version(analyzer), - false, - )?; - } + let maybe_new_range = cvar.cast_exprs(&var_ty, self, arena).into_expr_err(loc)?; + new_var.underlying_mut(self).into_expr_err(loc)?.ty = var_ty; - ctx.push_expr(ExprRet::Single(new_var.into()), analyzer) + if let Some((new_min, new_max)) = maybe_new_range { + new_var + .set_range_min(self, arena, new_min) + .into_expr_err(loc)?; + new_var + .set_range_max(self, arena, new_max) .into_expr_err(loc)?; - Ok(()) } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|i| cast_match(ctx, loc, analyzer, arena, ty, i, func_idx)), - } - } - self.parse_ctx_expr(arena, &input_exprs.unnamed_args().unwrap()[0], ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Cast had no target type".to_string())); - }; + if cvar.needs_length(self).into_expr_err(loc)? { + // input is indexable. get the length attribute, create a new length for the casted type + let _ = self.get_length(arena, ctx, new_var.latest_version(self), true, loc)?; + } - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); + ctx.push_expr(ExprRet::Single(new_var.into()), self) + .into_expr_err(loc)?; + Ok(()) } - - cast_match(ctx, loc, analyzer, arena, &ty, ret, func_idx) - }) + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|i| self.cast_inner(arena, ctx, var_ty.clone(), ty, i, loc)), + } } } diff --git a/crates/solc-expressions/src/func_call/mod.rs b/crates/solc-expressions/src/func_call/mod.rs index 2f963567..3e1015d4 100644 --- a/crates/solc-expressions/src/func_call/mod.rs +++ b/crates/solc-expressions/src/func_call/mod.rs @@ -4,4 +4,3 @@ pub mod helper; pub mod internal_call; pub mod intrinsic_call; pub mod modifier; -pub mod namespaced_call; diff --git a/crates/solc-expressions/src/func_call/modifier.rs b/crates/solc-expressions/src/func_call/modifier.rs index 0980ca73..8e69f3e0 100644 --- a/crates/solc-expressions/src/func_call/modifier.rs +++ b/crates/solc-expressions/src/func_call/modifier.rs @@ -1,15 +1,18 @@ //! Traits & blanket implementations that facilitate performing modifier function calls. -use crate::{func_caller::FuncCaller, helper::CallerHelper, ContextBuilder, ExpressionParser}; +use crate::{ + func_call::internal_call::InternalFuncCaller, func_caller::FuncCaller, helper::CallerHelper, + ContextBuilder, +}; use graph::{ elem::Elem, - nodes::{Concrete, Context, ContextNode, ExprRet, FunctionNode, ModifierState}, - AnalyzerBackend, Edge, GraphBackend, Node, + nodes::{Concrete, Context, ContextNode, ExprRet, FunctionNode, ModifierState, SubContextKind}, + AnalyzerBackend, Edge, GraphBackend, }; use shared::{ExprErr, IntoExprErr, RangeArena}; -use solang_parser::pt::{CodeLocation, Expression, Loc}; +use solang_parser::pt::{Expression, Loc}; impl ModifierCaller for T where T: AnalyzerBackend @@ -48,33 +51,33 @@ pub trait ModifierCaller: .modifier_input_vars(mod_state.num, self) .into_expr_err(loc)?; - input_exprs - .iter() - .try_for_each(|expr| self.parse_ctx_expr(arena, expr, func_ctx))?; self.apply_to_edges(func_ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let input_paths = if input_exprs.is_empty() { - ExprRet::Multi(vec![]) - } else { - let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - format!("No inputs to modifier, expected: {}", input_exprs.len()), - )); - }; - - if matches!(input_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(input_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - input_paths - }; - + if analyzer.debug_stack() { + tracing::trace!( + "stack for getting modifier inputs: {}, ctx: {},", + mod_state + .parent_ctx + .debug_expr_stack_str(analyzer) + .into_expr_err(loc)?, + mod_state.parent_ctx.path(analyzer) + ); + } + + let inputs = ExprRet::Multi( + mod_state + .parent_ctx + .pop_n_latest_exprs(input_exprs.len(), loc, analyzer) + .into_expr_err(loc)?, + ); + + if analyzer.debug_stack() { + tracing::trace!("modifier inputs: {}", inputs.debug_str(analyzer)); + } analyzer.func_call( arena, ctx, loc, - &input_paths, + &inputs, mod_node, None, Some(mod_state.clone()), @@ -101,7 +104,7 @@ pub trait ModifierCaller: ctx, modifier_state.loc, arena, - &|analyzer, arena, ctx, loc| { + &|analyzer, arena, ctx, _loc| { if modifier_state.num + 1 < mods.len() { // use the next modifier let mut mstate = modifier_state.clone(); @@ -112,27 +115,20 @@ pub trait ModifierCaller: .into_expr_err(mstate.loc)? .loc; - let pctx = Context::new_subctx( + let subctx_kind = SubContextKind::new_fn_call( ctx, Some(modifier_state.parent_ctx), - loc, - None, - None, + mods[mstate.num], false, + ); + let new_parent_subctx = Context::add_subctx( + subctx_kind, + loc, analyzer, Some(modifier_state.clone()), ) .unwrap(); - let new_parent_subctx = - ContextNode::from(analyzer.add_node(Node::Context(pctx))); - new_parent_subctx - .set_continuation_ctx( - analyzer, - modifier_state.parent_ctx, - "resume_from_modifier_nonfinal", - ) - .into_expr_err(loc)?; ctx.set_child_call(new_parent_subctx, analyzer) .into_expr_err(modifier_state.loc)?; @@ -148,26 +144,16 @@ pub trait ModifierCaller: )?; Ok(()) } else { - let pctx = Context::new_subctx( + let subctx_kind = SubContextKind::new_fn_call( ctx, Some(modifier_state.parent_ctx), - modifier_state.loc, - None, - None, + modifier_state.parent_fn, false, - analyzer, - None, - ) - .unwrap(); + ); + let new_parent_subctx = - ContextNode::from(analyzer.add_node(Node::Context(pctx))); - new_parent_subctx - .set_continuation_ctx( - analyzer, - modifier_state.parent_ctx, - "resume_from_modifier_final", - ) - .into_expr_err(loc)?; + Context::add_subctx(subctx_kind, modifier_state.loc, analyzer, None) + .unwrap(); ctx.set_child_call(new_parent_subctx, analyzer) .into_expr_err(modifier_state.loc)?; @@ -178,7 +164,6 @@ pub trait ModifierCaller: ctx, new_parent_subctx, modifier_state.parent_fn, - &modifier_state.renamed_inputs, None, ) } @@ -188,88 +173,40 @@ pub trait ModifierCaller: fn modifiers( &mut self, - arena: &mut RangeArena>, ctx: ContextNode, func: FunctionNode, ) -> Result, ExprErr> { - use std::fmt::Write; + // use std::fmt::Write; let binding = func.underlying(self).unwrap().clone(); + let modifiers = binding.modifiers_as_base(); if modifiers.is_empty() { Ok(vec![]) } else { - let res = modifiers - .iter() - .map(|modifier| { - assert_eq!(modifier.name.identifiers.len(), 1); - // construct arg string for function selector - let mut mod_name = format!("{}", modifier.name.identifiers[0]); - if let Some(args) = &modifier.args { - let args_str = args - .iter() - .map(|expr| { - let mctx = Context::new_subctx( - ctx, - None, - Loc::Implicit, - None, - None, - false, - self, - None, - ) - .into_expr_err(Loc::Implicit)?; - let callee_ctx = - ContextNode::from(self.add_node(Node::Context(mctx))); - let _res = ctx.set_child_call(callee_ctx, self); - self.parse_ctx_expr(arena, expr, callee_ctx)?; - let f: Vec = self.take_from_edge( - ctx, - expr.loc(), - arena, - &|analyzer, arena, ctx, loc| { - let ret = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap(); - Ok(ret.try_as_func_input_str(analyzer, arena)) - }, - )?; - - ctx.delete_child(self).into_expr_err(expr.loc())?; - Ok(f.first().unwrap().clone()) - }) - .collect::, ExprErr>>()? - .join(", "); - let _ = write!(mod_name, "{args_str}"); - } else { - let _ = write!(mod_name, "()"); + let mut mods = vec![]; + modifiers.iter().try_for_each(|modifier| { + assert_eq!(modifier.name.identifiers.len(), 1); + // // construct arg string for function selector + let mod_name = format!("{}", modifier.name.identifiers[0]); + let mod_loc = modifier.name.identifiers[0].loc; + let is_constructor = func.is_constructor(self).into_expr_err(mod_loc)?; + let mut found_mods = self.find_modifier(ctx, &mod_name, is_constructor).into_expr_err(mod_loc)?; + match found_mods.len() { + 0 => Err(ExprErr::FunctionNotFound(mod_loc, format!("Could not find modifier: {mod_name}"))), + 1 => { + mods.push(found_mods.swap_remove(0)); + Ok(()) } - let _ = write!(mod_name, ""); - let found: Option = ctx - .visible_modifiers(self) - .unwrap() - .iter() - .find(|modifier| modifier.name(self).unwrap() == mod_name) - .copied(); - Ok(found) - }) - .collect::>, ExprErr>>()? - .into_iter() - .flatten() - .collect::>(); - Ok(res) + n => Err(ExprErr::FunctionNotFound(mod_loc, format!("Could not find unique modifier: {mod_name}, found {n} modifiers with the same name"))), + } + })?; + Ok(mods) } } /// Sets the modifiers for a function - fn set_modifiers( - &mut self, - arena: &mut RangeArena>, - func: FunctionNode, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - let modifiers = self.modifiers(arena, ctx, func)?; + fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> { + let modifiers = self.modifiers(ctx, func)?; modifiers .iter() .enumerate() diff --git a/crates/solc-expressions/src/func_call/namespaced_call.rs b/crates/solc-expressions/src/func_call/namespaced_call.rs deleted file mode 100644 index 22d093a7..00000000 --- a/crates/solc-expressions/src/func_call/namespaced_call.rs +++ /dev/null @@ -1,555 +0,0 @@ -//! Traits & blanket implementations that facilitate performing namespaced function calls. - -use crate::assign::Assign; -use crate::{ - func_call::func_caller::{FuncCaller, NamedOrUnnamedArgs}, - func_call::helper::CallerHelper, - intrinsic_call::IntrinsicFuncCaller, - member_access::MemberAccess, - ContextBuilder, ExpressionParser, -}; -use graph::nodes::{Concrete, ContextVar}; -use graph::ContextEdge; -use graph::Edge; -use graph::VarType; - -use graph::{ - elem::Elem, - nodes::{ContextNode, ContextVarNode, ExprRet, FunctionNode}, - AnalyzerBackend, GraphBackend, Node, -}; - -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; - -use solang_parser::pt::{Expression, Identifier, Loc}; - -impl NameSpaceFuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + CallerHelper -{ -} -/// A trait for performing namespaced function calls (i.e. `MyContract.myFunc(...)`) -pub trait NameSpaceFuncCaller: - AnalyzerBackend + Sized + GraphBackend + CallerHelper -{ - #[tracing::instrument(level = "trace", skip_all)] - /// Call a namedspaced function, i.e. `MyContract.myFunc(...)` - fn call_name_spaced_func( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: &Loc, - member_expr: &Expression, - ident: &Identifier, - input_exprs: NamedOrUnnamedArgs, - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - tracing::trace!("Calling name spaced function"); - if let Variable(Identifier { name, .. }) = member_expr { - if name == "abi" { - let func_name = format!("abi.{}", ident.name); - let fn_node = self - .builtin_fn_or_maybe_add(&func_name) - .unwrap_or_else(|| panic!("No builtin function with name {func_name}")); - return self.intrinsic_func_call(arena, loc, &input_exprs, fn_node, ctx); - } else if name == "super" { - if let Some(contract) = ctx.maybe_associated_contract(self).into_expr_err(*loc)? { - let supers = contract.super_contracts(self); - let possible_funcs: Vec<_> = supers - .iter() - .filter_map(|con_node| { - con_node - .linearized_functions(self) - .ok()? - .into_iter() - .find(|(func_name, _func_node)| func_name.starts_with(&ident.name)) - .map(|(_, node)| node) - }) - .collect(); - - if possible_funcs.is_empty() { - return Err(ExprErr::FunctionNotFound( - *loc, - "Could not find function in super".to_string(), - )); - } - input_exprs.parse(arena, self, ctx, *loc)?; - return self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let inputs = if let Some(inputs) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - { - inputs - } else { - ExprRet::Multi(vec![]) - }; - if possible_funcs.len() == 1 { - let mut inputs = if let Some(ordered_param_names) = - possible_funcs[0].maybe_ordered_param_names(analyzer) - { - input_exprs.order(inputs, ordered_param_names).as_vec() - } else { - inputs.as_vec() - }; - let func = possible_funcs[0]; - if func.params(analyzer).len() < inputs.len() { - inputs = inputs[1..].to_vec(); - } - let inputs = ExprRet::Multi(inputs); - if inputs.has_killed() { - return ctx - .kill(analyzer, loc, inputs.killed_kind().unwrap()) - .into_expr_err(loc); - } - analyzer.setup_fn_call( - arena, - &ident.loc, - &inputs, - func.into(), - ctx, - None, - ) - } else { - // this is the annoying case due to function overloading & type inference on number literals - let mut lits = vec![false]; - lits.extend( - input_exprs - .exprs() - .iter() - .map(|expr| { - match expr { - Negate(_, expr) => { - // negative number potentially - matches!(**expr, NumberLiteral(..) | HexLiteral(..)) - } - NumberLiteral(..) | HexLiteral(..) => true, - _ => false, - } - }) - .collect::>(), - ); - - if inputs.has_killed() { - return ctx - .kill(analyzer, loc, inputs.killed_kind().unwrap()) - .into_expr_err(loc); - } - if let Some(func) = analyzer.disambiguate_fn_call( - arena, - &ident.name, - lits, - &inputs, - &possible_funcs, - ) { - analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) - } else { - Err(ExprErr::FunctionNotFound( - loc, - "Could not find function in super".to_string(), - )) - } - } - }); - } - } - } - - self.parse_ctx_expr(arena, member_expr, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Namespace function call had no namespace".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_namespaced_member(arena, ctx, loc, member_expr, ident, &input_exprs, ret) - }) - } - - /// Match the expression return for getting the member node - fn match_namespaced_member( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - member_expr: &Expression, - ident: &Identifier, - input_exprs: &NamedOrUnnamedArgs, - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::Single(inner) | ExprRet::SingleLiteral(inner) => self - .call_name_spaced_func_inner( - arena, - ctx, - loc, - member_expr, - ident, - input_exprs, - inner, - true, - ), - ExprRet::Multi(inner) => inner.into_iter().try_for_each(|ret| { - self.match_namespaced_member(arena, ctx, loc, member_expr, ident, input_exprs, ret) - }), - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), - ExprRet::Null => Err(ExprErr::NoLhs( - loc, - "No function found due to null".to_string(), - )), - } - } - - #[tracing::instrument(level = "trace", skip_all)] - /// Actually perform the namespaced function call - fn call_name_spaced_func_inner( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - member_expr: &Expression, - ident: &Identifier, - input_exprs: &NamedOrUnnamedArgs, - member: NodeIdx, - member_is_lit: bool, - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - tracing::trace!( - "namespaced function call: {:?}.{:?}(..)", - ContextVarNode::from(member).display_name(self), - ident.name - ); - - let funcs = self.visible_member_funcs(ctx, loc, member)?; - // filter down all funcs to those that match - let possible_funcs = funcs - .iter() - .filter(|func| { - func.name(self) - .unwrap() - .starts_with(&format!("{}(", ident.name)) - }) - .copied() - .collect::>(); - - ctx.push_expr(ExprRet::Single(member), self) - .into_expr_err(loc)?; - - input_exprs.parse(arena, self, ctx, loc)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(mut inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Namespace function call had no inputs".to_string(), - )); - }; - - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - if possible_funcs.is_empty() { - // check structs - let structs = ctx.visible_structs(analyzer).into_expr_err(loc)?; - let possible_structs = structs - .iter() - .filter(|strukt| { - let named_correctly = strukt - .name(analyzer) - .unwrap() - .starts_with(&ident.name.to_string()); - if !named_correctly { - false - } else { - // filter by params - let fields = strukt.fields(analyzer); - fields.len() == input_exprs.len() - } - }) - .copied() - .collect::>(); - - if possible_structs.len() == 1 { - let strukt = possible_structs[0]; - let var = ContextVar::new_from_struct(loc, strukt, ctx, analyzer) - .into_expr_err(loc)?; - let cvar = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - - strukt.fields(analyzer).iter().try_for_each(|field| { - let field_cvar = ContextVar::maybe_new_from_field( - analyzer, - loc, - ContextVarNode::from(cvar) - .underlying(analyzer) - .into_expr_err(loc)?, - field.underlying(analyzer).unwrap().clone(), - ) - .expect("Invalid struct field"); - - let fc_node = analyzer.add_node(Node::ContextVar(field_cvar)); - analyzer.add_edge(fc_node, cvar, Edge::Context(ContextEdge::AttrAccess("field"))); - analyzer.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); - ctx.add_var(fc_node.into(), analyzer).into_expr_err(loc)?; - let field_as_ret = ExprRet::Single(fc_node); - let Some(assignment) = inputs.take_one().into_expr_err(loc)? else { - return Err(ExprErr::NoRhs(loc, "Struct creation failed".to_string())); - }; - - if matches!(assignment, ExprRet::CtxKilled(_)) { - ctx.push_expr(assignment, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_assign_sides(arena, ctx, loc, &field_as_ret, &assignment)?; - let _ = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)?; - Ok(()) - })?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, _loc| { - ctx.push_expr(ExprRet::Single(cvar), analyzer) - .into_expr_err(loc)?; - Ok(()) - })?; - return Ok(()); - } - // TODO: this is extremely ugly. - if inputs.has_killed() { - return ctx - .kill(analyzer, loc, inputs.killed_kind().unwrap()) - .into_expr_err(loc); - } - let mut inputs = inputs.as_vec(); - if let Node::ContextVar(_) = analyzer.node(member) { - inputs.insert(0, ExprRet::Single(member)) - } - if let Node::ContextVar(_) = analyzer.node(member) { - if member_is_lit { - inputs.insert(0, ExprRet::SingleLiteral(member)) - } else { - inputs.insert(0, ExprRet::Single(member)) - } - } - let inputs = ExprRet::Multi(inputs); - - let as_input_str = inputs.try_as_func_input_str(analyzer, arena); - - let lits = inputs.literals_list().into_expr_err(loc)?; - if lits.iter().any(|i| *i) { - // try to disambiguate - let ty = if let Node::ContextVar(cvar) = analyzer.node(member) { - cvar.ty.ty_idx() - } else { - member - }; - - let possible_builtins: Vec<_> = analyzer - .builtin_fn_inputs() - .iter() - .filter_map(|(func_name, (inputs, _))| { - if func_name.starts_with(&ident.name) { - if let Some(input) = inputs.first() { - let try_cast = VarType::try_from_idx(analyzer, ty)? - .implicitly_castable_to( - &VarType::try_from_idx(analyzer, input.ty)?, - analyzer, - ); - let Ok(implicitly_castable) = try_cast else { - return None; - }; - if implicitly_castable { - Some(func_name.clone()) - } else { - None - } - } else { - // generic builtin function, return it - Some(func_name.clone()) - } - } else { - None - } - }) - .collect::>(); - let possible_builtins: Vec<_> = possible_builtins - .into_iter() - .filter_map(|name| { - analyzer - .builtin_fn_or_maybe_add(&name) - .map(FunctionNode::from) - }) - .collect(); - - let maybe_func = if possible_builtins.len() == 1 { - Some(possible_builtins[0]) - } else { - analyzer.disambiguate_fn_call( - arena, - &ident.name, - lits, - &inputs, - &possible_builtins, - ) - }; - if let Some(func) = maybe_func { - let expr = &MemberAccess( - loc, - Box::new(member_expr.clone()), - Identifier { - loc: ident.loc, - name: func - .name(analyzer) - .into_expr_err(loc)? - .split('(') - .collect::>()[0] - .to_string(), - }, - ); - analyzer.parse_ctx_expr(arena, expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Fallback function parse failure".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let mut modifier_input_exprs = vec![member_expr.clone()]; - modifier_input_exprs.extend(input_exprs.exprs()); - analyzer.match_intrinsic_fallback( - arena, - ctx, - &loc, - &NamedOrUnnamedArgs::Unnamed(&modifier_input_exprs), - ret, - ) - }) - } else { - // analyzer.match_intrinsic_fallback(ctx, &loc, &modifier_input_exprs, ret) - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not disambiguate builtin function, possible builtin functions: {:#?}", - possible_builtins - .iter() - .map(|i| i.name(analyzer).unwrap()) - .collect::>() - ), - )) - } - } else { - let expr = &MemberAccess( - loc, - Box::new(member_expr.clone()), - Identifier { - loc: ident.loc, - name: format!("{}{}", ident.name, as_input_str), - }, - ); - analyzer.parse_ctx_expr(arena, expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Fallback function parse failure".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let mut modifier_input_exprs = vec![member_expr.clone()]; - modifier_input_exprs.extend(input_exprs.exprs()); - analyzer.match_intrinsic_fallback( - arena, - ctx, - &loc, - &NamedOrUnnamedArgs::Unnamed(&modifier_input_exprs), - ret, - ) - }) - } - } else if possible_funcs.len() == 1 { - let mut inputs = if let Some(ordered_param_names) = - possible_funcs[0].maybe_ordered_param_names(analyzer) - { - input_exprs.order(inputs, ordered_param_names).as_vec() - } else { - inputs.as_vec() - }; - let func = possible_funcs[0]; - if func.params(analyzer).len() > inputs.len() { - // Add the member back in if its a context variable - if let Node::ContextVar(_) = analyzer.node(member) { - inputs.insert(0, ExprRet::Single(member)) - } - } - let inputs = ExprRet::Multi(inputs); - if inputs.has_killed() { - return ctx - .kill(analyzer, loc, inputs.killed_kind().unwrap()) - .into_expr_err(loc); - } - - analyzer.setup_fn_call(arena, &ident.loc, &inputs, func.into(), ctx, None) - } else { - // Add the member back in if its a context variable - let mut inputs = inputs.as_vec(); - if let Node::ContextVar(_) = analyzer.node(member) { - inputs.insert(0, ExprRet::Single(member)) - } - let inputs = ExprRet::Multi(inputs); - // this is the annoying case due to function overloading & type inference on number literals - let mut lits = vec![false]; - lits.extend( - input_exprs - .exprs() - .iter() - .map(|expr| { - match expr { - Negate(_, expr) => { - // negative number potentially - matches!(**expr, NumberLiteral(..) | HexLiteral(..)) - } - NumberLiteral(..) | HexLiteral(..) => true, - _ => false, - } - }) - .collect::>(), - ); - - if inputs.has_killed() { - return ctx - .kill(analyzer, loc, inputs.killed_kind().unwrap()) - .into_expr_err(loc); - } - if let Some(func) = - analyzer.disambiguate_fn_call(arena, &ident.name, lits, &inputs, &possible_funcs) - { - analyzer.setup_fn_call(arena, &loc, &inputs, func.into(), ctx, None) - } else { - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not disambiguate function, possible functions: {:#?}", - possible_funcs - .iter() - .map(|i| i.name(analyzer).unwrap()) - .collect::>() - ), - )) - } - } - }) - } -} diff --git a/crates/solc-expressions/src/list.rs b/crates/solc-expressions/src/list.rs index 52a24f25..7f602dde 100644 --- a/crates/solc-expressions/src/list.rs +++ b/crates/solc-expressions/src/list.rs @@ -1,78 +1,35 @@ -use crate::{ContextBuilder, ExpressionParser}; - use graph::{ - elem::Elem, - nodes::{Concrete, ContextNode, ContextVar, ExprRet}, + nodes::{ContextNode, ContextVar, ExprRet}, AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; -use shared::{ExprErr, IntoExprErr, RangeArena}; +use shared::{ExprErr, FlatExpr, IntoExprErr}; -use solang_parser::pt::{Expression, Loc, Parameter, ParameterList}; +use solang_parser::pt::{Expression, Loc}; impl List for T where T: AnalyzerBackend + Sized {} /// Dealing with list parsing and operations pub trait List: AnalyzerBackend + Sized { - #[tracing::instrument(level = "trace", skip_all)] - fn list( + fn list_inner( &mut self, - arena: &mut RangeArena>, ctx: ContextNode, + param: FlatExpr, + ret: ExprRet, loc: Loc, - params: &ParameterList, - ) -> Result<(), ExprErr> { - params.iter().try_for_each(|(loc, input)| { - if let Some(input) = input { - self.parse_ctx_expr(arena, &input.ty, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "List did not have left hand sides".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - ctx.append_tmp_expr(analyzer.match_ty(ctx, &loc, &ret, input)?, analyzer) - .into_expr_err(loc) - }) - } else { - // create a dummy var - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - ctx.append_tmp_expr(ExprRet::Null, analyzer) - .into_expr_err(loc) - }) - } - })?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "List did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } - - fn match_ty( - &mut self, - ctx: ContextNode, - loc: &Loc, - ty_ret: &ExprRet, - input: &Parameter, ) -> Result { - match ty_ret { + match ret { ExprRet::Null => Ok(ExprRet::Null), ExprRet::Single(ty) | ExprRet::SingleLiteral(ty) => { - if let Some(input_name) = &input.name { - let ty = VarType::try_from_idx(self, *ty).expect("Not a known type"); + let FlatExpr::Parameter(_, maybe_storage, maybe_name) = param else { + unreachable!() + }; + + if let Some(input_name) = &maybe_name { + let ty = VarType::try_from_idx(self, ty).expect("Not a known type"); let var = ContextVar { - loc: Some(*loc), + loc: Some(loc), name: input_name.to_string(), display_name: input_name.to_string(), - storage: input.storage.as_ref().map(|s| s.clone().into()), + storage: maybe_storage, is_tmp: false, is_symbolic: false, tmp_of: None, @@ -80,25 +37,25 @@ pub trait List: AnalyzerBackend + Sized { is_return: false, ty, }; - let input_node = self.add_node(Node::ContextVar(var)); - ctx.add_var(input_node.into(), self).into_expr_err(*loc)?; + let input_node = self.add_node(var); + ctx.add_var(input_node.into(), self).into_expr_err(loc)?; self.add_edge(input_node, ctx, Edge::Context(ContextEdge::Variable)); Ok(ExprRet::Single(input_node)) } else { - match self.node(*ty) { + match self.node(ty) { Node::ContextVar(_var) => { // reference the variable directly, don't create a temporary variable - Ok(ExprRet::Single(*ty)) + Ok(ExprRet::Single(ty)) } _ => { // create a tmp - let ty = VarType::try_from_idx(self, *ty).expect("Not a known type"); - let tmp_num = ctx.new_tmp(self).into_expr_err(*loc)?; + let ty = VarType::try_from_idx(self, ty).expect("Not a known type"); + let tmp_num = ctx.new_tmp(self).into_expr_err(loc)?; let new_lhs_underlying = ContextVar { - loc: Some(*loc), + loc: Some(loc), name: format!("tmp{tmp_num}"), display_name: format!("tmp{tmp_num}"), - storage: input.storage.as_ref().map(|s| s.clone().into()), + storage: maybe_storage, is_tmp: true, is_symbolic: false, tmp_of: None, @@ -106,8 +63,8 @@ pub trait List: AnalyzerBackend + Sized { is_return: false, ty, }; - let input_node = self.add_node(Node::ContextVar(new_lhs_underlying)); - ctx.add_var(input_node.into(), self).into_expr_err(*loc)?; + let input_node = self.add_node(new_lhs_underlying); + ctx.add_var(input_node.into(), self).into_expr_err(loc)?; self.add_edge(input_node, ctx, Edge::Context(ContextEdge::Variable)); Ok(ExprRet::Single(input_node)) } @@ -116,11 +73,11 @@ pub trait List: AnalyzerBackend + Sized { } ExprRet::Multi(inner) => Ok(ExprRet::Multi( inner - .iter() - .map(|i| self.match_ty(ctx, loc, i, input)) + .into_iter() + .map(|i| self.list_inner(ctx, param, i, loc)) .collect::>()?, )), - ExprRet::CtxKilled(kind) => Ok(ExprRet::CtxKilled(*kind)), + ExprRet::CtxKilled(kind) => Ok(ExprRet::CtxKilled(kind)), } } } diff --git a/crates/solc-expressions/src/literal.rs b/crates/solc-expressions/src/literal.rs index 6ced7c03..85d3f20c 100644 --- a/crates/solc-expressions/src/literal.rs +++ b/crates/solc-expressions/src/literal.rs @@ -6,7 +6,7 @@ use graph::{ use shared::{ExprErr, IntoExprErr, RangeArena}; use ethers_core::types::{Address, H256, I256, U256}; -use solang_parser::pt::{HexLiteral, Identifier, Loc}; +use solang_parser::pt::Loc; use std::str::FromStr; @@ -20,7 +20,7 @@ pub trait Literal: AnalyzerBackend + Sized { integer: &str, exponent: &str, negative: bool, - unit: &Option, + unit: Option<&str>, ) -> Result { let Ok(int) = U256::from_dec_str(integer) else { return Err(ExprErr::ParseError( @@ -69,10 +69,10 @@ pub trait Literal: AnalyzerBackend + Sized { integer: &str, exponent: &str, negative: bool, - unit: &Option, + unit: Option<&str>, ) -> Result<(), ExprErr> { let conc = self.concrete_number_from_str(loc, integer, exponent, negative, unit)?; - let concrete_node = ConcreteNode::from(self.add_node(Node::Concrete(conc))); + let concrete_node = ConcreteNode::from(self.add_node(conc)); let ccvar = Node::ContextVar( ContextVar::new_from_concrete(loc, ctx, concrete_node, self).into_expr_err(loc)?, ); @@ -84,8 +84,8 @@ pub trait Literal: AnalyzerBackend + Sized { Ok(()) } - fn unit_to_uint(&self, unit: &Identifier) -> U256 { - match &*unit.name { + fn unit_to_uint(&self, unit: &str) -> U256 { + match unit { "gwei" => U256::from(10).pow(9.into()), "ether" => U256::from(10).pow(18.into()), "minutes" => U256::from(60), @@ -105,7 +105,7 @@ pub trait Literal: AnalyzerBackend + Sized { integer: &str, fraction: &str, exponent: &str, - unit: &Option, + unit: Option<&str>, negative: bool, ) -> Result<(), ExprErr> { let int = @@ -167,7 +167,7 @@ pub trait Literal: AnalyzerBackend + Sized { .val .fit_size(); - ConcreteNode::from(self.add_node(Node::Concrete(evaled))) + ConcreteNode::from(self.add_node(evaled)) } else { let evaled = rational_range .maximize(self, arena) @@ -176,7 +176,7 @@ pub trait Literal: AnalyzerBackend + Sized { .unwrap() .val .fit_size(); - ConcreteNode::from(self.add_node(Node::Concrete(evaled))) + ConcreteNode::from(self.add_node(evaled)) }; let ccvar = Node::ContextVar( @@ -212,9 +212,9 @@ pub trait Literal: AnalyzerBackend + Sized { )); } let val = I256::from(-1i32) * raw; - ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Int(size, val)))) + ConcreteNode::from(self.add_node(Concrete::Int(size, val))) } else { - ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Uint(size, val)))) + ConcreteNode::from(self.add_node(Concrete::Uint(size, val))) }; let ccvar = Node::ContextVar( @@ -229,28 +229,24 @@ pub trait Literal: AnalyzerBackend + Sized { } /// hex"123123" - fn hex_literals(&mut self, ctx: ContextNode, hexes: &[HexLiteral]) -> Result<(), ExprErr> { + fn hex_literals(&mut self, ctx: ContextNode, loc: Loc, hex: &str) -> Result<(), ExprErr> { let mut h = vec![]; - hexes.iter().for_each(|sub_hex| { - if let Ok(hex_val) = hex::decode(&sub_hex.hex) { - h.extend(hex_val) - } - }); - - let mut loc = hexes[0].loc; - loc.use_end_from(&hexes[hexes.len() - 1].loc); + if let Ok(hex_val) = hex::decode(hex) { + h.extend(hex_val) + } let concrete_node = if h.len() <= 32 { let mut target = H256::default(); - let mut max = 1; + let mut max = 0; h.iter().enumerate().for_each(|(i, hex_byte)| { if *hex_byte != 0x00u8 { max = i as u8 + 1; } target.0[i] = *hex_byte; }); - ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Bytes(max, target)))) + ConcreteNode::from(self.add_node(Concrete::Bytes(max, target))) } else { + // hex"" ConcreteNode::from(self.add_node(Node::Concrete(Concrete::DynBytes(h)))) }; @@ -268,8 +264,7 @@ pub trait Literal: AnalyzerBackend + Sized { fn address_literal(&mut self, ctx: ContextNode, loc: Loc, addr: &str) -> Result<(), ExprErr> { let addr = Address::from_str(addr).map_err(|e| ExprErr::ParseError(loc, e.to_string()))?; - let concrete_node = - ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Address(addr)))); + let concrete_node = ConcreteNode::from(self.add_node(Concrete::Address(addr))); let ccvar = Node::ContextVar( ContextVar::new_from_concrete(loc, ctx, concrete_node, self).into_expr_err(loc)?, ); @@ -292,6 +287,10 @@ pub trait Literal: AnalyzerBackend + Sized { let range = split .get(4) .copied()? + .chars() + .filter(|c| !c.is_whitespace()) + .collect::(); + let range = range .trim_start_matches('[') .trim_end_matches(']') .split(',') @@ -311,22 +310,10 @@ pub trait Literal: AnalyzerBackend + Sized { } let min = self - .concrete_number_from_str( - Loc::Implicit, - min_str, - "", - min_neg, - &None, - ) + .concrete_number_from_str(Loc::Implicit, min_str, "", min_neg, None) .ok()?; let max = self - .concrete_number_from_str( - Loc::Implicit, - max_str, - "", - max_neg, - &None, - ) + .concrete_number_from_str(Loc::Implicit, max_str, "", max_neg, None) .ok()?; Some(TestCommand::Variable( @@ -358,8 +345,7 @@ pub trait Literal: AnalyzerBackend + Sized { } fn string_literal(&mut self, ctx: ContextNode, loc: Loc, s: &str) -> Result<(), ExprErr> { - let concrete_node = - ConcreteNode::from(self.add_node(Node::Concrete(Concrete::String(s.to_string())))); + let concrete_node = ConcreteNode::from(self.add_node(Concrete::String(s.to_string()))); let ccvar = Node::ContextVar( ContextVar::new_from_concrete(loc, ctx, concrete_node, self).into_expr_err(loc)?, ); @@ -372,7 +358,7 @@ pub trait Literal: AnalyzerBackend + Sized { } fn bool_literal(&mut self, ctx: ContextNode, loc: Loc, b: bool) -> Result<(), ExprErr> { - let concrete_node = ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Bool(b)))); + let concrete_node = ConcreteNode::from(self.add_node(Concrete::Bool(b))); let ccvar = Node::ContextVar( ContextVar::new_from_concrete(loc, ctx, concrete_node, self).into_expr_err(loc)?, ); @@ -393,7 +379,7 @@ mod tests { use graph::nodes::Context; use graph::nodes::Function; use pyrometer::Analyzer; - use solang_parser::pt::Loc; + use solang_parser::pt::HexLiteral; fn make_context_node_for_analyzer(analyzer: &mut Analyzer) -> ContextNode { // need to make a function, then provide the function to the new Context @@ -410,7 +396,7 @@ mod tests { num_literal: &str, exponent: &str, negative: bool, - unit: Option, + unit: Option<&str>, expected: Concrete, ) -> Result<()> { // setup @@ -424,7 +410,7 @@ mod tests { let loc = Loc::File(0, 0, 0); // create a number literal - analyzer.number_literal(ctx, loc, num_literal, exponent, negative, &unit)?; + analyzer.number_literal(ctx, loc, num_literal, exponent, negative, unit)?; // checks let stack = &ctx.underlying(&analyzer)?.expr_ret_stack; @@ -498,10 +484,7 @@ mod tests { fn test_number_literal_positive_with_zero_exponent_and_unit() -> Result<()> { let num_literal = "123"; let exponent = "0"; - let unit = Some(Identifier { - name: "ether".into(), - loc: Loc::File(0, 0, 0), - }); + let unit = Some("ether"); let expected = Concrete::Uint(72, U256::from_dec_str("123000000000000000000").unwrap()); test_number_literal(num_literal, exponent, false, unit, expected) } @@ -510,10 +493,7 @@ mod tests { fn test_number_literal_positive_with_unit() -> Result<()> { let num_literal = "123"; let exponent = ""; - let unit = Some(Identifier { - name: "ether".into(), - loc: Loc::File(0, 0, 0), - }); + let unit = Some("ether"); let expected = Concrete::Uint(72, U256::from_dec_str("123000000000000000000").unwrap()); test_number_literal(num_literal, exponent, false, unit, expected) } @@ -561,7 +541,7 @@ mod tests { fraction: &str, exponent: &str, negative: bool, - unit: Option, + unit: Option<&str>, expected: Concrete, ) -> Result<()> { // setup @@ -576,7 +556,7 @@ mod tests { // create a rational number literal analyzer.rational_number_literal( - arena, ctx, loc, integer, fraction, exponent, &unit, negative, + arena, ctx, loc, integer, fraction, exponent, unit, negative, )?; // checks @@ -645,10 +625,7 @@ mod tests { let integer = "1"; let fraction = "5"; let exponent = "0"; - let unit = Some(Identifier { - name: "ether".into(), - loc: Loc::File(0, 0, 0), - }); + let unit = Some("ether"); let expected = Concrete::Uint(64, U256::from_dec_str("1500000000000000000").unwrap()); test_rational_number_literal(integer, fraction, exponent, false, unit, expected) } @@ -806,8 +783,15 @@ mod tests { let arena = &mut arena_base; let ctx = make_context_node_for_analyzer(&mut analyzer); + let mut final_str = "".to_string(); + let mut loc = hex_literals[0].loc; + hex_literals.iter().for_each(|s| { + loc.use_end_from(&s.loc); + final_str.push_str(&s.hex); + }); + // create hex literals - analyzer.hex_literals(ctx, hex_literals)?; + analyzer.hex_literals(ctx, loc, &final_str)?; // checks let stack = &ctx.underlying(&analyzer)?.expr_ret_stack; @@ -862,7 +846,7 @@ mod tests { #[test] fn test_hex_literals_multiple() -> Result<()> { - let hex_literals = vec![ + let hex_literals = [ HexLiteral { hex: "7B".to_string(), // 123 in decimal loc: Loc::File(0, 0, 0), @@ -877,7 +861,7 @@ mod tests { bytes[0] = 0x7B; bytes[1] = 0xFF; let expected = Concrete::Bytes(2, H256::from_slice(&bytes)); - test_hex_literals(&hex_literals, expected) + test_hex_literals(&hex_literals[..], expected) } #[test] @@ -886,7 +870,7 @@ mod tests { hex: "".to_string(), loc: Loc::File(0, 0, 0), }; - let expected = Concrete::Bytes(1, H256::default()); + let expected = Concrete::Bytes(0, H256::default()); test_hex_literals(&[hex_literal], expected) } @@ -1015,7 +999,6 @@ mod tests { let cvar_node = ContextVarNode::from(stack[0].expect_single()?); assert!(cvar_node.is_const(&analyzer, arena)?); let min = cvar_node.evaled_range_min(&analyzer, arena)?.unwrap(); - println!("{min}"); let conc_value = min.maybe_concrete().unwrap().val; assert!( conc_value == expected, diff --git a/crates/solc-expressions/src/loops.rs b/crates/solc-expressions/src/loops.rs index efaefb0b..579f6775 100644 --- a/crates/solc-expressions/src/loops.rs +++ b/crates/solc-expressions/src/loops.rs @@ -1,15 +1,14 @@ -use crate::{variable::Variable, ContextBuilder, StatementParser}; -use graph::ContextEdge; -use graph::Edge; +use crate::variable::Variable; +use graph::nodes::SubContextKind; use graph::{ elem::Elem, nodes::{Concrete, Context, ContextNode}, - AnalyzerBackend, GraphBackend, Node, + AnalyzerBackend, GraphBackend, }; use shared::{ExprErr, IntoExprErr, RangeArena}; -use solang_parser::pt::{Expression, Loc, Statement}; +use solang_parser::pt::{Expression, Loc}; impl Looper for T where T: AnalyzerBackend + Sized + GraphBackend @@ -20,94 +19,65 @@ impl Looper for T where pub trait Looper: GraphBackend + AnalyzerBackend + Sized { - #[tracing::instrument(level = "trace", skip_all)] - /// Handles a for loop. Needs improvement - fn for_loop( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - ctx: ContextNode, - maybe_init: &Option>, - _maybe_limiter: &Option>, - _maybe_post: &Option>, - maybe_body: &Option>, - ) -> Result<(), ExprErr> { - // TODO: improve this - if let Some(initer) = maybe_init { - self.parse_ctx_statement(arena, initer, false, Some(ctx)); - } - - if let Some(body) = maybe_body { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - analyzer.reset_vars(arena, loc, ctx, body) - }) - } else { - Ok(()) - } - } - /// Resets all variables referenced in the loop because we don't elegantly handle loops fn reset_vars( &mut self, arena: &mut RangeArena>, + parent_ctx: ContextNode, + loop_ctx: ContextNode, loc: Loc, - ctx: ContextNode, - body: &Statement, ) -> Result<(), ExprErr> { - let og_ctx = ctx; - let sctx = Context::new_loop_subctx(ctx, loc, self).into_expr_err(loc)?; - let subctx = ContextNode::from(self.add_node(Node::Context(sctx))); - ctx.set_child_call(subctx, self).into_expr_err(loc)?; - self.add_edge(subctx, ctx, Edge::Context(ContextEdge::Loop)); - self.parse_ctx_statement(arena, body, false, Some(subctx)); - self.apply_to_edges(subctx, loc, arena, &|analyzer, arena, ctx, loc| { - let vars = subctx.local_vars(analyzer).clone(); - vars.iter().for_each(|(name, var)| { - // widen to max range - if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = var - .underlying(analyzer) - .unwrap() - .ty - .default_range(analyzer) - .unwrap() - { - let new_inheritor_var = analyzer - .advance_var_in_ctx(inheritor_var, loc, ctx) - .unwrap(); - let res = new_inheritor_var - .set_range_min(analyzer, arena, r.min) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_inheritor_var - .set_range_max(analyzer, arena, r.max) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } - } - }); + let subctx_kind = SubContextKind::new_fn_ret(loop_ctx, parent_ctx); + let ret_ctx = Context::add_subctx(subctx_kind, loc, self, None).into_expr_err(loc)?; + loop_ctx.set_child_call(ret_ctx, self).into_expr_err(loc)?; - let sctx = - Context::new_subctx(ctx, Some(og_ctx), loc, None, None, false, analyzer, None) - .into_expr_err(loc)?; - let sctx = ContextNode::from(analyzer.add_node(Node::Context(sctx))); - ctx.set_child_call(sctx, analyzer).into_expr_err(loc) - }) - } - - /// Handles a while-loop - fn while_loop( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - ctx: ContextNode, - _limiter: &Expression, - body: &Statement, - ) -> Result<(), ExprErr> { - // TODO: improve this - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - analyzer.reset_vars(arena, loc, ctx, body) + let vars = loop_ctx.local_vars(self).clone(); + vars.iter().try_for_each(|(name, var)| { + // widen to max range + if let Some(inheritor_var) = parent_ctx.var_by_name(self, name) { + let inheritor_var = + inheritor_var.latest_version_or_inherited_in_ctx(loop_ctx, self); + if let Some(r) = var + .underlying(self) + .unwrap() + .ty + .default_range(self) + .into_expr_err(loc)? + { + let new_inheritor_var = self.advance_var_in_ctx(inheritor_var, loc, ret_ctx)?; + new_inheritor_var + .set_range_min(self, arena, r.min) + .into_expr_err(loc)?; + new_inheritor_var + .set_range_max(self, arena, r.max) + .into_expr_err(loc)?; + Ok(()) + } else { + Ok(()) + } + } else if var.is_storage(self).into_expr_err(loc)? { + if let Some(r) = var + .underlying(self) + .unwrap() + .ty + .default_range(self) + .into_expr_err(loc)? + { + let new_inheritor_var = + self.advance_var_in_ctx(var.latest_version(self), loc, ret_ctx)?; + new_inheritor_var + .set_range_min(self, arena, r.min) + .into_expr_err(loc)?; + new_inheritor_var + .set_range_max(self, arena, r.max) + .into_expr_err(loc)?; + Ok(()) + } else { + Ok(()) + } + } else { + Ok(()) + } }) } } diff --git a/crates/solc-expressions/src/member_access/builtin_access.rs b/crates/solc-expressions/src/member_access/builtin_access.rs index 24f020fa..fc2f175d 100644 --- a/crates/solc-expressions/src/member_access/builtin_access.rs +++ b/crates/solc-expressions/src/member_access/builtin_access.rs @@ -1,13 +1,16 @@ use crate::LibraryAccess; use graph::{ - nodes::{BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, + nodes::{ + BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ExprRet, Function, FunctionNode, + FunctionParam, FunctionReturn, TyNode, + }, + AnalyzerBackend, ContextEdge, Edge, VarType, }; -use shared::{ExprErr, IntoExprErr}; +use shared::{ExprErr, GraphError, IntoExprErr}; use ethers_core::types::{I256, U256}; -use solang_parser::pt::{Expression, Identifier, Loc}; +use solang_parser::pt::{Expression, Loc}; impl BuiltinAccess for T where T: LibraryAccess + AnalyzerBackend + Sized @@ -21,279 +24,563 @@ pub trait BuiltinAccess: /// Perform member access on builtin types fn builtin_member_access( &mut self, - loc: Loc, ctx: ContextNode, node: BuiltInNode, + name: &str, is_storage: bool, - ident: &Identifier, - ) -> Result { + loc: Loc, + ) -> Result<(ExprRet, bool), ExprErr> { tracing::trace!("Looking for builtin member function"); - if let Some(ret) = self.library_func_search(ctx, node.0.into(), ident) { - Ok(ret) + if let Some(ret) = self.library_func_search(ctx, node.0.into(), name) { + Ok((ret, true)) } else { - match node.underlying(self).into_expr_err(loc)?.clone() { - Builtin::Address | Builtin::AddressPayable | Builtin::Payable => { - match &*ident.name { - "delegatecall" - | "call" - | "staticcall" - | "delegatecall(address, bytes)" - | "call(address, bytes)" - | "staticcall(address, bytes)" => { - // TODO: check if the address is known to be a certain type and the function signature is known - // and call into the function - let builtin_name = ident.name.split('(').collect::>()[0]; - let func_node = self.builtin_fn_or_maybe_add(builtin_name).unwrap(); - Ok(ExprRet::Single(func_node)) - } - "code" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::DynamicBytes); - let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) - .into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(node)) - } - "codehash" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::Bytes(32)); - let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) - .into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(node)) - } - "balance" => { - // TODO: try to be smarter based on the address input - let bn = self.builtin_or_add(Builtin::Uint(256)); - let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) - .into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(node)) - } - _ if ident.name.starts_with("send") => { - let bn = self.builtin_or_add(Builtin::Bool); - let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) - .into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); - ctx.add_var(node.into(), self).into_expr_err(loc)?; - self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(node)) - } - _ if ident.name.starts_with("transfer") => Ok(ExprRet::Multi(vec![])), - _ => Err(ExprErr::MemberAccessNotFound( - loc, + self.builtin_builtins(ctx, node, name, is_storage, loc) + } + } + + fn specialize_ty_fn( + &mut self, + node: TyNode, + name: &str, + ) -> Result, GraphError> { + match name { + "unwrap" => { + let func = self.builtin_fns().get("unwrap").unwrap().clone(); + let inputs = vec![ + FunctionParam { + loc: Loc::Builtin, + ty: node.0.into(), + order: 0, + storage: None, + name: None, + }, + FunctionParam { + loc: Loc::Builtin, + ty: node.0.into(), + order: 0, + storage: None, + name: None, + }, + ]; + let outputs = vec![FunctionReturn { + loc: Loc::Builtin, + ty: node.underlying_ty(self)?, + storage: None, + name: None, + }]; + Ok(Some(self.construct_specialized_fn( + func.clone(), + inputs, + outputs, + )?)) + } + "wrap" => { + let func = self.builtin_fns().get("wrap").unwrap().clone(); + let inputs = vec![ + FunctionParam { + loc: Loc::Builtin, + ty: node.0.into(), + order: 0, + storage: None, + name: None, + }, + FunctionParam { + loc: Loc::Builtin, + ty: node.underlying_ty(self)?, + order: 0, + storage: None, + name: None, + }, + ]; + let outputs = vec![FunctionReturn { + loc: Loc::Builtin, + ty: node.0.into(), + storage: None, + name: None, + }]; + Ok(Some(self.construct_specialized_fn( + func.clone(), + inputs, + outputs, + )?)) + } + _ => Ok(None), + } + } + + fn builtin_builtin_fn( + &mut self, + node: BuiltInNode, + name: &str, + num_inputs: usize, + is_storage: bool, + ) -> Result, GraphError> { + match node.underlying(self)?.clone() { + Builtin::Address | Builtin::AddressPayable | Builtin::Payable => { + match name { + "delegatecall" | "call" | "staticcall" | "send" | "transfer" => { + // TODO: check if the address is known to be a certain type and the function signature is known + // and call into the function + let builtin_name = name.split('(').collect::>()[0]; + let func_node = + FunctionNode::from(self.builtin_fn_or_maybe_add(builtin_name).unwrap()); + Ok(Some((func_node, true))) + } + _ => Ok(None), + } + } + + Builtin::String => match name.split('(').collect::>()[0] { + "concat" => { + let full_name = format!( + "concat({})", + (0..num_inputs) + .map(|_| "string") + .collect::>() + .join(", ") + ); + let specialized = + if let Some(specialized) = self.builtin_fn_nodes().get(&full_name) { + (*specialized).into() + } else { + // construct a specialized version of concat + let func = self.builtin_fns().get("concat").unwrap().clone(); + let base_input = FunctionParam { + loc: Loc::Builtin, + ty: self.builtin_or_add(Builtin::String), + order: 0, + storage: None, + name: None, + }; + let inputs = (0..num_inputs) + .map(|_| base_input.clone()) + .collect::>(); + let outputs = vec![FunctionReturn { + loc: Loc::Builtin, + ty: self.builtin_or_add(Builtin::String), + storage: None, + name: None, + }]; + self.construct_specialized_fn(func.clone(), inputs, outputs)? + }; + + Ok(Some((specialized, false))) + } + _ => Ok(None), + }, + Builtin::DynamicBytes => match name.split('(').collect::>()[0] { + "concat" => { + let full_name = format!( + "concat({})", + (0..num_inputs) + .map(|_| "bytes") + .collect::>() + .join(", ") + ); + let specialized = + if let Some(specialized) = self.builtin_fn_nodes().get(&full_name) { + (*specialized).into() + } else { + // construct a specialized version of concat + let func = self.builtin_fns().get("concat").unwrap().clone(); + let base_input = FunctionParam { + loc: Loc::Builtin, + ty: self.builtin_or_add(Builtin::DynamicBytes), + order: 0, + storage: None, + name: None, + }; + let inputs = (0..num_inputs) + .map(|_| base_input.clone()) + .collect::>(); + let outputs = vec![FunctionReturn { + loc: Loc::Builtin, + ty: self.builtin_or_add(Builtin::DynamicBytes), + storage: None, + name: None, + }]; + self.construct_specialized_fn(func.clone(), inputs, outputs)? + }; + + Ok(Some((specialized, false))) + } + _ => Ok(None), + }, + Builtin::Array(inner) => { + if name.starts_with("push") { + if is_storage { + let empty_push = num_inputs == 0; + let self_ty = VarType::try_from_idx(self, node.0.into()).unwrap(); + let full_name = if empty_push { + format!("push({})", self_ty.as_string(self)?) + } else { format!( - "Unknown member access on address: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )), + "push({}, {})", + self_ty.as_string(self)?, + inner.as_string(self)? + ) + }; + let specialized = + if let Some(specialized) = self.builtin_fn_nodes().get(&full_name) { + (*specialized).into() + } else { + // construct a specialized version of concat + let func = self.builtin_fns().get("push").unwrap(); + let inputs = if empty_push { + vec![FunctionParam { + loc: Loc::Builtin, + ty: self_ty.ty_idx(), + order: 0, + storage: None, + name: None, + }] + } else { + vec![ + FunctionParam { + loc: Loc::Builtin, + ty: self_ty.ty_idx(), + order: 0, + storage: None, + name: None, + }, + FunctionParam { + loc: Loc::Builtin, + ty: inner.ty_idx(), + order: 0, + storage: None, + name: None, + }, + ] + }; + let outputs = if empty_push { + vec![FunctionReturn { + loc: Loc::Builtin, + ty: inner.ty_idx(), + storage: None, + name: None, + }] + } else { + vec![] + }; + self.construct_specialized_fn(func.clone(), inputs, outputs)? + }; + + Ok(Some((specialized, true))) + } else { + Ok(None) + } + } else if name.starts_with("pop") { + if is_storage { + let self_ty = VarType::try_from_idx(self, node.0.into()).unwrap(); + let full_name = format!("pop({})", self_ty.as_string(self)?); + let specialized = + if let Some(specialized) = self.builtin_fn_nodes().get(&full_name) { + (*specialized).into() + } else { + // construct a specialized version of concat + let func = self.builtin_fns().get("pop").unwrap(); + let inputs = vec![FunctionParam { + loc: Loc::Builtin, + ty: self_ty.ty_idx(), + order: 0, + storage: None, + name: None, + }]; + let outputs = vec![FunctionReturn { + loc: Loc::Builtin, + ty: inner.ty_idx(), + storage: None, + name: None, + }]; + self.construct_specialized_fn(func.clone(), inputs, outputs)? + }; + Ok(Some((specialized, true))) + } else { + Ok(None) } + } else { + Ok(None) } - Builtin::Bool => Err(ExprErr::MemberAccessNotFound( - loc, - format!( - "Unknown member access on bool: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )), - Builtin::String => match ident.name.split('(').collect::>()[0] { - "concat" => { - let fn_node = self.builtin_fn_or_maybe_add("concat").unwrap(); - Ok(ExprRet::Single(fn_node)) + } + _ => Ok(None), + } + } + + fn construct_specialized_fn( + &mut self, + func: Function, + inputs: Vec, + outputs: Vec, + ) -> Result { + let func_node = FunctionNode::from(self.add_node(func)); + inputs.into_iter().rev().for_each(|input| { + let input_node = self.add_node(input); + self.add_edge(input_node, func_node, Edge::FunctionParam); + }); + outputs.into_iter().for_each(|output| { + let output_node = self.add_node(output); + self.add_edge(output_node, func_node, Edge::FunctionReturn); + }); + + let params = func_node.params(self); + let params_strs = params + .iter() + .map(|param| param.ty_str(self).unwrap()) + .collect::>(); + let underlying_mut = func_node.underlying_mut(self)?; + let name = underlying_mut.name.as_mut().unwrap(); + let full_name = format!("{}({})", name, params_strs.join(", ")); + name.name.clone_from(&full_name); + + self.add_edge(func_node, self.entry(), Edge::Func); + + self.builtin_fn_nodes_mut() + .insert(full_name, func_node.0.into()); + + Ok(func_node) + } + + fn builtin_builtins( + &mut self, + ctx: ContextNode, + node: BuiltInNode, + name: &str, + is_storage: bool, + loc: Loc, + ) -> Result<(ExprRet, bool), ExprErr> { + match node.underlying(self).into_expr_err(loc)?.clone() { + Builtin::Address | Builtin::AddressPayable | Builtin::Payable => { + match name { + "delegatecall" | "call" | "staticcall" | "send" | "transfer" => { + // TODO: check if the address is known to be a certain type and the function signature is known + // and call into the function + let builtin_name = name.split('(').collect::>()[0]; + let func_node = self.builtin_fn_or_maybe_add(builtin_name).unwrap(); + Ok((ExprRet::Single(func_node), true)) } - _ => Err(ExprErr::MemberAccessNotFound( - loc, - format!( - "Unknown member access on string: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )), - }, - Builtin::Bytes(size) => Err(ExprErr::MemberAccessNotFound( - loc, - format!("Unknown member access on bytes{}: {:?}", size, ident.name), - )), - Builtin::Rational => Err(ExprErr::MemberAccessNotFound( - loc, - format!( - "Unknown member access on rational: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )), - Builtin::DynamicBytes => match ident.name.split('(').collect::>()[0] { - "concat" => { - let fn_node = self.builtin_fn_or_maybe_add("concat").unwrap(); - Ok(ExprRet::Single(fn_node)) + "codehash" => { + // TODO: try to be smarter based on the address input + let bn = self.builtin_or_add(Builtin::Bytes(32)); + let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) + .into_expr_err(loc)?; + let node = self.add_node(cvar); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + Ok((ExprRet::Single(node), false)) + } + "code" => { + // TODO: try to be smarter based on the address input + let bn = self.builtin_or_add(Builtin::DynamicBytes); + let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) + .into_expr_err(loc)?; + let node = self.add_node(cvar); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + Ok((ExprRet::Single(node), false)) + } + "balance" => { + // TODO: try to be smarter based on the address input + let bn = self.builtin_or_add(Builtin::Uint(256)); + let cvar = ContextVar::new_from_builtin(loc, bn.into(), self) + .into_expr_err(loc)?; + let node = self.add_node(cvar); + ctx.add_var(node.into(), self).into_expr_err(loc)?; + self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + Ok((ExprRet::Single(node), false)) } _ => Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access on bytes: {:?}, ctx: {}", - ident.name, + "Unknown member access on address: \"{name}\", ctx: {}", ctx.path(self) ), )), - }, - Builtin::Array(_) => { - if ident.name.starts_with("push") { - if is_storage { - let fn_node = self.builtin_fn_or_maybe_add("push").unwrap(); - Ok(ExprRet::Single(fn_node)) - } else { - Err(ExprErr::NonStoragePush( - loc, - "Trying to push to nonstorage array is not supported".to_string(), - )) - } - } else if ident.name.starts_with("pop") { - if is_storage { - let fn_node = self.builtin_fn_or_maybe_add("pop").unwrap(); - Ok(ExprRet::Single(fn_node)) - } else { - Err(ExprErr::NonStoragePush( - loc, - "Trying to pop from nonstorage array is not supported".to_string(), - )) - } - } else { - Err(ExprErr::MemberAccessNotFound( - loc, - format!( - "Unknown member access on array[]: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )) - } } - Builtin::SizedArray(s, _) => Err(ExprErr::MemberAccessNotFound( - loc, - format!( - "Unknown member access on array[{s}]: {:?}, ctx: {}", - ident.name, - ctx.path(self) - ), - )), - Builtin::Mapping(_, _) => Err(ExprErr::MemberAccessNotFound( + } + Builtin::Bool => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on bool: \"{name}\", ctx: {}", + ctx.path(self) + ), + )), + Builtin::String => match name.split('(').collect::>()[0] { + "concat" => { + let fn_node = self.builtin_fn_or_maybe_add("concat").unwrap(); + Ok((ExprRet::Single(fn_node), false)) + } + _ => Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access on mapping: {:?}, ctx: {}", - ident.name, + "Unknown member access on string: \"{name}\", ctx: {}", ctx.path(self) ), )), - Builtin::Func(_, _) => Err(ExprErr::MemberAccessNotFound( + }, + Builtin::Bytes(size) => Err(ExprErr::MemberAccessNotFound( + loc, + format!("Unknown member access on bytes{}: {name}", size), + )), + Builtin::Rational => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on rational: \"{name}\", ctx: {}", + ctx.path(self) + ), + )), + Builtin::DynamicBytes => match name.split('(').collect::>()[0] { + "concat" => { + let fn_node = self.builtin_fn_or_maybe_add("concat").unwrap(); + Ok((ExprRet::Single(fn_node), false)) + } + _ => Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access on func: {:?}, ctx: {}", - ident.name, + "Unknown member access on bytes: \"{name}\", ctx: {}", ctx.path(self) ), )), - Builtin::Int(size) => { - let max = if size == 256 { - I256::MAX + }, + Builtin::Array(_) => { + if name.starts_with("push") { + if is_storage { + let fn_node = self.builtin_fn_or_maybe_add("push").unwrap(); + Ok((ExprRet::Single(fn_node), true)) } else { - I256::from_raw(U256::from(1u8) << U256::from(size - 1)) - I256::from(1) - }; - match &*ident.name { - "max" => { - let c = Concrete::Int(size, max); - let node = self.add_node(Node::Concrete(c)).into(); - let mut var = ContextVar::new_from_concrete(loc, ctx, node, self) - .into_expr_err(loc)?; - var.name = format!("int{size}.max"); - var.display_name.clone_from(&var.name); - var.is_tmp = true; - var.is_symbolic = false; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).into_expr_err(loc)?; - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(cvar)) - } - "min" => { - let min = max * I256::from(-1i32) - I256::from(1i32); - let c = Concrete::Int(size, min); - let node = self.add_node(Node::Concrete(c)).into(); - let mut var = ContextVar::new_from_concrete(loc, ctx, node, self) - .into_expr_err(loc)?; - var.name = format!("int{size}.min"); - var.display_name.clone_from(&var.name); - var.is_tmp = true; - var.is_symbolic = false; - let cvar = self.add_node(Node::ContextVar(var)); - ctx.add_var(cvar.into(), self).into_expr_err(loc)?; - self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(cvar)) - } - e => Err(ExprErr::MemberAccessNotFound( + Err(ExprErr::NonStoragePush( loc, - format!( - "Unknown type attribute on int{size}: {e:?}, ctx: {}", - ctx.path(self) - ), - )), + "Trying to push to nonstorage array is not supported".to_string(), + )) } + } else if name.starts_with("pop") { + if is_storage { + let fn_node = self.builtin_fn_or_maybe_add("pop").unwrap(); + Ok((ExprRet::Single(fn_node), true)) + } else { + Err(ExprErr::NonStoragePush( + loc, + "Trying to pop from nonstorage array is not supported".to_string(), + )) + } + } else { + Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on array[]: \"{name}\", ctx: {}", + ctx.path(self) + ), + )) } - Builtin::Uint(size) => match &*ident.name { + } + Builtin::SizedArray(s, _) => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on array[{s}]: \"{name}\", ctx: {}", + ctx.path(self) + ), + )), + Builtin::Mapping(_, _) => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on mapping: \"{name}\", ctx: {}", + ctx.path(self) + ), + )), + Builtin::Func(_, _) => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown member access on func: \"{name}\", ctx: {}", + ctx.path(self) + ), + )), + Builtin::Int(size) => { + let max = if size == 256 { + I256::MAX + } else { + I256::from_raw(U256::from(1u8) << U256::from(size - 1)) - I256::from(1) + }; + match name { "max" => { - let max = if size == 256 { - U256::MAX - } else { - U256::from(2).pow(U256::from(size)) - 1 - }; - let c = Concrete::Uint(size, max); - let node = self.add_node(Node::Concrete(c)).into(); + let c = Concrete::Int(size, max); + let node = self.add_node(c).into(); let mut var = ContextVar::new_from_concrete(loc, ctx, node, self) .into_expr_err(loc)?; - var.name = format!("uint{size}.max"); + var.name = format!("int{size}.max"); var.display_name.clone_from(&var.name); var.is_tmp = true; var.is_symbolic = false; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(cvar)) + Ok((ExprRet::Single(cvar), false)) } "min" => { - let min = U256::zero(); - let c = Concrete::from(min); - let node = self.add_node(Node::Concrete(c)).into(); + let min = max * I256::from(-1i32) - I256::from(1i32); + let c = Concrete::Int(size, min); + let node = self.add_node(c).into(); let mut var = ContextVar::new_from_concrete(loc, ctx, node, self) .into_expr_err(loc)?; - var.name = format!("uint{size}.min"); + var.name = format!("int{size}.min"); var.display_name.clone_from(&var.name); var.is_tmp = true; var.is_symbolic = false; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(cvar)) - } - "call" | "delegatecall" | "staticcall" if size == 160 => { - let builtin_name = ident.name.split('(').collect::>()[0]; - let func_node = self.builtin_fn_or_maybe_add(builtin_name).unwrap(); - Ok(ExprRet::Single(func_node)) + Ok((ExprRet::Single(cvar), false)) } e => Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown type attribute on uint{size}: {e:?}, ctx: {}", + "Unknown type attribute on int{size}: {e:?}, ctx: {}", ctx.path(self) ), )), - }, + } } + Builtin::Uint(size) => match name { + "max" => { + let max = if size == 256 { + U256::MAX + } else { + U256::from(2).pow(U256::from(size)) - 1 + }; + let c = Concrete::Uint(size, max); + let node = self.add_node(c).into(); + let mut var = + ContextVar::new_from_concrete(loc, ctx, node, self).into_expr_err(loc)?; + var.name = format!("uint{size}.max"); + var.display_name.clone_from(&var.name); + var.is_tmp = true; + var.is_symbolic = false; + let cvar = self.add_node(var); + ctx.add_var(cvar.into(), self).into_expr_err(loc)?; + self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + Ok((ExprRet::Single(cvar), false)) + } + "min" => { + let min = U256::zero(); + let c = Concrete::from(min); + let node = self.add_node(c).into(); + let mut var = + ContextVar::new_from_concrete(loc, ctx, node, self).into_expr_err(loc)?; + var.name = format!("uint{size}.min"); + var.display_name.clone_from(&var.name); + var.is_tmp = true; + var.is_symbolic = false; + let cvar = self.add_node(var); + ctx.add_var(cvar.into(), self).into_expr_err(loc)?; + self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + Ok((ExprRet::Single(cvar), false)) + } + e => Err(ExprErr::MemberAccessNotFound( + loc, + format!( + "Unknown type attribute on uint{size}: {e:?}, ctx: {}", + ctx.path(self) + ), + )), + }, } } } diff --git a/crates/solc-expressions/src/member_access/contract_access.rs b/crates/solc-expressions/src/member_access/contract_access.rs index 963610b8..5406f405 100644 --- a/crates/solc-expressions/src/member_access/contract_access.rs +++ b/crates/solc-expressions/src/member_access/contract_access.rs @@ -1,10 +1,11 @@ +use crate::member_access::library_access::LibraryAccess; use graph::{ - nodes::{Builtin, Concrete, ContextNode, ContextVar, ContractNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, + nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ContractNode, ExprRet}, + AnalyzerBackend, ContextEdge, Edge, }; -use shared::{ExprErr, IntoExprErr, NodeIdx}; +use shared::{ExprErr, IntoExprErr}; -use solang_parser::pt::{Expression, Identifier, Loc}; +use solang_parser::pt::{Expression, Loc}; impl ContractAccess for T where T: AnalyzerBackend + Sized {} @@ -13,106 +14,106 @@ pub trait ContractAccess: AnalyzerBackend /// Perform member access on a contract fn contract_member_access( &mut self, - member_idx: NodeIdx, - con_node: ContractNode, - ident: &Identifier, ctx: ContextNode, + maybe_parent: Option, + con_node: ContractNode, + name: &str, loc: Loc, - maybe_parent: Option, - ) -> Result { + ) -> Result<(ExprRet, bool), ExprErr> { tracing::trace!( "Contract member access: {}.{}", con_node .maybe_name(self) .into_expr_err(loc)? .unwrap_or_else(|| "interface".to_string()), - ident.name + name ); - if let Some(func) = con_node - .funcs(self) + if let Some((_, func)) = con_node + .linearized_functions(self, false) + .into_expr_err(loc)? .into_iter() - .find(|func_node| func_node.name(self).unwrap() == ident.name) + .find(|(func_name, _)| func_name == name) { if let Some(func_cvar) = ContextVar::maybe_from_user_ty(self, loc, func.0.into()) { - let fn_node = self.add_node(Node::ContextVar(func_cvar)); + let fn_node = self.add_node(func_cvar); // this prevents attaching a dummy node to the parent which could cause a cycle in the graph - if maybe_parent.is_some() { - self.add_edge(fn_node, member_idx, Edge::Context(ContextEdge::FuncAccess)); + if let Some(parent) = maybe_parent { + self.add_edge(fn_node, parent, Edge::Context(ContextEdge::FuncAccess)); } - Ok(ExprRet::Single(fn_node)) + Ok((ExprRet::Single(fn_node), false)) } else { Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unable to construct the function \"{}\" in contract \"{}\"", - ident.name, + "Unable to construct the function \"{name}\" in contract \"{}\"", con_node.name(self).into_expr_err(loc)? ), )) } + } else if let Some(ret) = self.library_func_search(ctx, con_node.0.into(), name) { + Ok((ret, true)) } else if let Some(func) = con_node .structs(self) .into_iter() - .find(|struct_node| struct_node.name(self).unwrap() == ident.name) + .find(|struct_node| struct_node.name(self).unwrap() == name) { if let Some(struct_cvar) = ContextVar::maybe_from_user_ty(self, loc, func.0.into()) { - let struct_node = self.add_node(Node::ContextVar(struct_cvar)); + let struct_node = self.add_node(struct_cvar); // this prevents attaching a dummy node to the parent which could cause a cycle in the graph - if maybe_parent.is_some() { + if let Some(parent) = maybe_parent { self.add_edge( struct_node, - member_idx, + parent, Edge::Context(ContextEdge::StructAccess), ); } - return Ok(ExprRet::Single(struct_node)); + return Ok((ExprRet::Single(struct_node), false)); } else { return Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unable to construct the struct \"{}\" in contract \"{}\"", - ident.name, + "Unable to construct the struct \"{name}\" in contract \"{}\"", con_node.name(self).into_expr_err(loc)? ), )); } } else { - match &*ident.name { + let res = match name { "name" => { let c = Concrete::from(con_node.name(self).unwrap()); - let cnode = self.add_node(Node::Concrete(c)); + let cnode = self.add_node(c); let cvar = ContextVar::new_from_concrete(loc, ctx, cnode.into(), self) .into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - return Ok(ExprRet::Single(node)); + Ok(ExprRet::Single(node)) } "creationCode" | "runtimeCode" => { let bn = self.builtin_or_add(Builtin::DynamicBytes); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - return Ok(ExprRet::Single(node)); + Ok(ExprRet::Single(node)) } "interfaceId" => { // TODO: actually calculate this let bn = self.builtin_or_add(Builtin::Bytes(4)); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(cvar)); + let node = self.add_node(cvar); ctx.add_var(node.into(), self).into_expr_err(loc)?; self.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - return Ok(ExprRet::Single(node)); + Ok(ExprRet::Single(node)) } _ => { // try to match just prefix if let Some(func) = con_node.funcs(self).into_iter().find(|func_node| { if let Some(prefix) = func_node.prefix_only_name(self).unwrap() { - prefix == ident.name + prefix == name } else { false } @@ -120,12 +121,12 @@ pub trait ContractAccess: AnalyzerBackend if let Some(func_cvar) = ContextVar::maybe_from_user_ty(self, loc, func.0.into()) { - let fn_node = self.add_node(Node::ContextVar(func_cvar)); + let fn_node = self.add_node(func_cvar); // this prevents attaching a dummy node to the parent which could cause a cycle in the graph - if maybe_parent.is_some() { + if let Some(parent) = maybe_parent { self.add_edge( fn_node, - member_idx, + parent, Edge::Context(ContextEdge::FuncAccess), ); } @@ -134,18 +135,16 @@ pub trait ContractAccess: AnalyzerBackend Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unable to construct the function \"{}\" in contract \"{}\"", - ident.name, + "Unable to construct the function \"{name}\" in contract \"{}\"", con_node.name(self).into_expr_err(loc)? ), )) } } else { - return Err(ExprErr::ContractFunctionNotFound( + Err(ExprErr::ContractFunctionNotFound( loc, format!( - "No function or struct with name {:?} in contract: {:?}. Functions: {:#?}", - ident.name, + "No function or struct with name \"{name}\" in contract: {:?}. Functions: {:#?}", con_node.name(self).unwrap(), con_node .funcs(self) @@ -153,10 +152,11 @@ pub trait ContractAccess: AnalyzerBackend .map(|func| func.name(self).unwrap()) .collect::>() ), - )); + )) } } - } + }; + Ok((res?, false)) } } } diff --git a/crates/solc-expressions/src/member_access/enum_access.rs b/crates/solc-expressions/src/member_access/enum_access.rs index cad05663..d7cafc63 100644 --- a/crates/solc-expressions/src/member_access/enum_access.rs +++ b/crates/solc-expressions/src/member_access/enum_access.rs @@ -2,11 +2,11 @@ use crate::LibraryAccess; use graph::{ nodes::{ContextNode, ContextVar, EnumNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; -use shared::{ExprErr, IntoExprErr, NodeIdx}; +use shared::{ExprErr, IntoExprErr}; -use solang_parser::pt::{Expression, Identifier, Loc}; +use solang_parser::pt::{Expression, Loc}; impl EnumAccess for T where T: LibraryAccess + AnalyzerBackend + Sized @@ -20,35 +20,33 @@ pub trait EnumAccess: /// Perform member access on an enum fn enum_member_access( &mut self, - _member_idx: NodeIdx, - enum_node: EnumNode, - ident: &Identifier, ctx: ContextNode, + enum_node: EnumNode, + name: &str, loc: Loc, - ) -> Result { - tracing::trace!("Enum member access: {}", ident.name); + ) -> Result<(ExprRet, bool), ExprErr> { + tracing::trace!("Enum member access: {}", name); if let Some(variant) = enum_node .variants(self) .into_expr_err(loc)? .iter() - .find(|variant| **variant == ident.name) + .find(|variant| **variant == name) { let var = ContextVar::new_from_enum_variant(self, ctx, loc, enum_node, variant.to_string()) .into_expr_err(loc)?; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); ctx.add_var(cvar.into(), self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(cvar)) - } else if let Some(ret) = self.library_func_search(ctx, enum_node.0.into(), ident) { - Ok(ret) + Ok((ExprRet::Single(cvar), false)) + } else if let Some(ret) = self.library_func_search(ctx, enum_node.0.into(), name) { + Ok((ret, true)) } else { Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access \"{}\" on enum \"{}\"", - ident.name, + "Unknown member access \"{name}\" on enum \"{}\"", enum_node.name(self).into_expr_err(loc)? ), )) diff --git a/crates/solc-expressions/src/member_access/library_access.rs b/crates/solc-expressions/src/member_access/library_access.rs index e2281ffa..69039b7e 100644 --- a/crates/solc-expressions/src/member_access/library_access.rs +++ b/crates/solc-expressions/src/member_access/library_access.rs @@ -5,7 +5,7 @@ use graph::{ use shared::{ExprErr, NodeIdx}; use petgraph::{visit::EdgeRef, Direction}; -use solang_parser::pt::{Expression, Identifier}; +use solang_parser::pt::Expression; use std::collections::BTreeSet; @@ -18,7 +18,7 @@ pub trait LibraryAccess: AnalyzerBackend + &mut self, ctx: ContextNode, ty: NodeIdx, - ident: &Identifier, + func_name: &str, ) -> Option { self.possible_library_funcs(ctx, ty) .iter() @@ -30,7 +30,7 @@ pub trait LibraryAccess: AnalyzerBackend + } }) .find_map(|(name, func)| { - if name == ident.name { + if name == func_name { Some(ExprRet::Single((*func).into())) } else { None @@ -39,9 +39,9 @@ pub trait LibraryAccess: AnalyzerBackend + } /// Get all possible library functions - fn possible_library_funcs(&mut self, ctx: ContextNode, ty: NodeIdx) -> BTreeSet { + fn possible_library_funcs(&mut self, ctx: ContextNode, ty: NodeIdx) -> Vec { tracing::trace!("looking for library functions of type: {:?}", self.node(ty)); - let mut funcs: BTreeSet = BTreeSet::new(); + let mut funcs: Vec = Vec::new(); if let Some(associated_contract) = ctx.maybe_associated_contract(self).unwrap() { // search for contract scoped `using` statements funcs.extend( @@ -60,6 +60,8 @@ pub trait LibraryAccess: AnalyzerBackend + ); } + funcs.sort(); + funcs.dedup(); funcs } } diff --git a/crates/solc-expressions/src/member_access/list_access.rs b/crates/solc-expressions/src/member_access/list_access.rs index e8dd77ba..cda0a467 100644 --- a/crates/solc-expressions/src/member_access/list_access.rs +++ b/crates/solc-expressions/src/member_access/list_access.rs @@ -1,9 +1,9 @@ -use crate::{ContextBuilder, ExpressionParser, Variable}; +use crate::Variable; use graph::{ elem::*, nodes::{BuiltInNode, Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, Range, SolcRange, VarType, + AnalyzerBackend, ContextEdge, Edge, Range, SolcRange, VarType, }; use shared::{ExprErr, IntoExprErr, RangeArena}; @@ -18,24 +18,11 @@ pub trait ListAccess: AnalyzerBackend + Si fn length( &mut self, arena: &mut RangeArena>, - loc: Loc, - input_expr: &Expression, ctx: ContextNode, + input: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, input_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Attempted to perform member access without a left-hand side".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_length(arena, ctx, loc, ret, true) - }) + self.match_length(arena, ctx, input, loc) } #[tracing::instrument(level = "trace", skip_all)] @@ -44,9 +31,8 @@ pub trait ListAccess: AnalyzerBackend + Si &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, elem_path: ExprRet, - _update_len_bound: bool, + loc: Loc, ) -> Result<(), ExprErr> { match elem_path { ExprRet::Null => { @@ -55,28 +41,25 @@ pub trait ListAccess: AnalyzerBackend + Si } ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), ExprRet::Single(arr) => { - self.get_length(arena, ctx, loc, arr.into(), false)?; + self.get_length(arena, ctx, arr.into(), false, loc)?; Ok(()) } e => todo!("here: {e:?}"), } } + #[tracing::instrument(level = "trace", skip_all)] fn get_length( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, array: ContextVarNode, return_var: bool, + loc: Loc, ) -> Result, ExprErr> { - let next_arr = self.advance_var_in_ctx( - array.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; + let array = array.latest_version_or_inherited_in_ctx(ctx, self); // search for latest length - if let Some(len_var) = next_arr.array_to_len_var(self) { + if let Some(len_var) = array.array_to_len_var(self) { let len_node = self.advance_var_in_ctx( len_var.latest_version_or_inherited_in_ctx(ctx, self), loc, @@ -90,22 +73,30 @@ pub trait ListAccess: AnalyzerBackend + Si Ok(Some(len_node)) } } else { - self.create_length(arena, ctx, loc, array, next_arr, return_var) + self.create_length(arena, ctx, array, return_var, loc) } } + #[tracing::instrument(level = "trace", skip_all)] fn create_length( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, array: ContextVarNode, - target_array: ContextVarNode, return_var: bool, + loc: Loc, ) -> Result, ExprErr> { // no length variable, create one let name = format!("{}.length", array.name(self).into_expr_err(loc)?); + // we have to force here to avoid length <-> array recursion + let target_arr = self.advance_var_in_ctx_forcible( + array.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + true, + )?; + // Create the range from the current length or default to [0, uint256.max] let len_min = Elem::from(array) .get_length() @@ -130,31 +121,23 @@ pub trait ListAccess: AnalyzerBackend + Si Some(range), ), }; - let len_node = ContextVarNode::from(self.add_node(Node::ContextVar(len_var))); + let len_node = ContextVarNode::from(self.add_node(len_var)); self.add_edge( len_node, - target_array, + target_arr, Edge::Context(ContextEdge::AttrAccess("length")), ); self.add_edge(len_node, ctx, Edge::Context(ContextEdge::Variable)); ctx.add_var(len_node, self).into_expr_err(loc)?; - // we have to force here to avoid length <-> array recursion - let next_target_arr = self.advance_var_in_ctx_forcible( - target_array.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - true, - )?; - let update_array_len = - Elem::from(target_array.latest_version_or_inherited_in_ctx(ctx, self)) - .set_length(len_node.into()); + let update_array_len = Elem::from(target_arr.latest_version_or_inherited_in_ctx(ctx, self)) + .set_length(len_node.into()); // Update the array - next_target_arr + target_arr .set_range_min(self, arena, update_array_len.clone()) .into_expr_err(loc)?; - next_target_arr + target_arr .set_range_max(self, arena, update_array_len.clone()) .into_expr_err(loc)?; @@ -203,7 +186,7 @@ pub trait ListAccess: AnalyzerBackend + Si range, ), }; - let len_node = self.add_node(Node::ContextVar(len_var)); + let len_node = self.add_node(len_var); let next_arr = self .advance_var_in_ctx( diff --git a/crates/solc-expressions/src/member_access/member_trait.rs b/crates/solc-expressions/src/member_access/member_trait.rs index 6c6967b8..46571c79 100644 --- a/crates/solc-expressions/src/member_access/member_trait.rs +++ b/crates/solc-expressions/src/member_access/member_trait.rs @@ -1,7 +1,4 @@ -use crate::{ - BuiltinAccess, ContextBuilder, ContractAccess, EnumAccess, Env, ExpressionParser, ListAccess, - StructAccess, -}; +use crate::{BuiltinAccess, ContractAccess, EnumAccess, Env, ListAccess, StructAccess}; use graph::{ elem::Elem, @@ -13,7 +10,7 @@ use graph::{ }; use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; -use solang_parser::pt::{Expression, Identifier, Loc}; +use solang_parser::pt::{Expression, Loc}; impl MemberAccessParts for T where T: BuiltinAccess + ContractAccess + EnumAccess + ListAccess + StructAccess @@ -40,92 +37,87 @@ pub trait MemberAccess: fn member_access( &mut self, arena: &mut RangeArena>, - loc: Loc, - member_expr: &Expression, - ident: &Identifier, ctx: ContextNode, - ) -> Result<(), ExprErr> { + member: ExprRet, + name: &str, + loc: Loc, + ) -> Result { // TODO: this is wrong as it overwrites a function call of the form elem.length(...) i believe - if ident.name == "length" { - return self.length(arena, loc, member_expr, ctx); + if name == "length" { + self.length(arena, ctx, member, loc)?; + return Ok(false); } - self.parse_ctx_expr(arena, member_expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Attempted to perform member access without a left-hand side".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_member(ctx, loc, ident, ret) - }) + self.match_member(ctx, member, name, loc) } /// Match on [`ExprRet`]s and call the member access for each fn match_member( &mut self, ctx: ContextNode, + member: ExprRet, + name: &str, loc: Loc, - ident: &Identifier, - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret { + ) -> Result { + match member { ExprRet::Single(idx) | ExprRet::SingleLiteral(idx) => { - ctx.push_expr(self.member_access_inner(loc, idx, ident, ctx)?, self) - .into_expr_err(loc)?; - Ok(()) + let (inner, was_lib_func) = self.member_access_inner(ctx, idx, name, loc)?; + ctx.push_expr(inner, self).into_expr_err(loc)?; + Ok(was_lib_func) } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|ret| self.match_member(ctx, loc, ident, ret)), - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), - ExprRet::Null => Ok(()), + ExprRet::Multi(inner) => { + inner.into_iter().try_for_each(|member| { + let _ = self.match_member(ctx, member, name, loc)?; + Ok(()) + })?; + Ok(false) + } + ExprRet::CtxKilled(kind) => { + ctx.kill(self, loc, kind).into_expr_err(loc)?; + Ok(false) + } + ExprRet::Null => Ok(false), } } /// Perform the member access fn member_access_inner( &mut self, - loc: Loc, - member_idx: NodeIdx, - ident: &Identifier, ctx: ContextNode, - ) -> Result { + member_idx: NodeIdx, + name: &str, + loc: Loc, + ) -> Result<(ExprRet, bool), ExprErr> { match self.node(member_idx) { - Node::ContextVar(cvar) => { - self.member_access_var_ty(cvar.clone(), loc, member_idx, ident, ctx) + Node::ContextVar(_) => { + self.member_access_var(ctx, ContextVarNode::from(member_idx), name, loc) } - Node::Contract(_c) => self.contract_member_access( - member_idx, - ContractNode::from(member_idx), - ident, - ctx, - loc, - None, - ), - Node::Struct(_c) => self.struct_member_access( - member_idx, - StructNode::from(member_idx), - ident, - ctx, - loc, - None, - ), - Node::Enum(_c) => { - self.enum_member_access(member_idx, EnumNode::from(member_idx), ident, ctx, loc) + Node::Contract(_c) => { + self.contract_member_access(ctx, None, ContractNode::from(member_idx), name, loc) + } + Node::Struct(_c) => { + let var = + self.add_node(ContextVar::maybe_from_user_ty(self, loc, member_idx).unwrap()); + self.struct_var_member_access( + ctx, + var.into(), + StructNode::from(member_idx), + name, + loc, + ) + } + Node::Enum(_c) => self.enum_member_access(ctx, EnumNode::from(member_idx), name, loc), + Node::Ty(_ty) => self.ty_member_access(ctx, TyNode::from(member_idx), name, loc), + Node::Msg(_msg) => { + let res = self.msg_access(ctx, name, loc)?; + Ok((res, false)) } - Node::Ty(_ty) => { - self.ty_member_access(member_idx, TyNode::from(member_idx), ident, ctx, loc, None) + Node::Block(_b) => { + let res = self.block_access(ctx, name, loc)?; + Ok((res, false)) } - Node::Msg(_msg) => self.msg_access(loc, ctx, &ident.name), - Node::Block(_b) => self.block_access(loc, ctx, &ident.name), Node::Builtin(ref _b) => { - self.builtin_member_access(loc, ctx, BuiltInNode::from(member_idx), false, ident) + self.builtin_member_access(ctx, BuiltInNode::from(member_idx), name, false, loc) } e => Err(ExprErr::Todo( loc, @@ -148,58 +140,58 @@ pub trait MemberAccess: cvar.ty ); match &cvar.ty { - VarType::User(TypeNode::Contract(con_node), _) => { - let cnode = *con_node; - let mut funcs = cnode.linearized_functions(self).into_expr_err(loc)?; - self - .possible_library_funcs(ctx, cnode.0.into()) - .into_iter() - .for_each(|func| { - let name = func.name(self).unwrap(); - funcs.entry(name).or_insert(func); - }); - funcs.values().copied().collect() - }, - VarType::BuiltIn(bn, _) => self - .possible_library_funcs(ctx, bn.0.into()) - .into_iter() - .collect::>(), - VarType::Concrete(cnode) => { - let b = cnode.underlying(self).unwrap().as_builtin(); - let bn = self.builtin_or_add(b); - self.possible_library_funcs(ctx, bn) + VarType::User(TypeNode::Contract(con_node), _) => { + let cnode = *con_node; + let mut funcs = cnode.linearized_functions(self, false).into_expr_err(loc)?; + self + .possible_library_funcs(ctx, cnode.0.into()) .into_iter() - .collect::>() - } - VarType::User(TypeNode::Struct(sn), _) => self - .possible_library_funcs(ctx, sn.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Enum(en), _) => self - .possible_library_funcs(ctx, en.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Ty(ty), _) => self - .possible_library_funcs(ctx, ty.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Error(err), _) => self - .possible_library_funcs(ctx, err.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Func(func_node), _) => self - .possible_library_funcs(ctx, func_node.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Unresolved(n), _) => { - match self.node(*n) { - Node::Unresolved(ident) => { - return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name))) + .for_each(|func| { + let name = func.name(self).unwrap(); + funcs.entry(name).or_insert(func); + }); + funcs.values().copied().collect() + }, + VarType::BuiltIn(bn, _) => self + .possible_library_funcs(ctx, bn.0.into()) + .into_iter() + .collect::>(), + VarType::Concrete(cnode) => { + let b = cnode.underlying(self).unwrap().as_builtin(); + let bn = self.builtin_or_add(b); + self.possible_library_funcs(ctx, bn) + .into_iter() + .collect::>() + } + VarType::User(TypeNode::Struct(sn), _) => self + .possible_library_funcs(ctx, sn.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Enum(en), _) => self + .possible_library_funcs(ctx, en.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Ty(ty), _) => self + .possible_library_funcs(ctx, ty.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Error(err), _) => self + .possible_library_funcs(ctx, err.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Func(func_node), _) => self + .possible_library_funcs(ctx, func_node.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Unresolved(n), _) => { + match self.node(*n) { + Node::Unresolved(ident) => { + return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name))) + } + _ => unreachable!() } - _ => unreachable!() } } - } } Node::Contract(_) => ContractNode::from(member_idx).funcs(self), Node::Concrete(_) @@ -222,55 +214,50 @@ pub trait MemberAccess: } /// Perform member access for a variable type - fn member_access_var_ty( + fn member_access_var( &mut self, - cvar: ContextVar, - loc: Loc, - member_idx: NodeIdx, - ident: &Identifier, ctx: ContextNode, - ) -> Result { - match &cvar.ty { + cvar: ContextVarNode, + name: &str, + loc: Loc, + ) -> Result<(ExprRet, bool), ExprErr> { + match cvar.ty(self).into_expr_err(loc)? { VarType::User(TypeNode::Struct(struct_node), _) => { - self.struct_member_access(member_idx, *struct_node, ident, ctx, loc, Some(cvar)) + self.struct_var_member_access(ctx, cvar, *struct_node, name, loc) } VarType::User(TypeNode::Enum(enum_node), _) => { - self.enum_member_access(member_idx, *enum_node, ident, ctx, loc) + self.enum_member_access(ctx, *enum_node, name, loc) } VarType::User(TypeNode::Func(func_node), _) => { - self.func_member_access(*func_node, ident, ctx, loc) + Ok((self.func_member_access(ctx, *func_node, name, loc)?, false)) } VarType::User(TypeNode::Ty(ty_node), _) => { - self.ty_member_access(member_idx, *ty_node, ident, ctx, loc, Some(cvar)) + self.ty_member_access(ctx, *ty_node, name, loc) } VarType::User(TypeNode::Contract(con_node), _) => { - self.contract_member_access(member_idx, *con_node, ident, ctx, loc, Some(cvar)) + self.contract_member_access(ctx, Some(cvar), *con_node, name, loc) } VarType::BuiltIn(bn, _) => self.builtin_member_access( - loc, ctx, *bn, - ContextVarNode::from(member_idx) - .is_storage(self) - .into_expr_err(loc)?, - ident, + name, + cvar.is_storage(self).into_expr_err(loc)?, + loc, ), VarType::Concrete(cn) => { let builtin = cn.underlying(self).into_expr_err(loc)?.as_builtin(); let bn = self.builtin_or_add(builtin).into(); self.builtin_member_access( - loc, ctx, bn, - ContextVarNode::from(member_idx) - .is_storage(self) - .into_expr_err(loc)?, - ident, + name, + cvar.is_storage(self).into_expr_err(loc)?, + loc, ) } e => Err(ExprErr::UnhandledCombo( loc, - format!("Unhandled member access: {:?}, {:?}", e, ident), + format!("Unhandled member access: {:?}, {:?}", e, name), )), } } @@ -278,24 +265,21 @@ pub trait MemberAccess: /// Perform a `TyNode` member access fn ty_member_access( &mut self, - _member_idx: NodeIdx, - ty_node: TyNode, - ident: &Identifier, ctx: ContextNode, + ty_node: TyNode, + name: &str, loc: Loc, - _maybe_parent: Option, - ) -> Result { - let name = ident.name.split('(').collect::>()[0]; - if let Some(func) = self.library_func_search(ctx, ty_node.0.into(), ident) { - Ok(func) + ) -> Result<(ExprRet, bool), ExprErr> { + let name = name.split('(').collect::>()[0]; + if let Some(func) = self.library_func_search(ctx, ty_node.0.into(), name) { + Ok((func, true)) } else if let Some(func) = self.builtin_fn_or_maybe_add(name) { - Ok(ExprRet::Single(func)) + Ok((ExprRet::Single(func), false)) } else { Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access \"{}\" on struct \"{}\"", - ident.name, + "Unknown member access \"{name}\" on struct \"{}\"", ty_node.name(self).into_expr_err(loc)? ), )) @@ -305,18 +289,18 @@ pub trait MemberAccess: /// Access function members fn func_member_access( &mut self, - func_node: FunctionNode, - ident: &Identifier, ctx: ContextNode, + func_node: FunctionNode, + name: &str, loc: Loc, ) -> Result { let prefix_only_name = func_node .prefix_only_name(self) .into_expr_err(loc)? .unwrap(); - let name = format!("{}.{}", prefix_only_name, ident.name); - tracing::trace!("Function member access: {}", name); - match &*ident.name { + let func_mem_name = format!("{}.{}", prefix_only_name, name); + tracing::trace!("Function member access: {}", func_mem_name); + match &*func_mem_name { "selector" => { let mut out = [0; 32]; keccak_hash::keccak_256(prefix_only_name.as_bytes(), &mut out); @@ -325,14 +309,14 @@ pub trait MemberAccess: let selector_node = ConcreteNode::from(self.add_node(selector_conc)); let var = ContextVar::new_from_concrete(loc, ctx, selector_node, self) .into_expr_err(loc)?; - let cvar = self.add_node(Node::ContextVar(var)); + let cvar = self.add_node(var); Ok(ExprRet::Single(cvar)) } _ => Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access \"{}\" on function \"{}\"", - ident.name, prefix_only_name + "Unknown member access \"{name}\" on function \"{}\"", + prefix_only_name ), )), } diff --git a/crates/solc-expressions/src/member_access/struct_access.rs b/crates/solc-expressions/src/member_access/struct_access.rs index f0187d01..2a88fc2d 100644 --- a/crates/solc-expressions/src/member_access/struct_access.rs +++ b/crates/solc-expressions/src/member_access/struct_access.rs @@ -2,11 +2,11 @@ use crate::LibraryAccess; use graph::{ nodes::{ContextNode, ContextVar, ContextVarNode, ExprRet, StructNode}, - AnalyzerBackend, ContextEdge, Edge, Node, + AnalyzerBackend, ContextEdge, Edge, }; -use shared::{ExprErr, IntoExprErr, NodeIdx}; +use shared::{ExprErr, IntoExprErr}; -use solang_parser::pt::{Expression, Identifier, Loc}; +use solang_parser::pt::{Expression, Loc}; impl StructAccess for T where T: LibraryAccess + AnalyzerBackend + Sized @@ -17,65 +17,46 @@ pub trait StructAccess: LibraryAccess + AnalyzerBackend + Sized { /// Perform member access on a struct - fn struct_member_access( + fn struct_var_member_access( &mut self, - member_idx: NodeIdx, - struct_node: StructNode, - ident: &Identifier, ctx: ContextNode, + cvar: ContextVarNode, + struct_node: StructNode, + field_name: &str, loc: Loc, - maybe_parent: Option, - ) -> Result { - let name = format!( - "{}.{}", - if member_idx.index() != struct_node.0 { - ContextVarNode::from(member_idx).name(self).unwrap() - } else { - struct_node.name(self).into_expr_err(loc)? - }, - ident.name - ); - tracing::trace!("Struct member access: {}", name); - if let Some(field) = ctx - .struct_field_access_by_name_recurse(self, loc, &name) - .into_expr_err(loc)? - { - Ok(ExprRet::Single( - field.latest_version_or_inherited_in_ctx(ctx, self).into(), - )) - } else if let Some(field) = struct_node.find_field(self, ident) { - println!("here1234"); - let cvar = if let Some(parent) = maybe_parent { - parent - } else { - ContextVar::maybe_from_user_ty(self, loc, struct_node.into()).unwrap() - }; + ) -> Result<(ExprRet, bool), ExprErr> { + let cvar_name = cvar.name(self).unwrap(); + let name = format!("{cvar_name}.{field_name}"); + tracing::trace!("Struct member access: {cvar_name}.{field_name}"); + + if let Some(field) = cvar.field_of_struct(field_name, self).into_expr_err(loc)? { + return Ok((ExprRet::Single(field.into()), false)); + } + + if let Some(field) = struct_node.find_field(self, field_name) { if let Some(field_cvar) = ContextVar::maybe_new_from_field( self, loc, - &cvar, + cvar.underlying(self).unwrap(), field.underlying(self).unwrap().clone(), ) { - let fc_node = self.add_node(Node::ContextVar(field_cvar)); + let fc_node = self.add_node(field_cvar); self.add_edge( fc_node, - ContextVarNode::from(member_idx).first_version(self), + cvar.first_version(self), Edge::Context(ContextEdge::AttrAccess("field")), ); - // ctx.add_var(fc_node.into(), self).into_expr_err(loc)?; - // self.add_edge(fc_node, ctx, Edge::Context(ContextEdge::Variable)); - Ok(ExprRet::Single(fc_node)) + Ok((ExprRet::Single(fc_node), false)) } else { panic!("Couldn't create field variable"); } - } else if let Some(func) = self.library_func_search(ctx, struct_node.0.into(), ident) { - Ok(func) + } else if let Some(func) = self.library_func_search(ctx, struct_node.0.into(), &name) { + Ok((func, true)) } else { Err(ExprErr::MemberAccessNotFound( loc, format!( - "Unknown member access \"{}\" on struct \"{}\"", - ident.name, + "Unknown member access \"{name}\" on struct \"{}\"", struct_node.name(self).into_expr_err(loc)? ), )) diff --git a/crates/solc-expressions/src/pre_post_in_decrement.rs b/crates/solc-expressions/src/pre_post_in_decrement.rs index 9550c32d..554238f7 100644 --- a/crates/solc-expressions/src/pre_post_in_decrement.rs +++ b/crates/solc-expressions/src/pre_post_in_decrement.rs @@ -1,4 +1,4 @@ -use crate::{context_builder::ContextBuilder, variable::Variable, ExpressionParser}; +use crate::variable::Variable; use graph::{ elem::*, @@ -18,107 +18,6 @@ impl PrePostIncDecrement for T where pub trait PrePostIncDecrement: AnalyzerBackend + Sized { - /// Handle a preincrement - fn pre_increment( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - tracing::trace!("PreIncrement variable pop"); - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "PreIncrement operation had no right hand side".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_in_de_crement(arena, ctx, true, true, loc, &ret) - }) - } - - /// Handle a postincrement - fn post_increment( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - tracing::trace!("PostIncrement variable pop"); - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "PostIncrement operation had no right hand side".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_in_de_crement(arena, ctx, false, true, loc, &ret) - }) - } - - /// Handle a predecrement - fn pre_decrement( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - tracing::trace!("PreDecrement variable pop"); - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "PreDecrement operation had no right hand side".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_in_de_crement(arena, ctx, true, false, loc, &ret) - }) - } - - /// Handle a postdecrement - fn post_decrement( - &mut self, - arena: &mut RangeArena>, - expr: &Expression, - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, expr, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - tracing::trace!("PostDecrement variable pop"); - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "PostDecrement operation had no right hand side".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_in_de_crement(arena, ctx, false, false, loc, &ret) - }) - } - /// Match on the [`ExprRet`]s of a pre-or-post in/decrement and performs it fn match_in_de_crement( &mut self, @@ -135,12 +34,14 @@ pub trait PrePostIncDecrement: Ok(()) } ExprRet::SingleLiteral(var) => { + // ie: 5++; (not valid syntax) ContextVarNode::from(*var) .try_increase_size(self, arena) .into_expr_err(loc)?; self.match_in_de_crement(arena, ctx, pre, increment, loc, &ExprRet::Single(*var)) } ExprRet::Single(var) => { + // ie: a++; let cvar = ContextVarNode::from(*var).latest_version_or_inherited_in_ctx(ctx, self); let elem = Elem::from(cvar); let one = Elem::from(Concrete::from(U256::from(1))).cast(elem.clone()); @@ -228,9 +129,12 @@ pub trait PrePostIncDecrement: Ok(()) } } - ExprRet::Multi(inner) => inner.iter().try_for_each(|expr| { - self.match_in_de_crement(arena, ctx, pre, increment, loc, expr) - }), + ExprRet::Multi(inner) => { + // ie: (5, 5)++; (invalid syntax) + inner.iter().try_for_each(|expr| { + self.match_in_de_crement(arena, ctx, pre, increment, loc, expr) + }) + } ExprRet::Null => Ok(()), } } diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index f8117774..96d6792a 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -1,4 +1,4 @@ -use crate::{BinOp, ContextBuilder, ExpressionParser, Variable}; +use crate::{BinOp, Variable}; use graph::{ elem::*, @@ -12,10 +12,7 @@ use graph::{ use shared::{ExprErr, IntoExprErr, RangeArena}; use ethers_core::types::I256; -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Loc}, -}; +use solang_parser::pt::Loc; use std::cmp::Ordering; @@ -23,637 +20,44 @@ impl Require for T where T: Variable + BinOp + Sized + AnalyzerBackend {} /// Deals with require and assert statements, as well as adjusts bounds for variables pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { - /// Inverts a comparator expression - fn inverse_expr(&self, expr: Expression) -> Expression { - match expr { - Expression::Equal(loc, lhs, rhs) => Expression::NotEqual(loc, lhs, rhs), - Expression::NotEqual(loc, lhs, rhs) => Expression::Equal(loc, lhs, rhs), - Expression::Less(loc, lhs, rhs) => Expression::MoreEqual(loc, lhs, rhs), - Expression::More(loc, lhs, rhs) => Expression::LessEqual(loc, lhs, rhs), - Expression::MoreEqual(loc, lhs, rhs) => Expression::Less(loc, lhs, rhs), - Expression::LessEqual(loc, lhs, rhs) => Expression::More(loc, lhs, rhs), - // Expression::And(loc, lhs, rhs) => { - // Expression::Or(loc, Box::new(self.inverse_expr(*lhs)), Box::new(self.inverse_expr(*rhs))) - // } - // Expression::Or(loc, lhs, rhs) => { - // Expression::And(loc, Box::new(self.inverse_expr(*lhs)), Box::new(self.inverse_expr(*rhs))) - // } - // Expression::Not(loc, lhs) => { - // Expression::Equal(loc, lhs, Box::new(Expression::BoolLiteral(loc, true))) - // } - e => Expression::Not(e.loc(), Box::new(e)), - } - } - - /// Handles a require expression - #[tracing::instrument(level = "trace", skip_all)] - fn handle_require( - &mut self, - arena: &mut RangeArena>, - inputs: &[Expression], - ctx: ContextNode, - ) -> Result<(), ExprErr> { - ctx.add_gas_cost(self, shared::gas::BIN_OP_GAS) - .into_expr_err(inputs[0].loc())?; - match inputs.first().expect("No lhs input for require statement") { - Expression::Equal(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `==` had no right hand side".to_string(), - )); - }; - - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `==` had no left hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - ) - }) - }) - } - Expression::NotEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `!=` had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `!=` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Neq, - RangeOp::Neq, - (RangeOp::Eq, RangeOp::Neq), - ) - }) - }) - } - Expression::Less(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `<` had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `<` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Lt, - RangeOp::Gt, - (RangeOp::Gt, RangeOp::Lt), - ) - }) - }) - } - Expression::More(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `>` had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `>` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Gt, - RangeOp::Lt, - (RangeOp::Lt, RangeOp::Gt), - ) - }) - }) - } - Expression::MoreEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `>=` had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `>=` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Gte, - RangeOp::Lte, - (RangeOp::Lte, RangeOp::Gte), - ) - }) - }) - } - Expression::LessEqual(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, rhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Require operation `<=` had no right hand side".to_string(), - )); - }; - let rhs_paths = rhs_paths.flatten(); - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, lhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `<=` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths.flatten(), - &rhs_paths, - RangeOp::Lte, - RangeOp::Gte, - (RangeOp::Gte, RangeOp::Lte), - ) - }) - }) - } - Expression::Not(loc, lhs) => { - self.parse_ctx_expr(arena, lhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `NOT` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let cnode = ConcreteNode::from( - analyzer.add_node(Node::Concrete(Concrete::Bool(false))), - ); - let tmp_false = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, analyzer) - .into_expr_err(loc)?, - ); - let rhs_paths = - ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_false)).into()); - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths, - &rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - ) - }) - } - Expression::And(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, lhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `&&` had no left hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, rhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `&&` had no left hand side".to_string(), - )); - }; - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let cnode = ConcreteNode::from( - analyzer.add_node(Node::Concrete(Concrete::Bool(true))), - ); - let tmp_true = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, analyzer) - .into_expr_err(loc)?, - ); - let node = analyzer.add_node(tmp_true); - ctx.add_var(node.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - let tmp_rhs_paths = ExprRet::Single(node); - - // NOTE: the following is *sequence dependent* - // we want to update the parts *before* the `And` op - // to ensure the ctx_dep is correct - - // update the part's bounds - let lhs_cvar = - ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - let underlying = lhs_cvar.underlying(analyzer).into_expr_err(loc)?; - if let Some(tmp) = underlying.tmp_of { - if let Some((op, _inv_op, pair)) = tmp.op.require_parts() { - analyzer.handle_require_inner( - arena, - ctx, - loc, - &ExprRet::Single(tmp.lhs.into()), - &ExprRet::Single(tmp.rhs.unwrap().into()), - op, - op, - pair, - )?; - } - } - - // update the part's bounds - let rhs_cvar = - ContextVarNode::from(rhs_paths.expect_single().into_expr_err(loc)?); - let underlying = rhs_cvar.underlying(analyzer).into_expr_err(loc)?; - if let Some(tmp) = underlying.tmp_of { - if let Some((op, _inv_op, pair)) = tmp.op.require_parts() { - analyzer.handle_require_inner( - arena, - ctx, - loc, - &ExprRet::Single(tmp.lhs.into()), - &ExprRet::Single(tmp.rhs.unwrap().into()), - op, - op, - pair, - )?; - } - } - - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths, - &tmp_rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - )?; - - analyzer.handle_require_inner( - arena, - ctx, - loc, - &rhs_paths, - &tmp_rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - )?; - - Ok(()) - }) - }) - } - Expression::Or(loc, lhs, rhs) => { - self.parse_ctx_expr(arena, lhs, ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `||` had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_expr(arena, rhs, ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation `||` had no left hand side".to_string(), - )); - }; - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let lhs_cvar = - ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - let rhs_cvar = - ContextVarNode::from(rhs_paths.expect_single().into_expr_err(loc)?); - - let elem = Elem::Expr(RangeExpr::new( - lhs_cvar.into(), - RangeOp::Or, - rhs_cvar.into(), - )); - let range = SolcRange::new(elem.clone(), elem, vec![]); - - let new_lhs_underlying = ContextVar { - loc: Some(loc), - name: format!( - "tmp{}({} {} {})", - ctx.new_tmp(analyzer).into_expr_err(loc)?, - lhs_cvar.name(analyzer).into_expr_err(loc)?, - RangeOp::Or.to_string(), - rhs_cvar.name(analyzer).into_expr_err(loc)? - ), - display_name: format!( - "({} {} {})", - lhs_cvar.display_name(analyzer).into_expr_err(loc)?, - RangeOp::Or.to_string(), - rhs_cvar.display_name(analyzer).into_expr_err(loc)? - ), - storage: None, - is_tmp: true, - is_symbolic: lhs_cvar.is_symbolic(analyzer).into_expr_err(loc)? - || rhs_cvar.is_symbolic(analyzer).into_expr_err(loc)?, - is_return: false, - tmp_of: Some(TmpConstruction::new( - lhs_cvar, - RangeOp::Or, - Some(rhs_cvar), - )), - dep_on: { - let mut deps = - lhs_cvar.dependent_on(analyzer, true).into_expr_err(loc)?; - deps.extend( - rhs_cvar.dependent_on(analyzer, true).into_expr_err(loc)?, - ); - Some(deps) - }, - ty: VarType::BuiltIn( - analyzer.builtin_or_add(Builtin::Bool).into(), - Some(range), - ), - }; - let or_var = ContextVarNode::from( - analyzer.add_node(Node::ContextVar(new_lhs_underlying)), - ); - let cnode = ConcreteNode::from( - analyzer.add_node(Node::Concrete(Concrete::Bool(true))), - ); - let tmp_true = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, analyzer) - .into_expr_err(loc)?, - ); - let node = analyzer.add_node(tmp_true); - ctx.add_var(node.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - let rhs_paths = ExprRet::Single(node); - analyzer.handle_require_inner( - arena, - ctx, - loc, - &ExprRet::Single(or_var.into()), - &rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - ) - }) - }) - } - other => { - self.parse_ctx_expr(arena, other, ctx)?; - self.apply_to_edges(ctx, other.loc(), arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Require operation had no left hand side".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - let tmp_true = analyzer.add_concrete_var(ctx, Concrete::Bool(true), loc)?; - let rhs_paths = ExprRet::Single(tmp_true.0.into()); - analyzer.handle_require_inner( - arena, - ctx, - loc, - &lhs_paths, - &rhs_paths, - RangeOp::Eq, - RangeOp::Eq, - (RangeOp::Neq, RangeOp::Eq), - ) - }) - } - } - } - /// Do matching on [`ExprRet`]s to actually perform the require statement evaluation fn handle_require_inner( &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, lhs_paths: &ExprRet, rhs_paths: &ExprRet, op: RangeOp, - rhs_op: RangeOp, - recursion_ops: (RangeOp, RangeOp), + loc: Loc, ) -> Result<(), ExprErr> { match (lhs_paths, rhs_paths) { (_, ExprRet::Null) | (ExprRet::Null, _) => Ok(()), (_, ExprRet::CtxKilled(..)) | (ExprRet::CtxKilled(..), _) => Ok(()), - (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { - ContextVarNode::from(*lhs) - .cast_from(&ContextVarNode::from(*rhs), self, arena) - .into_expr_err(loc)?; - self.handle_require_inner( + (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => self + .handle_require_inner( arena, ctx, - loc, &ExprRet::Single(*lhs), - rhs_paths, + &ExprRet::Single(*rhs), op, - rhs_op, - recursion_ops, - ) + loc, + ), + (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: require(5 == a); + ContextVarNode::from(*lhs) + .cast_from(&ContextVarNode::from(*rhs), self, arena) + .into_expr_err(loc)?; + self.handle_require_inner(arena, ctx, &ExprRet::Single(*lhs), rhs_paths, op, loc) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: require(a == 5); ContextVarNode::from(*rhs) .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; - self.handle_require_inner( - arena, - ctx, - loc, - lhs_paths, - &ExprRet::Single(*rhs), - op, - rhs_op, - recursion_ops, - ) + self.handle_require_inner(arena, ctx, lhs_paths, &ExprRet::Single(*rhs), op, loc) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: require(a == b); let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let new_lhs = self.advance_var_in_ctx(lhs_cvar, loc, ctx)?; @@ -661,73 +65,46 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); let new_rhs = self.advance_var_in_ctx(rhs_cvar, loc, ctx)?; - self.require(arena, new_lhs, new_rhs, ctx, loc, op, rhs_op, recursion_ops)?; + self.require(arena, ctx, new_lhs, new_rhs, op, loc)?; Ok(()) } (l @ ExprRet::Single(_) | l @ ExprRet::SingleLiteral(_), ExprRet::Multi(rhs_sides)) => { + // ie: require(a == (b, c)); (not possible) rhs_sides.iter().try_for_each(|expr_ret| { - self.handle_require_inner( - arena, - ctx, - loc, - l, - expr_ret, - op, - rhs_op, - recursion_ops, - ) + self.handle_require_inner(arena, ctx, l, expr_ret, op, loc) }) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { + // ie: require((a, b) == c); (not possible) lhs_sides.iter().try_for_each(|expr_ret| { - self.handle_require_inner( - arena, - ctx, - loc, - expr_ret, - r, - op, - rhs_op, - recursion_ops, - ) + self.handle_require_inner(arena, ctx, expr_ret, r, op, loc) }) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { + // ie: require((a, b) == (c, d)); (not possible) + // ie: require((a, b) == (c, d, e)); (not possible) // try to zip sides if they are the same length if lhs_sides.len() == rhs_sides.len() { + // ie: require((a, b) == (c, d)); (not possible) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.handle_require_inner( arena, ctx, - loc, lhs_expr_ret, rhs_expr_ret, op, - rhs_op, - recursion_ops, + loc, ) }, ) } else { + // ie: require((a, b) == (c, d, e)); (not possible) rhs_sides.iter().try_for_each(|rhs_expr_ret| { - self.handle_require_inner( - arena, - ctx, - loc, - lhs_paths, - rhs_expr_ret, - op, - rhs_op, - recursion_ops, - ) + self.handle_require_inner(arena, ctx, lhs_paths, rhs_expr_ret, op, loc) }) } } - (e, f) => Err(ExprErr::UnhandledCombo( - loc, - format!("Unhandled combination in require: {:?} {:?}", e, f), - )), } } @@ -738,23 +115,22 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { fn require( &mut self, arena: &mut RangeArena>, + ctx: ContextNode, mut new_lhs: ContextVarNode, mut new_rhs: ContextVarNode, - ctx: ContextNode, - loc: Loc, op: RangeOp, - rhs_op: RangeOp, - _recursion_ops: (RangeOp, RangeOp), + loc: Loc, ) -> Result, ExprErr> { tracing::trace!( - "require: {} {} {}", + "require: {} {op} {}", new_lhs.display_name(self).into_expr_err(loc)?, - op.to_string(), new_rhs.display_name(self).into_expr_err(loc)? ); let mut any_unsat = false; let mut tmp_cvar = None; + let rhs_op = op.require_rhs().unwrap(); + if let Some(lhs_range) = new_lhs .latest_version_or_inherited_in_ctx(ctx, self) .range(self) @@ -823,9 +199,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (new_lhs.display_name(self).into_expr_err(loc)?).to_string() } else { format!( - "({} {} {rhs_display_name})", + "({} {op} {rhs_display_name})", new_lhs.display_name(self).into_expr_err(loc)?, - op.to_string(), ) }; @@ -843,7 +218,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { "tmp{}({} {} {})", ctx.new_tmp(self).into_expr_err(loc)?, new_lhs.name(self).into_expr_err(loc)?, - op.to_string(), + op, new_rhs.name(self).into_expr_err(loc)?, ), display_name: display_name.clone(), @@ -866,12 +241,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ), }; - let conditional_cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(conditional_var))); + let conditional_cvar = ContextVarNode::from(self.add_node(conditional_var)); ctx.add_var(conditional_cvar, self).into_expr_err(loc)?; self.add_edge(conditional_cvar, ctx, Edge::Context(ContextEdge::Variable)); - let cnode = ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Bool(true)))); + let cnode = ConcreteNode::from(self.add_node(Concrete::Bool(true))); let tmp_true = Node::ContextVar( ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, self) .into_expr_err(loc)?, @@ -887,7 +261,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { "tmp{}(({} {} {}) == true)", ctx.new_tmp(self).into_expr_err(loc)?, new_lhs.name(self).into_expr_err(loc)?, - op.to_string(), + op, new_rhs.name(self).into_expr_err(loc)?, ), display_name: format!("{display_name} == true"), @@ -922,7 +296,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ), }; - let cvar = ContextVarNode::from(self.add_node(Node::ContextVar(tmp_var))); + let cvar = ContextVarNode::from(self.add_node(tmp_var)); ctx.add_var(cvar, self).into_expr_err(loc)?; self.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); @@ -945,18 +319,6 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { new_lhs.display_name(self).unwrap(), new_lhs.is_tmp(self).unwrap() ); - // if let Some(tmp) = new_lhs.tmp_of(self).into_expr_err(loc)? { - // if tmp.op.inverse().is_some() && !matches!(op, RangeOp::Eq | RangeOp::Neq) { - // // self.range_recursion(tmp, recursion_ops, new_rhs, ctx, loc, &mut any_unsat)?; - // } else { - // match tmp.op { - // RangeOp::Not => {} - // _ => { - // self.uninvertable_range_recursion(arena, tmp, new_lhs, new_rhs, loc, ctx); - // } - // } - // } - // } Ok(tmp_cvar) } @@ -1057,7 +419,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } RangeOp::Neq => { // check if contains - let mut elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); + let mut elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)) + .minimize(self, arena) + .unwrap(); // potentially add the const var as a range exclusion if let Some(Ordering::Equal) = nonconst_range @@ -1132,7 +496,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { nonconst_var.display_name(self).unwrap(), nonconst_range.max, nonconst_var.display_name(self).unwrap(), - op.to_string(), + op, const_var.display_name(self).unwrap(), const_var.evaled_range_min(self, arena).unwrap().unwrap() ))); @@ -1387,8 +751,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { lhs_elem, )); - let new_new_lhs = self.advance_var_in_curr_ctx(new_lhs, loc)?; - let new_new_rhs = self.advance_var_in_curr_ctx(new_rhs, loc)?; + let new_new_lhs = self.advance_var_in_ctx(new_lhs, loc, ctx)?; + let new_new_rhs = self.advance_var_in_ctx(new_rhs, loc, ctx)?; new_new_lhs .set_range_min(self, arena, new_min.clone()) @@ -1421,8 +785,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let one = Concrete::one(&min_conc.val).expect("Cannot decrement range elem by one"); - let new_new_lhs = self.advance_var_in_curr_ctx(new_lhs, loc)?; - let new_new_rhs = self.advance_var_in_curr_ctx(new_rhs, loc)?; + let new_new_lhs = self.advance_var_in_ctx(new_lhs, loc, ctx)?; + let new_new_rhs = self.advance_var_in_ctx(new_rhs, loc, ctx)?; new_new_lhs .latest_version_or_inherited_in_ctx(ctx, self) @@ -1517,11 +881,12 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } tmp.expect_single().into_expr_err(loc)? }); - let new_underlying_lhs = self.advance_var_in_curr_ctx( + let new_underlying_lhs = self.advance_var_in_ctx( tmp_construction .lhs .latest_version_or_inherited_in_ctx(ctx, self), loc, + ctx, )?; if let Some(lhs_range) = new_underlying_lhs .underlying(self) @@ -1574,13 +939,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let (needs_inverse, adjusted_gt_rhs) = match tmp_construction.op { RangeOp::Sub(..) => { let concrete = ConcreteNode( - self.add_node(Node::Concrete(Concrete::Int(256, I256::from(-1i32)))) - .index(), + self.add_node(Concrete::Int(256, I256::from(-1i32))).index(), ); let lhs_cvar = ContextVar::new_from_concrete(loc, ctx, concrete, self) .into_expr_err(loc)?; - let tmp_lhs = - ContextVarNode::from(self.add_node(Node::ContextVar(lhs_cvar))); + let tmp_lhs = ContextVarNode::from(self.add_node(lhs_cvar)); // tmp_rhs = rhs_cvar * -1 let tmp_rhs = self.op( @@ -1746,9 +1109,10 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { e => panic!("here {e:?}"), }; - let new_underlying_rhs = self.advance_var_in_curr_ctx( + let new_underlying_rhs = self.advance_var_in_ctx( rhs.latest_version_or_inherited_in_ctx(ctx, self), loc, + ctx, )?; if let Some(lhs_range) = new_underlying_rhs .underlying(self) diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs index d27a5e1f..d7ae27af 100644 --- a/crates/solc-expressions/src/variable.rs +++ b/crates/solc-expressions/src/variable.rs @@ -7,7 +7,7 @@ use graph::{ }; use shared::{ExprErr, GraphError, IntoExprErr, NodeIdx, RangeArena}; -use solang_parser::pt::{Expression, Identifier, Loc, StorageLocation, VariableDeclaration}; +use solang_parser::pt::{Expression, Identifier, Loc, StorageLocation}; impl Variable for T where T: AnalyzerBackend + Sized {} /// Deals with variable retrieval, parsing, and versioning @@ -76,10 +76,12 @@ pub trait Variable: AnalyzerBackend + Size } else if (self.env_variable(arena, ident, target_ctx)?).is_some() { Ok(()) } else if let Some(idxs) = self.user_types().get(&ident.name).cloned() { + tracing::trace!("Getting variable via user_types"); let idx = if idxs.len() == 1 { idxs[0] } else { // disambiguate by scope + tracing::trace!("disambiguating by scope"); let in_scope = if let Some(contract) = ctx .maybe_associated_contract(self) .into_expr_err(ident.loc)? @@ -95,7 +97,7 @@ pub trait Variable: AnalyzerBackend + Size } else { vec![] }; - if let Some(idx) = self.disambiguate(ctx, ident.loc, idxs, in_scope, location) { + if let Some(idx) = self.disambiguate(ctx, idxs, in_scope, location) { idx } else { return Err(ExprErr::ParseError( @@ -141,7 +143,7 @@ pub trait Variable: AnalyzerBackend + Size } }; - let new_cvarnode = self.add_node(Node::ContextVar(var)); + let new_cvarnode = self.add_node(var); ctx.add_var(new_cvarnode.into(), self) .into_expr_err(ident.loc)?; @@ -159,6 +161,15 @@ pub trait Variable: AnalyzerBackend + Size ); } + if let Some(strukt) = ContextVarNode::from(new_cvarnode) + .maybe_struct(self) + .into_expr_err(ident.loc)? + { + strukt + .add_fields_to_cvar(self, ident.loc, ContextVarNode::from(new_cvarnode)) + .into_expr_err(ident.loc)?; + } + target_ctx .push_expr(ExprRet::Single(new_cvarnode), self) .into_expr_err(ident.loc)?; @@ -192,22 +203,20 @@ pub trait Variable: AnalyzerBackend + Size fn disambiguate( &mut self, ctx: ContextNode, - loc: Loc, mut idxs: Vec, inscope_storage: Vec, location: Option, ) -> Option { // disambiguate based on left hand side if it exists - if let Some(maybe_lhs) = ctx.pop_expr_latest(loc, self).ok()? { + if let Some(maybe_lhs) = ctx.underlying(self).ok()?.expr_ret_stack.first() { + tracing::trace!("Disambiguate based on lhs: {}", maybe_lhs.debug_str(self)); if let ExprRet::Single(lhs_idx) = maybe_lhs { - if let Some(var_ty) = VarType::try_from_idx(self, lhs_idx) { + if let Some(var_ty) = VarType::try_from_idx(self, *lhs_idx) { if idxs.contains(&var_ty.ty_idx()) { - ctx.push_expr(maybe_lhs, self).ok()?; return Some(var_ty.ty_idx()); } } } - ctx.push_expr(maybe_lhs, self).ok()?; } // disambiguate based on storage location @@ -314,7 +323,10 @@ pub trait Variable: AnalyzerBackend + Size &mut self, arena: &mut RangeArena>, ctx: ContextNode, - var_decl: &VariableDeclaration, + (name, storage): ( + Option, + Option, + ), loc: Loc, lhs_paths: &ExprRet, rhs_paths: Option<&ExprRet>, @@ -333,20 +345,20 @@ pub trait Variable: AnalyzerBackend + Size self.match_var_def( arena, ctx, - var_decl, + (name, storage), loc, lhs_paths, Some(&ExprRet::Single(rhs_cvar.into())), ) } (ExprRet::Single(ty), Some(ExprRet::Single(rhs))) => { - let name = var_decl.name.clone().expect("Variable wasn't named"); + let name = name.clone().expect("Variable wasn't named"); let ty = VarType::try_from_idx(self, *ty).expect("Not a known type"); let var = ContextVar { loc: Some(loc), name: name.to_string(), display_name: name.to_string(), - storage: var_decl.storage.as_ref().map(|s| s.clone().into()), + storage, is_tmp: false, is_symbolic: true, tmp_of: None, @@ -354,7 +366,7 @@ pub trait Variable: AnalyzerBackend + Size is_return: false, ty, }; - let lhs = ContextVarNode::from(self.add_node(Node::ContextVar(var))); + let lhs = ContextVarNode::from(self.add_node(var)); ctx.add_var(lhs, self).into_expr_err(loc)?; self.add_edge(lhs, ctx, Edge::Context(ContextEdge::Variable)); @@ -374,14 +386,14 @@ pub trait Variable: AnalyzerBackend + Size Ok(false) } (ExprRet::Single(ty), None) => { - let name = var_decl.name.clone().expect("Variable wasn't named"); + let name = name.clone().expect("Variable wasn't named"); let ty = VarType::try_from_idx(self, *ty).expect("Not a known type"); let maybe_struct = ty.maybe_struct(); let var = ContextVar { loc: Some(loc), name: name.to_string(), display_name: name.to_string(), - storage: var_decl.storage.as_ref().map(|s| s.clone().into()), + storage: storage.as_ref().copied(), is_tmp: false, is_symbolic: true, tmp_of: None, @@ -389,7 +401,7 @@ pub trait Variable: AnalyzerBackend + Size is_return: false, ty, }; - let lhs = ContextVarNode::from(self.add_node(Node::ContextVar(var))); + let lhs = ContextVarNode::from(self.add_node(var)); ctx.add_var(lhs, self).into_expr_err(loc)?; self.add_edge(lhs, ctx, Edge::Context(ContextEdge::Variable)); if let Some(strukt) = maybe_struct { @@ -401,19 +413,25 @@ pub trait Variable: AnalyzerBackend + Size } (l @ ExprRet::Single(_lhs), Some(ExprRet::Multi(rhs_sides))) => Ok(rhs_sides .iter() - .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, l, Some(expr_ret))) + .map(|expr_ret| { + self.match_var_def(arena, ctx, (name.clone(), storage), loc, l, Some(expr_ret)) + }) .collect::, ExprErr>>()? .iter() .all(|e| *e)), (ExprRet::Multi(lhs_sides), r @ Some(ExprRet::Single(_))) => Ok(lhs_sides .iter() - .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, expr_ret, r)) + .map(|expr_ret| { + self.match_var_def(arena, ctx, (name.clone(), storage), loc, expr_ret, r) + }) .collect::, ExprErr>>()? .iter() .all(|e| *e)), (ExprRet::Multi(lhs_sides), None) => Ok(lhs_sides .iter() - .map(|expr_ret| self.match_var_def(arena, ctx, var_decl, loc, expr_ret, None)) + .map(|expr_ret| { + self.match_var_def(arena, ctx, (name.clone(), storage), loc, expr_ret, None) + }) .collect::, ExprErr>>()? .iter() .all(|e| *e)), @@ -427,7 +445,7 @@ pub trait Variable: AnalyzerBackend + Size self.match_var_def( arena, ctx, - var_decl, + (name.clone(), storage), loc, lhs_expr_ret, Some(rhs_expr_ret), @@ -443,7 +461,7 @@ pub trait Variable: AnalyzerBackend + Size self.match_var_def( arena, ctx, - var_decl, + (name.clone(), storage), loc, lhs_paths, Some(rhs_expr_ret), @@ -454,7 +472,7 @@ pub trait Variable: AnalyzerBackend + Size .all(|e| *e)) } } - (_e, _f) => Err(ExprErr::Todo( + (_, _) => Err(ExprErr::Todo( loc, "Unhandled ExprRet combination in `match_var_def`".to_string(), )), @@ -544,7 +562,7 @@ pub trait Variable: AnalyzerBackend + Size } new_cvar.loc = Some(loc); - new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); + new_cvarnode = self.add_node(new_cvar); if old_ctx != ctx { tracing::trace!( "moving var {} into new context: from {} to {}", @@ -569,8 +587,7 @@ pub trait Variable: AnalyzerBackend + Size .collect::>(); while let Some((field, parent)) = struct_stack.pop() { let underlying = field.underlying(self).into_expr_err(loc)?; - let new_field_in_inheritor = - self.add_node(Node::ContextVar(underlying.clone())); + let new_field_in_inheritor = self.add_node(underlying.clone()); self.add_edge( new_field_in_inheritor, parent, @@ -585,12 +602,33 @@ pub trait Variable: AnalyzerBackend + Size .collect::>(), ); } + + if let Some(len_var) = cvar_node.array_to_len_var(self) { + let new_len_var = len_var + .latest_version(self) + .underlying(self) + .into_expr_err(loc)? + .clone(); + let new_len_node = self.add_node(new_len_var); + ctx.add_var(new_len_node.into(), self).into_expr_err(loc)?; + self.add_edge(new_len_node, ctx, Edge::Context(ContextEdge::Variable)); + self.add_edge( + new_len_node, + new_cvarnode, + Edge::Context(ContextEdge::AttrAccess("length")), + ); + self.add_edge( + new_len_node, + len_var, + Edge::Context(ContextEdge::InheritedVariable), + ); + } } else { self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } } else { new_cvar.loc = Some(loc); - new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); + new_cvarnode = self.add_node(new_cvar); self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } } @@ -641,7 +679,7 @@ pub trait Variable: AnalyzerBackend + Size new_cvar.loc = Some(loc); // new_cvar.display_name = format!("{}_{}", new_cvar.name, cvar_node.prev_versions(self)); - new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); + new_cvarnode = self.add_node(new_cvar); if old_ctx != ctx { tracing::trace!( "moving var {} into new context: from {} to {}", @@ -666,8 +704,7 @@ pub trait Variable: AnalyzerBackend + Size .collect::>(); while let Some((field, parent)) = struct_stack.pop() { let underlying = field.underlying(self).into_expr_err(loc)?; - let new_field_in_inheritor = - self.add_node(Node::ContextVar(underlying.clone())); + let new_field_in_inheritor = self.add_node(underlying.clone()); self.add_edge( new_field_in_inheritor, parent, @@ -682,12 +719,32 @@ pub trait Variable: AnalyzerBackend + Size .collect::>(), ); } + if let Some(len_var) = cvar_node.array_to_len_var(self) { + let new_len_var = len_var + .latest_version(self) + .underlying(self) + .into_expr_err(loc)? + .clone(); + let new_len_node = self.add_node(new_len_var); + ctx.add_var(new_len_node.into(), self).into_expr_err(loc)?; + self.add_edge(new_len_node, ctx, Edge::Context(ContextEdge::Variable)); + self.add_edge( + new_len_node, + new_cvarnode, + Edge::Context(ContextEdge::AttrAccess("length")), + ); + self.add_edge( + new_len_node, + len_var, + Edge::Context(ContextEdge::InheritedVariable), + ); + } } else { self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } } else { new_cvar.loc = Some(loc); - new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); + new_cvarnode = self.add_node(new_cvar); self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); } } @@ -697,35 +754,6 @@ pub trait Variable: AnalyzerBackend + Size Ok(ContextVarNode::from(new_cvarnode)) } - /// Creates a new version of a variable in it's current context - fn advance_var_in_curr_ctx( - &mut self, - cvar_node: ContextVarNode, - loc: Loc, - ) -> Result { - tracing::trace!( - "advancing variable: {}", - cvar_node.display_name(self).into_expr_err(loc)? - ); - if let Some(cvar) = cvar_node.next_version(self) { - panic!( - "Not latest version of: {}", - cvar.display_name(self).unwrap() - ); - } - let mut new_cvar = cvar_node - .latest_version(self) - .underlying(self) - .into_expr_err(loc)? - .clone(); - new_cvar.loc = Some(loc); - - let new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); - self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); - - Ok(ContextVarNode::from(new_cvarnode)) - } - /// Clones a variable and adds it to the graph fn advance_var_underlying(&mut self, cvar_node: ContextVarNode, loc: Loc) -> &mut ContextVar { assert_eq!(None, cvar_node.next_version(self)); @@ -735,7 +763,7 @@ pub trait Variable: AnalyzerBackend + Size .unwrap() .clone(); new_cvar.loc = Some(loc); - let new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); + let new_cvarnode = self.add_node(new_cvar); self.add_edge(new_cvarnode, cvar_node.0, Edge::Context(ContextEdge::Prev)); ContextVarNode::from(new_cvarnode) .underlying_mut(self) diff --git a/crates/solc-expressions/src/yul/mod.rs b/crates/solc-expressions/src/yul/mod.rs index d7a4fd29..ba5e42b4 100644 --- a/crates/solc-expressions/src/yul/mod.rs +++ b/crates/solc-expressions/src/yul/mod.rs @@ -1,8 +1,6 @@ //! Traits and blanket implementations for parsing with yul statements and expressions mod yul_builder; -mod yul_cond_op; mod yul_funcs; pub use yul_builder::*; -pub use yul_cond_op::*; pub use yul_funcs::*; diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs index 6cc709c1..45540ffc 100644 --- a/crates/solc-expressions/src/yul/yul_builder.rs +++ b/crates/solc-expressions/src/yul/yul_builder.rs @@ -1,376 +1,16 @@ //! Trait and blanket implementation for parsing yul-based statements and expressions -use crate::{yul::YulCondOp, yul::YulFuncCaller, ContextBuilder, ExpressionParser}; - use graph::{ - elem::Elem, - nodes::{ - BuiltInNode, Builtin, Concrete, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, - }, - AnalyzerBackend, ContextEdge, Edge, Node, SolcRange, VarType, + nodes::{BuiltInNode, Builtin, ContextNode, ContextVar, ContextVarNode, ExprRet}, + AnalyzerBackend, ContextEdge, Edge, SolcRange, VarType, }; -use shared::{ExprErr, IntoExprErr, RangeArena}; +use shared::{ExprErr, IntoExprErr}; -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Loc, YulExpression, YulFor, YulStatement, YulSwitch}, -}; +use solang_parser::pt::{Expression, Loc}; -impl YulBuilder for T where - T: AnalyzerBackend + Sized + ExpressionParser -{ -} +impl YulBuilder for T where T: AnalyzerBackend + Sized {} /// Trait that processes Yul statements and expressions -pub trait YulBuilder: - AnalyzerBackend + Sized + ExpressionParser -{ - #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] - /// Parse a yul statement - fn parse_ctx_yul_statement( - &mut self, - arena: &mut RangeArena>, - stmt: &YulStatement, - ctx: ContextNode, - ) where - Self: Sized, - { - if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) { - return; - } - if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) { - if live_edges.is_empty() { - self.parse_ctx_yul_stmt_inner(arena, stmt, ctx) - } else { - live_edges.iter().for_each(|fork_ctx| { - self.parse_ctx_yul_stmt_inner(arena, stmt, *fork_ctx); - }); - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - /// After doing some setup in `parse_ctx_yul_statement`, actually parse a yul statement - fn parse_ctx_yul_stmt_inner( - &mut self, - arena: &mut RangeArena>, - stmt: &YulStatement, - ctx: ContextNode, - ) where - Self: Sized, - { - use YulStatement::*; - // println!("ctx: {}, yul stmt: {:?}", ctx.path(self), stmt); - - let res = ctx - .pop_expr_latest(stmt.loc(), self) - .into_expr_err(stmt.loc()); - let _ = self.add_if_err(res); - - if ctx.is_killed(self).unwrap() { - return; - } - let ret = self.apply_to_edges(ctx, stmt.loc(), arena, &|analyzer, arena, ctx, _loc| { - match stmt { - Assign(loc, yul_exprs, yul_expr) => { - match yul_exprs - .iter() - .try_for_each(|expr| analyzer.parse_ctx_yul_expr(arena, expr, ctx)) - { - Ok(()) => analyzer.apply_to_edges( - ctx, - *loc, - arena, - &|analyzer, arena, ctx, loc| { - let Some(lhs_side) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "No left hand side assignments in yul block".to_string(), - )); - }; - if matches!(lhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_yul_expr(arena, yul_expr, ctx)?; - analyzer.apply_to_edges( - ctx, - loc, - arena, - &|analyzer, arena, ctx, loc| { - let Some(rhs_side) = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No right hand side assignments in yul block" - .to_string(), - )); - }; - - if matches!(rhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_assign_sides( - arena, ctx, loc, &lhs_side, &rhs_side, - ) - }, - ) - }, - ), - Err(e) => Err(e), - } - } - VariableDeclaration(loc, yul_idents, maybe_yul_expr) => { - let nodes = yul_idents - .iter() - .map(|ident| { - let b_ty = analyzer.builtin_or_add(Builtin::Uint(256)); - let var = ContextVar { - loc: Some(ident.loc), - name: ident.id.name.clone(), - display_name: ident.id.name.clone(), - storage: None, - is_tmp: false, - tmp_of: None, - dep_on: None, - is_symbolic: true, - is_return: false, - ty: VarType::try_from_idx(analyzer, b_ty).unwrap(), - }; - let cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - ctx.add_var(cvar, analyzer).unwrap(); - analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - analyzer.advance_var_in_ctx(cvar, *loc, ctx).unwrap() - }) - .collect::>(); - - if let Some(yul_expr) = maybe_yul_expr { - analyzer.parse_ctx_yul_expr(arena, yul_expr, ctx)?; - analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No right hand side assignments in yul block".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_assign_yul(ctx, loc, &nodes, ret) - }) - } else { - Ok(()) - } - } - If(loc, yul_expr, yul_block) => { - analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let ret = analyzer.yul_cond_op_stmt(arena, loc, yul_expr, yul_block, ctx); - let _ = analyzer.add_if_err(ret); - Ok(()) - }) - } - For(YulFor { - loc, - init_block: _, - condition: _, - post_block: _, - execution_block: _, - }) => { - let sctx = - Context::new_subctx(ctx, None, *loc, None, None, false, analyzer, None) - .into_expr_err(*loc)?; - let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx))); - ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?; - analyzer.apply_to_edges(subctx, *loc, arena, &|analyzer, arena, subctx, loc| { - let vars = subctx.local_vars(analyzer).clone(); - vars.iter().for_each(|(name, var)| { - // widen to max range - if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = var - .underlying(analyzer) - .unwrap() - .ty - .default_range(analyzer) - .unwrap() - { - let new_inheritor_var = analyzer - .advance_var_in_ctx(inheritor_var, loc, ctx) - .unwrap(); - let res = new_inheritor_var - .set_range_min(analyzer, arena, r.min) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_inheritor_var - .set_range_max(analyzer, arena, r.max) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } - } - }); - Ok(()) - }) - } - Switch(YulSwitch { - loc, - condition, - cases, - default, - }) => analyzer.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - analyzer.yul_switch_stmt( - arena, - loc, - condition.clone(), - cases.to_vec(), - default.clone(), - ctx, - ) - }), - Leave(loc) => Err(ExprErr::Todo( - *loc, - "Yul `leave` statements are not currently supported".to_string(), - )), - Break(loc) => Err(ExprErr::Todo( - *loc, - "Yul `break` statements are not currently supported".to_string(), - )), - Continue(loc) => Err(ExprErr::Todo( - *loc, - "Yul `continue` statements are not currently supported".to_string(), - )), - Block(yul_block) => { - yul_block - .statements - .iter() - .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(arena, stmt, ctx)); - Ok(()) - } - FunctionDefinition(yul_func_def) => Err(ExprErr::Todo( - yul_func_def.loc(), - "Yul `function` defintions are not currently supported".to_string(), - )), - FunctionCall(yul_func_call) => analyzer.yul_func_call(arena, yul_func_call, ctx), - Error(loc) => Err(ExprErr::ParseError( - *loc, - "Could not parse this yul statement".to_string(), - )), - } - }); - let _ = self.add_if_err(ret); - } - - #[tracing::instrument(level = "trace", skip_all)] - /// Parse a yul expression - fn parse_ctx_yul_expr( - &mut self, - arena: &mut RangeArena>, - expr: &YulExpression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - tracing::trace!("Parsing yul expression: {expr:?}"); - - let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; - if edges.is_empty() { - self.parse_ctx_yul_expr_inner(arena, expr, ctx) - } else { - edges - .iter() - .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(arena, expr, *fork_ctx))?; - Ok(()) - } - } - - /// After performing some setup in `parse_ctx_yul_expr`, actually parse the yul expression - fn parse_ctx_yul_expr_inner( - &mut self, - arena: &mut RangeArena>, - expr: &YulExpression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - use YulExpression::*; - match expr { - BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b), - NumberLiteral(loc, int, expr, unit) => { - self.number_literal(ctx, *loc, int, expr, false, unit) - } - HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), - HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]), - StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string), - Variable(ident) => { - self.variable(arena, ident, ctx, None, None)?; - self.apply_to_edges(ctx, ident.loc, arena, &|analyzer, _arena, edge_ctx, loc| { - if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { - if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?) - .is_memory(analyzer) - .into_expr_err(loc)? - { - // its a memory based variable, push a uint instead - let b = Builtin::Uint(256); - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - edge_ctx - .push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - } else { - edge_ctx.push_expr(ret, analyzer).into_expr_err(loc) - } - } else { - Err(ExprErr::Unresolved( - ident.loc, - format!("Could not find variable with name: {}", ident.name), - )) - } - }) - } - FunctionCall(yul_func_call) => self.yul_func_call(arena, yul_func_call, ctx), - SuffixAccess(loc, yul_member_expr, ident) => { - self.parse_inputs(arena, ctx, *loc, &[*yul_member_expr.clone()])?; - - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Ok(Some(lhs)) = ctx.pop_expr_latest(loc, analyzer) else { - return Err(ExprErr::NoLhs( - loc, - "`.slot` had no left hand side".to_string(), - )); - }; - match &*ident.name { - "slot" => { - let slot_var = analyzer.slot( - ctx, - loc, - lhs.expect_single().into_expr_err(loc)?.into(), - ); - ctx.push_expr(ExprRet::Single(slot_var.into()), analyzer) - .into_expr_err(loc)?; - Ok(()) - } - _ => Err(ExprErr::Todo( - expr.loc(), - format!("Yul member access `{}` not yet supported", ident.name), - )), - } - }) - } - } - } - +pub trait YulBuilder: AnalyzerBackend + Sized { /// Match [`ExprRet`] from the sides of an `YulAssign` to perform the assignment fn match_assign_yul( &mut self, @@ -457,7 +97,7 @@ pub trait YulBuilder: SolcRange::try_from_builtin(&Builtin::Uint(256)), ), }; - let slot_node = self.add_node(Node::ContextVar(slot_var)); + let slot_node = self.add_node(slot_var); self.add_edge(slot_node, lhs, Edge::Context(ContextEdge::SlotAccess)); self.add_edge(slot_node, ctx, Edge::Context(ContextEdge::Variable)); diff --git a/crates/solc-expressions/src/yul/yul_cond_op.rs b/crates/solc-expressions/src/yul/yul_cond_op.rs deleted file mode 100644 index aa8b58b7..00000000 --- a/crates/solc-expressions/src/yul/yul_cond_op.rs +++ /dev/null @@ -1,414 +0,0 @@ -use crate::{require::Require, yul::YulBuilder, ContextBuilder}; - -use graph::{ - elem::*, - nodes::{Concrete, ConcreteNode, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, -}; -use shared::{ExprErr, IntoExprErr, NodeIdx, RangeArena}; - -use ethers_core::types::U256; -use solang_parser::pt::{ - CodeLocation, Expression, Identifier, Loc, YulBlock, YulExpression, YulFunctionCall, - YulStatement, YulSwitchOptions, -}; - -impl YulCondOp for T where - T: AnalyzerBackend + Require + Sized -{ -} - -/// Trait for dealing with conditional operations in yul -pub trait YulCondOp: - AnalyzerBackend + Require + Sized -{ - #[tracing::instrument(level = "trace", skip_all)] - /// Handle a yul conditional operation statement - fn yul_cond_op_stmt( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - if_expr: &YulExpression, - true_stmt: &YulBlock, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let tctx = - Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) - .into_expr_err(loc)?; - let true_subctx = ContextNode::from(analyzer.add_node(Node::Context(tctx))); - let fctx = - Context::new_subctx(ctx, None, loc, Some("false"), None, false, analyzer, None) - .into_expr_err(loc)?; - let false_subctx = ContextNode::from(analyzer.add_node(Node::Context(fctx))); - ctx.set_child_fork(true_subctx, false_subctx, analyzer) - .into_expr_err(loc)?; - true_subctx - .set_continuation_ctx(analyzer, ctx, "yul_fork_true") - .into_expr_err(loc)?; - false_subctx - .set_continuation_ctx(analyzer, ctx, "yul_fork_false") - .into_expr_err(loc)?; - let ctx_fork = analyzer.add_node(Node::ContextFork); - analyzer.add_edge(ctx_fork, ctx, Edge::Context(ContextEdge::ContextFork)); - analyzer.add_edge( - NodeIdx::from(true_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - analyzer.add_edge( - NodeIdx::from(false_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - - analyzer.parse_ctx_yul_expr(arena, if_expr, true_subctx)?; - analyzer.apply_to_edges(true_subctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "True conditional had no lhs".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_yul_true(arena, ctx, if_expr.loc(), &ret) - })?; - - analyzer.parse_ctx_yul_statement( - arena, - &YulStatement::Block(true_stmt.clone()), - true_subctx, - ); - // let false_expr = YulExpression::FunctionCall(Box::new(YulFunctionCall { - // loc, - // id: Identifier { - // loc, - // name: "iszero".to_string(), - // }, - // arguments: vec![if_expr.clone()], - // })); - analyzer.parse_ctx_yul_expr(arena, if_expr, false_subctx)?; - analyzer.apply_to_edges(false_subctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "False conditional had no lhs".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_yul_false(arena, ctx, if_expr.loc(), &ret) - }) - }) - } - - #[tracing::instrument(level = "trace", skip_all)] - /// Handle a yul if-else - fn yul_if_else( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - if_else_chain: &IfElseChain, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let tctx = - Context::new_subctx(ctx, None, loc, Some("true"), None, false, analyzer, None) - .into_expr_err(loc)?; - let true_subctx = ContextNode::from(analyzer.add_node(Node::Context(tctx))); - let fctx = - Context::new_subctx(ctx, None, loc, Some("false"), None, false, analyzer, None) - .into_expr_err(loc)?; - let false_subctx = ContextNode::from(analyzer.add_node(Node::Context(fctx))); - ctx.set_child_fork(true_subctx, false_subctx, analyzer) - .into_expr_err(loc)?; - true_subctx - .set_continuation_ctx(analyzer, ctx, "yul_fork_true") - .into_expr_err(loc)?; - false_subctx - .set_continuation_ctx(analyzer, ctx, "yul_fork_false") - .into_expr_err(loc)?; - let ctx_fork = analyzer.add_node(Node::ContextFork); - analyzer.add_edge(ctx_fork, ctx, Edge::Context(ContextEdge::ContextFork)); - analyzer.add_edge( - NodeIdx::from(true_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - analyzer.add_edge( - NodeIdx::from(false_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - - let if_expr_loc = if_else_chain.if_expr.loc(); - analyzer.apply_to_edges( - true_subctx, - if_expr_loc, - arena, - &|analyzer, arena, ctx, loc| { - analyzer.parse_ctx_yul_expr(arena, &if_else_chain.if_expr, true_subctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - let Some(true_vars) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul switch statement was missing a case discriminator".to_string(), - )); - }; - - if matches!(true_vars, ExprRet::CtxKilled(_)) { - ctx.push_expr(true_vars, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_yul_true(arena, ctx, loc, &true_vars)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_yul_statement(arena, &if_else_chain.true_stmt, ctx); - Ok(()) - }) - }) - }, - )?; - - if let Some(next) = &if_else_chain.next { - match next { - ElseOrDefault::Default(default) => analyzer.apply_to_edges( - false_subctx, - loc, - arena, - &|analyzer, arena, ctx, _loc| { - analyzer.parse_ctx_yul_statement(arena, default, ctx); - Ok(()) - }, - ), - ElseOrDefault::Else(iec) => analyzer.apply_to_edges( - false_subctx, - loc, - arena, - &|analyzer, arena, ctx, loc| analyzer.yul_if_else(arena, loc, iec, ctx), - ), - } - } else { - Ok(()) - } - }) - } - - /// Helper for the `true` evaluation of a yul conditional - fn match_yul_true( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - true_cvars: &ExprRet, - ) -> Result<(), ExprErr> { - match true_cvars { - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, *kind).into_expr_err(loc)?, - ExprRet::Single(_true_cvar) | ExprRet::SingleLiteral(_true_cvar) => { - let cnode = ConcreteNode::from( - self.add_node(Node::Concrete(Concrete::Uint(1, U256::from(0)))), - ); - let tmp_true = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, self) - .into_expr_err(loc)?, - ); - let rhs_paths = - ExprRet::Single(ContextVarNode::from(self.add_node(tmp_true)).into()); - - self.handle_require_inner( - arena, - ctx, - loc, - true_cvars, - &rhs_paths, - RangeOp::Gt, - RangeOp::Lt, - (RangeOp::Lt, RangeOp::Gt), - )?; - } - ExprRet::Multi(ref true_paths) => { - // TODO: validate this - // we only take one because we just need the context out of the return - true_paths - .iter() - .take(1) - .try_for_each(|expr_ret| self.match_yul_true(arena, ctx, loc, expr_ret))?; - } - ExprRet::Null => {} - } - Ok(()) - } - - /// Helper for the `false` evaluation of a yul conditional - fn match_yul_false( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - false_cvars: &ExprRet, - ) -> Result<(), ExprErr> { - match false_cvars { - ExprRet::CtxKilled(kind) => ctx.kill(self, loc, *kind).into_expr_err(loc)?, - ExprRet::Single(_false_cvar) | ExprRet::SingleLiteral(_false_cvar) => { - let cnode = ConcreteNode::from( - self.add_node(Node::Concrete(Concrete::Uint(1, U256::from(0)))), - ); - let tmp_true = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, self) - .into_expr_err(loc)?, - ); - let rhs_paths = - ExprRet::Single(ContextVarNode::from(self.add_node(tmp_true)).into()); - - self.handle_require_inner( - arena, - ctx, - loc, - false_cvars, - &rhs_paths, - RangeOp::Eq, - RangeOp::Neq, - (RangeOp::Neq, RangeOp::Eq), - )?; - } - ExprRet::Multi(ref false_paths) => { - // TODO: validate this - // we only take one because we just need the context out of the return - false_paths - .iter() - .take(1) - .try_for_each(|expr_ret| self.match_yul_false(arena, ctx, loc, expr_ret))?; - } - ExprRet::Null => {} - } - - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all)] - /// Handle a yul swithc statement by converting it into an if-else chain - fn yul_switch_stmt( - &mut self, - arena: &mut RangeArena>, - loc: Loc, - condition: YulExpression, - cases: Vec, - default: Option, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - let iec = IfElseChain::from(loc, (condition, cases, default))?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, _loc| { - analyzer.yul_if_else(arena, loc, &iec, ctx) - }) - } -} - -#[derive(Clone, Debug)] -/// A yul-based if-else chain, which represents a switch statement -pub struct IfElseChain { - pub if_expr: YulExpression, - pub true_stmt: YulStatement, - pub next: Option, -} - -#[derive(Clone, Debug)] -/// Wrapper over a switch statement that denotes either another else statement or the default case -pub enum ElseOrDefault { - Else(Box), - Default(YulStatement), -} - -impl From for ElseOrDefault { - fn from(iec: IfElseChain) -> Self { - Self::Else(Box::new(iec)) - } -} - -impl IfElseChain { - pub fn from_child(ed: ElseOrDefault) -> Option { - match ed { - ElseOrDefault::Else(iec) => Some(*iec), - _ => None, - } - } -} - -impl From for ElseOrDefault { - fn from(default: YulSwitchOptions) -> Self { - match default { - YulSwitchOptions::Default(_loc, block) => { - ElseOrDefault::Default(YulStatement::Block(block)) - } - _ => unreachable!("case as default"), - } - } -} - -pub type SwitchInfo = ( - YulExpression, - Vec, - Option, -); - -impl IfElseChain { - pub fn from(loc: Loc, (condition, cases, default): SwitchInfo) -> Result { - let mut child: Option = default.map(|default| default.into()); - - cases.into_iter().rev().for_each(|case| { - let mut chain_part: IfElseChain = From::from((condition.clone(), case)); - if let Some(c) = child.take() { - chain_part.next = c.into(); - } - child = Some(chain_part.into()); - }); - let Some(child) = child else { - return Err(ExprErr::NoRhs( - loc, - "No cases or default found for switch statement".to_string(), - )); - }; - - let Some(iec) = IfElseChain::from_child(child) else { - return Err(ExprErr::NoRhs( - loc, - "No cases or default found for switch statement".to_string(), - )); - }; - Ok(iec) - } -} - -impl From<(YulExpression, YulSwitchOptions)> for IfElseChain { - fn from((condition, case): (YulExpression, YulSwitchOptions)) -> Self { - match case { - YulSwitchOptions::Case(loc, expr, stmt) => { - let if_expr = YulExpression::FunctionCall(Box::new(YulFunctionCall { - loc, - id: Identifier { - loc, - name: "eq".to_string(), - }, - arguments: vec![condition, expr], - })); - IfElseChain { - if_expr, - true_stmt: YulStatement::Block(stmt), - next: None, - } - } - YulSwitchOptions::Default(_loc, _block) => { - unreachable!("We shouldn't have a `default` case in cases - only in the `default` input parameter") - } - } - } -} diff --git a/crates/solc-expressions/src/yul/yul_funcs.rs b/crates/solc-expressions/src/yul/yul_funcs.rs index 82320d1b..a4124f5d 100644 --- a/crates/solc-expressions/src/yul/yul_funcs.rs +++ b/crates/solc-expressions/src/yul/yul_funcs.rs @@ -1,4 +1,4 @@ -use crate::{assign::Assign, variable::Variable, yul::YulBuilder, BinOp, Cmp, ContextBuilder, Env}; +use crate::{assign::Assign, variable::Variable, BinOp, Cmp, Env}; use graph::{ elem::*, @@ -8,13 +8,10 @@ use graph::{ }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, SolcRange, VarType, }; -use shared::{ExprErr, IntoExprErr, RangeArena, StorageLocation}; +use shared::{ExprErr, FlatExpr, IntoExprErr, RangeArena, StorageLocation}; use ethers_core::types::U256; -use solang_parser::pt::{Expression, Loc, YulExpression, YulFunctionCall}; - -use std::cell::RefCell; -use std::rc::Rc; +use solang_parser::pt::{Expression, Loc}; impl YulFuncCaller for T where T: AnalyzerBackend + Sized + GraphBackend @@ -26,111 +23,74 @@ pub trait YulFuncCaller: fn yul_func_call( &mut self, arena: &mut RangeArena>, - func_call: &YulFunctionCall, ctx: ContextNode, + stack: &mut Vec, + name: &str, + inputs: ExprRet, + assembly_block_idx: usize, + loc: Loc, ) -> Result<(), ExprErr> { - let YulFunctionCall { loc, id, arguments } = func_call; - - match &*id.name { + match name { "caller" => { - let t = self.msg_access(*loc, ctx, "sender")?; - ctx.push_expr(t, self).into_expr_err(*loc) + let t = self.msg_access(ctx, "sender", loc)?; + ctx.push_expr(t, self).into_expr_err(loc) } "origin" => { - let t = self.msg_access(*loc, ctx, "origin")?; - ctx.push_expr(t, self).into_expr_err(*loc) + let t = self.msg_access(ctx, "origin", loc)?; + ctx.push_expr(t, self).into_expr_err(loc) } "gasprice" => { - let t = self.msg_access(*loc, ctx, "gasprice")?; - ctx.push_expr(t, self).into_expr_err(*loc) + let t = self.msg_access(ctx, "gasprice", loc)?; + ctx.push_expr(t, self).into_expr_err(loc) } "callvalue" => { - let t = self.msg_access(*loc, ctx, "value")?; - ctx.push_expr(t, self).into_expr_err(*loc) - } - "pop" => { - let _ = ctx.pop_expr_latest(*loc, self).into_expr_err(*loc)?; - Ok(()) + let t = self.msg_access(ctx, "value", loc)?; + ctx.push_expr(t, self).into_expr_err(loc) } + "pop" => Ok(()), "hash" | "basefee" | "chainid" | "coinbase" | "difficulty" | "gaslimit" | "number" - | "prevrandao" | "timestamp" => { - let t = self.block_access(*loc, ctx, &id.name)?; - ctx.push_expr(t, self).into_expr_err(*loc) + | "prevrandao" | "timestamp" | "blobhash" | "blobbasefee" => { + let t = self.block_access(ctx, name, loc)?; + ctx.push_expr(t, self).into_expr_err(loc) } "log0" | "log1" | "log2" | "log3" | "log4" => { ctx.push_expr(ExprRet::Multi(vec![]), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } - "stop" | "revert" | "selfdestruct" | "invalid" => { - ctx.kill(self, *loc, KilledKind::Revert).into_expr_err(*loc) + "selfdestruct" => ctx.kill(self, loc, KilledKind::Ended).into_expr_err(loc), + "stop" | "revert" | "invalid" => { + ctx.kill(self, loc, KilledKind::Revert).into_expr_err(loc) } "return" => { - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(offset) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs(loc, "Yul Return had no offset".to_string())); - }; - if matches!(offset, ExprRet::CtxKilled(_)) { - ctx.push_expr(offset, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.parse_ctx_yul_expr(arena, &arguments[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(size) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs(loc, "Yul Return had no size".to_string())); - }; - if matches!(size, ExprRet::CtxKilled(_)) { - ctx.push_expr(size, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.return_yul(ctx, loc, size)?; - ctx.kill(analyzer, loc, KilledKind::Ended) - .into_expr_err(loc)?; - // ctx.push_expr(ExprRet::CtxKilled(KilledKind::Ended), analyzer) - // .into_expr_err(loc)?; - Ok(()) - }) - }) + let [offset, size] = inputs.into_sized(); + self.return_yul(ctx, offset, size, loc)?; + ctx.kill(self, loc, KilledKind::Ended).into_expr_err(loc)?; + Ok(()) } "not" => { - if arguments.len() != 1 { + if inputs.len() != 1 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `not` expected 1 argument found: {:?}", - arguments.len() + inputs.len() ), )); } - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoRhs( - loc, - "Not operation had no element".to_string(), - )); - }; - - if matches!(lhs, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.bit_not_inner(arena, ctx, loc, lhs.flatten()) - }) + let [lhs] = inputs.into_sized(); + self.bit_not_inner(arena, ctx, lhs.flatten(), loc) } "add" | "sub" | "mul" | "div" | "sdiv" | "mod" | "smod" | "exp" | "and" | "or" | "xor" | "shl" | "shr" | "sar" => { - let op = match &*id.name { + let op = match name { "add" => RangeOp::Add(true), "sub" => RangeOp::Sub(true), "mul" => RangeOp::Mul(true), "div" | "sdiv" => RangeOp::Div(true), "mod" | "smod" => RangeOp::Mod, - "exp" => RangeOp::Exp, + "exp" => RangeOp::Exp(true), "and" => RangeOp::BitAnd, "or" => RangeOp::BitOr, "xor" => RangeOp::BitXor, @@ -139,197 +99,128 @@ pub trait YulFuncCaller: _ => unreachable!(), }; - if arguments.len() != 2 { + if inputs.len() != 2 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `{}` expects 2 arguments found: {:?}", - id.name, - arguments.len() + name, + inputs.len() ), )); } - let inputs: Vec = if matches!(&*id.name, "shl" | "shr" | "sar") { + let [lhs, rhs] = if matches!(name, "shl" | "shr" | "sar") { // yul shifts are super dumb and are reversed. - vec![arguments[1].clone(), arguments[0].clone()] + let [rhs, lhs] = inputs.into_sized(); + [lhs, rhs] } else { - vec![arguments[0].clone(), arguments[1].clone()] + let [lhs, rhs] = inputs.into_sized(); + [lhs, rhs] }; - self.parse_inputs(arena, ctx, *loc, &inputs)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(inputs) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul Binary operation had no inputs".to_string(), - )); - }; - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - inputs.expect_length(2).into_expr_err(loc)?; - let inputs = inputs.as_vec(); - - // we have to cast the inputs into an EVM word, which is effectively a u256. - let word_ty = analyzer.builtin_or_add(Builtin::Uint(256)); - let cast_ty = VarType::try_from_idx(analyzer, word_ty).unwrap(); - let lhs_paths = - ContextVarNode::from(inputs[0].expect_single().into_expr_err(loc)?); - lhs_paths - .cast_from_ty(cast_ty.clone(), analyzer, arena) - .into_expr_err(loc)?; + // we have to cast the inputs into an EVM word, which is effectively a u256. + let word_ty = self.builtin_or_add(Builtin::Uint(256)); + let cast_ty = VarType::try_from_idx(self, word_ty).unwrap(); + let lhs_paths = ContextVarNode::from(lhs.expect_single().into_expr_err(loc)?); + lhs_paths + .cast_from_ty(cast_ty.clone(), self, arena) + .into_expr_err(loc)?; - let rhs_paths = - ContextVarNode::from(inputs[1].expect_single().into_expr_err(loc)?); - rhs_paths - .cast_from_ty(cast_ty, analyzer, arena) - .into_expr_err(loc)?; + let rhs_paths = ContextVarNode::from(rhs.expect_single().into_expr_err(loc)?); + rhs_paths + .cast_from_ty(cast_ty, self, arena) + .into_expr_err(loc)?; - analyzer.op_match( - arena, - ctx, - loc, - &ExprRet::Single(lhs_paths.latest_version(analyzer).into()), - &ExprRet::Single(rhs_paths.latest_version(analyzer).into()), - op, - false, - ) - }) + self.op_match( + arena, + ctx, + loc, + &ExprRet::Single(lhs_paths.latest_version(self).into()), + &ExprRet::Single(rhs_paths.latest_version(self).into()), + op, + false, + ) } "lt" | "gt" | "slt" | "sgt" | "eq" => { - let op = match &*id.name { + let op = match name { "lt" | "slt" => RangeOp::Lt, "gt" | "sgt" => RangeOp::Gt, "eq" => RangeOp::Eq, _ => unreachable!(), }; - if arguments.len() != 2 { + if inputs.len() != 2 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `{}` expects 2 arguments found: {:?}", - id.name, - arguments.len() + name, + inputs.len() ), )); } - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul Binary operation had no right hand side".to_string(), - )); - }; - - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } + let [lhs_paths, rhs_paths] = inputs.into_sized(); - analyzer.parse_ctx_yul_expr(arena, &arguments[1], ctx)?; - analyzer.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let Some(rhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Yul Binary operation had no left hand side".to_string(), - )); - }; - - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.cmp_inner(arena, ctx, loc, &lhs_paths, op, &rhs_paths)?; - let Some(result) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "Yul Binary operation had no return".to_string(), - )); - }; + self.cmp_inner(arena, ctx, loc, &lhs_paths, op, &rhs_paths)?; + let result = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); - let res = ContextVarNode::from(result.expect_single().into_expr_err(loc)?); - let next = analyzer.advance_var_in_ctx(res, loc, ctx)?; - let expr = Elem::Expr(RangeExpr::new( - Elem::from(res), - RangeOp::Cast, - Elem::from(Concrete::Uint(256, U256::zero())), - )); + let res = ContextVarNode::from(result.expect_single().into_expr_err(loc)?); + let next = self.advance_var_in_ctx(res, loc, ctx)?; + let expr = Elem::Expr(RangeExpr::new( + Elem::from(res), + RangeOp::Cast, + Elem::from(Concrete::Uint(256, U256::zero())), + )); - next.set_range_min(analyzer, arena, expr.clone()) - .into_expr_err(loc)?; - next.set_range_max(analyzer, arena, expr) - .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(next.into()), analyzer) - .into_expr_err(loc) - }) - }) + next.set_range_min(self, arena, expr.clone()) + .into_expr_err(loc)?; + next.set_range_max(self, arena, expr).into_expr_err(loc)?; + ctx.push_expr(ExprRet::Single(next.into()), self) + .into_expr_err(loc) } "iszero" => { - if arguments.len() != 1 { + if inputs.len() != 1 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `iszero` expects 1 arguments found: {:?}", - arguments.len() + inputs.len() ), )); } - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `iszero` operation had no input".to_string(), - )); - }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } + let [lhs_paths] = inputs.into_sized(); - let cnode = ConcreteNode::from( - analyzer.add_node(Node::Concrete(Concrete::from(U256::from(0)))), - ); - let tmp_true = Node::ContextVar( - ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, analyzer) - .into_expr_err(loc)?, - ); - let rhs_paths = - ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_true)).into()); + let cnode = ConcreteNode::from(self.add_node(Concrete::from(U256::from(0)))); + let tmp_true = ContextVar::new_from_concrete(Loc::Implicit, ctx, cnode, self) + .into_expr_err(loc)?; + let rhs_paths = + ExprRet::Single(ContextVarNode::from(self.add_node(tmp_true)).into()); - analyzer.cmp_inner(arena, ctx, loc, &lhs_paths, RangeOp::Eq, &rhs_paths) - }) + self.cmp_inner(arena, ctx, loc, &lhs_paths, RangeOp::Eq, &rhs_paths) } "addmod" | "mulmod" => { let b = Builtin::Uint(256); - let var = ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "msize" | "pc" | "mload" | "sload" | "gas" | "returndatasize" => { // TODO: actually handle this. @MemoryModel let b = Builtin::Uint(256); - let var = ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "calldatacopy" => { @@ -339,99 +230,87 @@ pub trait YulFuncCaller: "calldatasize" => { // TODO: actually handle this. @MemoryModel let b = Builtin::Uint(256); - let var = ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "calldataload" => { - if arguments.len() != 1 { + if inputs.len() != 1 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `calldataload` expects 1 arguments found: {:?}", - arguments.len() + inputs.len() ), )); } - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `calldataload` operation had no input".to_string(), - )); - }; - // TODO: check const version - let b = Builtin::Uint(256); - let mut var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - var.display_name = format!( - "calldata[{}:{}+32]", - elem.display_name(analyzer).into_expr_err(loc)?, - elem.display_name(analyzer).into_expr_err(loc)? - ); - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - }) + let [lhs_paths] = inputs.into_sized(); + // TODO: check const version + let b = Builtin::Uint(256); + let mut var = + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); + var.display_name = format!( + "calldata[{}:{}+32]", + elem.display_name(self).into_expr_err(loc)?, + elem.display_name(self).into_expr_err(loc)? + ); + let node = self.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc) } "keccak256" => { let b = Builtin::Bytes(32); - let var = ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "call" | "delegatecall" | "callcode" | "staticcall" => { let b = Builtin::Uint(256); let mut var = - ContextVar::new_from_builtin(*loc, self.builtin_or_add(b.clone()).into(), self) - .into_expr_err(*loc)?; - var.display_name = format!("{id}_success"); + ContextVar::new_from_builtin(loc, self.builtin_or_add(b.clone()).into(), self) + .into_expr_err(loc)?; + var.display_name = format!("{name}_success"); let mut range = SolcRange::try_from_builtin(&b).unwrap(); range.min = Elem::from(Concrete::from(U256::from(0))); range.max = Elem::from(Concrete::from(U256::from(1))); - var.ty.set_range(range).into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + var.ty.set_range(range).into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "create" | "create2" => { let b = Builtin::Address; let mut var = - ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - var.display_name = format!("{id}_success"); - let node = self.add_node(Node::ContextVar(var)); + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + var.display_name = format!("{name}_success"); + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "returndatacopy" => { ctx.push_expr(ExprRet::Multi(vec![]), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "byte" => { let b = Builtin::Uint(8); - let var = ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; - let node = self.add_node(Node::ContextVar(var)); + let var = ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "mstore" | "mstore8" => { @@ -443,274 +322,214 @@ pub trait YulFuncCaller: // widen to any max range let latest_var = var.latest_version_or_inherited_in_ctx(ctx, self); if matches!( - latest_var.underlying(self).into_expr_err(*loc)?.storage, + latest_var.underlying(self).into_expr_err(loc)?.storage, Some(StorageLocation::Memory(_)) ) { - let res = latest_var.ty(self).into_expr_err(*loc)?; + let res = latest_var.ty(self).into_expr_err(loc)?; if let Some(r) = res.default_range(self).unwrap() { - let new_var = self.advance_var_in_ctx(latest_var, *loc, ctx).unwrap(); - let res = new_var - .set_range_min(self, arena, r.min) - .into_expr_err(*loc); + let new_var = self.advance_var_in_ctx(latest_var, loc, ctx).unwrap(); + let res = new_var.set_range_min(self, arena, r.min).into_expr_err(loc); let _ = self.add_if_err(res); - let res = new_var - .set_range_max(self, arena, r.max) - .into_expr_err(*loc); + let res = new_var.set_range_max(self, arena, r.max).into_expr_err(loc); let _ = self.add_if_err(res); } } Ok(()) })?; ctx.push_expr(ExprRet::Multi(vec![]), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "sstore" => { - if arguments.len() != 2 { + if inputs.len() != 2 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `{}` expects 2 arguments found: {:?}", - id.name, - arguments.len() + name, + inputs.len() ), )); } + let [value, slot] = inputs.into_sized(); + let cvar = ContextVarNode::from(slot.expect_single().unwrap()); - self.parse_inputs(arena, ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, arena, ctx, loc| { - let Some(mut lhs_paths) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::InvalidFunctionInput( - loc, - "Yul `sload` operation had no inputs".to_string(), - )); - }; - - if lhs_paths.expect_length(2).into_expr_err(loc).is_err() { - return Err(ExprErr::NoRhs( - loc, - "Yul `sload` operation had no rhs".to_string(), - )); - } - let value = lhs_paths.take_one().into_expr_err(loc)?.unwrap(); - let slot = lhs_paths.take_one().into_expr_err(loc)?.unwrap(); - let cvar = ContextVarNode::from(slot.expect_single().unwrap()); - - if let Some(slot) = cvar.slot_to_storage(analyzer) { - analyzer.match_assign_sides( - arena, - ctx, - loc, - &ExprRet::Single(slot.into()), - &value, - )?; - } else { - // TODO: improve this. We now handle `slot` but should try to figure out storage layout - let vars = ctx.local_vars(analyzer).clone(); - vars.iter().try_for_each(|(_name, var)| { - // widen to any max range - let latest_var = var.latest_version(analyzer); - if matches!( - latest_var.underlying(analyzer).into_expr_err(loc)?.storage, - Some(StorageLocation::Storage(_)) - ) { - let res = latest_var.ty(analyzer).into_expr_err(loc)?; - if let Some(r) = res.default_range(analyzer).unwrap() { - let new_var = - analyzer.advance_var_in_ctx(latest_var, loc, ctx).unwrap(); - let res = new_var - .set_range_min(analyzer, arena, r.min) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_var - .set_range_max(analyzer, arena, r.max) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } + if let Some(slot) = cvar.slot_to_storage(self) { + self.match_assign_sides( + arena, + ctx, + loc, + &ExprRet::Single(slot.into()), + &value, + )?; + } else { + // TODO: improve this. We now handle `slot` but should try to figure out storage layout + let vars = ctx.local_vars(self).clone(); + vars.iter().try_for_each(|(_name, var)| { + // widen to any max range + let latest_var = var.latest_version(self); + if matches!( + latest_var.underlying(self).into_expr_err(loc)?.storage, + Some(StorageLocation::Storage(_)) + ) { + let res = latest_var.ty(self).into_expr_err(loc)?; + if let Some(r) = res.default_range(self).unwrap() { + let new_var = + self.advance_var_in_ctx(latest_var, loc, ctx).unwrap(); + let res = + new_var.set_range_min(self, arena, r.min).into_expr_err(loc); + let _ = self.add_if_err(res); + let res = + new_var.set_range_max(self, arena, r.max).into_expr_err(loc); + let _ = self.add_if_err(res); } - Ok(()) - })?; - } - Ok(()) - })?; + } + Ok(()) + })?; + } ctx.push_expr(ExprRet::Multi(vec![]), self) - .into_expr_err(*loc)?; + .into_expr_err(loc)?; Ok(()) } "balance" => { - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `balance` operation had no input".to_string(), - )); - }; + let [lhs_paths] = inputs.into_sized(); - let b = Builtin::Uint(256); - let mut var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - var.display_name = format!( - "balance({})", - elem.display_name(analyzer).into_expr_err(loc)? - ); - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - }) + let b = Builtin::Uint(256); + let mut var = + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); + var.display_name = + format!("balance({})", elem.display_name(self).into_expr_err(loc)?); + let node = self.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc) } "selfbalance" => { let b = Builtin::Uint(256); let mut var = - ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; var.display_name = "selfbalance()".to_string(); - let node = self.add_node(Node::ContextVar(var)); + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc) + .into_expr_err(loc) } "address" => { let b = Builtin::Address; let mut var = - ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; var.display_name = "address()".to_string(); - let node = self.add_node(Node::ContextVar(var)); + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc) + .into_expr_err(loc) } "extcodesize" => { - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `extcodesize` operation had no input".to_string(), - )); - }; - - let b = Builtin::Uint(256); - let mut var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - var.display_name = format!( - "extcodesize({})", - elem.display_name(analyzer).into_expr_err(loc)? - ); - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - }) + let [lhs_paths] = inputs.into_sized(); + let b = Builtin::Uint(256); + let mut var = + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); + var.display_name = format!( + "extcodesize({})", + elem.display_name(self).into_expr_err(loc)? + ); + let node = self.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc) } "codesize" => { let b = Builtin::Uint(256); let mut var = - ContextVar::new_from_builtin(*loc, self.builtin_or_add(b).into(), self) - .into_expr_err(*loc)?; + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; var.display_name = "codesize()".to_string(); - let node = self.add_node(Node::ContextVar(var)); + let node = self.add_node(var); ctx.push_expr(ExprRet::Single(node), self) - .into_expr_err(*loc) + .into_expr_err(loc) } "codecopy" => { - if arguments.len() != 3 { + if inputs.len() != 3 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `{}` expects 3 arguments found: {:?}", - id.name, - arguments.len() + name, + inputs.len() ), )); } - self.parse_inputs(arena, ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(_lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `codecopy` operation had no input".to_string(), - )); - }; - ctx.push_expr(ExprRet::Multi(vec![]), analyzer) - .into_expr_err(loc) - }) + let [_, _, _, _] = inputs.into_sized(); + ctx.push_expr(ExprRet::Multi(vec![]), self) + .into_expr_err(loc) } "extcodecopy" => { - if arguments.len() != 4 { + if inputs.len() != 4 { return Err(ExprErr::InvalidFunctionInput( - *loc, + loc, format!( "Yul function: `{}` expects 4 arguments found: {:?}", - id.name, - arguments.len() + name, + inputs.len() ), )); } - self.parse_inputs(arena, ctx, *loc, arguments)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(_lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `extcodecopy` operation had no input".to_string(), - )); - }; - ctx.push_expr(ExprRet::Multi(vec![]), analyzer) - .into_expr_err(loc) - }) + let [_, _, _, _] = inputs.into_sized(); + ctx.push_expr(ExprRet::Multi(vec![]), self) + .into_expr_err(loc) } "extcodehash" => { - self.parse_ctx_yul_expr(arena, &arguments[0], ctx)?; - self.apply_to_edges(ctx, *loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "Yul `extcodesize` operation had no input".to_string(), - )); - }; + let [lhs_paths] = inputs.into_sized(); - let b = Builtin::Bytes(32); - let mut var = ContextVar::new_from_builtin( + let b = Builtin::Bytes(32); + let mut var = + ContextVar::new_from_builtin(loc, self.builtin_or_add(b).into(), self) + .into_expr_err(loc)?; + let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); + var.display_name = format!( + "extcodehash({})", + elem.display_name(self).into_expr_err(loc)? + ); + let node = self.add_node(Node::ContextVar(var)); + ctx.push_expr(ExprRet::Single(node), self) + .into_expr_err(loc) + } + _ => { + let assoc_fn = ctx.associated_fn(self).into_expr_err(loc)?; + let all_yul_fns = assoc_fn.yul_funcs(self, assembly_block_idx); + if let Some(yul_fn) = all_yul_fns + .iter() + .find(|yul_fn| yul_fn.name(self).unwrap() == name) + { + let exprs = yul_fn.exprs(self).unwrap().clone(); + let end = stack.split_off(ctx.parse_idx(self)); + stack.extend(exprs); + stack.extend(end); + let inputs = inputs.as_vec(); + inputs + .into_iter() + .try_for_each(|i| ctx.push_expr(i, self).into_expr_err(loc)) + } else { + Err(ExprErr::Todo( loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let elem = ContextVarNode::from(lhs_paths.expect_single().into_expr_err(loc)?); - var.display_name = format!( - "extcodehash({})", - elem.display_name(analyzer).into_expr_err(loc)? - ); - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - }) - } - _ => Err(ExprErr::Todo( - *loc, - format!("Unhandled yul function: \"{}\"", id.name), - )), + format!("Unhandled yul function: \"{}\"", name), + )) + } + } } } - fn return_yul(&mut self, ctx: ContextNode, loc: Loc, size: ExprRet) -> Result<(), ExprErr> { + fn return_yul( + &mut self, + ctx: ContextNode, + _offset: ExprRet, + size: ExprRet, + loc: Loc, + ) -> Result<(), ExprErr> { match size { ExprRet::CtxKilled(kind) => ctx.kill(self, loc, kind).into_expr_err(loc), ExprRet::Single(size) | ExprRet::SingleLiteral(size) => { @@ -729,7 +548,7 @@ pub trait YulFuncCaller: } var.is_return = true; var.ty.set_range(range).into_expr_err(loc)?; - let node = self.add_node(Node::ContextVar(var)); + let node = self.add_node(var); self.add_edge(node, ctx, Edge::Context(ContextEdge::Return)); ctx.add_return_node( loc, @@ -741,72 +560,10 @@ pub trait YulFuncCaller: ExprRet::Multi(sizes) => { sizes .into_iter() - .try_for_each(|size| self.return_yul(ctx, loc, size))?; + .try_for_each(|size| self.return_yul(ctx, _offset.clone(), size, loc))?; Ok(()) } ExprRet::Null => Ok(()), } } - - // fn byte_index(&mut self, var: ExprRet, index: ExprRet) -> Result { - // match (var, index) { - // (ExprRet::Single(var_idx) - // | ExprRet::Single(var_idx), - // ExprRet::Single(index_idx) - // | ExprRet::Single(index_idx), - // ) => { - - // } - // } - // } - - #[tracing::instrument(level = "trace", skip_all)] - fn parse_inputs( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - inputs: &[YulExpression], - ) -> Result<(), ExprErr> { - let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() { - Rc::new(RefCell::new(true)) - } else { - Rc::new(RefCell::new(false)) - }; - - inputs.iter().try_for_each(|input| { - self.parse_ctx_yul_expr(arena, input, ctx)?; - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - if *append.borrow() { - ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc) - } else { - *append.borrow_mut() = true; - ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc) - } - }) - })?; - if !inputs.is_empty() { - self.apply_to_edges(ctx, loc, arena, &|analyzer, _arena, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - Ok(()) - } - } }