diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0704f115..9553b8e0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,9 +2,9 @@ name: Rust on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] + branches: ["master"] env: CARGO_TERM_COLOR: always diff --git a/.gitignore b/.gitignore index aaec7a35..bee8a6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ **/.swp .vscode .env -notes.md \ No newline at end of file +notes.md +mini.sol diff --git a/crates/analyzers/src/lib.rs b/crates/analyzers/src/lib.rs index f469f356..42b17a3e 100644 --- a/crates/analyzers/src/lib.rs +++ b/crates/analyzers/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::too_many_arguments)] + pub mod bounds; use ariadne::{Cache, Label, Report, ReportKind, Span}; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 7bc3e7e4..7b272ef4 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -107,6 +107,9 @@ struct Args { /// Post pyrometer debugging information to debugging site #[clap(long)] pub debug_site: bool, + + #[clap(long)] + pub minimize_debug: Option, } pub fn subscriber() { @@ -238,10 +241,10 @@ fn main() { let mut analyzer = Analyzer { max_depth: args.max_stack_depth, root: Root::RemappingsDirectory(env::current_dir().unwrap()), + debug_panic: args.debug_panic || args.minimize_debug.is_some(), + minimize_debug: args.minimize_debug, ..Default::default() }; - println!("debug panic: {}", args.debug_panic); - analyzer.debug_panic = args.debug_panic; let (current_path, sol) = if args.path.ends_with(".sol") { let sol = fs::read_to_string(args.path.clone()).expect("Could not find file"); @@ -490,10 +493,15 @@ fn main() { } } } else if let Some(ctx) = FunctionNode::from(func).maybe_body_ctx(&mut analyzer) { - let analysis = analyzer - .bounds_for_all(arena, &file_mapping, ctx, config) - .as_cli_compat(&file_mapping); - analysis.print_reports(&mut source_map, &analyzer, arena); + if !matches!( + ctx.underlying(&analyzer).unwrap().killed, + Some((_, graph::nodes::KilledKind::DebugIgnored)) + ) { + let analysis = analyzer + .bounds_for_all(arena, &file_mapping, ctx, config) + .as_cli_compat(&file_mapping); + analysis.print_reports(&mut source_map, &analyzer, arena); + } } } } else { diff --git a/crates/graph/src/graph_elements.rs b/crates/graph/src/graph_elements.rs index c365b503..308847a9 100644 --- a/crates/graph/src/graph_elements.rs +++ b/crates/graph/src/graph_elements.rs @@ -261,6 +261,8 @@ pub enum Edge { LibraryFunction(NodeIdx), /// A connection for a builtin function BuiltinFunction, + /// A connection from one contract to another contract + UsingContract(Loc), } impl Heirarchical for Edge { @@ -272,7 +274,7 @@ impl Heirarchical for Edge { Contract | Ty | Field | Enum | Struct | Error | Event | Var | InheritedContract | Modifier | FallbackFunc | Constructor | ReceiveFunc | LibraryFunction(_) - | BuiltinFunction | Func => 2, + | BuiltinFunction | Func | UsingContract(_) => 2, Context(_) | ErrorParam | FunctionParam | FunctionReturn | FuncModifier(_) => 3, } @@ -333,6 +335,8 @@ pub enum ContextEdge { InheritedStorageVariable, /// A connection to the calldata variable CalldataVariable, + /// A contract variable (storage, consts, immutables, etc) + ContractVariable, /// A connection between a variable and a parent variable where the child is some attribute on the parent /// (i.e. `.length`) diff --git a/crates/graph/src/lib.rs b/crates/graph/src/lib.rs index 2f35381d..e7826836 100644 --- a/crates/graph/src/lib.rs +++ b/crates/graph/src/lib.rs @@ -1,3 +1,6 @@ +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + mod graph_elements; mod range; mod var_type; diff --git a/crates/graph/src/nodes/context/expr_ret.rs b/crates/graph/src/nodes/context/expr_ret.rs index 969f67a3..b27930c7 100644 --- a/crates/graph/src/nodes/context/expr_ret.rs +++ b/crates/graph/src/nodes/context/expr_ret.rs @@ -16,6 +16,8 @@ pub enum KilledKind { Revert, /// Unexpected parse error. This is likely a bug or invalid solidity. See the `errors` section of the CLI output or rerun with `--debug` for more information ParseError, + /// This context was not evaluated because it was not on the path to analyzing the requested context to debug + DebugIgnored, } impl KilledKind { @@ -27,6 +29,7 @@ impl KilledKind { Unreachable => "Unsatisifiable bounds, therefore dead code", Revert => "Execution guaranteed to revert here!", ParseError => "Unexpected parse error. This is likely a bug or invalid solidity. See the `errors` section of the CLI output or rerun with `--debug` for more information", + DebugIgnored => "Ignored due to debug_ctx_path being set", } } } diff --git a/crates/graph/src/nodes/context/querying.rs b/crates/graph/src/nodes/context/querying.rs index 301a58e9..17706f9e 100644 --- a/crates/graph/src/nodes/context/querying.rs +++ b/crates/graph/src/nodes/context/querying.rs @@ -5,7 +5,7 @@ use crate::{ AnalyzerBackend, ContextEdge, Edge, GraphBackend, }; -use shared::{GraphError, Search}; +use shared::{GraphError, NodeIdx, Search}; use std::collections::{BTreeMap, BTreeSet}; impl ContextNode { @@ -68,6 +68,32 @@ impl ContextNode { } } + pub fn keep_inscope_tys( + &self, + idxs: &mut Vec, + analyzer: &mut impl AnalyzerBackend, + ) -> Result<(), GraphError> { + let mut tys = self + .visible_structs(analyzer)? + .iter() + .map(|i| i.0.into()) + .collect::>(); + if let Some(contract) = self.maybe_associated_contract(analyzer)? { + tys.extend(contract.visible_nodes(analyzer)); + } + + if let Some(source) = self.maybe_associated_source(analyzer) { + tys.extend(source.visible_nodes(analyzer)?); + } + + tys.sort(); + tys.dedup(); + + idxs.retain(|i| tys.contains(i)); + + Ok(()) + } + /// Gets visible functions pub fn visible_modifiers( &self, @@ -226,7 +252,8 @@ impl ContextNode { let mut structs = source.visible_structs(analyzer)?; let contract = self.associated_contract(analyzer)?; - structs.extend(contract.visible_structs(analyzer)); + let contract_visible = contract.visible_structs(analyzer); + structs.extend(contract_visible); structs.sort(); structs.dedup(); diff --git a/crates/graph/src/nodes/context/typing.rs b/crates/graph/src/nodes/context/typing.rs index 9f94b3b8..be3a1ae0 100644 --- a/crates/graph/src/nodes/context/typing.rs +++ b/crates/graph/src/nodes/context/typing.rs @@ -12,7 +12,7 @@ impl ContextNode { Ok(underlying.fn_call.is_none() && underlying.ext_fn_call.is_none() && !underlying.is_fork) } - pub fn has_continuation(&self, analyzer: &mut impl GraphBackend) -> Result { + pub fn has_continuation(&self, analyzer: &impl GraphBackend) -> Result { Ok(self.underlying(analyzer)?.continuation_of.is_some()) } diff --git a/crates/graph/src/nodes/context/underlying.rs b/crates/graph/src/nodes/context/underlying.rs index 00091d55..604fa4b2 100644 --- a/crates/graph/src/nodes/context/underlying.rs +++ b/crates/graph/src/nodes/context/underlying.rs @@ -60,6 +60,8 @@ pub struct Context { pub cache: ContextCache, /// A difference logic solver used for determining reachability pub dl_solver: DLSolver, + /// Functions applied (but not reparsed) in this context + pub applies: Vec, } impl Context { @@ -89,6 +91,7 @@ impl Context { number_of_live_edges: 0, cache: Default::default(), dl_solver: Default::default(), + applies: Default::default(), } } @@ -238,6 +241,7 @@ impl Context { associated_contract: None, }, dl_solver: parent_ctx.underlying(analyzer)?.dl_solver.clone(), + applies: Default::default(), }) } @@ -300,6 +304,7 @@ impl Context { associated_contract: None, }, dl_solver: parent_ctx.underlying(analyzer)?.dl_solver.clone(), + applies: Default::default(), }) } diff --git a/crates/graph/src/nodes/context/var/node.rs b/crates/graph/src/nodes/context/var/node.rs index 16893625..efe13bce 100644 --- a/crates/graph/src/nodes/context/var/node.rs +++ b/crates/graph/src/nodes/context/var/node.rs @@ -118,15 +118,32 @@ impl ContextVarNode { ) } - pub fn maybe_ctx(&self, analyzer: &impl GraphBackend) -> Option { + pub fn is_struct_field(&self, analyzer: &impl GraphBackend) -> bool { + self.struct_parent(analyzer).is_some() + } + + pub fn struct_parent(&self, analyzer: &impl GraphBackend) -> Option { let first = self.first_version(analyzer); analyzer .graph() .edges_directed(first.0.into(), Direction::Outgoing) - .filter(|edge| *edge.weight() == Edge::Context(ContextEdge::Variable)) - .map(|edge| ContextNode::from(edge.target())) - .take(1) - .next() + .find(|edge| *edge.weight() == Edge::Context(ContextEdge::AttrAccess("field"))) + .map(|edge| edge.target().into()) + } + + pub fn maybe_ctx(&self, analyzer: &impl GraphBackend) -> Option { + if let Some(struct_parent) = self.struct_parent(analyzer) { + struct_parent.maybe_ctx(analyzer) + } else { + let first = self.first_version(analyzer); + analyzer + .graph() + .edges_directed(first.0.into(), Direction::Outgoing) + .filter(|edge| *edge.weight() == Edge::Context(ContextEdge::Variable)) + .map(|edge| ContextNode::from(edge.target())) + .take(1) + .next() + } } pub fn maybe_storage_var(&self, analyzer: &impl GraphBackend) -> Option { @@ -212,6 +229,10 @@ impl ContextVarNode { Ok(self.underlying(analyzer)?.tmp_of()) } + pub fn is_struct(&self, analyzer: &impl GraphBackend) -> Result { + Ok(!self.struct_to_fields(analyzer)?.is_empty()) + } + pub fn struct_to_fields( &self, analyzer: &impl GraphBackend, @@ -229,6 +250,24 @@ impl ContextVarNode { } } + pub fn field_of_struct( + &self, + name: &str, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + let fields = self.struct_to_fields(analyzer)?; + Ok(fields + .iter() + .find(|field| { + if let Ok(field_name) = field.name(analyzer) { + field_name.ends_with(&format!(".{}", name)) + } else { + false + } + }) + .copied()) + } + pub fn array_to_len_var(&self, analyzer: &impl GraphBackend) -> Option { if let Some(len) = analyzer .graph() diff --git a/crates/graph/src/nodes/context/var/typing.rs b/crates/graph/src/nodes/context/var/typing.rs index 132054df..cfd602b8 100644 --- a/crates/graph/src/nodes/context/var/typing.rs +++ b/crates/graph/src/nodes/context/var/typing.rs @@ -1,11 +1,13 @@ use crate::{ elem::Elem, - nodes::{Builtin, Concrete, ContextNode, ContextVarNode}, + nodes::{ + Builtin, Concrete, ContextNode, ContextVarNode, EnumNode, ErrorNode, StructNode, TyNode, + }, range::{ elem::{RangeElem, RangeExpr, RangeOp}, RangeEval, }, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, VarType, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, TypeNode, VarType, }; use shared::{GraphError, RangeArena, Search, StorageLocation}; @@ -89,6 +91,58 @@ impl ContextVarNode { )) } + pub fn maybe_struct( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let VarType::User(TypeNode::Struct(ut), _) = self.ty(analyzer)? { + Ok(Some(*ut)) + } else { + Ok(None) + } + } + + pub fn maybe_enum(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + if let VarType::User(TypeNode::Enum(ut), _) = self.ty(analyzer)? { + Ok(Some(*ut)) + } else { + Ok(None) + } + } + + pub fn maybe_ty_node( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let VarType::User(TypeNode::Ty(ut), _) = self.ty(analyzer)? { + Ok(Some(*ut)) + } else { + Ok(None) + } + } + + pub fn maybe_err_node( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let VarType::User(TypeNode::Error(ut), _) = self.ty(analyzer)? { + Ok(Some(*ut)) + } else { + Ok(None) + } + } + + pub fn maybe_usertype( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let VarType::User(ut, _) = self.ty(analyzer)? { + Ok(Some(*ut)) + } else { + Ok(None) + } + } + pub fn is_return_assignment(&self, analyzer: &impl GraphBackend) -> bool { analyzer .graph() @@ -554,7 +608,13 @@ impl ContextVarNode { if let Some(to_range) = to_ty.range(analyzer)? { let mut min_expr = (*self) .range_min(analyzer)? - .unwrap() + .unwrap_or_else(|| { + panic!( + "{:?}, {} had no min", + self.loc(analyzer).unwrap(), + self.display_name(analyzer).unwrap() + ) + }) .cast(to_range.min.clone()) .min( (*self) diff --git a/crates/graph/src/nodes/context/var/versioning.rs b/crates/graph/src/nodes/context/var/versioning.rs index ad2cfdcd..cd8e834e 100644 --- a/crates/graph/src/nodes/context/var/versioning.rs +++ b/crates/graph/src/nodes/context/var/versioning.rs @@ -16,6 +16,34 @@ impl ContextVarNode { latest } + pub fn latest_version_or_inherited_in_ctx( + &self, + ctx: ContextNode, + analyzer: &impl GraphBackend, + ) -> Self { + let mut latest = *self; + + if let Some(struct_parent) = self.struct_parent(analyzer) { + if let Ok(name) = self.name(analyzer) { + let name = name + .split('.') + .skip(1) + .take(1) + .next() + .expect("weird struct name"); + let parent_latest = struct_parent.latest_version_or_inherited_in_ctx(ctx, analyzer); + if let Ok(Some(t)) = parent_latest.field_of_struct(name, analyzer) { + latest = t; + } + } + } + + while let Some(next) = latest.next_version_or_inherited_in_ctx(ctx, analyzer) { + latest = next; + } + latest + } + pub fn latest_version_less_than(&self, idx: NodeIdx, analyzer: &impl GraphBackend) -> Self { let mut latest = *self; while let Some(next) = latest.next_version(analyzer) { @@ -172,6 +200,30 @@ impl ContextVarNode { .next() } + pub fn next_version_or_inherited_in_ctx( + &self, + ctx: ContextNode, + analyzer: &impl GraphBackend, + ) -> Option { + analyzer + .graph() + .edges_directed(self.0.into(), Direction::Incoming) + .find_map(|edge| { + if Edge::Context(ContextEdge::Prev) == *edge.weight() { + Some(ContextVarNode::from(edge.source())) + } else if Edge::Context(ContextEdge::InheritedVariable) == *edge.weight() { + let t = ContextVarNode::from(edge.source()); + if t.ctx(analyzer) == ctx { + Some(t) + } else { + None + } + } else { + None + } + }) + } + pub fn next_version_or_inheriteds(&self, analyzer: &impl GraphBackend) -> Vec { analyzer .graph() diff --git a/crates/graph/src/nodes/context/variables.rs b/crates/graph/src/nodes/context/variables.rs index 5ba2e83c..ff27c40f 100644 --- a/crates/graph/src/nodes/context/variables.rs +++ b/crates/graph/src/nodes/context/variables.rs @@ -1,9 +1,10 @@ use crate::{ - nodes::{ContextNode, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, + nodes::{ContextNode, ContextVarNode, ExprRet, VarNode}, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, TypeNode, }; use shared::GraphError; +use petgraph::visit::EdgeRef; use solang_parser::pt::Loc; use std::collections::BTreeMap; @@ -33,6 +34,18 @@ impl ContextNode { Ok(()) } + /// 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))); + Ok(()) + } + /// Add a variable to this context pub fn add_var( &self, @@ -102,6 +115,164 @@ impl ContextNode { } } + /// Gets a variable by name or recurses up the relevant scopes/contexts until it is found + pub fn struct_field_access_by_name_recurse( + &self, + analyzer: &mut impl AnalyzerBackend, + loc: Loc, + full_name: &str, + ) -> Result, GraphError> { + let split = full_name.split('.').collect::>(); + if split.len() != 2 { + return Ok(None); + } + + let member_name = split[0]; + let field_name = split[1]; + + // get the member + let Some(member) = self.var_by_name_or_recurse(analyzer, member_name)? else { + return Ok(None); + }; + + // maybe move var into this context + let member = self.maybe_move_var(member, loc, analyzer)?; + let global_first = member.global_first_version(analyzer); + + let mut curr = member; + let mut field = None; + // recursively search for the field by looking at all major versions of the member (i.e. first version + // of the variable in a context) + while field.is_none() && curr != global_first { + field = curr.field_of_struct(field_name, analyzer)?; + if let Some(prev) = curr.previous_or_inherited_version(analyzer) { + curr = prev; + } else { + break; + } + } + + if let Some(field) = field { + if let Some(ctx) = curr.maybe_ctx(analyzer) { + if ctx != *self { + tracing::trace!( + "moving field access {} from {} to {}", + field.display_name(analyzer).unwrap(), + ctx.path(analyzer), + self.path(analyzer) + ); + let mut new_cvar = field.latest_version(analyzer).underlying(analyzer)?.clone(); + new_cvar.loc = Some(loc); + + let new_cvarnode = analyzer.add_node(Node::ContextVar(new_cvar)); + analyzer.add_edge( + new_cvarnode, + member, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + } + } + } + + Ok(field) + } + + /// Gets all storage variables associated with a context + pub fn storage_vars(&self, analyzer: &impl AnalyzerBackend) -> Vec { + self.underlying(analyzer) + .unwrap() + .cache + .vars + .clone() + .into_iter() + .filter(|(_, var)| var.is_storage(analyzer).unwrap()) + .map(|(_, var)| var) + .collect::>() + } + + pub fn contract_vars_referenced(&self, analyzer: &impl AnalyzerBackend) -> Vec { + // let mut storage_vars = self.storage_vars(analyzer); + let all_vars = self.all_vars(analyzer); + let all_contract_vars = all_vars + .into_iter() + .filter_map(|var| { + if analyzer + .graph() + .edges_directed(var.1 .0.into(), petgraph::Direction::Outgoing) + .any(|edge| { + matches!(edge.weight(), Edge::Context(ContextEdge::ContractVariable)) + }) + { + Some(var.1) + } else { + None + } + }) + .collect::>(); + + all_contract_vars + .iter() + .filter_map(|var| { + let name = var.name(analyzer).unwrap(); + Some( + analyzer + .user_types() + .get(&name)? + .iter() + .filter_map(|idx| match analyzer.node(*idx) { + Node::Var(_) => Some(VarNode::from(*idx)), + _ => None, + }) + .collect::>(), + ) + }) + .flatten() + .collect() + } + + pub fn usertype_vars_referenced(&self, analyzer: &impl AnalyzerBackend) -> Vec { + let vars = self.all_vars(analyzer); + vars.iter() + .filter_map(|(_, var)| var.maybe_usertype(analyzer).ok()) + .flatten() + .collect() + } + + pub fn contract_vars_referenced_global(&self, analyzer: &impl AnalyzerBackend) -> Vec { + let mut reffed_storage = self.contract_vars_referenced(analyzer); + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Incoming) + .filter(|edge| matches!(edge.weight(), Edge::Context(ContextEdge::Continue(_)))) + .map(|edge| ContextNode::from(edge.source())) + .for_each(|cont| { + reffed_storage.extend(cont.contract_vars_referenced_global(analyzer)); + }); + + reffed_storage.sort_by(|a, b| a.0.cmp(&b.0)); + reffed_storage.dedup(); + reffed_storage + } + + pub fn usertype_vars_referenced_global( + &self, + analyzer: &impl AnalyzerBackend, + ) -> Vec { + let mut reffed_usertypes = self.usertype_vars_referenced(analyzer); + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Incoming) + .filter(|edge| matches!(edge.weight(), Edge::Context(ContextEdge::Continue(_)))) + .map(|edge| ContextNode::from(edge.source())) + .for_each(|cont| { + reffed_usertypes.extend(cont.usertype_vars_referenced_global(analyzer)); + }); + + reffed_usertypes.sort(); + reffed_usertypes.dedup(); + reffed_usertypes + } + /// Gets all variables associated with a context pub fn vars<'a>( &self, @@ -110,6 +281,17 @@ impl ContextNode { &self.underlying(analyzer).unwrap().cache.vars } + /// Gets all variables associated with a context + pub fn all_vars(&self, analyzer: &impl GraphBackend) -> BTreeMap { + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Incoming) + .filter(|edge| matches!(edge.weight(), Edge::Context(ContextEdge::Variable))) + .map(|edge| ContextVarNode::from(edge.source())) + .map(|cvar| (cvar.name(analyzer).unwrap(), cvar)) + .collect() + } + /// Gets all variables associated with a context pub fn local_vars<'a>( &self, @@ -256,11 +438,22 @@ impl ContextNode { loc: Loc, analyzer: &mut impl AnalyzerBackend, ) -> Result { + if let Ok(name) = var.name(analyzer) { + if let Some(ret_var) = self.latest_var_by_name(analyzer, &name) { + return Ok(ret_var); + } + } + let var = var.latest_version(analyzer); if let Some(ctx) = var.maybe_ctx(analyzer) { if ctx != *self { tracing::trace!( - "moving var {} from {}", + "moving var {}from {} to {}", + if let Ok(name) = var.display_name(analyzer) { + format!("{name} ") + } else { + "".to_string() + }, ctx.path(analyzer), self.path(analyzer) ); @@ -268,6 +461,7 @@ impl ContextNode { new_cvar.loc = Some(loc); let new_cvarnode = analyzer.add_node(Node::ContextVar(new_cvar)); + self.add_var(ContextVarNode::from(new_cvarnode), analyzer)?; analyzer.add_edge(new_cvarnode, *self, Edge::Context(ContextEdge::Variable)); analyzer.add_edge( new_cvarnode, diff --git a/crates/graph/src/nodes/context/versioning.rs b/crates/graph/src/nodes/context/versioning.rs index d4e9e0f4..4b96f7e5 100644 --- a/crates/graph/src/nodes/context/versioning.rs +++ b/crates/graph/src/nodes/context/versioning.rs @@ -282,6 +282,31 @@ impl ContextNode { } } + /// Gets all descendents as context nodes recursively + pub fn family_tree( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + if let Some(child) = self.underlying(analyzer)?.child { + let mut tree = vec![]; + match child { + CallFork::Call(c) => { + tree.push(c); + tree.extend(c.family_tree(analyzer)?) + } + CallFork::Fork(w1, w2) => { + tree.push(w1); + tree.push(w2); + tree.extend(w1.family_tree(analyzer)?); + tree.extend(w2.family_tree(analyzer)?); + } + } + Ok(tree) + } else { + Ok(vec![]) + } + } + /// Adds a fork to the context pub fn set_child_fork( &self, @@ -313,7 +338,7 @@ impl ContextNode { } } - pub fn set_join_forks( + pub fn set_apply_forks( &self, loc: Loc, end_worlds: Vec, @@ -504,6 +529,16 @@ impl ContextNode { Ok(parents) } + /// 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 { + parent_ctx.genesis(analyzer) + } else { + Ok(*self) + } + } + /// Gets all calls recursively pub fn recursive_calls( &self, diff --git a/crates/graph/src/nodes/contract_ty.rs b/crates/graph/src/nodes/contract_ty.rs index ee528f60..42694f9b 100644 --- a/crates/graph/src/nodes/contract_ty.rs +++ b/crates/graph/src/nodes/contract_ty.rs @@ -1,5 +1,8 @@ use crate::{ - nodes::{Concrete, FunctionNode, SourceUnitNode, SourceUnitPartNode, StructNode, VarNode}, + nodes::{ + Concrete, EnumNode, ErrorNode, FunctionNode, SourceUnitNode, SourceUnitPartNode, + StructNode, TyNode, VarNode, + }, range::elem::Elem, AnalyzerBackend, AsDotStr, Edge, GraphBackend, Node, }; @@ -102,6 +105,10 @@ impl ContractNode { self.name(analyzer) ) }); + self.underlying_mut(analyzer) + .unwrap() + .inherits + .push(ContractNode::from(*found)); analyzer.add_edge(*found, *self, Edge::InheritedContract); }); } @@ -115,7 +122,7 @@ impl ContractNode { inherits.extend( inherits .iter() - .flat_map(|i| i.direct_inherited_contracts(analyzer)) + .flat_map(|i| i.all_inherited_contracts(analyzer)) .collect::>(), ); inherits.into_iter().collect::>() @@ -148,7 +155,7 @@ impl ContractNode { /// Gets all associated functions from the underlying node data for the [`Contract`] pub fn funcs(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { analyzer - .search_children_depth(self.0.into(), &Edge::Func, 1, 0) + .search_children_depth(self.0.into(), &Edge::Func, 0, 0) .into_iter() .map(FunctionNode::from) .collect() @@ -156,7 +163,7 @@ impl ContractNode { pub fn constructor(&self, analyzer: &(impl GraphBackend + Search)) -> Option { analyzer - .search_children_depth(self.0.into(), &Edge::Constructor, 1, 0) + .search_children_depth(self.0.into(), &Edge::Constructor, 0, 0) .into_iter() .map(FunctionNode::from) .take(1) @@ -166,7 +173,7 @@ impl ContractNode { /// 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 - .search_children_depth(self.0.into(), &Edge::Var, 1, 0) + .search_children_depth(self.0.into(), &Edge::Var, 0, 0) .into_iter() .map(VarNode::from) .collect() @@ -188,7 +195,7 @@ impl ContractNode { analyzer: &mut (impl Search + AnalyzerBackend), ) -> BTreeMap { analyzer - .search_children_depth(self.0.into(), &Edge::Func, 1, 0) + .search_children_depth(self.0.into(), &Edge::Func, 0, 0) .into_iter() .map(|i| { let fn_node = FunctionNode::from(i); @@ -225,12 +232,36 @@ impl ContractNode { pub fn structs(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { analyzer - .search_children_depth(self.0.into(), &Edge::Struct, 1, 0) + .search_children_depth(self.0.into(), &Edge::Struct, 0, 0) .into_iter() .map(StructNode::from) .collect() } + pub fn enums(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { + analyzer + .search_children_depth(self.0.into(), &Edge::Enum, 0, 0) + .into_iter() + .map(EnumNode::from) + .collect() + } + + pub fn tys(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { + analyzer + .search_children_depth(self.0.into(), &Edge::Ty, 0, 0) + .into_iter() + .map(TyNode::from) + .collect() + } + + pub fn errs(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { + analyzer + .search_children_depth(self.0.into(), &Edge::Error, 0, 0) + .into_iter() + .map(ErrorNode::from) + .collect() + } + pub fn visible_structs(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { let mut structs = self.structs(analyzer); let inherited = self.all_inherited_contracts(analyzer); @@ -243,6 +274,34 @@ impl ContractNode { structs } + pub fn visible_local_nodes(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { + let mut nodes = self + .structs(analyzer) + .iter() + .map(|i| i.0.into()) + .collect::>(); + nodes.extend(self.enums(analyzer).iter().map(|i| NodeIdx::from(i.0))); + nodes.extend(self.tys(analyzer).iter().map(|i| NodeIdx::from(i.0))); + nodes.extend(self.errs(analyzer).iter().map(|i| NodeIdx::from(i.0))); + nodes.extend( + self.direct_storage_vars(analyzer) + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + nodes + } + + pub fn visible_nodes(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { + let mut nodes = self.visible_local_nodes(analyzer); + let inherited = self.all_inherited_contracts(analyzer); + nodes.extend( + inherited + .iter() + .flat_map(|c| c.visible_local_nodes(analyzer)), + ); + nodes + } + /// Gets all associated modifiers from the underlying node data for the [`Contract`] pub fn modifiers(&self, analyzer: &(impl GraphBackend + Search)) -> Vec { analyzer diff --git a/crates/graph/src/nodes/debug_reconstruction.rs b/crates/graph/src/nodes/debug_reconstruction.rs new file mode 100644 index 00000000..f286397d --- /dev/null +++ b/crates/graph/src/nodes/debug_reconstruction.rs @@ -0,0 +1,554 @@ +use crate::{ + nodes::{ContractNode, EnumNode, ErrorNode, FunctionNode, StructNode, TyNode, VarNode}, + AnalyzerBackend, Edge, TypeNode, +}; + +use shared::GraphError; + +use petgraph::{visit::EdgeRef, Direction}; +use solang_parser::pt::Loc; + +use std::collections::BTreeMap; + +#[derive(Debug, Clone)] +pub struct FuncReconstructionReqs { + pub storage: Vec, + pub usertypes: Vec, +} + +impl FuncReconstructionReqs { + pub fn enums(&self) -> Vec { + self.usertypes + .iter() + .filter_map(|ut| { + if let TypeNode::Enum(ret) = ut { + Some(*ret) + } else { + None + } + }) + .collect() + } + pub fn structs(&self) -> Vec { + self.usertypes + .iter() + .filter_map(|ut| { + if let TypeNode::Struct(ret) = ut { + Some(*ret) + } else { + None + } + }) + .collect() + } + pub fn errs(&self) -> Vec { + self.usertypes + .iter() + .filter_map(|ut| { + if let TypeNode::Error(ret) = ut { + Some(*ret) + } else { + None + } + }) + .collect() + } + pub fn tys(&self) -> Vec { + self.usertypes + .iter() + .filter_map(|ut| { + if let TypeNode::Ty(ret) = ut { + Some(*ret) + } else { + None + } + }) + .collect() + } +} + +impl ContractNode { + pub fn reconstruct_name_src<'a>( + &self, + analyzer: &'a impl AnalyzerBackend, + ) -> Result<&'a str, GraphError> { + let mut loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + let name_loc = self.underlying(analyzer)?.name.clone().unwrap().loc; + loc.use_end_from(&name_loc); + Ok(&analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()]) + } + + pub fn reconstruct_inherits( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let used_inherits = self + .direct_inherited_contracts(analyzer) + .iter() + .filter_map(|inherited| { + if contract_to_funcs.contains_key(&Some(*inherited)) { + Some(inherited.name(analyzer).unwrap()) + } else { + None + } + }) + .collect::>(); + if used_inherits.is_empty() { + Ok("".to_string()) + } else { + Ok(format!("is {}", used_inherits.join(", "))) + } + } + + pub fn reconstruct_usings( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let contract_to_using_locs = contract_to_funcs + .keys() + .filter_map(|maybe_c| { + Some(( + (*maybe_c)?, + analyzer + .graph() + .edges_directed((*maybe_c)?.0.into(), petgraph::Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::UsingContract(_))) + .filter(|edge| { + contract_to_funcs.contains_key(&Some(ContractNode::from(edge.target()))) + }) + .map(|edge| { + let Edge::UsingContract(loc) = edge.weight() else { + panic!("here") + }; + *loc + }) + .collect::>(), + )) + }) + .collect::>>(); + + let mut using_str = Default::default(); + if let Some(locs) = contract_to_using_locs.get(self) { + using_str = locs + .iter() + .map(|loc| { + let file_no = loc.try_file_no().unwrap(); + format!( + "{};", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + ) + }) + .collect::>() + .join("\n"); + } + + if using_str.is_empty() { + Ok(using_str) + } else { + Ok(format!(" {using_str}\n")) + } + } + + pub fn reconstruct_funcs( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + if let Some(funcs_and_storage) = contract_to_funcs.get(&Some(*self)) { + Ok(format!( + " {}\n", + funcs_and_storage + .iter() + .map(|(func, _)| func.reconstruct_src(analyzer).unwrap()) + .collect::>() + .join("\n") + )) + } else { + Ok("".to_string()) + } + } + + pub fn reconstruct_storage( + &self, + analyzer: &impl AnalyzerBackend, + used_storage: Vec, + ) -> Result { + if used_storage.is_empty() { + Ok("".to_string()) + } else { + Ok(format!( + " {}\n", + self.direct_storage_vars(analyzer) + .iter() + .filter_map(|storage_var| { + if used_storage.contains(storage_var) { + Some(format!("{};", storage_var.reconstruct_src(analyzer).ok()?)) + } else { + None + } + }) + .collect::>() + .join("\n ") + )) + } + } + + pub fn reconstruct_structs( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let mut used = contract_to_funcs + .values() + .flat_map(|func_and_vars| { + func_and_vars + .iter() + .flat_map(|(_, reqs)| { + reqs.structs() + .iter() + .filter_map(|var| { + if var.maybe_associated_contract(analyzer) == Some(*self) { + Some(*var) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + used.sort_by(|a, b| a.0.cmp(&b.0)); + used.dedup(); + + if used.is_empty() { + Ok("".to_string()) + } else { + Ok(format!( + " {}\n", + self.structs(analyzer) + .iter() + .filter_map(|strukt| { + if used.contains(strukt) { + Some(strukt.reconstruct_src(analyzer).ok()?.to_string()) + } else { + None + } + }) + .collect::>() + .join("\n ") + )) + } + } + + pub fn reconstruct_enums( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let mut used = contract_to_funcs + .values() + .flat_map(|func_and_vars| { + func_and_vars + .iter() + .flat_map(|(_, reqs)| { + reqs.enums() + .iter() + .filter_map(|var| { + if var.maybe_associated_contract(analyzer) == Some(*self) { + Some(*var) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + used.sort_by(|a, b| a.0.cmp(&b.0)); + used.dedup(); + + if used.is_empty() { + Ok("".to_string()) + } else { + Ok(format!( + " {}\n", + self.enums(analyzer) + .iter() + .filter_map(|enu| { + if used.contains(enu) { + Some(enu.reconstruct_src(analyzer).ok()?.to_string()) + } else { + None + } + }) + .collect::>() + .join("\n ") + )) + } + } + + pub fn reconstruct_tys( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let mut used = contract_to_funcs + .values() + .flat_map(|func_and_vars| { + func_and_vars + .iter() + .flat_map(|(_, reqs)| { + reqs.tys() + .iter() + .filter_map(|var| { + if var.maybe_associated_contract(analyzer) == Some(*self) { + Some(*var) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + used.sort_by(|a, b| a.0.cmp(&b.0)); + used.dedup(); + + if used.is_empty() { + Ok("".to_string()) + } else { + Ok(format!( + " {}\n", + self.tys(analyzer) + .iter() + .filter_map(|ty| { + if used.contains(ty) { + Some(format!("{};", ty.reconstruct_src(analyzer).ok()?)) + } else { + None + } + }) + .collect::>() + .join("\n ") + )) + } + } + + pub fn reconstruct_errs( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let mut used = contract_to_funcs + .values() + .flat_map(|func_and_vars| { + func_and_vars + .iter() + .flat_map(|(_, reqs)| { + reqs.errs() + .iter() + .filter_map(|var| { + if var.maybe_associated_contract(analyzer) == Some(*self) { + Some(*var) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + used.sort_by(|a, b| a.0.cmp(&b.0)); + used.dedup(); + + if used.is_empty() { + Ok("".to_string()) + } else { + Ok(format!( + " {}\n", + self.errs(analyzer) + .iter() + .filter_map(|err| { + if used.contains(err) { + Some(format!("{};", err.reconstruct_src(analyzer).ok()?)) + } else { + None + } + }) + .collect::>() + .join("\n ") + )) + } + } + + pub fn reconstruct( + &self, + analyzer: &impl AnalyzerBackend, + contract_to_funcs: &BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + >, + ) -> Result { + let mut used_storage = contract_to_funcs + .values() + .flat_map(|func_and_vars| { + func_and_vars + .iter() + .flat_map(|(_, reqs)| { + reqs.storage + .iter() + .filter_map(|var| { + if var.maybe_associated_contract(analyzer) == Some(*self) { + Some(*var) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + used_storage.sort_by(|a, b| a.0.cmp(&b.0)); + used_storage.dedup(); + + let reconstructed_name = self.reconstruct_name_src(analyzer)?; + let inherited = self.reconstruct_inherits(analyzer, contract_to_funcs)?; + let usings = self.reconstruct_usings(analyzer, contract_to_funcs)?; + let storage = self.reconstruct_storage(analyzer, used_storage)?; + let structs = self.reconstruct_structs(analyzer, contract_to_funcs)?; + let enums = self.reconstruct_enums(analyzer, contract_to_funcs)?; + let tys = self.reconstruct_tys(analyzer, contract_to_funcs)?; + let _errs = self.reconstruct_errs(analyzer, contract_to_funcs)?; + let funcs = self.reconstruct_funcs(analyzer, contract_to_funcs)?; + Ok(format!( + "{reconstructed_name} {inherited} {{\n{usings}{structs}{enums}{tys}{storage}{funcs}\n}}" + )) + } + + pub fn using_contracts(&self, analyzer: &impl AnalyzerBackend) -> Vec { + analyzer + .graph() + .edges_directed(self.0.into(), Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::UsingContract(_))) + .map(|edge| ContractNode::from(edge.target())) + .collect() + } +} + +impl FunctionNode { + pub fn reconstruct_src(&self, analyzer: &impl AnalyzerBackend) -> Result { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + + // let overriding = self.get_overriding(analyzer)?; + if let Some(body_loc) = self.body_loc(analyzer)? { + let body = + &analyzer.file_mapping().get(&file_no).unwrap()[body_loc.start()..body_loc.end()]; + Ok(format!( + "{} {body}", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } else { + Ok(format!( + "{};", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } + } + + pub fn maybe_used_storage(&self, analyzer: &mut impl AnalyzerBackend) -> Option> { + self.maybe_body_ctx(analyzer) + .map(|body_ctx| body_ctx.contract_vars_referenced_global(analyzer)) + } + + pub fn maybe_used_usertypes( + &self, + analyzer: &mut impl AnalyzerBackend, + ) -> Option> { + self.maybe_body_ctx(analyzer) + .map(|body_ctx| body_ctx.usertype_vars_referenced_global(analyzer)) + } + + pub fn reconstruction_requirements( + &self, + analyzer: &mut impl AnalyzerBackend, + ) -> FuncReconstructionReqs { + FuncReconstructionReqs { + storage: self.maybe_used_storage(analyzer).unwrap_or_default(), + usertypes: self.maybe_used_usertypes(analyzer).unwrap_or_default(), + } + } +} + +impl TyNode { + pub fn reconstruct_src(&self, analyzer: &impl AnalyzerBackend) -> Result { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + Ok(format!( + "{};\n", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } +} + +impl EnumNode { + pub fn reconstruct_src(&self, analyzer: &impl AnalyzerBackend) -> Result { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + Ok(format!( + "{}\n", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } +} + +impl StructNode { + pub fn reconstruct_src(&self, analyzer: &impl AnalyzerBackend) -> Result { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + Ok(format!( + "{}\n", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } +} + +impl ErrorNode { + pub fn reconstruct_src(&self, analyzer: &impl AnalyzerBackend) -> Result { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + Ok(format!( + "{};\n", + &analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()] + )) + } +} diff --git a/crates/graph/src/nodes/enum_ty.rs b/crates/graph/src/nodes/enum_ty.rs index 5594bdd0..69e69949 100644 --- a/crates/graph/src/nodes/enum_ty.rs +++ b/crates/graph/src/nodes/enum_ty.rs @@ -1,8 +1,13 @@ -use crate::{nodes::Concrete, range::elem::Elem, AsDotStr, GraphBackend, Node, SolcRange}; +use crate::{ + nodes::{Concrete, ContractNode}, + range::elem::Elem, + AsDotStr, Edge, GraphBackend, Node, SolcRange, +}; use shared::{GraphError, NodeIdx, RangeArena}; use ethers_core::types::U256; +use petgraph::visit::EdgeRef; use solang_parser::pt::{EnumDefinition, Identifier, Loc}; /// An index in the graph that references a [`Enum`] node @@ -49,7 +54,7 @@ impl EnumNode { .underlying(analyzer)? .name .clone() - .expect("Unnamed contract") + .expect("Unnamed enum") .name) } @@ -83,6 +88,23 @@ impl EnumNode { let max = Concrete::from(val).into(); Ok(SolcRange::new(min, max, vec![])) } + + pub fn maybe_associated_contract(&self, analyzer: &impl GraphBackend) -> Option { + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::Enum)) + .filter_map(|edge| { + let node = edge.target(); + match analyzer.node(node) { + Node::Contract(_) => Some(ContractNode::from(node)), + _ => None, + } + }) + .take(1) + .next() + .map(ContractNode::from) + } } impl From for NodeIdx { diff --git a/crates/graph/src/nodes/err_ty.rs b/crates/graph/src/nodes/err_ty.rs index 1f72f8fa..cd157529 100644 --- a/crates/graph/src/nodes/err_ty.rs +++ b/crates/graph/src/nodes/err_ty.rs @@ -1,6 +1,12 @@ -use crate::{nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, Node}; +use crate::{ + nodes::{Concrete, ContractNode}, + range::elem::Elem, + AnalyzerBackend, AsDotStr, Edge, GraphBackend, Node, +}; use shared::{GraphError, NodeIdx, RangeArena}; + +use petgraph::visit::EdgeRef; use solang_parser::pt::{ErrorDefinition, ErrorParameter, Expression, Identifier, Loc}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] @@ -18,6 +24,32 @@ impl ErrorNode { ))), } } + + pub fn name(&self, analyzer: &impl GraphBackend) -> Result { + Ok(self + .underlying(analyzer)? + .name + .clone() + .expect("Unnamed error") + .name) + } + + pub fn maybe_associated_contract(&self, analyzer: &impl GraphBackend) -> Option { + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::Error)) + .filter_map(|edge| { + let node = edge.target(); + match analyzer.node(node) { + Node::Contract(_) => Some(ContractNode::from(node)), + _ => None, + } + }) + .take(1) + .next() + .map(ContractNode::from) + } } impl AsDotStr for ErrorNode { fn as_dot_str( diff --git a/crates/graph/src/nodes/func_ty.rs b/crates/graph/src/nodes/func_ty.rs index 4b0676ea..1adbcf3d 100644 --- a/crates/graph/src/nodes/func_ty.rs +++ b/crates/graph/src/nodes/func_ty.rs @@ -1,6 +1,5 @@ use crate::{ - nodes::Concrete, - nodes::{ContextNode, ContractNode, SourceUnitNode, SourceUnitPartNode}, + nodes::{Concrete, ContextNode, ContractNode, SourceUnitNode, SourceUnitPartNode}, range::elem::Elem, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, Node, SolcRange, VarType, }; @@ -17,6 +16,12 @@ use solang_parser::{ }; use std::collections::BTreeMap; +pub enum FuncVis { + Pure, + View, + Mut, +} + #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FunctionNode(pub usize); impl FunctionNode { @@ -127,15 +132,31 @@ impl FunctionNode { pub fn name(&self, analyzer: &impl GraphBackend) -> Result { match self.underlying(analyzer)?.ty { FunctionTy::Constructor => Ok(format!( - "constructor({})", + "{}.constructor({})", + self.maybe_slow_associated_contract(analyzer) + .unwrap() + .name(analyzer) + .unwrap(), self.params(analyzer) .iter() .map(|param| { param.ty_str(analyzer).unwrap() }) .collect::>() .join(", ") )), - FunctionTy::Receive => Ok("receive()".to_string()), - FunctionTy::Fallback => Ok("fallback()".to_string()), + FunctionTy::Receive => Ok(format!( + "{}.receive()", + self.maybe_slow_associated_contract(analyzer) + .unwrap() + .name(analyzer) + .unwrap() + )), + FunctionTy::Fallback => Ok(format!( + "{}.fallback()", + self.maybe_slow_associated_contract(analyzer) + .unwrap() + .name(analyzer) + .unwrap() + )), _ => Ok(self .underlying(analyzer)? .name @@ -214,6 +235,44 @@ impl FunctionNode { } } + pub fn maybe_slow_associated_contract( + &self, + analyzer: &impl GraphBackend, + ) -> Option { + if let Some(maybe_contract) = self + .underlying(analyzer) + .unwrap() + .cache + .maybe_associated_contract + { + maybe_contract + } else { + let contract = analyzer + .graph() + .edges_directed(self.0.into(), Direction::Outgoing) + .filter(|edge| { + matches!( + *edge.weight(), + Edge::Func + | Edge::Modifier + | Edge::Constructor + | Edge::ReceiveFunc + | Edge::FallbackFunc + ) + }) + .filter_map(|edge| { + let node = edge.target(); + match analyzer.node(node) { + Node::Contract(_) => Some(ContractNode::from(node)), + _ => None, + } + }) + .take(1) + .next(); + contract + } + } + pub fn maybe_associated_contract( &self, analyzer: &mut impl AnalyzerBackend, @@ -497,6 +556,16 @@ impl FunctionNode { .any(|attr| matches!(attr, FunctionAttribute::Mutability(Mutability::View(_))))) } + pub fn visibility(&self, analyzer: &impl GraphBackend) -> Result { + if self.is_pure(analyzer)? { + Ok(FuncVis::Pure) + } else if self.is_view(analyzer)? { + Ok(FuncVis::View) + } else { + Ok(FuncVis::Mut) + } + } + pub fn get_overriding( &self, other: &Self, diff --git a/crates/graph/src/nodes/mod.rs b/crates/graph/src/nodes/mod.rs index 819a05b2..5bfc3f86 100644 --- a/crates/graph/src/nodes/mod.rs +++ b/crates/graph/src/nodes/mod.rs @@ -39,3 +39,6 @@ pub use source_unit_part::*; mod source_unit; pub use source_unit::*; + +mod debug_reconstruction; +pub use debug_reconstruction::*; diff --git a/crates/graph/src/nodes/source_unit.rs b/crates/graph/src/nodes/source_unit.rs index cf3a578c..14ea0ce9 100644 --- a/crates/graph/src/nodes/source_unit.rs +++ b/crates/graph/src/nodes/source_unit.rs @@ -1,5 +1,8 @@ use crate::{ - nodes::{Concrete, ContractNode, FunctionNode, SourceUnitPartNode, StructNode, VarNode}, + nodes::{ + Concrete, ContractNode, EnumNode, ErrorNode, FunctionNode, SourceUnitPartNode, StructNode, + TyNode, VarNode, + }, range::elem::Elem, AsDotStr, GraphBackend, Node, }; @@ -87,6 +90,45 @@ impl SourceUnitNode { Ok(&self.underlying(analyzer)?.parts) } + pub fn visible_nodes(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + let mut vis = self + .visible_funcs(analyzer)? + .iter() + .map(|i| i.0.into()) + .collect::>(); + vis.extend( + self.visible_structs(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + vis.extend( + self.visible_enums(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + vis.extend( + self.visible_errors(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + vis.extend( + self.visible_tys(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + vis.extend( + self.visible_constants(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + vis.extend( + self.visible_contracts(analyzer)? + .iter() + .map(|i| NodeIdx::from(i.0)), + ); + Ok(vis) + } + pub fn visible_funcs( &self, analyzer: &impl GraphBackend, @@ -111,6 +153,36 @@ impl SourceUnitNode { Ok(nodes) } + pub fn visible_enums(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + let mut nodes = vec![]; + self.parts(analyzer)?.iter().try_for_each(|part| { + nodes.extend(part.visible_enums(analyzer)?); + Ok(()) + })?; + Ok(nodes) + } + + pub fn visible_errors( + &self, + analyzer: &impl GraphBackend, + ) -> Result, GraphError> { + let mut nodes = vec![]; + self.parts(analyzer)?.iter().try_for_each(|part| { + nodes.extend(part.visible_errors(analyzer)?); + Ok(()) + })?; + Ok(nodes) + } + + pub fn visible_tys(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { + let mut nodes = vec![]; + self.parts(analyzer)?.iter().try_for_each(|part| { + nodes.extend(part.visible_tys(analyzer)?); + Ok(()) + })?; + Ok(nodes) + } + pub fn visible_constants( &self, analyzer: &impl GraphBackend, diff --git a/crates/graph/src/nodes/source_unit_part.rs b/crates/graph/src/nodes/source_unit_part.rs index b4042c74..e83f40e1 100644 --- a/crates/graph/src/nodes/source_unit_part.rs +++ b/crates/graph/src/nodes/source_unit_part.rs @@ -1,5 +1,7 @@ use crate::{ - nodes::{Concrete, ContractNode, FunctionNode, StructNode, VarNode}, + nodes::{ + Concrete, ContractNode, EnumNode, ErrorNode, FunctionNode, StructNode, TyNode, VarNode, + }, range::elem::Elem, AsDotStr, GraphBackend, Node, }; @@ -14,6 +16,9 @@ pub struct SourceUnitPart { pub structs: Vec, pub constants: Vec, pub contracts: Vec, + pub enums: Vec, + pub tys: Vec, + pub errors: Vec, } impl SourceUnitPart { @@ -113,6 +118,27 @@ impl SourceUnitPartNode { Ok(&self.underlying(analyzer)?.contracts) } + pub fn visible_enums<'a>( + &self, + analyzer: &'a impl GraphBackend, + ) -> Result<&'a Vec, GraphError> { + Ok(&self.underlying(analyzer)?.enums) + } + + pub fn visible_tys<'a>( + &self, + analyzer: &'a impl GraphBackend, + ) -> Result<&'a Vec, GraphError> { + Ok(&self.underlying(analyzer)?.tys) + } + + pub fn visible_errors<'a>( + &self, + analyzer: &'a impl GraphBackend, + ) -> Result<&'a Vec, GraphError> { + Ok(&self.underlying(analyzer)?.errors) + } + pub fn add_func( &self, func: FunctionNode, @@ -148,4 +174,27 @@ impl SourceUnitPartNode { self.underlying_mut(analyzer)?.constants.push(constant); Ok(()) } + + pub fn add_enum( + &self, + enu: EnumNode, + analyzer: &mut impl GraphBackend, + ) -> Result<(), GraphError> { + self.underlying_mut(analyzer)?.enums.push(enu); + Ok(()) + } + + pub fn add_ty(&self, ty: TyNode, analyzer: &mut impl GraphBackend) -> Result<(), GraphError> { + self.underlying_mut(analyzer)?.tys.push(ty); + Ok(()) + } + + pub fn add_error( + &self, + error: ErrorNode, + analyzer: &mut impl GraphBackend, + ) -> Result<(), GraphError> { + self.underlying_mut(analyzer)?.errors.push(error); + Ok(()) + } } diff --git a/crates/graph/src/nodes/struct_ty.rs b/crates/graph/src/nodes/struct_ty.rs index f3c39066..1fbd0ca4 100644 --- a/crates/graph/src/nodes/struct_ty.rs +++ b/crates/graph/src/nodes/struct_ty.rs @@ -1,6 +1,7 @@ use crate::{ - nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, Edge, GraphBackend, Node, - VarType, + nodes::{Concrete, ContractNode}, + range::elem::Elem, + AnalyzerBackend, AsDotStr, Edge, GraphBackend, Node, VarType, }; use shared::{GraphError, NodeIdx, RangeArena}; @@ -64,6 +65,23 @@ impl StructNode { .map(|edge| FieldNode::from(edge.source())) .find(|field_node| field_node.name(analyzer).unwrap() == ident.name) } + + pub fn maybe_associated_contract(&self, analyzer: &impl GraphBackend) -> Option { + analyzer + .graph() + .edges_directed(self.0.into(), Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::Struct)) + .filter_map(|edge| { + let node = edge.target(); + match analyzer.node(node) { + Node::Contract(_) => Some(ContractNode::from(node)), + _ => None, + } + }) + .take(1) + .next() + .map(ContractNode::from) + } } impl AsDotStr for StructNode { diff --git a/crates/graph/src/nodes/ty_ty.rs b/crates/graph/src/nodes/ty_ty.rs index cdd0097f..054db2ad 100644 --- a/crates/graph/src/nodes/ty_ty.rs +++ b/crates/graph/src/nodes/ty_ty.rs @@ -1,9 +1,12 @@ use crate::{ - nodes::Concrete, range::elem::Elem, AnalyzerBackend, AsDotStr, GraphBackend, Node, VarType, + nodes::{Concrete, ContractNode}, + range::elem::Elem, + AnalyzerBackend, AsDotStr, Edge, GraphBackend, Node, VarType, }; use shared::{GraphError, NodeIdx, RangeArena}; +use petgraph::visit::EdgeRef; use solang_parser::pt::{Expression, Identifier, Loc, TypeDefinition}; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] @@ -25,6 +28,23 @@ impl TyNode { pub fn name(&self, analyzer: &impl GraphBackend) -> Result { Ok(self.underlying(analyzer)?.name.to_string()) } + + pub fn maybe_associated_contract(&self, analyzer: &impl GraphBackend) -> Option { + analyzer + .graph() + .edges_directed(self.0.into(), petgraph::Direction::Outgoing) + .filter(|edge| matches!(*edge.weight(), Edge::Ty)) + .filter_map(|edge| { + let node = edge.target(); + match analyzer.node(node) { + Node::Contract(_) => Some(ContractNode::from(node)), + _ => None, + } + }) + .take(1) + .next() + .map(ContractNode::from) + } } impl From for NodeIdx { diff --git a/crates/graph/src/nodes/var_ty.rs b/crates/graph/src/nodes/var_ty.rs index 13893402..42a4e948 100644 --- a/crates/graph/src/nodes/var_ty.rs +++ b/crates/graph/src/nodes/var_ty.rs @@ -53,15 +53,21 @@ impl VarNode { parent: NodeIdx, ) -> Result<(), GraphError> { if let Some(expr) = self.underlying(analyzer)?.initializer_expr.clone() { - tracing::trace!("Parsing variable initializer"); + tracing::trace!( + "Parsing variable initializer for {}", + self.underlying(analyzer)?.name.as_ref().unwrap().name + ); let init = analyzer.parse_expr(arena, &expr, Some(parent)); + 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)? { - set = true; - self.underlying_mut(analyzer)?.initializer = Some(initer.ty_idx()); + if let Some(conc_idx) = initer.builtin_to_concrete_idx(analyzer, arena)? { + set = true; + self.underlying_mut(analyzer)?.initializer = Some(conc_idx); + } } } } @@ -175,6 +181,15 @@ impl VarNode { .map(|edge| ContextVarNode::from(edge.source())) .collect() } + + pub fn reconstruct_src<'a>( + &self, + analyzer: &'a impl AnalyzerBackend, + ) -> Result<&'a str, GraphError> { + let loc = self.underlying(analyzer)?.loc; + let file_no = loc.try_file_no().unwrap(); + Ok(&analyzer.file_mapping().get(&file_no).unwrap()[loc.start()..loc.end()]) + } } impl AsDotStr for VarNode { diff --git a/crates/graph/src/range/elem/concrete.rs b/crates/graph/src/range/elem/concrete.rs index 0ccf48ae..218d756b 100644 --- a/crates/graph/src/range/elem/concrete.rs +++ b/crates/graph/src/range/elem/concrete.rs @@ -145,6 +145,7 @@ impl RangeElem for RangeConcrete { Ok(()) } + #[allow(clippy::only_used_in_recursion)] fn range_eq(&self, other: &Self, arena: &mut RangeArena>) -> bool { match (self.val.into_u256(), other.val.into_u256()) { (Some(self_val), Some(other_val)) => self_val == other_val, diff --git a/crates/graph/src/range/elem/elem_enum/impls.rs b/crates/graph/src/range/elem/elem_enum/impls.rs index 65351d07..b657ffe9 100644 --- a/crates/graph/src/range/elem/elem_enum/impls.rs +++ b/crates/graph/src/range/elem/elem_enum/impls.rs @@ -557,7 +557,6 @@ impl Elem { pub fn arenaized_flattened( &self, max: bool, - analyzer: &mut impl GraphBackend, arena: &mut RangeArena>, ) -> Option>> { if let Some(idx) = arena.idx(self) { @@ -586,7 +585,7 @@ impl Elem { } c @ Elem::Concrete(_) => Some(Box::new(c.clone())), c @ Elem::Null => Some(Box::new(c.clone())), - Elem::Arena(idx) => Elem::Arena(*idx).arenaized_flattened(max, analyzer, arena), + Elem::Arena(idx) => Elem::Arena(*idx).arenaized_flattened(max, arena), } } else { None diff --git a/crates/graph/src/range/elem/expr/collapse.rs b/crates/graph/src/range/elem/expr/collapse.rs index d043005b..641ea576 100644 --- a/crates/graph/src/range/elem/expr/collapse.rs +++ b/crates/graph/src/range/elem/expr/collapse.rs @@ -109,7 +109,7 @@ pub fn collapse( match (expr.op, op) { (RangeOp::Sub(false), _) if ORD_OPS.contains(&op) => { - if let Some(res) = sub_ord_rules(x, y, op, &z, ords, arena) { + if let Some(res) = sub_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { MaybeCollapsed::Not(Elem::Expr(expr), z) @@ -170,14 +170,14 @@ pub fn collapse( match (expr.op, op) { (RangeOp::Sub(false), _) if ORD_OPS.contains(&op) => { - if let Some(res) = sub_ord_rules(x, y, op, &z, ords, arena) { + if let Some(res) = sub_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { MaybeCollapsed::Not(Elem::Expr(expr), z) } } (RangeOp::Add(false), _) if ORD_OPS.contains(&op) => { - if let Some(res) = add_ord_rules(x, y, op, &z, ords, arena) { + if let Some(res) = add_ord_rules(x, y, op, &z, ords) { MaybeCollapsed::Collapsed(res) } else { MaybeCollapsed::Not(Elem::Expr(expr), z) @@ -186,9 +186,9 @@ pub fn collapse( (RangeOp::Eq, RangeOp::Eq) => { // ((x == y) == z) // can skip if x and z eq - if ords.x_eq_z() || ords.y_eq_z() { - MaybeCollapsed::Collapsed(Elem::Expr(expr)) - } else if z.range_eq(&Elem::from(Concrete::from(true)), arena) { + if (ords.x_eq_z() || ords.y_eq_z()) + || z.range_eq(&Elem::from(Concrete::from(true)), arena) + { MaybeCollapsed::Collapsed(Elem::Expr(expr)) } else { MaybeCollapsed::Not(Elem::Expr(expr), z) diff --git a/crates/graph/src/range/elem/expr/mod.rs b/crates/graph/src/range/elem/expr/mod.rs index 5fd3cb74..d7023ee4 100644 --- a/crates/graph/src/range/elem/expr/mod.rs +++ b/crates/graph/src/range/elem/expr/mod.rs @@ -282,12 +282,8 @@ impl RangeElem for RangeExpr { ) -> bool { self.flattened_min.is_some() && self.flattened_max.is_some() || { if let Some(idx) = self.arena_idx(arena) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Expr(ref arenaized) = *t { - arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() - } else { - false - } + if let Some(Elem::Expr(ref arenaized)) = arena.ranges.get(idx) { + arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() } else { false } @@ -304,12 +300,8 @@ impl RangeElem for RangeExpr { ) -> (bool, bool) { let (arena_cached_min, arena_cached_max) = { if let Some(idx) = self.arena_idx(arena) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Expr(ref arenaized) = *t { - (arenaized.minimized.is_some(), arenaized.maximized.is_some()) - } else { - (false, false) - } + if let Some(Elem::Expr(ref arenaized)) = arena.ranges.get(idx) { + (arenaized.minimized.is_some(), arenaized.maximized.is_some()) } else { (false, false) } @@ -528,7 +520,7 @@ impl RangeElem for RangeExpr { ) -> Result, GraphError> { let Elem::Expr(this) = this else { this.cache_flatten(analyzer, arena)?; - if let Some(t) = this.arenaized_flattened(false, analyzer, arena) { + if let Some(t) = this.arenaized_flattened(false, arena) { return Ok(*t); } else { return Ok(this.clone()); @@ -588,7 +580,7 @@ impl RangeElem for RangeExpr { ) -> Result, GraphError> { let Elem::Expr(this) = this else { this.cache_flatten(analyzer, arena)?; - if let Some(t) = this.arenaized_flattened(true, analyzer, arena) { + if let Some(t) = this.arenaized_flattened(true, arena) { return Ok(*t); } else { return Ok(this.clone()); @@ -643,11 +635,9 @@ impl RangeElem for RangeExpr { if self.flattened_max.is_none() { if let Some(idx) = self.arena_idx(arena) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Expr(ref arenaized) = *t { - if arenaized.flattened_max.is_some() { - return Ok(()); - } + if let Some(Elem::Expr(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_max.is_some() { + return Ok(()); } }; } else { @@ -665,11 +655,9 @@ impl RangeElem for RangeExpr { if self.flattened_min.is_none() { if let Some(idx) = self.arena_idx(arena) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Expr(ref arenaized) = *t { - if arenaized.flattened_min.is_some() { - return Ok(()); - } + if let Some(Elem::Expr(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_min.is_some() { + return Ok(()); } }; } else { diff --git a/crates/graph/src/range/elem/expr/simplify/add.rs b/crates/graph/src/range/elem/expr/simplify/add.rs index 463d0885..f7e30a0e 100644 --- a/crates/graph/src/range/elem/expr/simplify/add.rs +++ b/crates/graph/src/range/elem/expr/simplify/add.rs @@ -6,15 +6,12 @@ use crate::{ }, }; -use shared::RangeArena; - pub fn add_ord_rules( x: &Elem, y: &Elem, ord_op: RangeOp, z: &Elem, ords: Ords, - arena: &mut RangeArena>, ) -> Option> { match ord_op { RangeOp::Eq => { @@ -118,7 +115,7 @@ pub fn add_ord_rules( RangeOp::Max => { // max{x + y, z} // same as gt but return lhs or rhs instead - match add_ord_rules(x, y, RangeOp::Gt, z, ords, arena) { + match add_ord_rules(x, y, RangeOp::Gt, z, ords) { Some(Elem::Concrete(RangeConcrete { val: Concrete::Bool(b), .. @@ -139,7 +136,7 @@ pub fn add_ord_rules( RangeOp::Min => { // min{x - y, z} // same as lt but return lhs or rhs instead - match add_ord_rules(x, y, RangeOp::Lt, z, ords, arena) { + match add_ord_rules(x, y, RangeOp::Lt, z, ords) { Some(Elem::Concrete(RangeConcrete { val: Concrete::Bool(b), .. diff --git a/crates/graph/src/range/elem/expr/simplify/sub.rs b/crates/graph/src/range/elem/expr/simplify/sub.rs index a941bcd4..83d0e00e 100644 --- a/crates/graph/src/range/elem/expr/simplify/sub.rs +++ b/crates/graph/src/range/elem/expr/simplify/sub.rs @@ -6,15 +6,12 @@ use crate::{ }, }; -use shared::RangeArena; - pub fn sub_ord_rules( x: &Elem, y: &Elem, ord_op: RangeOp, z: &Elem, ords: Ords, - arena: &mut RangeArena>, ) -> Option> { match ord_op { RangeOp::Eq => { @@ -133,7 +130,7 @@ pub fn sub_ord_rules( RangeOp::Max => { // max{x - y, z} // same as gt but return lhs or rhs instead - match sub_ord_rules(x, y, RangeOp::Gt, z, ords, arena) { + match sub_ord_rules(x, y, RangeOp::Gt, z, ords) { Some(Elem::Concrete(RangeConcrete { val: Concrete::Bool(b), .. @@ -154,7 +151,7 @@ pub fn sub_ord_rules( RangeOp::Min => { // min{x - y, z} // same as lt but return lhs or rhs instead - match sub_ord_rules(x, y, RangeOp::Lt, z, ords, arena) { + match sub_ord_rules(x, y, RangeOp::Lt, z, ords) { Some(Elem::Concrete(RangeConcrete { val: Concrete::Bool(b), .. diff --git a/crates/graph/src/range/elem/reference.rs b/crates/graph/src/range/elem/reference.rs index 0980b431..530bfc32 100644 --- a/crates/graph/src/range/elem/reference.rs +++ b/crates/graph/src/range/elem/reference.rs @@ -187,12 +187,8 @@ impl RangeElem for Reference { ) -> bool { self.flattened_min.is_some() && self.flattened_max.is_some() || { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() - } else { - false - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + arenaized.flattened_min.is_some() && arenaized.flattened_max.is_some() } else { false } @@ -209,12 +205,8 @@ impl RangeElem for Reference { ) -> (bool, bool) { let (arena_cached_min, arena_cached_max) = { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - (arenaized.minimized.is_some(), arenaized.maximized.is_some()) - } else { - (false, false) - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + (arenaized.minimized.is_some(), arenaized.maximized.is_some()) } else { (false, false) } @@ -237,12 +229,10 @@ impl RangeElem for Reference { if self.flattened_max.is_none() { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - if arenaized.flattened_max.is_some() { - tracing::trace!("reference cache flatten hit"); - return Ok(()); - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_max.is_some() { + tracing::trace!("reference cache flatten hit"); + return Ok(()); } } } @@ -255,12 +245,10 @@ impl RangeElem for Reference { } if self.flattened_min.is_none() { if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - if arenaized.flattened_min.is_some() { - tracing::trace!("reference cache flatten hit"); - return Ok(()); - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_min.is_some() { + tracing::trace!("reference cache flatten hit"); + return Ok(()); } } } @@ -292,12 +280,10 @@ impl RangeElem for Reference { } if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - tracing::trace!("reference maximize cache hit"); - if let Some(MinMaxed::Maximized(cached)) = arenaized.maximized.clone() { - return Ok(*cached); - } + 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); } } } @@ -332,12 +318,10 @@ impl RangeElem for Reference { } if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - if let Some(MinMaxed::Minimized(cached)) = arenaized.minimized.clone() { - tracing::trace!("reference minimize cache hit"); - return Ok(*cached); - } + 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); } } } @@ -372,12 +356,10 @@ impl RangeElem for Reference { } if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - if arenaized.flattened_max.is_some() { - tracing::trace!("reference simplify maximize cache hit"); - return Ok(*arenaized.flattened_max.clone().unwrap()); - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_max.is_some() { + tracing::trace!("reference simplify maximize cache hit"); + return Ok(*arenaized.flattened_max.clone().unwrap()); } } } @@ -406,12 +388,10 @@ impl RangeElem for Reference { } if let Some(idx) = arena.idx(&Elem::Reference(Reference::new(self.idx))) { - if let Some(t) = arena.ranges.get(idx) { - if let Elem::Reference(ref arenaized) = *t { - if arenaized.flattened_min.is_some() { - tracing::trace!("reference simplify minimize cache hit"); - return Ok(*arenaized.flattened_min.clone().unwrap()); - } + if let Some(Elem::Reference(ref arenaized)) = arena.ranges.get(idx) { + if arenaized.flattened_min.is_some() { + tracing::trace!("reference simplify minimize cache hit"); + return Ok(*arenaized.flattened_min.clone().unwrap()); } } } diff --git a/crates/graph/src/range/exec/bitwise.rs b/crates/graph/src/range/exec/bitwise.rs index 087f8165..3f74dfb5 100644 --- a/crates/graph/src/range/exec/bitwise.rs +++ b/crates/graph/src/range/exec/bitwise.rs @@ -1,6 +1,5 @@ use crate::nodes::{Builtin, Concrete}; use crate::range::{elem::*, exec_traits::*}; -use crate::GraphBackend; use shared::RangeArena; @@ -242,7 +241,6 @@ pub fn exec_bit_and( rhs_min: &Elem, rhs_max: &Elem, maximize: bool, - analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Option> { match (lhs_min, lhs_max, rhs_min, rhs_max) { @@ -253,7 +251,6 @@ pub fn exec_bit_and( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -264,7 +261,6 @@ pub fn exec_bit_and( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -275,7 +271,6 @@ pub fn exec_bit_and( &d.as_sized_bytes()?, rhs_max, maximize, - analyzer, arena, ); } @@ -286,7 +281,6 @@ pub fn exec_bit_and( rhs_min, &d.as_sized_bytes()?, maximize, - analyzer, arena, ); } @@ -387,7 +381,6 @@ pub fn exec_bit_or( rhs_min: &Elem, rhs_max: &Elem, maximize: bool, - analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Option> { match (lhs_min, lhs_max, rhs_min, rhs_max) { @@ -398,7 +391,6 @@ pub fn exec_bit_or( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -409,7 +401,6 @@ pub fn exec_bit_or( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -420,7 +411,6 @@ pub fn exec_bit_or( &d.as_sized_bytes()?, rhs_max, maximize, - analyzer, arena, ); } @@ -431,7 +421,6 @@ pub fn exec_bit_or( rhs_min, &d.as_sized_bytes()?, maximize, - analyzer, arena, ); } @@ -506,7 +495,6 @@ pub fn exec_bit_xor( rhs_min: &Elem, rhs_max: &Elem, maximize: bool, - analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Option> { match (lhs_min, lhs_max, rhs_min, rhs_max) { @@ -517,7 +505,6 @@ pub fn exec_bit_xor( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -528,7 +515,6 @@ pub fn exec_bit_xor( rhs_min, rhs_max, maximize, - analyzer, arena, ); } @@ -539,7 +525,6 @@ pub fn exec_bit_xor( &d.as_sized_bytes()?, rhs_max, maximize, - analyzer, arena, ); } @@ -550,7 +535,6 @@ pub fn exec_bit_xor( rhs_min, &d.as_sized_bytes()?, maximize, - analyzer, arena, ); } @@ -634,15 +618,14 @@ pub fn exec_bit_not( lhs_min: &Elem, lhs_max: &Elem, maximize: bool, - analyzer: &impl GraphBackend, arena: &mut RangeArena>, ) -> Option> { match (lhs_min, lhs_max) { (Elem::ConcreteDyn(d), _) => { - return exec_bit_not(&d.as_sized_bytes()?, lhs_max, maximize, analyzer, arena); + return exec_bit_not(&d.as_sized_bytes()?, lhs_max, maximize, arena); } (_, Elem::ConcreteDyn(d)) => { - return exec_bit_not(lhs_min, &d.as_sized_bytes()?, maximize, analyzer, arena); + return exec_bit_not(lhs_min, &d.as_sized_bytes()?, maximize, arena); } _ => {} } diff --git a/crates/graph/src/range/exec/exec_op.rs b/crates/graph/src/range/exec/exec_op.rs index 876e8e14..1f89c625 100644 --- a/crates/graph/src/range/exec/exec_op.rs +++ b/crates/graph/src/range/exec/exec_op.rs @@ -32,14 +32,12 @@ impl ExecOp for RangeExpr { let res = self.exec(self.spread(analyzer, arena)?, maximize, analyzer, arena)?; if let Some(idx) = idx { - if let Some(t) = arena.ranges.get_mut(idx) { - if let Elem::Expr(expr) = &mut *t { - tracing::trace!("setting cache"); - if maximize { - expr.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); - } else { - expr.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); - } + if let Some(Elem::Expr(expr)) = arena.ranges.get_mut(idx) { + tracing::trace!("setting cache"); + if maximize { + expr.maximized = Some(MinMaxed::Maximized(Box::new(res.clone()))); + } else { + expr.minimized = Some(MinMaxed::Minimized(Box::new(res.clone()))); } } } @@ -73,13 +71,11 @@ impl ExecOp for RangeExpr { } if let Some(idx) = self.arena_idx(arena) { - if let Some(t) = arena.ranges.get_mut(idx) { - if let Elem::Expr(expr) = &mut *t { - if maximize { - expr.maximized.clone_from(&self.maximized); - } else { - expr.minimized.clone_from(&self.minimized); - } + if let Some(Elem::Expr(expr)) = arena.ranges.get_mut(idx) { + if maximize { + expr.maximized.clone_from(&self.maximized); + } else { + expr.minimized.clone_from(&self.minimized); } } } @@ -127,138 +123,6 @@ impl ExecOp for RangeExpr { let finished = false; let mut ret = Ok(Elem::Null); - // if self.op == RangeOp::Cast { - // // for a cast we can *actually* evaluate dynamic elem if lhs side is concrete - // if lhs_is_conc { - // ret = self.exec_op(maximize, analyzer); - // finished = true; - // } else { - // // we can drop the cast if the max of the dynamic lhs is less than the cast - // let concretized_lhs = self.lhs.maximize(analyzer, arena)?; - // if matches!( - // concretized_lhs.range_ord(&self.rhs, analyzer), - // Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) - // ) { - // ret = Ok(*self.lhs.clone()); - // finished = true; - // } - // } - // } else if matches!(self.op, RangeOp::Concat | RangeOp::Memcopy) { - // // we can always execute a concat or memcopy - // ret = self.exec_op(maximize, analyzer); - // finished = true; - // } else if matches!( - // self.op, - // RangeOp::SetIndices | RangeOp::SetLength | RangeOp::GetLength | RangeOp::GetIndex - // ) { - // match self.op { - // RangeOp::GetLength => { - // ret = if maximize { - // Ok(lhs_max - // .range_get_length() - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // } else { - // Ok(lhs_min - // .range_get_length() - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // }; - // finished = true; - // } - // RangeOp::SetLength => { - // ret = if maximize { - // Ok(lhs_max - // .range_set_length(&rhs_max) - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // } else { - // Ok(lhs_min - // .range_set_length(&rhs_min) - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // }; - // finished = true; - // } - // RangeOp::GetIndex => { - // if maximize { - // let res = match lhs_max { - // Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val - // .iter() - // .try_fold( - // None, - // |mut acc: Option>, (key, (val, _))| { - // if matches!( - // key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - // Some(true) - // ) { - // if acc.is_none() - // || matches!( - // acc.clone().unwrap().range_ord(val, arena), - // Some(std::cmp::Ordering::Greater) - // ) - // { - // acc = Some(val.clone()); - // Ok(acc) - // } else { - // Ok(acc) - // } - // } else { - // Ok(acc) - // } - // }, - // )? - // .unwrap_or_else(|| Elem::Null), - // _ => Elem::Expr(self.clone()), - // }; - // ret = Ok(res); - // finished = true; - // } else { - // let res = match lhs_max { - // Elem::ConcreteDyn(RangeDyn { ref val, .. }) => val - // .iter() - // .try_fold( - // None, - // |mut acc: Option>, (key, (val, _))| { - // if matches!( - // key.overlaps_dual(&rhs_min, &rhs_max, true, analyzer)?, - // Some(true) - // ) { - // if acc.is_none() - // || matches!( - // acc.clone().unwrap().range_ord(val, arena), - // Some(std::cmp::Ordering::Less) - // ) - // { - // acc = Some(val.clone()); - // Ok(acc) - // } else { - // Ok(acc) - // } - // } else { - // Ok(acc) - // } - // }, - // )? - // .unwrap_or_else(|| Elem::Null), - // _ => Elem::Expr(self.clone()), - // }; - // ret = Ok(res); - // finished = true; - // } - // } - // RangeOp::SetIndices => { - // ret = if maximize { - // Ok(lhs_max - // .range_set_indices(&rhs_max) - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // } else { - // Ok(lhs_min - // .range_set_indices(&rhs_min) - // .unwrap_or_else(|| Elem::Expr(self.clone()))) - // }; - // finished = true; - // } - // _ => unreachable!(), - // } - // } - let parts = (lhs_min, lhs_max, rhs_min, rhs_max); match (lhs_is_conc, rhs_is_conc, finished) { (true, true, false) => { @@ -425,16 +289,14 @@ impl ExecOp for RangeExpr { 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, analyzer, arena, - ), - RangeOp::BitOr => exec_bit_or( - &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, - ), - RangeOp::BitXor => exec_bit_xor( - &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, - ), - RangeOp::BitNot => exec_bit_not(&lhs_min, &lhs_max, maximize, analyzer, arena), + RangeOp::BitAnd => { + exec_bit_and(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, arena) + } + RangeOp::BitOr => exec_bit_or(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, arena), + RangeOp::BitXor => { + exec_bit_xor(&lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, arena) + } + RangeOp::BitNot => exec_bit_not(&lhs_min, &lhs_max, maximize, arena), RangeOp::Shl => exec_shl( &lhs_min, &lhs_max, &rhs_min, &rhs_max, maximize, analyzer, arena, ), diff --git a/crates/graph/src/solvers/brute.rs b/crates/graph/src/solvers/brute.rs index 1839b7e2..7bdb130a 100644 --- a/crates/graph/src/solvers/brute.rs +++ b/crates/graph/src/solvers/brute.rs @@ -465,7 +465,7 @@ impl SolcSolver for BruteBinSearchSolver { if all_good { Ok(AtomicSolveStatus::Sat(mapping)) } else { - println!("thought we solved but we didnt"); + // println!("thought we solved but we didnt"); Ok(AtomicSolveStatus::Indeterminate) } } else { @@ -497,8 +497,6 @@ impl SolcSolver for BruteBinSearchSolver { let _atomic = &self.atomics[i]; let lmr = self.lmrs[i].clone(); - // println!("solving: {i}, {}, successful passes: {}", atomic.idxs[0].display_name(analyzer).unwrap(), self.successful_passes); - // println!("initial range: [{min_s},{max_s}], is_const: {}", atomic.idxs[0].is_const(analyzer)?); match self.check( i, (lmr.low, lmr.mid, lmr.high), @@ -688,7 +686,7 @@ impl SolcSolver for BruteBinSearchSolver { match dl_solver.solve_partial(analyzer, arena)? { SolveStatus::Unsat => { - println!("TRUE UNSAT"); + // println!("TRUE UNSAT"); return Ok((false, None)); } SolveStatus::Sat { diff --git a/crates/graph/src/var_type.rs b/crates/graph/src/var_type.rs index f7cfa9d1..84e9f6a3 100644 --- a/crates/graph/src/var_type.rs +++ b/crates/graph/src/var_type.rs @@ -1,7 +1,7 @@ use crate::{ nodes::{ - BuiltInNode, Builtin, Concrete, ConcreteNode, ContractNode, EnumNode, FunctionNode, - StructNode, TyNode, + BuiltInNode, Builtin, Concrete, ConcreteNode, ContractNode, EnumNode, ErrorNode, + FunctionNode, StructNode, TyNode, }, range::{ elem::{Elem, RangeElem}, @@ -316,6 +316,34 @@ impl VarType { } } + pub fn builtin_to_concrete_idx( + &self, + analyzer: &mut impl AnalyzerBackend, + arena: &mut RangeArena>, + ) -> Result, GraphError> { + match self { + Self::BuiltIn(bn, Some(range)) => { + let Some(min) = range.evaled_range_min(analyzer, arena)?.maybe_concrete() else { + return Ok(None); + }; + let Some(max) = range.evaled_range_max(analyzer, arena)?.maybe_concrete() else { + return Ok(None); + }; + if min.val == max.val { + let builtin = bn.underlying(analyzer)?; + let Some(conc) = min.val.cast(builtin.clone()) else { + return Ok(None); + }; + let conc_idx = analyzer.add_node(Node::Concrete(conc)); + Ok(Some(conc_idx)) + } else { + Ok(None) + } + } + _ => Ok(None), + } + } + pub fn try_literal_cast( self, other: &Self, @@ -407,7 +435,6 @@ impl VarType { } pub fn range(&self, analyzer: &impl GraphBackend) -> Result, GraphError> { - println!("here: {:?}", self); match self { Self::User(_, Some(range)) => Ok(Some(range.clone())), Self::BuiltIn(_, Some(range)) => Ok(Some(range.clone())), @@ -770,17 +797,55 @@ pub enum TypeNode { Contract(ContractNode), Struct(StructNode), Enum(EnumNode), + Error(ErrorNode), Ty(TyNode), Func(FunctionNode), Unresolved(NodeIdx), } +impl From for TypeNode { + fn from(c: ContractNode) -> Self { + TypeNode::Contract(c) + } +} + +impl From for TypeNode { + fn from(c: StructNode) -> Self { + TypeNode::Struct(c) + } +} + +impl From for TypeNode { + fn from(c: EnumNode) -> Self { + TypeNode::Enum(c) + } +} + +impl From for TypeNode { + fn from(c: TyNode) -> Self { + TypeNode::Ty(c) + } +} + +impl From for TypeNode { + fn from(c: ErrorNode) -> Self { + TypeNode::Error(c) + } +} + +impl From for TypeNode { + fn from(c: FunctionNode) -> Self { + TypeNode::Func(c) + } +} + impl TypeNode { pub fn as_string(&self, analyzer: &impl GraphBackend) -> Result { match self { TypeNode::Contract(n) => n.name(analyzer), TypeNode::Struct(n) => n.name(analyzer), TypeNode::Enum(n) => n.name(analyzer), + TypeNode::Error(n) => n.name(analyzer), TypeNode::Ty(n) => n.name(analyzer), TypeNode::Func(n) => Ok(format!("function {}", n.name(analyzer)?)), TypeNode::Unresolved(n) => Ok(format!("UnresolvedType<{:?}>", analyzer.node(*n))), @@ -797,6 +862,7 @@ impl TypeNode { Node::Contract(..) => Ok(TypeNode::Contract((*n).into())), Node::Struct(..) => Ok(TypeNode::Struct((*n).into())), Node::Enum(..) => Ok(TypeNode::Enum((*n).into())), + Node::Error(..) => Ok(TypeNode::Error((*n).into())), Node::Ty(..) => Ok(TypeNode::Ty((*n).into())), Node::Function(..) => Ok(TypeNode::Func((*n).into())), _ => Err(GraphError::NodeConfusion( @@ -814,6 +880,7 @@ impl From for NodeIdx { TypeNode::Contract(n) => n.into(), TypeNode::Struct(n) => n.into(), TypeNode::Enum(n) => n.into(), + TypeNode::Error(n) => n.into(), TypeNode::Ty(n) => n.into(), TypeNode::Func(n) => n.into(), TypeNode::Unresolved(n) => n, diff --git a/crates/pyrometer/benches/flamegraphs/parse.svg b/crates/pyrometer/benches/flamegraphs/parse.svg deleted file mode 100644 index f21d8ff0..00000000 --- a/crates/pyrometer/benches/flamegraphs/parse.svg +++ /dev/null @@ -1,491 +0,0 @@ -Flame Graph Reset ZoomSearch parse`DYLD-STUB$$fcntl (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<core::option::Option<shared::range::SolcRange>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<pyrometer::Analyzer> (2 samples, 5.56%)parse`c..parse`core::ptr::drop_in_place<petgraph::graph_impl::Graph<shared::Node,shared::Edge,petgraph::Directed,usize>> (2 samples, 5.56%)parse`c..parse`core::ptr::drop_in_place<petgraph::graph_impl::Node<shared::Node,usize>> (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memset (1 samples, 2.78%)li..parse`pyrometer::context::exprs::cmp::Cmp::cmp (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cmp::Cmp::cmp::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cmp::Cmp::cmp::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::false_fork_if_cvar (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::true_fork_if_cvar::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::member_access (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`shared::context::ContextNode::visible_funcs (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::IntoIter<K,V,A> as core::ops::drop::Drop>::drop (1 samples, 2.78%)pa..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`core::ops::function::impls::_<impl core::ops::function::FnMut<A> for &mut F>::call_mut (1 samples, 2.78%)pa..parse`alloc::fmt::format::format_inner (1 samples, 2.78%)pa..parse`core::fmt::write (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memmove (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::match_var_def (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::match_var_def::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::try_set_range_max (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::fallback_range (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<T> as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign_exprs (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign_exprs::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign_exprs::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::match_assign_sides (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::advance_var_in_ctx (1 samples, 2.78%)pa..parse`<T as core::option::SpecOptionPartialEq>::eq (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<T> as core::cmp::PartialEq>::eq (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::set_range_max (1 samples, 2.78%)pa..parse`shared::range::elem_ty::Elem<T>::contains_node (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::intrinsic_call::IntrinsicFuncCaller::intrinsic_func_call (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require_inner (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`shared::range::RangeEval::unsat (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as shared::range::Range<shared::nodes::concrete::Concrete>>::evaled_range_min (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::maximize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::maximize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as shared::range::Range<shared::nodes::concrete::Concrete>>::evaled_range_min (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::maximize (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as shared::range::Range<shared::nodes::concrete::Concrete>>::evaled_range_max (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::maximize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::maximize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::update_nonconst_from_nonconst (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::set_range_max (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::cache_range (1 samples, 2.78%)pa..parse`shared::nodes::VarType::range (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<T> as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<T> as core::clone::Clone>::clone (1 samples, 2.78%)pa..libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 2.78%)li..libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (6 samples, 16.67%)parse`pyrometer::context::..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (2 samples, 5.56%)parse`p..parse`core::iter::traits::iterator::Iterator::try_fold (2 samples, 5.56%)parse`c..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::apply_to_edges (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::require::Require::require (2 samples, 5.56%)parse`p..parse`shared::context::var::ContextVarNode::update_deps (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::set_range_max (1 samples, 2.78%)pa..parse`shared::context::var::ContextVar::set_range_max (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::SolcRange> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`pyrometer::context::exprs::variable::Variable::variable (10 samples, 27.78%)parse`pyrometer::context::exprs::variable::Va..parse`pyrometer::context::exprs::env::Env::env_variable (10 samples, 27.78%)parse`pyrometer::context::exprs::env::Env::en..parse`pyrometer::context::func_call::FuncCaller::resume_from_modifier (10 samples, 27.78%)parse`pyrometer::context::func_call::FuncCall..parse`pyrometer::context::func_call::FuncCaller::resume_from_modifier::_{{closure}} (10 samples, 27.78%)parse`pyrometer::context::func_call::FuncCall..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (10 samples, 27.78%)parse`pyrometer::context::func_call::FuncCall..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (10 samples, 27.78%)parse`pyrometer::context::ContextBuilder::par..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (9 samples, 25.00%)parse`pyrometer::context::func_call::Fun..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::func_call::FuncCaller::func_call (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (8 samples, 22.22%)parse`pyrometer::context::func_call..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (8 samples, 22.22%)parse`pyrometer::context::ContextBu..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (8 samples, 22.22%)parse`pyrometer::context::ContextBu..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (8 samples, 22.22%)parse`pyrometer::context::ContextBu..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (8 samples, 22.22%)parse`pyrometer::context::ContextBu..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (8 samples, 22.22%)parse`pyrometer::context::ContextBu..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::false_fork_if_cvar (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::true_fork_if_cvar::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::member_access (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::member_access::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::match_member (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::member_access_inner (1 samples, 2.78%)pa..parse`shared::context::var::ContextVar::new_from_builtin (1 samples, 2.78%)pa..parse`shared::nodes::Builtin::as_string (1 samples, 2.78%)pa..parse`alloc::fmt::format::format_inner (1 samples, 2.78%)pa..parse`core::fmt::write (1 samples, 2.78%)pa..parse`core::fmt::Formatter::pad_integral (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::call_modifier_for_fn (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::func_call::FuncCaller::call_modifier_for_fn::_{{closure}} (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::func_call::FuncCaller::func_call (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (11 samples, 30.56%)parse`pyrometer::context::func_call::FuncCaller::..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`shared::context::ContextNode::visible_funcs (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_depth (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_depth (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`alloc::fmt::format::format_inner (1 samples, 2.78%)pa..parse`core::fmt::write (1 samples, 2.78%)pa..parse`<&mut W as core::fmt::Write>::write_str (1 samples, 2.78%)pa..parse`alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle (1 samples, 2.78%)pa..parse`alloc::raw_vec::finish_grow (1 samples, 2.78%)pa..libsystem_malloc.dylib`realloc (1 samples, 2.78%)li..libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 2.78%)li..libsystem_malloc.dylib`nanov2_realloc (1 samples, 2.78%)li..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memcmp (1 samples, 2.78%)li..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`shared::range::RangeEval::unsat (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as shared::range::Range<shared::nodes::concrete::Concrete>>::evaled_range_min (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::RangeExpr<shared::nodes::concrete::Concrete> as shared::range::elem_ty::ExecOp<shared::nodes::concrete::Concrete>>::exec_op (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 2.78%)li..libsystem_malloc.dylib`tiny_malloc_should_clear (1 samples, 2.78%)li..libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 2.78%)li..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::true_fork_if_cvar::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::member_access::MemberAccess::member_access (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`alloc::fmt::format::format_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (1 samples, 2.78%)pa..parse`core::iter::traits::iterator::Iterator::try_fold (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`shared::context::var::ContextVar::new_from_concrete (1 samples, 2.78%)pa..parse`shared::nodes::concrete::Concrete::as_string (1 samples, 2.78%)pa..parse`<primitive_types::U256 as core::fmt::Display>::fmt (1 samples, 2.78%)pa..parse`compiler_builtins::int::specialized_div_rem::u128_div_rem (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (5 samples, 13.89%)parse`pyrometer::cont..parse`pyrometer::context::func_call::namespaced_call::NameSpaceFuncCaller::call_name_spaced_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::namespaced_call::NameSpaceFuncCaller::match_namespaced_member (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::parse_inputs (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`shared::context::ContextNode::visible_funcs (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::BTreeMap<K,V> as core::iter::traits::collect::FromIterator<(K,V)>>::from_iter (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memmove (1 samples, 2.78%)li..parse`pyrometer::context::func_call::FuncCaller::call_modifier_for_fn (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::call_modifier_for_fn::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::variable::Variable::variable (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::env::Env::env_variable (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::resume_from_modifier (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::resume_from_modifier::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`shared::context::ContextNode::visible_funcs (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_depth (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::IntoIter<K,V,A> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`alloc::collections::btree::navigate::_<impl alloc::collections::btree::node::Handle<alloc::collections::btree::node::NodeRef<alloc::collections::btree::node::marker::Dying,K,V,alloc::collections::btree::node::marker::Leaf>,alloc::collections::btree::node::marker::Edge>>::deallocating_next_unchecked (1 samples, 2.78%)pa..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`shared::range::RangeEval::unsat (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as shared::range::Range<shared::nodes::concrete::Concrete>>::evaled_range_min (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete> as shared::range::elem::RangeElem<shared::nodes::concrete::Concrete>>::minimize (1 samples, 2.78%)pa..parse`core::iter::traits::iterator::Iterator::try_fold (3 samples, 8.33%)parse`core::..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::apply_to_edges (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (2 samples, 5.56%)parse`p..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (2 samples, 5.56%)parse`p..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::update_nonconst_from_nonconst (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::set_range_min (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::fallback_range (1 samples, 2.78%)pa..parse`<shared::range::SolcRange as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<shared::range::elem_ty::Elem<T> as core::clone::Clone>::clone (1 samples, 2.78%)pa..libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 2.78%)li..libsystem_malloc.dylib`nanov2_find_block_and_allocate (1 samples, 2.78%)li..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`shared::analyzer::Search::search_children_exclude_via (1 samples, 2.78%)pa..parse`<alloc::collections::btree::set::BTreeSet<T> as core::iter::traits::collect::FromIterator<T>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`<core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::IntoIter<K,V,A> as core::ops::drop::Drop>::drop (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (11 samples, 30.56%)parse`pyrometer::context::ContextBuilder::parse_c..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (5 samples, 13.89%)parse`pyrometer::cont..parse`shared::context::ContextNode::visible_funcs (1 samples, 2.78%)pa..parse`shared::nodes::contract_ty::ContractNode::linearized_functions (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::BTreeMap<K,V> as core::iter::traits::collect::FromIterator<(K,V)>>::from_iter (1 samples, 2.78%)pa..parse`alloc::collections::btree::map::BTreeMap<K,V,A>::bulk_build_from_sorted_iter (1 samples, 2.78%)pa..parse`alloc::collections::btree::append::_<impl alloc::collections::btree::node::NodeRef<alloc::collections::btree::node::marker::Owned,K,V,alloc::collections::btree::node::marker::LeafOrInternal>>::bulk_push (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::assign_exprs (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (1 samples, 2.78%)pa..parse`core::iter::traits::iterator::Iterator::try_fold (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::BTreeMap<K,V> as core::iter::traits::collect::FromIterator<(K,V)>>::from_iter (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..parse`core::ops::function::impls::_<impl core::ops::function::FnMut<A> for &mut F>::call_mut (1 samples, 2.78%)pa..parse`shared::context::var::ContextVarNode::try_set_range_max (1 samples, 2.78%)pa..parse`shared::context::var::ContextVar::try_set_range_max (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::SolcRange> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..parse`core::ptr::drop_in_place<shared::range::elem_ty::Elem<shared::nodes::concrete::Concrete>> (1 samples, 2.78%)pa..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::assign_exprs (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_expr::_{{closure}}::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op_match (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::bin_op::BinOp::op (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::require (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::advance_var_in_ctx (1 samples, 2.78%)pa..parse`<shared::context::var::ContextVar as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::namespaced_call::NameSpaceFuncCaller::call_name_spaced_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::namespaced_call::NameSpaceFuncCaller::match_namespaced_member (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::namespaced_call::NameSpaceFuncCaller::call_name_spaced_func_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`shared::context::Context::new_subctx (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::BTreeMap<K,V,A> as core::clone::Clone>::clone::clone_subtree (1 samples, 2.78%)pa..parse`<alloc::collections::btree::map::BTreeMap<K,V,A> as core::clone::Clone>::clone::clone_subtree (1 samples, 2.78%)pa..parse`<alloc::string::String as core::clone::Clone>::clone (1 samples, 2.78%)pa..libsystem_malloc.dylib`nanov2_allocate_outlined (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::apply_to_edges (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}}::_{{closure}} (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (4 samples, 11.11%)parse`pyrometer:..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (3 samples, 8.33%)parse`pyrome..parse`pyrometer::context::func_call::FuncCaller::named_fn_call_expr (1 samples, 2.78%)pa..parse`core::iter::traits::iterator::Iterator::try_fold (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::internal_call::InternalFuncCaller::call_internal_func::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::setup_fn_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::apply_to_edges (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memmove (1 samples, 2.78%)li..parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (27 samples, 75.00%)parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_innerparse`pyrometer::context::func_call::FuncCaller::func_call_inner (27 samples, 75.00%)parse`pyrometer::context::func_call::FuncCaller::func_call_innerparse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}} (27 samples, 75.00%)parse`pyrometer::context::func_call::FuncCaller::func_call_inner::_{{closure}}parse`pyrometer::context::func_call::FuncCaller::execute_call_inner (16 samples, 44.44%)parse`pyrometer::context::func_call::FuncCaller::execute_call_innerparse`pyrometer::context::ContextBuilder::parse_ctx_statement (16 samples, 44.44%)parse`pyrometer::context::ContextBuilder::parse_ctx_statementparse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (16 samples, 44.44%)parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_innerparse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closure}} (16 samples, 44.44%)parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner::_{{closur..parse`pyrometer::context::ContextBuilder::parse_ctx_statement (16 samples, 44.44%)parse`pyrometer::context::ContextBuilder::parse_ctx_statementparse`pyrometer::context::ContextBuilder::parse_ctx_stmt_inner (16 samples, 44.44%)parse`pyrometer::context::ContextBuilder::parse_ctx_stmt_innerparse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt (5 samples, 13.89%)parse`pyrometer::cont..parse`pyrometer::context::exprs::cond_op::CondOp::cond_op_stmt::_{{closure}} (5 samples, 13.89%)parse`pyrometer::cont..parse`pyrometer::context::exprs::cond_op::CondOp::false_fork_if_cvar (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::cond_op::CondOp::true_fork_if_cvar::_{{closure}} (1 samples, 2.78%)pa..parse`pyrometer::context::exprs::require::Require::handle_require (1 samples, 2.78%)pa..parse`pyrometer::context::ContextBuilder::parse_ctx_expr (1 samples, 2.78%)pa..parse`pyrometer::context::func_call::FuncCaller::fn_call_expr (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (1 samples, 2.78%)pa..libsystem_malloc.dylib`_nanov2_free (1 samples, 2.78%)li..parse`pyrometer::Analyzer::final_pass (28 samples, 77.78%)parse`pyrometer::Analyzer::final_passparse`shared::nodes::func_ty::FunctionNode::set_params_and_ret (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memmove (1 samples, 2.78%)li..parse`pyrometer::Analyzer::parse_source_unit (1 samples, 2.78%)pa..parse`pyrometer::Analyzer::parse_source_unit_part (1 samples, 2.78%)pa..parse`pyrometer::Analyzer::parse_contract_def (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T,A> as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<solang_parser::pt::Statement as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<alloc::vec::Vec<T,A> as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<solang_parser::pt::Statement as core::clone::Clone>::clone (1 samples, 2.78%)pa..parse`<solang_parser::pt::Expression as core::clone::Clone>::clone (1 samples, 2.78%)pa..libsystem_platform.dylib`_platform_memmove (1 samples, 2.78%)li..parse`lalrpop_util::state_machine::Parser<D,I>::next_token (1 samples, 2.78%)pa..parse`<solang_parser::lexer::Lexer as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`<itertools::peek_nth::PeekNth<I> as core::iter::traits::iterator::Iterator>::next (1 samples, 2.78%)pa..parse`solang_parser::solidity::__parse__SourceUnit::__reduce280 (1 samples, 2.78%)pa..parse`parse::main (34 samples, 94.44%)parse`parse::mainparse`pyrometer::Analyzer::parse (32 samples, 88.89%)parse`pyrometer::Analyzer::parseparse`solang_parser::parse (3 samples, 8.33%)parse`solang..parse`lalrpop_util::state_machine::Parser<D,I>::drive (3 samples, 8.33%)parse`lalrpo..parse`solang_parser::solidity::__parse__SourceUnit::__reduce (1 samples, 2.78%)pa..parse`solang_parser::solidity::__parse__SourceUnit::__reduce280 (1 samples, 2.78%)pa..all (36 samples, 100%)dyld`start (36 samples, 100.00%)dyld`startparse`main (36 samples, 100.00%)parse`mainparse`std::rt::lang_start_internal (35 samples, 97.22%)parse`std::rt::lang_start_internalparse`std::rt::lang_start::_{{closure}} (35 samples, 97.22%)parse`std::rt::lang_start::_{{closure}}parse`std::sys_common::backtrace::__rust_begin_short_backtrace (35 samples, 97.22%)parse`std::sys_common::backtrace::__rust_begin_short_backtraceparse`pyrometer::Analyzer::parse (1 samples, 2.78%)pa.. \ No newline at end of file diff --git a/crates/pyrometer/src/analyzer.rs b/crates/pyrometer/src/analyzer.rs index 4825fbf2..a8fcaa1e 100644 --- a/crates/pyrometer/src/analyzer.rs +++ b/crates/pyrometer/src/analyzer.rs @@ -4,7 +4,7 @@ use graph::elem::Elem; use graph::{nodes::*, ContextEdge, Edge, Node, VarType}; use reqwest::Client; use serde::{Deserialize, Serialize}; -use shared::{AnalyzerLike, GraphLike, JoinStats, NodeIdx, Search}; +use shared::{AnalyzerLike, ApplyStats, GraphLike, NodeIdx, Search}; use shared::{ExprErr, IntoExprErr, RangeArena, USE_DEBUG_SITE}; use solc_expressions::{FnCallBuilder, StatementParser}; use tokio::runtime::Runtime; @@ -116,7 +116,7 @@ pub struct Analyzer { /// A mapping of a solidity builtin to the index in the graph pub builtins: AHashMap, /// A mapping of a user type's name to the index in the graph (i.e. `struct A` would mapped `A` -> index) - pub user_types: AHashMap, + pub user_types: AHashMap>, /// A mapping of solidity builtin function to a [Function] struct, i.e. `ecrecover` -> `Function { name: "ecrecover", ..}` pub builtin_fns: AHashMap, /// A mapping of solidity builtin functions to their indices in the graph @@ -136,11 +136,13 @@ pub struct Analyzer { /// Per function, a list of functions that are called pub fn_calls_fns: BTreeMap>, - pub join_stats: JoinStats, + pub apply_stats: ApplyStats, /// An arena of ranges pub range_arena: RangeArena>, /// Parsed functions pub handled_funcs: Vec, + /// Target Context to debug + pub minimize_debug: Option, } impl Default for Analyzer { @@ -167,7 +169,7 @@ impl Default for Analyzer { parse_fn: NodeIdx::from(0).into(), debug_panic: false, fn_calls_fns: Default::default(), - join_stats: JoinStats::default(), + apply_stats: ApplyStats::default(), range_arena: RangeArena { ranges: vec![Elem::Null], map: { @@ -177,6 +179,7 @@ impl Default for Analyzer { }, }, handled_funcs: Vec::default(), + minimize_debug: None, }; a.builtin_fn_inputs = builtin_fns::builtin_fns_inputs(&mut a); @@ -252,19 +255,19 @@ impl Analyzer { format!(" Max depth of Contexts: {}", self.max_context_depth()), format!(" Max width of Contexts: {}", self.max_context_width()), format!(""), - format!(" Number of joins: {}, {} completed, {} variables reduced", self.join_stats.total_joins(), self.join_stats.completed_joins(), self.join_stats.reduced_vars()), - format!(" Number of pure joins: {}, {} completed, {} variables reduced", self.join_stats.total_pure_joins(), self.join_stats.completed_pure_joins(), self.join_stats.pure_reduced_vars()), - format!(" Number of simple pure joins: {}, {} completed, {} variables reduced", self.join_stats.pure_no_children_joins.num_joins, self.join_stats.pure_no_children_joins.completed_joins, self.join_stats.pure_no_children_joins.vars_reduced), - format!(" Number of children pure joins: {}, {} completed, {} variables reduced", self.join_stats.pure_children_no_forks_joins.num_joins, self.join_stats.pure_children_no_forks_joins.completed_joins, self.join_stats.pure_children_no_forks_joins.vars_reduced), - format!(" Number of fork children pure joins: {}, {} completed, {} variables reduced", self.join_stats.pure_children_forks_joins.num_joins, self.join_stats.pure_children_forks_joins.completed_joins, self.join_stats.pure_children_forks_joins.vars_reduced), - format!(" Number of view joins: {}, {} completed, {} variables reduced", self.join_stats.total_view_joins(), self.join_stats.completed_view_joins(), self.join_stats.view_reduced_vars()), - format!(" Number of simple view joins: {}, {} completed, {} variables reduced", self.join_stats.view_no_children_joins.num_joins, self.join_stats.view_no_children_joins.completed_joins, self.join_stats.view_no_children_joins.vars_reduced), - format!(" Number of children view joins: {}, {} completed, {} variables reduced", self.join_stats.view_children_no_forks_joins.num_joins, self.join_stats.view_children_no_forks_joins.completed_joins, self.join_stats.view_children_no_forks_joins.vars_reduced), - format!(" Number of fork children view joins: {}, {} completed, {} variables reduced", self.join_stats.view_children_forks_joins.num_joins, self.join_stats.view_children_forks_joins.completed_joins, self.join_stats.view_children_forks_joins.vars_reduced), - format!(" Number of mut joins: {}, {} completed, {} variables reduced", self.join_stats.total_mut_joins(), self.join_stats.completed_mut_joins(), self.join_stats.mut_reduced_vars()), - format!(" Number of simple mut joins: {}, {} completed, {} variables reduced", self.join_stats.mut_no_children_joins.num_joins, self.join_stats.mut_no_children_joins.completed_joins, self.join_stats.mut_no_children_joins.vars_reduced), - format!(" Number of children mut joins: {}, {} completed, {} variables reduced", self.join_stats.mut_children_no_forks_joins.num_joins, self.join_stats.mut_children_no_forks_joins.completed_joins, self.join_stats.mut_children_no_forks_joins.vars_reduced), - format!(" Number of fork children mut joins: {}, {} completed, {} variables reduced", self.join_stats.mut_children_forks_joins.num_joins, self.join_stats.mut_children_forks_joins.completed_joins, self.join_stats.mut_children_forks_joins.vars_reduced), + format!(" Number of applies: {}, {} completed, {} variables reduced", self.apply_stats.total_applies(), self.apply_stats.completed_applies(), self.apply_stats.reduced_vars()), + format!(" Number of pure applies: {}, {} completed, {} variables reduced", self.apply_stats.total_pure_applies(), self.apply_stats.completed_pure_applies(), self.apply_stats.pure_reduced_vars()), + format!(" Number of simple pure applies: {}, {} completed, {} variables reduced", self.apply_stats.pure_no_children_applies.num_applies, self.apply_stats.pure_no_children_applies.completed_applies, self.apply_stats.pure_no_children_applies.vars_reduced), + format!(" Number of children pure applies: {}, {} completed, {} variables reduced", self.apply_stats.pure_children_no_forks_applies.num_applies, self.apply_stats.pure_children_no_forks_applies.completed_applies, self.apply_stats.pure_children_no_forks_applies.vars_reduced), + format!(" Number of fork children pure applies: {}, {} completed, {} variables reduced", self.apply_stats.pure_children_forks_applies.num_applies, self.apply_stats.pure_children_forks_applies.completed_applies, self.apply_stats.pure_children_forks_applies.vars_reduced), + format!(" Number of view applies: {}, {} completed, {} variables reduced", self.apply_stats.total_view_applies(), self.apply_stats.completed_view_applies(), self.apply_stats.view_reduced_vars()), + format!(" Number of simple view applies: {}, {} completed, {} variables reduced", self.apply_stats.view_no_children_applies.num_applies, self.apply_stats.view_no_children_applies.completed_applies, self.apply_stats.view_no_children_applies.vars_reduced), + format!(" Number of children view applies: {}, {} completed, {} variables reduced", self.apply_stats.view_children_no_forks_applies.num_applies, self.apply_stats.view_children_no_forks_applies.completed_applies, self.apply_stats.view_children_no_forks_applies.vars_reduced), + format!(" Number of fork children view applies: {}, {} completed, {} variables reduced", self.apply_stats.view_children_forks_applies.num_applies, self.apply_stats.view_children_forks_applies.completed_applies, self.apply_stats.view_children_forks_applies.vars_reduced), + format!(" Number of mut applies: {}, {} completed, {} variables reduced", self.apply_stats.total_mut_applies(), self.apply_stats.completed_mut_applies(), self.apply_stats.mut_reduced_vars()), + format!(" Number of simple mut applies: {}, {} completed, {} variables reduced", self.apply_stats.mut_no_children_applies.num_applies, self.apply_stats.mut_no_children_applies.completed_applies, self.apply_stats.mut_no_children_applies.vars_reduced), + format!(" Number of children mut applies: {}, {} completed, {} variables reduced", self.apply_stats.mut_children_no_forks_applies.num_applies, self.apply_stats.mut_children_no_forks_applies.completed_applies, self.apply_stats.mut_children_no_forks_applies.vars_reduced), + format!(" Number of fork children mut applies: {}, {} completed, {} variables reduced", self.apply_stats.mut_children_forks_applies.num_applies, self.apply_stats.mut_children_forks_applies.completed_applies, self.apply_stats.mut_children_forks_applies.vars_reduced), format!(""), format!("====================================="), ] @@ -715,10 +718,12 @@ impl Analyzer { } EnumDefinition(def) => { let node = self.parse_enum_def(def); + s_node.add_enum(node, self).unwrap(); self.add_edge(node, sup_node, Edge::Enum); } ErrorDefinition(def) => { let node = self.parse_err_def(arena, def); + s_node.add_error(node, self).unwrap(); self.add_edge(node, sup_node, Edge::Error); } VariableDefinition(def) => { @@ -745,6 +750,7 @@ impl Analyzer { } TypeDefinition(def) => { let node = self.parse_ty_def(arena, def); + s_node.add_ty(node, self).unwrap(); self.add_edge(node, sup_node, Edge::Ty); } EventDefinition(_def) => todo!(), @@ -1048,19 +1054,47 @@ impl Analyzer { let inherits = contract.inherits.clone(); let con_name = contract.name.clone().unwrap().name; let con_node: ContractNode = - if let Some(user_ty_node) = self.user_types.get(&con_name).cloned() { - let unresolved = self.node_mut(user_ty_node); - *unresolved = Node::Contract(contract); - user_ty_node.into() + if let Some(user_ty_nodes) = self.user_types.get(&con_name).cloned() { + // assert we only have at most one unknown at a time for a given name + assert!( + user_ty_nodes.iter().fold(0, |mut acc, idx| { + if matches!(self.node(*idx), Node::Unresolved(_)) { + acc += 1; + } + acc + }) <= 1 + ); + let mut ret = None; + // see if we can fill the unknown with this contract + for user_ty_node in user_ty_nodes.iter() { + if matches!(self.node(*user_ty_node), Node::Unresolved(_)) { + let unresolved = self.node_mut(*user_ty_node); + *unresolved = Node::Contract(contract.clone()); + ret = Some(ContractNode::from(*user_ty_node)); + break; + } + } + match ret { + Some(ret) => ret, + None => { + // no unresolved to fill + let node = self.add_node(Node::Contract(contract)); + let entry = self.user_types.entry(con_name).or_default(); + entry.push(node); + node.into() + } + } } else { let node = self.add_node(Node::Contract(contract)); - self.user_types.insert(con_name, node); + let entry = self.user_types.entry(con_name).or_default(); + entry.push(node); node.into() }; inherits.iter().for_each(|contract_node| { self.add_edge(*contract_node, con_node, Edge::InheritedContract); }); + let mut usings = vec![]; let mut func_nodes = vec![]; let mut vars = vec![]; @@ -1100,7 +1134,7 @@ impl Analyzer { EventDefinition(_def) => {} Annotation(_anno) => todo!(), Using(using) => usings.push((*using.clone(), con_node.0.into())), - StraySemicolon(_loc) => todo!(), + StraySemicolon(_loc) => {} }); (con_node, func_nodes, usings, unhandled_inherits, vars) } @@ -1136,10 +1170,12 @@ impl Analyzer { match &using_def.list { UsingList::Library(ident_paths) => { ident_paths.identifiers.iter().for_each(|ident| { - if let Some(hopefully_contract) = self.user_types.get(&ident.name) { - match self.node(*hopefully_contract) { - Node::Contract(_) => { - let funcs = ContractNode::from(*hopefully_contract).funcs(self); + if let Some(idxs) = self.user_types.get(&ident.name).cloned() { + let mut found = false; + for idx in idxs.iter() { + if let Node::Contract(_) = self.node(*idx) { + found = true; + let funcs = ContractNode::from(*idx).funcs(self); let relevant_funcs: Vec<_> = funcs .iter() .filter_map(|func| { @@ -1154,15 +1190,27 @@ impl Analyzer { }) .copied() .collect(); + + if matches!(self.node(scope_node), Node::Contract(_)) { + self.add_edge( + scope_node, + *idx, + Edge::UsingContract(using_def.loc), + ); + } + relevant_funcs.iter().for_each(|func| { self.add_edge(ty_idx, *func, Edge::LibraryFunction(scope_node)); }); + break; } - _ => self.add_expr_err(ExprErr::ParseError( + } + if !found && !idxs.is_empty() { + self.add_expr_err(ExprErr::ParseError( using_def.loc(), "Tried to use a non-contract as a contract in a `using` statement" .to_string(), - )), + )) } } else { panic!("Cannot find library contract {}", ident.name); @@ -1172,25 +1220,32 @@ impl Analyzer { UsingList::Functions(vec_ident_paths) => { vec_ident_paths.iter().for_each(|ident_paths| { if ident_paths.path.identifiers.len() == 2 { - if let Some(hopefully_contract) = + if let Some(idxs) = self.user_types.get(&ident_paths.path.identifiers[0].name) { - if let Some(func) = ContractNode::from(*hopefully_contract) - .funcs(self) - .iter() - .find(|func| { - func.name(self) - .unwrap() - .starts_with(&ident_paths.path.identifiers[1].name) - }) - { - self.add_edge(ty_idx, *func, Edge::LibraryFunction(scope_node)); - } else { - panic!( - "Cannot find library function {}.{}", - ident_paths.path.identifiers[0].name, - ident_paths.path.identifiers[1].name - ); + for idx in idxs { + if let Node::Contract(_) = self.node(*idx) { + if let Some(func) = + ContractNode::from(*idx).funcs(self).iter().find(|func| { + func.name(self) + .unwrap() + .starts_with(&ident_paths.path.identifiers[1].name) + }) + { + self.add_edge( + ty_idx, + *func, + Edge::LibraryFunction(scope_node), + ); + } else { + panic!( + "Cannot find library function {}.{}", + ident_paths.path.identifiers[0].name, + ident_paths.path.identifiers[1].name + ); + } + break; + } } } else { panic!( @@ -1237,13 +1292,40 @@ impl Analyzer { let name = enu.name.clone().expect("Enum was not named").name; // check if we have an unresolved type by the same name - let enu_node: EnumNode = if let Some(user_ty_node) = self.user_types.get(&name).cloned() { - let unresolved = self.node_mut(user_ty_node); - *unresolved = Node::Enum(enu); - user_ty_node.into() + let enu_node: EnumNode = if let Some(user_ty_nodes) = self.user_types.get(&name).cloned() { + // assert we only have at most one unknown at a time for a given name + assert!( + user_ty_nodes.iter().fold(0, |mut acc, idx| { + if matches!(self.node(*idx), Node::Unresolved(_)) { + acc += 1; + } + acc + }) <= 1 + ); + let mut ret = None; + // see if we can fill the unknown with this contract + for user_ty_node in user_ty_nodes.iter() { + if matches!(self.node(*user_ty_node), Node::Unresolved(_)) { + let unresolved = self.node_mut(*user_ty_node); + *unresolved = Node::Enum(enu.clone()); + ret = Some(EnumNode::from(*user_ty_node)); + break; + } + } + match ret { + Some(ret) => ret, + None => { + // no unresolved to fill + let node = self.add_node(enu); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); + node.into() + } + } } else { let node = self.add_node(enu); - self.user_types.insert(name, node); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); node.into() }; @@ -1263,13 +1345,40 @@ impl Analyzer { // check if we have an unresolved type by the same name let strukt_node: StructNode = - if let Some(user_ty_node) = self.user_types.get(&name).cloned() { - let unresolved = self.node_mut(user_ty_node); - *unresolved = Node::Struct(strukt); - user_ty_node.into() + if let Some(user_ty_nodes) = self.user_types.get(&name).cloned() { + // assert we only have at most one unknown at a time for a given name + assert!( + user_ty_nodes.iter().fold(0, |mut acc, idx| { + if matches!(self.node(*idx), Node::Unresolved(_)) { + acc += 1; + } + acc + }) <= 1 + ); + let mut ret = None; + // see if we can fill the unknown with this contract + for user_ty_node in user_ty_nodes.iter() { + if matches!(self.node(*user_ty_node), Node::Unresolved(_)) { + let unresolved = self.node_mut(*user_ty_node); + *unresolved = Node::Struct(strukt.clone()); + ret = Some(StructNode::from(*user_ty_node)); + break; + } + } + match ret { + Some(ret) => ret, + None => { + // no unresolved to fill + let node = self.add_node(Node::Struct(strukt)); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); + node.into() + } + } } else { let node = self.add_node(strukt); - self.user_types.insert(name, node); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); node.into() }; @@ -1377,8 +1486,9 @@ impl Analyzer { } let needs_final_pass = var.initializer_expr.is_some(); let var_node = VarNode::from(self.add_node(var)); - self.user_types - .insert(var_node.name(self).unwrap(), var_node.into()); + let name = var_node.name(self).unwrap(); + let entry = self.user_types.entry(name).or_default(); + entry.push(var_node.into()); (var_node, func, needs_final_pass) } @@ -1390,19 +1500,46 @@ impl Analyzer { tracing::trace!("Parsing type definition"); let ty = Ty::new(self, arena, ty_def.clone()); let name = ty.name.name.clone(); - let ty_node: TyNode = if let Some(user_ty_node) = self.user_types.get(&name).cloned() { - let unresolved = self.node_mut(user_ty_node); - *unresolved = Node::Ty(ty); - user_ty_node.into() + let ty_node: TyNode = if let Some(user_ty_nodes) = self.user_types.get(&name).cloned() { + // assert we only have at most one unknown at a time for a given name + assert!( + user_ty_nodes.iter().fold(0, |mut acc, idx| { + if matches!(self.node(*idx), Node::Unresolved(_)) { + acc += 1; + } + acc + }) <= 1 + ); + let mut ret = None; + // see if we can fill the unknown with this contract + for user_ty_node in user_ty_nodes.iter() { + if matches!(self.node(*user_ty_node), Node::Unresolved(_)) { + let unresolved = self.node_mut(*user_ty_node); + *unresolved = Node::Ty(ty.clone()); + ret = Some(TyNode::from(*user_ty_node)); + break; + } + } + match ret { + Some(ret) => ret, + None => { + // no unresolved to fill + let node = self.add_node(Node::Ty(ty)); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); + node.into() + } + } } else { let node = self.add_node(Node::Ty(ty)); - self.user_types.insert(name, node); + let entry = self.user_types.entry(name).or_default(); + entry.push(node); node.into() }; ty_node } - fn post_source_to_site(file_no: usize, path: &PathBuf, source: &str) + fn post_source_to_site(file_no: usize, path: &Path, source: &str) where Self: std::marker::Sized, Self: AnalyzerLike, diff --git a/crates/pyrometer/src/analyzer_backend.rs b/crates/pyrometer/src/analyzer_backend.rs index 5f05e91f..87e1e218 100644 --- a/crates/pyrometer/src/analyzer_backend.rs +++ b/crates/pyrometer/src/analyzer_backend.rs @@ -2,12 +2,13 @@ use crate::Analyzer; use graph::{ elem::Elem, nodes::{ - BlockNode, Builtin, Concrete, ConcreteNode, ContextVar, Function, FunctionNode, - FunctionParam, FunctionParamNode, FunctionReturn, MsgNode, + BlockNode, Builtin, Concrete, ConcreteNode, ContextNode, ContextVar, ContractNode, + FuncReconstructionReqs, Function, FunctionNode, FunctionParam, FunctionParamNode, + FunctionReturn, KilledKind, MsgNode, }, - AnalyzerBackend, Edge, Node, VarType, + AnalyzerBackend, Edge, GraphBackend, Node, TypeNode, VarType, }; -use shared::{AnalyzerLike, ExprErr, GraphLike, IntoExprErr, JoinStats, NodeIdx, RangeArena}; +use shared::{AnalyzerLike, ApplyStats, ExprErr, GraphLike, IntoExprErr, NodeIdx, RangeArena}; use ahash::AHashMap; use ethers_core::types::U256; @@ -17,6 +18,7 @@ use solang_parser::{ }; use std::collections::BTreeMap; +use std::io::Write; impl AnalyzerBackend for Analyzer { fn add_concrete_var( @@ -42,6 +44,7 @@ impl AnalyzerLike for Analyzer { type FunctionNode = FunctionNode; type FunctionParam = FunctionParam; type FunctionReturn = FunctionReturn; + type ContextNode = ContextNode; type Builtin = Builtin; fn builtin_fn_nodes(&self) -> &AHashMap { @@ -60,8 +63,213 @@ impl AnalyzerLike for Analyzer { self.max_width } + fn minimize_err(&mut self, ctx: ContextNode) -> String { + let genesis = ctx.genesis(self).unwrap(); + let family_tree = genesis.family_tree(self).unwrap(); + let mut needed_functions = family_tree + .iter() + .map(|c| c.associated_fn(self).unwrap()) + .collect::>(); + let applies = family_tree + .into_iter() + .flat_map(|c| c.underlying(self).unwrap().applies.clone()) + .collect::>(); + needed_functions.extend(applies); + needed_functions.sort_by(|a, b| a.0.cmp(&b.0)); + needed_functions.dedup(); + + fn recurse_find( + contract: ContractNode, + target_contract: ContractNode, + analyzer: &impl GraphBackend, + ) -> Option> { + let mut inherited = contract.direct_inherited_contracts(analyzer); + if inherited.contains(&target_contract) { + Some(vec![contract]) + } else if inherited.is_empty() { + None + } else { + while !inherited.is_empty() { + if let Some(mut c) = + recurse_find(inherited.pop().unwrap(), target_contract, analyzer) + { + c.push(contract); + return Some(c); + } + } + None + } + } + + let mut contract_to_funcs: BTreeMap< + Option, + Vec<(FunctionNode, FuncReconstructionReqs)>, + > = Default::default(); + + let mut tys = vec![]; + let mut enums = vec![]; + let mut structs = vec![]; + let mut errs = vec![]; + needed_functions.into_iter().for_each(|func| { + let maybe_func_contract = func.maybe_associated_contract(self); + let reqs = func.reconstruction_requirements(self); + reqs.usertypes.iter().for_each(|var| { + let maybe_associated_contract = match var { + TypeNode::Contract(c) => Some(*c), + TypeNode::Enum(c) => c.maybe_associated_contract(self), + TypeNode::Error(c) => c.maybe_associated_contract(self), + TypeNode::Struct(c) => c.maybe_associated_contract(self), + TypeNode::Ty(c) => c.maybe_associated_contract(self), + TypeNode::Func(_) => None, + TypeNode::Unresolved(_) => None, + }; + reqs.storage.iter().for_each(|var| { + if let Some(c) = var.maybe_associated_contract(self) { + contract_to_funcs.entry(Some(c)).or_default(); + + if let Some(func_c) = maybe_func_contract { + if let Some(needed) = recurse_find(func_c, c, self) { + needed.into_iter().for_each(|c| { + contract_to_funcs.entry(Some(c)).or_default(); + }); + } + } + } + }); + + if let Some(c) = maybe_associated_contract { + contract_to_funcs.entry(Some(c)).or_default(); + + if let Some(func_c) = maybe_func_contract { + if let Some(needed) = recurse_find(func_c, c, self) { + needed.into_iter().for_each(|c| { + contract_to_funcs.entry(Some(c)).or_default(); + }); + } + } + } else { + match var { + TypeNode::Enum(c) => enums.push(*c), + TypeNode::Error(c) => errs.push(*c), + TypeNode::Struct(c) => structs.push(*c), + TypeNode::Ty(c) => tys.push(*c), + _ => {} + } + } + }); + let entry = contract_to_funcs.entry(maybe_func_contract).or_default(); + entry.push((func, reqs)); + }); + + // println!("{:#?}", contract_to_funcs); + + let contracts = contract_to_funcs.keys().collect::>(); + let contract_str = contracts + .iter() + .filter_map(|maybe_contract| { + if let Some(contract) = maybe_contract { + Some(contract.reconstruct(self, &contract_to_funcs).ok()?) + } else { + None + } + }) + .collect::>() + .join("\n\n"); + let func_str = if let Some(free_floating_funcs) = contract_to_funcs.get(&None) { + free_floating_funcs + .iter() + .map(|(func, _)| func.reconstruct_src(self).unwrap()) + .collect::>() + .join("\n") + } else { + "".to_string() + }; + let enum_str = enums + .iter() + .map(|enu| enu.reconstruct_src(self).unwrap()) + .collect::>() + .join("\n"); + let enum_str = if enum_str.is_empty() { + "".to_string() + } else { + format!("{enum_str}\n") + }; + let struct_str = structs + .iter() + .map(|strukt| strukt.reconstruct_src(self).unwrap()) + .collect::>() + .join("\n"); + let struct_str = if struct_str.is_empty() { + "".to_string() + } else { + format!("{struct_str}\n") + }; + let ty_str = tys + .iter() + .map(|ty| ty.reconstruct_src(self).unwrap()) + .collect::>() + .join("\n"); + let ty_str = if ty_str.is_empty() { + "".to_string() + } else { + format!("{ty_str}\n") + }; + let err_str = errs + .iter() + .map(|err| err.reconstruct_src(self).unwrap()) + .collect::>() + .join("\n"); + let err_str = if err_str.is_empty() { + "".to_string() + } else { + format!("{err_str}\n") + }; + + format!("{err_str}{ty_str}{enum_str}{struct_str}{func_str}\n{contract_str}") + } + fn add_expr_err(&mut self, err: ExprErr) { if self.debug_panic() { + if let Some(path) = self.minimize_debug().clone() { + let reconstruction_edge: ContextNode = self + .graph + .node_indices() + .find_map(|node| match self.node(node) { + Node::Context(context) if context.killed.is_some() => { + match context.killed.unwrap() { + (_, KilledKind::ParseError) => { + // println!("found context: {}", context.path); + let edges = graph::nodes::ContextNode::from(node) + .all_edges(self) + .unwrap(); + let reconstruction_edge = *edges + .iter() + .filter(|c| c.underlying(self).unwrap().killed.is_some()) + .take(1) + .next() + .unwrap_or(&ContextNode::from(node)); + + Some(reconstruction_edge) + } + _ => None, + } + } + _ => None, + }) + .unwrap(); + + let min_str = self.minimize_err(reconstruction_edge); + // println!("reconstructed source:\n{} placed in {}", min_str, path); + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) // Create the file if it doesn't exist + .truncate(true) // truncate if it does + .open(path) + .unwrap(); + + file.write_all(min_str.as_bytes()).unwrap(); + } panic!("Encountered an error: {err:?}"); } if !self.expr_errs.contains(&err) { @@ -103,10 +311,10 @@ impl AnalyzerLike for Analyzer { fn builtins_mut(&mut self) -> &mut AHashMap { &mut self.builtins } - fn user_types(&self) -> &AHashMap { + fn user_types(&self) -> &AHashMap> { &self.user_types } - fn user_types_mut(&mut self) -> &mut AHashMap { + fn user_types_mut(&mut self) -> &mut AHashMap> { &mut self.user_types } @@ -135,11 +343,26 @@ impl AnalyzerLike for Analyzer { } } Variable(ident) => { - if let Some(idx) = self.user_types.get(&ident.name) { - *idx + // println!("variable ident: {}", ident.name); + if let Some(idxs) = self.user_types.get(&ident.name) { + if idxs.len() == 1 { + idxs[0] + } else { + let contract = idxs.iter().find(|maybe_contract| { + matches!(self.node(**maybe_contract), Node::Contract(..)) + }); + let strukt = idxs.iter().find(|maybe_struct| { + matches!(self.node(**maybe_struct), Node::Struct(..)) + }); + match (contract, strukt) { + (Some(_), Some(strukt)) => *strukt, + _ => panic!("This should be invalid solidity"), + } + } } else { let node = self.add_node(Node::Unresolved(ident.clone())); - self.user_types.insert(ident.name.clone(), node); + let entry = self.user_types.entry(ident.name.clone()).or_default(); + entry.push(node); node } } @@ -263,8 +486,8 @@ impl AnalyzerLike for Analyzer { &mut self.fn_calls_fns } - fn join_stats_mut(&mut self) -> &mut JoinStats { - &mut self.join_stats + fn apply_stats_mut(&mut self) -> &mut ApplyStats { + &mut self.apply_stats } fn handled_funcs(&self) -> &[FunctionNode] { @@ -275,16 +498,16 @@ impl AnalyzerLike for Analyzer { &mut self.handled_funcs } - fn file_mapping(&self) -> BTreeMap { - let mut file_mapping: BTreeMap = BTreeMap::new(); - for (source_path, _, o_file_no, _) in self.sources.iter() { + fn file_mapping(&self) -> BTreeMap { + let mut file_mapping: BTreeMap = BTreeMap::new(); + for (_, sol, o_file_no, _) in self.sources.iter() { if let Some(file_no) = o_file_no { - file_mapping.insert( - *file_no, - source_path.path_to_solidity_source().display().to_string(), - ); + file_mapping.insert(*file_no, sol); } } file_mapping } + fn minimize_debug(&self) -> &Option { + &self.minimize_debug + } } diff --git a/crates/pyrometer/src/graph_backend.rs b/crates/pyrometer/src/graph_backend.rs index 1f3a7523..ac8fbc14 100644 --- a/crates/pyrometer/src/graph_backend.rs +++ b/crates/pyrometer/src/graph_backend.rs @@ -1,11 +1,10 @@ use crate::Analyzer; -use graph::elem::RangeElem; -use graph::nodes::Concrete; use graph::{ - as_dot_str, nodes::ContextNode, AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, - Node, + as_dot_str, + elem::{Elem, RangeElem}, + nodes::{Concrete, ContextNode, ContextVarNode}, + AnalyzerBackend, AsDotStr, ContextEdge, Edge, GraphBackend, Node, TOKYO_NIGHT_COLORS, }; -use graph::{elem::Elem, nodes::ContextVarNode, TOKYO_NIGHT_COLORS}; use reqwest::Client; use serde::{Deserialize, Serialize}; use shared::RangeArena; @@ -572,10 +571,10 @@ pub fn arena_mermaid_node( format!("{indent}{}{{{{\"{}\"}}}}", idx.index(), arena_idx) } ArenaNode::ELEM(label) => { - format!("{indent}{}(\"{}\")", idx.index(), label.replace('"', "'")) + format!("{indent}{}(\"{}\")", idx.index(), label.replace('\"', "'")) } ArenaNode::CVAR(label) => { - format!("{indent}{}(\"{}\")", idx.index(), label.replace('"', "'")) + format!("{indent}{}(\"{}\")", idx.index(), label.replace('\"', "'")) } }; @@ -665,6 +664,33 @@ impl GraphDot for Analyzer { .map(|edge| (edge.source(), edge.target(), *edge.weight(), edge.id())) .collect::)>>(), ); + + let mut struct_parts = children + .iter() + .filter(|child| { + if matches!(g.node(**child), Node::ContextVar(..)) { + ContextVarNode::from(**child).is_struct(g).unwrap_or(false) + } else { + false + } + }) + .copied() + .collect::>(); + let strukt_children = struct_parts + .iter() + .flat_map(|strukt| { + g.children_exclude(*strukt, 0, &[Edge::Context(ContextEdge::Subcontext)]) + }) + .collect::>(); + struct_parts.extend(strukt_children); + + let edges_for_structs = g + .edges_for_nodes(&struct_parts) + .into_iter() + .filter(|(_, _, e, _)| *e != Edge::Context(ContextEdge::InputVariable)) + .collect::>(); + children_edges.extend(edges_for_structs); + let preindent = " ".repeat(4 * depth.saturating_sub(1)); let indent = " ".repeat(4 * depth); let child_node_str = children @@ -1275,8 +1301,11 @@ pub fn mermaid_node( } // color the forks - let ctx_node = graph::nodes::ContextVarNode::from(current_node).ctx(g); - gather_context_info(g, indent, ctx_node, current_node, &mut node_str); + if let Some(ctx_node) = + graph::nodes::ContextVarNode::from(current_node).maybe_ctx(g) + { + gather_context_info(g, indent, ctx_node, current_node, &mut node_str); + } } Node::Context(ctx) => { // highlight self diff --git a/crates/pyrometer/src/lib.rs b/crates/pyrometer/src/lib.rs index be40bd1c..b08cde03 100644 --- a/crates/pyrometer/src/lib.rs +++ b/crates/pyrometer/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::type_complexity)] mod analyzer; mod analyzer_backend; mod builtin_fns; diff --git a/crates/pyrometer/tests/test_data/assembly.sol b/crates/pyrometer/tests/test_data/assembly.sol index 0c99af1a..45940764 100644 --- a/crates/pyrometer/tests/test_data/assembly.sol +++ b/crates/pyrometer/tests/test_data/assembly.sol @@ -1,53 +1,61 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + contract Assembly { - function switchStatement(uint256 x) public returns (uint256) { - uint256 y = x; + function switchStatement(uint256 x) public pure returns (uint256) { + uint256 y = x; + y; assembly { switch mod(x, 10) - case 1 { - x := add(x, 1) - } - case 2 { - x := add(x, 2) - } - case 3 { - x := add(x, 3) - } - case 4 { - x := add(x, 4) - } - default { - x := add(x, 10) - } + case 1 { + x := add(x, 1) + } + case 2 { + x := add(x, 2) + } + case 3 { + x := add(x, 3) + } + case 4 { + x := add(x, 4) + } + default { + x := add(x, 10) + } } return x; } - function hasInline(uint256 a, uint256 b) public returns (uint256, uint256){ - assembly { - a := add(100, a) - a := sub(a, 10) - a := div(a, 2) - a := mul(a, 2) - if lt(b, 200) { - b := 100 - } + function hasInline( + uint256 a, + uint256 b + ) public pure returns (uint256, uint256) { + assembly { + a := add(100, a) + a := sub(a, 10) + a := div(a, 2) + a := mul(a, 2) + if lt(b, 200) { + b := 100 + } - // let c := 0x200 - // let d := true - } + // let c := 0x200 + // let d := true + } - if (b < 200) { - require(b == 100); - } - return (a, b); - } + if (b < 200) { + require(b == 100); + } + return (a, b); + } - function varDecl(uint256 a) public { - assembly { - let b := 200 - let c := 0x200 - let d := true - } - } -} \ No newline at end of file + function varDecl(uint256 a) public pure { + a; + assembly { + let b := 200 + let c := 0x200 + let d := true + } + } +} diff --git a/crates/pyrometer/tests/test_data/cast.sol b/crates/pyrometer/tests/test_data/cast.sol index b31a7228..392e792a 100644 --- a/crates/pyrometer/tests/test_data/cast.sol +++ b/crates/pyrometer/tests/test_data/cast.sol @@ -1,9 +1,12 @@ +// SPDX-License-Identifier: MIT or APACHE2 +pragma solidity ^0.8.0; + type ShortString is bytes32; type MyUint is uint256; type MyInt is int256; contract Cast { - function u_int(uint256 x) public { + function u_int(uint256 x) public pure { uint248 x_uint248 = uint248(x); uint240 x_uint240 = uint240(x); uint232 x_uint232 = uint232(x); @@ -36,13 +39,44 @@ contract Cast { uint16 x_uint16 = uint16(x); uint8 x_uint8 = uint8(x); uint256 x_uint256 = uint256(x_uint8); + x_uint256; + x_uint248; + x_uint240; + x_uint232; + x_uint224; + x_uint216; + x_uint208; + x_uint200; + x_uint192; + x_uint184; + x_uint176; + x_uint168; + x_uint160; + x_uint152; + x_uint144; + x_uint136; + x_uint128; + x_uint120; + x_uint112; + x_uint104; + x_uint96; + x_uint88; + x_uint80; + x_uint72; + x_uint64; + x_uint56; + x_uint48; + x_uint40; + x_uint32; + x_uint24; + x_uint16; } - function u_int_conc() public { + function u_int_conc() public pure { u_int(100); } - function i_nt(int256 x) public { + function i_nt(int256 x) public pure { int248 x_int248 = int248(x); int240 x_int240 = int240(x); int232 x_int232 = int232(x); @@ -74,85 +108,147 @@ contract Cast { int24 x_int24 = int24(x); int16 x_int16 = int16(x); int8 x_int8 = int8(x); - int8 x_int256 = int256(x_int8); + int256 x_int256 = int256(x_int8); + x_int256; + x_int248; + x_int240; + x_int232; + x_int224; + x_int216; + x_int208; + x_int200; + x_int192; + x_int184; + x_int176; + x_int168; + x_int160; + x_int152; + x_int144; + x_int136; + x_int128; + x_int120; + x_int112; + x_int104; + x_int96; + x_int88; + x_int80; + x_int72; + x_int64; + x_int56; + x_int48; + x_int40; + x_int32; + x_int24; + x_int16; } - function i_nt_conc_pos() public { + function i_nt_conc_pos() public pure { i_nt(100); } - function i_nt_conc_neg() public { + function i_nt_conc_neg() public pure { i_nt(-100); } - function u_i_nt(int256 x) public { + function u_i_nt(int256 x) public pure { uint256 x_uint256 = uint256(x); - uint248 x_uint248 = uint248(x); int248 x_int248 = int248(x); - uint240 x_uint240 = uint240(x); + uint248 x_uint248 = uint248(x_int248); int240 x_int240 = int240(x); - uint232 x_uint232 = uint232(x); + uint240 x_uint240 = uint240(x_int240); int232 x_int232 = int232(x); - uint224 x_uint224 = uint224(x); + uint232 x_uint232 = uint232(x_int232); int224 x_int224 = int224(x); - uint216 x_uint216 = uint216(x); + uint224 x_uint224 = uint224(x_int224); int216 x_int216 = int216(x); - uint208 x_uint208 = uint208(x); + uint216 x_uint216 = uint216(x_int216); int208 x_int208 = int208(x); - uint200 x_uint200 = uint200(x); + uint208 x_uint208 = uint208(x_int208); int200 x_int200 = int200(x); - uint192 x_uint192 = uint192(x); + uint200 x_uint200 = uint200(x_int200); int192 x_int192 = int192(x); - uint184 x_uint184 = uint184(x); + uint192 x_uint192 = uint192(x_int192); int184 x_int184 = int184(x); - uint176 x_uint176 = uint176(x); + uint184 x_uint184 = uint184(x_int184); int176 x_int176 = int176(x); - uint168 x_uint168 = uint168(x); + uint176 x_uint176 = uint176(x_int176); int168 x_int168 = int168(x); - uint160 x_uint160 = uint160(x); + uint168 x_uint168 = uint168(x_int168); int160 x_int160 = int160(x); - uint152 x_uint152 = uint152(x); + uint160 x_uint160 = uint160(x_int160); int152 x_int152 = int152(x); - uint144 x_uint144 = uint144(x); + uint152 x_uint152 = uint152(x_int152); int144 x_int144 = int144(x); - uint136 x_uint136 = uint136(x); + uint144 x_uint144 = uint144(x_int144); int136 x_int136 = int136(x); - uint128 x_uint128 = uint128(x); + uint136 x_uint136 = uint136(x_int136); int128 x_int128 = int128(x); - uint120 x_uint120 = uint120(x); + uint128 x_uint128 = uint128(x_int128); int120 x_int120 = int120(x); - uint112 x_uint112 = uint112(x); + uint120 x_uint120 = uint120(x_int120); int112 x_int112 = int112(x); - uint104 x_uint104 = uint104(x); + uint112 x_uint112 = uint112(x_int112); int104 x_int104 = int104(x); - uint96 x_uint96 = uint96(x); + uint104 x_uint104 = uint104(x_int104); int96 x_int96 = int96(x); - uint88 x_uint88 = uint88(x); + uint96 x_uint96 = uint96(x_int96); int88 x_int88 = int88(x); - uint80 x_uint80 = uint80(x); + uint88 x_uint88 = uint88(x_int88); int80 x_int80 = int80(x); - uint72 x_uint72 = uint72(x); + uint80 x_uint80 = uint80(x_int80); int72 x_int72 = int72(x); - uint64 x_uint64 = uint64(x); + uint72 x_uint72 = uint72(x_int72); int64 x_int64 = int64(x); - uint56 x_uint56 = uint56(x); + uint64 x_uint64 = uint64(x_int64); int56 x_int56 = int56(x); - uint48 x_uint48 = uint48(x); + uint56 x_uint56 = uint56(x_int56); int48 x_int48 = int48(x); - uint40 x_uint40 = uint40(x); + uint48 x_uint48 = uint48(x_int48); int40 x_int40 = int40(x); - uint32 x_uint32 = uint32(x); + uint40 x_uint40 = uint40(x_int40); int32 x_int32 = int32(x); - uint24 x_uint24 = uint24(x); + uint32 x_uint32 = uint32(x_int32); int24 x_int24 = int24(x); - uint16 x_uint16 = uint16(x); + uint24 x_uint24 = uint24(x_int24); int16 x_int16 = int16(x); - uint8 x_uint8 = uint8(x); + uint16 x_uint16 = uint16(x_int16); int8 x_int8 = int8(x); - x_uint256 = uint256(x_int8); + uint8 x_uint8 = uint8(x_int8); + x_uint256 = uint256(int256(x_int8)); + x_uint248; + x_uint240; + x_uint232; + x_uint224; + x_uint216; + x_uint208; + x_uint200; + x_uint192; + x_uint184; + x_uint176; + x_uint168; + x_uint160; + x_uint152; + x_uint144; + x_uint136; + x_uint128; + x_uint120; + x_uint112; + x_uint104; + x_uint96; + x_uint88; + x_uint80; + x_uint72; + x_uint64; + x_uint56; + x_uint48; + x_uint40; + x_uint32; + x_uint24; + x_uint16; + x_uint8; } - function b_ytes(bytes32 x) public { + function b_ytes(bytes32 x) public pure { bytes31 x_bytes31 = bytes31(x); bytes30 x_bytes30 = bytes30(x); bytes29 x_bytes29 = bytes29(x); @@ -185,98 +281,136 @@ contract Cast { bytes2 x_bytes2 = bytes2(x); bytes1 x_bytes1 = bytes1(x); bytes32 x_bytes32 = bytes32(x_bytes1); + x_bytes31; + x_bytes30; + x_bytes29; + x_bytes28; + x_bytes27; + x_bytes26; + x_bytes25; + x_bytes24; + x_bytes23; + x_bytes22; + x_bytes21; + x_bytes20; + x_bytes19; + x_bytes18; + x_bytes17; + x_bytes16; + x_bytes15; + x_bytes14; + x_bytes13; + x_bytes12; + x_bytes11; + x_bytes10; + x_bytes9; + x_bytes8; + x_bytes7; + x_bytes6; + x_bytes5; + x_bytes4; + x_bytes3; + x_bytes2; + x_bytes1; + x_bytes32; } - function b_ytes_uint(bytes32 x) public returns (uint256) { + function b_ytes_uint(bytes32 x) public pure returns (uint256) { uint256 x_uint256 = uint256(x); return x_uint256; } - function u_int_bytes(uint256 x) public returns (bytes32) { + function u_int_bytes(uint256 x) public pure returns (bytes32) { bytes32 x_bytes32 = bytes32(x); return x_bytes32; } - function u_int_addr(uint160 x) public returns (address) { + function u_int_addr(uint160 x) public pure returns (address) { return address(x); } - function addr_uint(address x) public returns (uint160) { + function addr_uint(address x) public pure returns (uint160) { return uint160(x); } - function b_ytes_uint_conc() public returns (bytes32) { + function b_ytes_uint_conc() public pure returns (bytes32) { bytes32 round_trip = u_int_bytes(b_ytes_uint(hex"1337")); require(round_trip == bytes32(hex"1337")); return round_trip; } - function addr_uint_conc() public returns (address) { + function addr_uint_conc() public pure returns (address) { address round_trip = u_int_addr(addr_uint(address(1337))); require(round_trip == address(1337)); return round_trip; } - function userStr() internal { + function userStr() internal pure { bytes32 x = bytes32("test"); ShortString a = ShortString.wrap(x); bytes32 b = ShortString.unwrap(a); require(b == x); } - function userUint() internal { + function userUint() internal pure { uint256 x = 100; MyUint a = MyUint.wrap(x); uint256 b = MyUint.unwrap(a); require(b == x); } - - function downcast_uint_conc() public returns (uint64) { + function downcast_uint_conc() public pure returns (uint64) { uint128 y = type(uint128).max; y -= type(uint32).max; return uint64(y); } - function downcast_int_conc() public returns (int64) { + function downcast_int_conc() public pure returns (int64) { int128 x = type(int128).max; x -= type(int32).max; return int64(x); } - function userInt() internal { + function userInt() internal pure { int256 x = -100; MyInt a = MyInt.wrap(x); int256 b = MyInt.unwrap(a); require(b == x); } - function int_uint_int_conc() internal { + function int_uint_int_conc() internal pure { int256 a = -100; uint256 b = uint(a); int256 c = int(b); require(-100 == c); } - function int_uint_int(int a) internal { + function int_uint_int(int a) internal pure { uint256 b = uint(a); int256 c = int(b); + c; } } - contract FuncCast { - function a(bytes32 vs) public { + function a(bytes32 vs) public pure { b(vs); } - function b(bytes32 vs) public { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + + function b(bytes32 vs) public pure returns (bool) { + bytes32 s = vs & + bytes32( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); // uint8 v = uint8((uint256(vs) >> 255) + 27); return c(s); } - function c(bytes32 s) public returns (bool) { - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + function c(bytes32 s) public pure returns (bool) { + if ( + uint256(s) > + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + ) { return true; } else { return false; @@ -284,11 +418,12 @@ contract FuncCast { } function foo() public { - address a = address(0); - a.call(hex""); - a.delegatecall(hex""); - a.delegatecall(msg.data); + address ad = address(0); + (bool r, bytes memory k) = ad.call(hex""); + (r, k) = ad.delegatecall(hex""); + (r, k) = ad.delegatecall(msg.data); bytes memory data = hex"01234567"; + data; } } diff --git a/crates/pyrometer/tests/test_data/func_override.sol b/crates/pyrometer/tests/test_data/func_override.sol index b8d72769..7f0c5851 100644 --- a/crates/pyrometer/tests/test_data/func_override.sol +++ b/crates/pyrometer/tests/test_data/func_override.sol @@ -7,7 +7,7 @@ contract B is C { } -contract A is B { +contract A is B, C { function a(uint256 x) internal override returns (uint256) { return x + 5; } @@ -19,4 +19,4 @@ contract A is B { require(ret == 200); return ret; } -} \ No newline at end of file +} diff --git a/crates/pyrometer/tests/test_data/function_calls.sol b/crates/pyrometer/tests/test_data/function_calls.sol index 137f959f..e687f402 100644 --- a/crates/pyrometer/tests/test_data/function_calls.sol +++ b/crates/pyrometer/tests/test_data/function_calls.sol @@ -1,43 +1,43 @@ -contract InternalFuncCalls { - address _owner; +// contract InternalFuncCalls { +// address _owner; - function transferOwnership(address newOwner) public virtual { - innerRequire(newOwner); - _transferOwnership(newOwner); - } +// function transferOwnership(address newOwner) public virtual { +// innerRequire(newOwner); +// _transferOwnership(newOwner); +// } - function innerRequire(address newOwner) public virtual { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - } +// function innerRequire(address newOwner) public virtual { +// require(newOwner != address(0), "Ownable: new owner is the zero address"); +// } - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = _owner; - _owner = newOwner; - } -} +// function _transferOwnership(address newOwner) internal virtual { +// address oldOwner = _owner; +// _owner = newOwner; +// } +// } -contract B { - uint256 public a; +// contract B { +// uint256 public a; - function addToA(uint256 x) public { - a += x; - } +// function addToA(uint256 x) public { +// a += x; +// } - constructor(uint256 x) { - a = x; - } -} +// 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)).addToA(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(); + // } function multiReturn() public returns (uint256, uint256, uint256, uint256) { return (1, 2, 3, 4); @@ -60,26 +60,26 @@ contract ExternalFuncCalls { } } -contract K { - struct L { - uint b; - uint c; - } +// contract K { +// struct L { +// uint b; +// uint c; +// } - function foo() internal { - L memory l = L(2, 3); - require(l.b == 2); - require(l.c == 3); - } -} +// function foo() internal { +// 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); - } +// contract Disambiguation { +// function foo(address from, address to, uint256 id) public { +// foo(from, to, id, 0); +// } - function foo(address from, address to, uint256 id, uint num) internal {} +// function foo(address from, address to, uint256 id, uint num) internal {} - function foo(address by, address from, address to, uint256 id) internal {} -} +// function foo(address by, address from, address to, uint256 id) internal {} +// } diff --git a/crates/shared/src/analyzer_like.rs b/crates/shared/src/analyzer_like.rs index 1145e4af..70832e81 100644 --- a/crates/shared/src/analyzer_like.rs +++ b/crates/shared/src/analyzer_like.rs @@ -5,92 +5,92 @@ use ahash::AHashMap; use std::collections::BTreeMap; #[derive(Debug, Clone, Copy, Default)] -pub struct JoinStats { - pub pure_no_children_joins: JoinStat, - pub pure_children_no_forks_joins: JoinStat, - pub pure_children_forks_joins: JoinStat, - - pub view_no_children_joins: JoinStat, - pub view_children_no_forks_joins: JoinStat, - pub view_children_forks_joins: JoinStat, - - pub mut_no_children_joins: JoinStat, - pub mut_children_no_forks_joins: JoinStat, - pub mut_children_forks_joins: JoinStat, +pub struct ApplyStats { + pub pure_no_children_applies: ApplyStat, + pub pure_children_no_forks_applies: ApplyStat, + pub pure_children_forks_applies: ApplyStat, + + pub view_no_children_applies: ApplyStat, + pub view_children_no_forks_applies: ApplyStat, + pub view_children_forks_applies: ApplyStat, + + pub mut_no_children_applies: ApplyStat, + pub mut_children_no_forks_applies: ApplyStat, + pub mut_children_forks_applies: ApplyStat, } -impl JoinStats { - pub fn total_joins(&self) -> usize { - self.total_pure_joins() + self.total_view_joins() + self.total_mut_joins() +impl ApplyStats { + pub fn total_applies(&self) -> usize { + self.total_pure_applies() + self.total_view_applies() + self.total_mut_applies() } - pub fn completed_joins(&self) -> usize { - self.completed_pure_joins() + self.completed_view_joins() + self.completed_mut_joins() + pub fn completed_applies(&self) -> usize { + self.completed_pure_applies() + self.completed_view_applies() + self.completed_mut_applies() } pub fn reduced_vars(&self) -> usize { self.pure_reduced_vars() + self.view_reduced_vars() + self.mut_reduced_vars() } - pub fn total_pure_joins(&self) -> usize { - self.pure_no_children_joins.num_joins - + self.pure_children_no_forks_joins.num_joins - + self.pure_children_forks_joins.num_joins + pub fn total_pure_applies(&self) -> usize { + self.pure_no_children_applies.num_applies + + self.pure_children_no_forks_applies.num_applies + + self.pure_children_forks_applies.num_applies } - pub fn completed_pure_joins(&self) -> usize { - self.pure_no_children_joins.completed_joins - + self.pure_children_no_forks_joins.completed_joins - + self.pure_children_forks_joins.completed_joins + pub fn completed_pure_applies(&self) -> usize { + self.pure_no_children_applies.completed_applies + + self.pure_children_no_forks_applies.completed_applies + + self.pure_children_forks_applies.completed_applies } pub fn pure_reduced_vars(&self) -> usize { - self.pure_no_children_joins.vars_reduced - + self.pure_children_no_forks_joins.vars_reduced - + self.pure_children_forks_joins.vars_reduced + self.pure_no_children_applies.vars_reduced + + self.pure_children_no_forks_applies.vars_reduced + + self.pure_children_forks_applies.vars_reduced } - pub fn total_view_joins(&self) -> usize { - self.view_no_children_joins.num_joins - + self.view_children_no_forks_joins.num_joins - + self.view_children_forks_joins.num_joins + pub fn total_view_applies(&self) -> usize { + self.view_no_children_applies.num_applies + + self.view_children_no_forks_applies.num_applies + + self.view_children_forks_applies.num_applies } - pub fn completed_view_joins(&self) -> usize { - self.view_no_children_joins.completed_joins - + self.view_children_no_forks_joins.completed_joins - + self.view_children_forks_joins.completed_joins + pub fn completed_view_applies(&self) -> usize { + self.view_no_children_applies.completed_applies + + self.view_children_no_forks_applies.completed_applies + + self.view_children_forks_applies.completed_applies } pub fn view_reduced_vars(&self) -> usize { - self.view_no_children_joins.vars_reduced - + self.view_children_no_forks_joins.vars_reduced - + self.view_children_forks_joins.vars_reduced + self.view_no_children_applies.vars_reduced + + self.view_children_no_forks_applies.vars_reduced + + self.view_children_forks_applies.vars_reduced } - pub fn total_mut_joins(&self) -> usize { - self.mut_no_children_joins.num_joins - + self.mut_children_no_forks_joins.num_joins - + self.mut_children_forks_joins.num_joins + pub fn total_mut_applies(&self) -> usize { + self.mut_no_children_applies.num_applies + + self.mut_children_no_forks_applies.num_applies + + self.mut_children_forks_applies.num_applies } - pub fn completed_mut_joins(&self) -> usize { - self.mut_no_children_joins.completed_joins - + self.mut_children_no_forks_joins.completed_joins - + self.mut_children_forks_joins.completed_joins + pub fn completed_mut_applies(&self) -> usize { + self.mut_no_children_applies.completed_applies + + self.mut_children_no_forks_applies.completed_applies + + self.mut_children_forks_applies.completed_applies } pub fn mut_reduced_vars(&self) -> usize { - self.mut_no_children_joins.vars_reduced - + self.mut_children_no_forks_joins.vars_reduced - + self.mut_children_forks_joins.vars_reduced + self.mut_no_children_applies.vars_reduced + + self.mut_children_no_forks_applies.vars_reduced + + self.mut_children_forks_applies.vars_reduced } } #[derive(Debug, Clone, Copy, Default)] -pub struct JoinStat { - pub num_joins: usize, - pub completed_joins: usize, +pub struct ApplyStat { + pub num_applies: usize, + pub completed_applies: usize, pub vars_reduced: usize, } @@ -113,6 +113,8 @@ pub trait AnalyzerLike: GraphLike { type FunctionParam; /// Type of a function return paramter type FunctionReturn; + /// Type of a context node + type ContextNode; /// Type of a builtin type Builtin; @@ -127,8 +129,8 @@ pub trait AnalyzerLike: GraphLike { fn max_depth(&self) -> usize; /// Returns the configured max fork width fn max_width(&self) -> usize; - fn user_types(&self) -> &AHashMap; - fn user_types_mut(&mut self) -> &mut AHashMap; + fn user_types(&self) -> &AHashMap>; + fn user_types_mut(&mut self) -> &mut AHashMap>; fn parse_expr( &mut self, arena: &mut RangeArena, @@ -142,6 +144,7 @@ pub trait AnalyzerLike: GraphLike { fn add_expr_err(&mut self, err: Self::ExprErr); fn expr_errs(&self) -> Vec; + #[allow(clippy::type_complexity)] fn builtin_fn_inputs( &self, ) -> &AHashMap, Vec)>; @@ -180,8 +183,10 @@ pub trait AnalyzerLike: GraphLike { } } - fn join_stats_mut(&mut self) -> &mut JoinStats; + fn apply_stats_mut(&mut self) -> &mut ApplyStats; fn handled_funcs(&self) -> &[Self::FunctionNode]; fn handled_funcs_mut(&mut self) -> &mut Vec; - fn file_mapping(&self) -> BTreeMap; + fn file_mapping(&self) -> BTreeMap; + fn minimize_debug(&self) -> &Option; + fn minimize_err(&mut self, ctx: Self::ContextNode) -> String; } diff --git a/crates/shared/src/graph_like.rs b/crates/shared/src/graph_like.rs index ae073436..a82fb954 100644 --- a/crates/shared/src/graph_like.rs +++ b/crates/shared/src/graph_like.rs @@ -147,6 +147,7 @@ pub trait GraphDot: GraphLike { } /// Creates a subgraph for visually identifying contexts and subcontexts + #[allow(clippy::too_many_arguments)] fn cluster_str( &self, arena: &mut RangeArena, diff --git a/crates/shared/src/search.rs b/crates/shared/src/search.rs index ea5ebb6c..6683a84c 100644 --- a/crates/shared/src/search.rs +++ b/crates/shared/src/search.rs @@ -453,7 +453,7 @@ where this_children } - /// Gets all children edges recursively + /// Gets all edge quads for a set of nodes fn edges_for_nodes( &self, nodes: &BTreeSet, diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs index f4ef4b73..cb458b57 100644 --- a/crates/solc-expressions/src/array.rs +++ b/crates/solc-expressions/src/array.rs @@ -141,8 +141,8 @@ pub trait Array: AnalyzerBackend + Sized { ctx.kill(self, loc, kind).into_expr_err(loc) } (ExprRet::Single(parent), ExprRet::Single(index)) | (ExprRet::Single(parent), ExprRet::SingleLiteral(index)) => { - let index = ContextVarNode::from(index).latest_version(self); - let parent = ContextVarNode::from(parent).latest_version(self); + let index = ContextVarNode::from(index).latest_version_or_inherited_in_ctx(ctx, self); + let parent = ContextVarNode::from(parent).latest_version_or_inherited_in_ctx(ctx, self); let _ = self.index_into_array_raw(arena, ctx, loc, index, parent, true, false)?; Ok(()) } @@ -168,11 +168,11 @@ pub trait Array: AnalyzerBackend + Sized { let len_var = self .get_length(arena, ctx, loc, parent, true)? .unwrap() - .latest_version(self); + .latest_version_or_inherited_in_ctx(ctx, self); self.require( arena, - len_var.latest_version(self), - idx.latest_version(self), + len_var.latest_version_or_inherited_in_ctx(ctx, self), + idx.latest_version_or_inherited_in_ctx(ctx, self), ctx, loc, RangeOp::Gt, @@ -187,7 +187,7 @@ pub trait Array: AnalyzerBackend + Sized { index.name(self).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(self); + let index_var = index_var.latest_version_or_inherited_in_ctx(ctx, self); let index_var = self.advance_var_in_ctx(index_var, loc, ctx)?; if !return_var { ctx.push_expr(ExprRet::Single(index_var.into()), self) @@ -273,13 +273,19 @@ pub trait Array: AnalyzerBackend + Sized { if !return_var { ctx.push_expr( - ExprRet::Single(idx_access_cvar.latest_version(self).into()), + ExprRet::Single( + idx_access_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + ), self, ) .into_expr_err(loc)?; Ok(None) } else { - Ok(Some(idx_access_cvar.latest_version(self))) + Ok(Some( + idx_access_cvar.latest_version_or_inherited_in_ctx(ctx, self), + )) } } } @@ -296,7 +302,11 @@ pub trait Array: AnalyzerBackend + Sized { // Was indeed an indexed value if let Some(index) = maybe_index_access.index_access_to_index(self) { // Found the associated index - let next_arr = self.advance_var_in_ctx(arr.latest_version(self), loc, ctx)?; + let next_arr = self.advance_var_in_ctx( + arr.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + )?; if next_arr .underlying(self) .into_expr_err(loc)? @@ -330,8 +340,8 @@ pub trait Array: AnalyzerBackend + Sized { arena, ctx, loc, - next_arr.latest_version(self), - next_arr.latest_version(self), + next_arr.latest_version_or_inherited_in_ctx(ctx, self), + next_arr.latest_version_or_inherited_in_ctx(ctx, self), )?; } } @@ -346,7 +356,11 @@ pub trait Array: AnalyzerBackend + Sized { 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(self), loc, ctx)?; + 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()) @@ -366,7 +380,11 @@ pub trait Array: AnalyzerBackend + Sized { new_length: ContextVarNode, backing_arr: ContextVarNode, ) -> Result<(), ExprErr> { - let next_arr = self.advance_var_in_ctx(backing_arr.latest_version(self), loc, ctx)?; + 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(new_length.into()); let min = Elem::from(backing_arr).set_length(new_len); @@ -397,7 +415,11 @@ pub trait Array: AnalyzerBackend + Sized { access: ContextVarNode, backing_arr: ContextVarNode, ) -> Result<(), ExprErr> { - let next_arr = self.advance_var_in_ctx(backing_arr.latest_version(self), loc, ctx)?; + let next_arr = self.advance_var_in_ctx( + backing_arr.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + )?; if next_arr .underlying(self) .into_expr_err(loc)? @@ -433,7 +455,7 @@ pub trait Array: AnalyzerBackend + Sized { loc, parent_nested_index, next_arr, - backing_arr.latest_version(self), + backing_arr.latest_version_or_inherited_in_ctx(ctx, self), ) } else { Ok(()) @@ -448,7 +470,11 @@ pub trait Array: AnalyzerBackend + Sized { 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(self), loc, ctx)?; + 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()); @@ -468,7 +494,11 @@ pub trait Array: AnalyzerBackend + Sized { 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(self), loc, ctx)?; + 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()); diff --git a/crates/solc-expressions/src/assign.rs b/crates/solc-expressions/src/assign.rs index edda6a55..f11f18d9 100644 --- a/crates/solc-expressions/src/assign.rs +++ b/crates/solc-expressions/src/assign.rs @@ -22,32 +22,40 @@ pub trait Assign: AnalyzerBackend + Sized rhs_expr: &Expression, ctx: ContextNode, ) -> Result<(), ExprErr> { - self.parse_ctx_expr(arena, rhs_expr, ctx)?; + self.parse_ctx_expr(arena, lhs_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( + let Some(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoLhs( loc, - "Assign operation had no right hand side".to_string(), + "Assign operation had no left hand side".to_string(), )); }; - if matches!(rhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_paths, analyzer).into_expr_err(loc)?; + if matches!(lhs_paths, ExprRet::CtxKilled(_)) { + ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.parse_ctx_expr(arena, lhs_expr, ctx)?; + + 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(lhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( + let Some(rhs_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoRhs( loc, - "Assign operation had no left hand side".to_string(), + "Assign operation had no right hand side".to_string(), )); }; - if matches!(lhs_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_paths, analyzer).into_expr_err(loc)?; + 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.flatten(), &rhs_paths)?; + analyzer.match_assign_sides(arena, ctx, loc, &lhs_paths, &rhs_paths)?; Ok(()) }) }) @@ -69,19 +77,19 @@ pub trait Assign: AnalyzerBackend + Sized Ok(()) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); - // let res = rhs_cvar - // .literal_cast_from(&lhs_cvar, self) - // .into_expr_err(loc); - // let _ = self.add_if_err(res); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); ctx.push_expr(self.assign(arena, loc, lhs_cvar, rhs_cvar, ctx)?, self) .into_expr_err(loc)?; Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); ctx.push_expr(self.assign(arena, loc, lhs_cvar, rhs_cvar, ctx)?, self) .into_expr_err(loc)?; Ok(()) @@ -132,8 +140,8 @@ pub trait Assign: AnalyzerBackend + Sized .into_expr_err(loc)?; let (new_lower_bound, new_upper_bound) = ( - Elem::from(rhs_cvar.latest_version(self)), - Elem::from(rhs_cvar.latest_version(self)), + Elem::from(rhs_cvar.latest_version_or_inherited_in_ctx(ctx, self)), + Elem::from(rhs_cvar.latest_version_or_inherited_in_ctx(ctx, self)), ); let needs_forcible = new_lower_bound @@ -144,9 +152,18 @@ pub trait Assign: AnalyzerBackend + Sized .into_expr_err(loc)?; let new_lhs = if needs_forcible { - self.advance_var_in_ctx_forcible(lhs_cvar.latest_version(self), loc, ctx, true)? + self.advance_var_in_ctx_forcible( + lhs_cvar.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + true, + )? } else { - self.advance_var_in_ctx(lhs_cvar.latest_version(self), loc, ctx)? + self.advance_var_in_ctx( + lhs_cvar.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + )? }; new_lhs.underlying_mut(self).into_expr_err(loc)?.tmp_of = @@ -234,13 +251,21 @@ pub trait Assign: AnalyzerBackend + Sized 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(self))?; + 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(fields) = rhs_cvar.struct_to_fields(self) { + if let Ok(fields) = rhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .struct_to_fields(self) + { if !fields.is_empty() { fields.into_iter().for_each(|field| { let mut new_var = field.underlying(self).unwrap().clone(); @@ -260,7 +285,12 @@ pub trait Assign: AnalyzerBackend + Sized } // advance the rhs variable to avoid recursion issues - self.advance_var_in_ctx_forcible(rhs_cvar.latest_version(self), loc, ctx, true)?; + self.advance_var_in_ctx_forcible( + rhs_cvar.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + true, + )?; Ok(ExprRet::Single(new_lhs.into())) } } diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index bc6943d2..01c02663 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -75,8 +75,10 @@ pub trait BinOp: AnalyzerBackend + Sized { "No right hand side provided for binary operation".to_string(), )), (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); lhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; rhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; ctx.push_expr( @@ -90,8 +92,10 @@ pub trait BinOp: AnalyzerBackend + Sized { ContextVarNode::from(*lhs) .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); ctx.push_expr( self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self, @@ -103,8 +107,10 @@ pub trait BinOp: AnalyzerBackend + Sized { ContextVarNode::from(*rhs) .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); ctx.push_expr( self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self, @@ -113,8 +119,10 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); ctx.push_expr( self.op(arena, loc, lhs_cvar, rhs_cvar, ctx, op, assign)?, self, @@ -209,14 +217,22 @@ pub trait BinOp: AnalyzerBackend + Sized { } }; - let new_rhs = rhs_cvar.latest_version(self); + let new_rhs = rhs_cvar.latest_version_or_inherited_in_ctx(ctx, self); let expr = Elem::Expr(RangeExpr::::new( - Elem::from(Reference::new(lhs_cvar.latest_version(self).into())), + Elem::from(Reference::new( + lhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + )), op, - Elem::from(Reference::new(rhs_cvar.latest_version(self).into())), + Elem::from(Reference::new( + rhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + )), )); - let new_lhs = new_lhs.latest_version(self); + let new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); new_lhs .set_range_min(self, arena, expr.clone()) .into_expr_err(loc)?; @@ -225,7 +241,12 @@ pub trait BinOp: AnalyzerBackend + Sized { .into_expr_err(loc)?; // to prevent some recursive referencing, forcibly increase lhs_cvar - self.advance_var_in_ctx_forcible(lhs_cvar.latest_version(self), loc, ctx, true)?; + self.advance_var_in_ctx_forcible( + lhs_cvar.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + true, + )?; if !unchecked { match op { @@ -268,7 +289,9 @@ pub trait BinOp: AnalyzerBackend + Sized { } } - Ok(ExprRet::Single(new_lhs.latest_version(self).into())) + Ok(ExprRet::Single( + new_lhs.latest_version_or_inherited_in_ctx(ctx, self).into(), + )) } #[tracing::instrument(level = "trace", skip_all)] @@ -343,7 +366,11 @@ pub trait BinOp: AnalyzerBackend + Sized { }; let expr = Elem::Expr(RangeExpr::::new( - Elem::from(Reference::new(lhs_cvar.latest_version(self).into())), + Elem::from(Reference::new( + lhs_cvar + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + )), RangeOp::BitNot, Elem::Null, )); @@ -428,7 +455,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ctx: ContextNode, ) -> Result, ExprErr> { // x - y >= type(x).min - let new_lhs = new_lhs.latest_version(self); + let new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; // in checked subtraction, we have to make sure x - y >= type(x).min ==> x >= type(x).min + y @@ -440,7 +467,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - tmp_lhs.latest_version(self), + tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, ctx, loc, @@ -471,7 +498,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - tmp_lhs.latest_version(self), + tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, ctx, loc, @@ -498,7 +525,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ctx: ContextNode, ) -> Result, ExprErr> { // lhs + rhs <= type(lhs).max - let new_lhs = new_lhs.latest_version(self); + let new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; // get type(lhs).max @@ -509,7 +536,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - tmp_lhs.latest_version(self), + tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, ctx, loc, @@ -542,7 +569,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - new_lhs.latest_version(self), + new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, ctx, loc, @@ -570,7 +597,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ctx: ContextNode, ) -> Result, ExprErr> { // lhs * rhs <= type(lhs).max - let new_lhs = new_lhs.latest_version(self); + let new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; // get type(lhs).max @@ -581,7 +608,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - tmp_lhs.latest_version(self), + tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, ctx, loc, @@ -635,7 +662,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - new_lhs.latest_version(self), + new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, ctx, loc, @@ -682,7 +709,7 @@ pub trait BinOp: AnalyzerBackend + Sized { } // lhs ** rhs <= type(lhs).max - let new_lhs = new_lhs.latest_version(self); + let new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); let tmp_lhs = self.advance_var_in_ctx_forcible(new_lhs, loc, ctx, true)?; // get type(lhs).max @@ -693,7 +720,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - tmp_lhs.latest_version(self), + tmp_lhs.latest_version_or_inherited_in_ctx(ctx, self), max, ctx, loc, @@ -725,7 +752,7 @@ pub trait BinOp: AnalyzerBackend + Sized { if self .require( arena, - new_lhs.latest_version(self), + new_lhs.latest_version_or_inherited_in_ctx(ctx, self), min, ctx, loc, diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs index 6195f5e5..59f81700 100644 --- a/crates/solc-expressions/src/cmp.rs +++ b/crates/solc-expressions/src/cmp.rs @@ -171,8 +171,10 @@ pub trait Cmp: AnalyzerBackend + Sized { self.cmp_inner(arena, ctx, loc, &ExprRet::Single(*rhs), op, rhs_paths) } (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let lhs_cvar = + ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); lhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; rhs_cvar.try_increase_size(self, arena).into_expr_err(loc)?; self.cmp_inner( diff --git a/crates/solc-expressions/src/context_builder/expr.rs b/crates/solc-expressions/src/context_builder/expr.rs index 2cf18a49..1230d848 100644 --- a/crates/solc-expressions/src/context_builder/expr.rs +++ b/crates/solc-expressions/src/context_builder/expr.rs @@ -413,7 +413,7 @@ pub trait ExpressionParser: PostDecrement(loc, expr) => self.post_decrement(arena, expr, *loc, ctx), // Misc. - Variable(ident) => self.variable(arena, ident, ctx, None), + 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) { diff --git a/crates/solc-expressions/src/context_builder/mod.rs b/crates/solc-expressions/src/context_builder/mod.rs index e57f28fa..65130aca 100644 --- a/crates/solc-expressions/src/context_builder/mod.rs +++ b/crates/solc-expressions/src/context_builder/mod.rs @@ -88,11 +88,18 @@ pub trait ContextBuilder: .and_then(|i| i) .into_expr_err(*loc); - let latest = ContextVarNode::from(*expr).latest_version(self); + let latest = + 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 {:?}", + ctx.path(self), + latest.ty(self).unwrap(), + target_var.ty(self).unwrap(), + ); let next = self .advance_var_in_ctx_forcible(latest, *loc, ctx, true) .unwrap(); diff --git a/crates/solc-expressions/src/context_builder/stmt.rs b/crates/solc-expressions/src/context_builder/stmt.rs index 51f24c90..530dd124 100644 --- a/crates/solc-expressions/src/context_builder/stmt.rs +++ b/crates/solc-expressions/src/context_builder/stmt.rs @@ -311,7 +311,27 @@ pub trait StatementParser: return Ok(()); } - analyzer.parse_ctx_expr(arena, &var_decl.ty, ctx)?; + 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, @@ -356,7 +376,14 @@ pub trait StatementParser: } } } else { - let res = self.parse_ctx_expr(arena, &var_decl.ty, ctx); + 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; } diff --git a/crates/solc-expressions/src/env.rs b/crates/solc-expressions/src/env.rs index 54d8591f..f5c9436a 100644 --- a/crates/solc-expressions/src/env.rs +++ b/crates/solc-expressions/src/env.rs @@ -64,7 +64,11 @@ pub trait Env: AnalyzerBackend + Sized { let name = format!("block.{}", ident_name); tracing::trace!("Block Env member access: {}", name); if let Some(attr_var) = ctx.var_by_name_or_recurse(self, &name).into_expr_err(loc)? { - Ok(ExprRet::Single(attr_var.latest_version(self).into())) + Ok(ExprRet::Single( + attr_var + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + )) } else { let (node, name) = match ident_name { "hash" => { @@ -295,7 +299,11 @@ pub trait Env: AnalyzerBackend + Sized { tracing::trace!("Msg Env member access: {}", name); if let Some(attr_var) = ctx.var_by_name_or_recurse(self, &name).into_expr_err(loc)? { - Ok(ExprRet::Single(attr_var.latest_version(self).into())) + Ok(ExprRet::Single( + attr_var + .latest_version_or_inherited_in_ctx(ctx, self) + .into(), + )) } else { let (node, name) = match ident_name { "data" => { diff --git a/crates/solc-expressions/src/func_call/join.rs b/crates/solc-expressions/src/func_call/apply.rs similarity index 64% rename from crates/solc-expressions/src/func_call/join.rs rename to crates/solc-expressions/src/func_call/apply.rs index ba5945ed..d7cf18ce 100644 --- a/crates/solc-expressions/src/func_call/join.rs +++ b/crates/solc-expressions/src/func_call/apply.rs @@ -6,7 +6,7 @@ use crate::variable::Variable; use graph::{ elem::{Elem, RangeElem, RangeExpr, RangeOp}, nodes::{ - Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, FuncVis, FunctionNode, FunctionParamNode, KilledKind, }, AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, SolcRange, VarType, @@ -17,20 +17,20 @@ use solang_parser::pt::{Expression, Loc}; use std::collections::BTreeMap; -impl FuncJoiner for T where +impl FuncApplier for T where T: AnalyzerBackend + Sized + GraphBackend + CallerHelper - + JoinStatTracker + + ApplyStatTracker { } /// A trait for calling a function -pub trait FuncJoiner: - GraphBackend + AnalyzerBackend + Sized + JoinStatTracker +pub trait FuncApplier: + GraphBackend + AnalyzerBackend + Sized + ApplyStatTracker { #[tracing::instrument(level = "trace", skip_all)] - fn join( + fn apply( &mut self, arena: &mut RangeArena>, ctx: ContextNode, @@ -41,8 +41,8 @@ pub trait FuncJoiner: seen: &mut Vec, ) -> Result { tracing::trace!( - "Trying to join function: {}", - func.name(self).into_expr_err(loc)? + "Trying to apply function: {}", + func.loc_specified_name(self).into_expr_err(loc)? ); // ensure no modifiers (for now) // if pure function: @@ -50,169 +50,182 @@ pub trait FuncJoiner: // grab return node's simplified range // replace fundamentals with function inputs // update ctx name in place - - if func.is_pure(self).into_expr_err(loc)? { - // pure functions are guaranteed to not require the use of state, so - // the only things we care about are function inputs and function outputs - if let Some(body_ctx) = func.maybe_body_ctx(self) { - if body_ctx - .underlying(self) - .into_expr_err(loc)? - .child - .is_some() - { - tracing::trace!("Joining function: {}", func.name(self).into_expr_err(loc)?); - let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; - match edges.len() { - 0 => {} - 1 => { - self.join_pure( - arena, - loc, - func, - params, - func_inputs, - body_ctx, - edges[0], - ctx, - false, - )?; - return Ok(true); - } - 2.. => { - tracing::trace!( - "Branching pure join function: {}", - func.name(self).into_expr_err(loc)? - ); - // self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { - let new_forks = ctx.set_join_forks(loc, edges.clone(), self).unwrap(); - edges.into_iter().zip(new_forks.iter()).try_for_each( - |(edge, new_fork)| { - let res = self.join_pure( - arena, - loc, - func, - params, - func_inputs, - body_ctx, - edge, - *new_fork, - true, - )?; - if !res { - new_fork - .kill(self, loc, KilledKind::Unreachable) - .into_expr_err(loc)?; - Ok(()) - } else { - Ok(()) - } - }, - )?; - return Ok(true); + // + match func.visibility(self).into_expr_err(loc)? { + FuncVis::Pure => { + // pure functions are guaranteed to not require the use of state, so + // the only things we care about are function inputs and function outputs + if let Some(body_ctx) = func.maybe_body_ctx(self) { + if body_ctx + .underlying(self) + .into_expr_err(loc)? + .child + .is_some() + { + tracing::trace!( + "Applying function: {}", + func.name(self).into_expr_err(loc)? + ); + let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; + match edges.len() { + 0 => {} + 1 => { + self.apply_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + edges[0], + ctx, + false, + )?; + return Ok(true); + } + 2.. => { + tracing::trace!( + "Branching pure apply function: {}", + func.name(self).into_expr_err(loc)? + ); + // self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + let new_forks = + ctx.set_apply_forks(loc, edges.clone(), self).unwrap(); + edges.into_iter().zip(new_forks.iter()).try_for_each( + |(edge, new_fork)| { + let res = self.apply_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + edge, + *new_fork, + true, + )?; + if !res { + new_fork + .kill(self, loc, KilledKind::Unreachable) + .into_expr_err(loc)?; + Ok(()) + } else { + Ok(()) + } + }, + )?; + return Ok(true); + } } + } else { + tracing::trace!( + "Childless pure apply: {}", + func.name(self).into_expr_err(loc)? + ); + self.apply_pure( + arena, + loc, + func, + params, + func_inputs, + body_ctx, + body_ctx, + ctx, + false, + )?; + return Ok(true); } } else { - tracing::trace!( - "Childless pure join: {}", - func.name(self).into_expr_err(loc)? - ); - self.join_pure( - arena, - loc, - func, - params, - func_inputs, - body_ctx, - body_ctx, - ctx, - false, - )?; - return Ok(true); - } - } else { - tracing::trace!("Pure function not processed"); - if ctx.associated_fn(self) == Ok(func) { - return Ok(false); - } + tracing::trace!("Pure function not processed"); + if ctx.associated_fn(self) == Ok(func) { + return Ok(false); + } - if seen.contains(&func) { - return Ok(false); - } + if seen.contains(&func) { + return Ok(false); + } - 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.handled_funcs_mut().push(func); + if let Some(body) = &func.underlying(self).unwrap().body.clone() { + self.parse_ctx_statement(arena, body, false, Some(func)); + } - seen.push(func); - return self.join(arena, ctx, loc, func, params, func_inputs, seen); + seen.push(func); + return self.apply(arena, ctx, loc, func, params, func_inputs, seen); + } } - } else if func.is_view(self).into_expr_err(loc)? { - if let Some(body_ctx) = func.maybe_body_ctx(self) { - if body_ctx - .underlying(self) - .into_expr_err(loc)? - .child - .is_some() - { - let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; - if edges.len() == 1 { - tracing::trace!( - "View join function: {}", - func.name(self).into_expr_err(loc)? - ); - self.add_completed_view(false, false, false, body_ctx); + FuncVis::View => { + if let Some(body_ctx) = func.maybe_body_ctx(self) { + if body_ctx + .underlying(self) + .into_expr_err(loc)? + .child + .is_some() + { + let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; + if edges.len() == 1 { + tracing::trace!( + "View apply function: {}", + func.name(self).into_expr_err(loc)? + ); + self.add_completed_view(false, false, false, body_ctx); + } else { + tracing::trace!( + "Branching view apply function: {}", + func.name(self).into_expr_err(loc)? + ); + self.add_completed_view(false, false, true, body_ctx); + } } else { tracing::trace!( - "Branching view join function: {}", + "Childless view apply function: {}", func.name(self).into_expr_err(loc)? ); - self.add_completed_view(false, false, true, body_ctx); + self.add_completed_view(false, true, false, body_ctx); } } else { - tracing::trace!( - "Childless view join function: {}", - func.name(self).into_expr_err(loc)? - ); - self.add_completed_view(false, true, false, body_ctx); + tracing::trace!("View function not processed"); } - } else { - tracing::trace!("View function not processed"); } - } else if let Some(body_ctx) = func.maybe_body_ctx(self) { - if body_ctx - .underlying(self) - .into_expr_err(loc)? - .child - .is_some() - { - let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; - if edges.len() == 1 { - tracing::trace!("Mut join function: {}", func.name(self).into_expr_err(loc)?); - self.add_completed_mut(false, false, false, body_ctx); + FuncVis::Mut => { + if let Some(body_ctx) = func.maybe_body_ctx(self) { + if body_ctx + .underlying(self) + .into_expr_err(loc)? + .child + .is_some() + { + let edges = body_ctx.successful_edges(self).into_expr_err(loc)?; + if edges.len() == 1 { + tracing::trace!( + "Mut apply function: {}", + func.name(self).into_expr_err(loc)? + ); + self.add_completed_mut(false, false, false, body_ctx); + } else { + tracing::trace!( + "Branching mut apply function: {}", + func.name(self).into_expr_err(loc)? + ); + self.add_completed_mut(false, false, true, body_ctx); + } + } else { + tracing::trace!( + "Childless mut apply function: {}", + func.name(self).into_expr_err(loc)? + ); + self.add_completed_mut(false, true, false, body_ctx); + } } else { - tracing::trace!( - "Branching mut join function: {}", - func.name(self).into_expr_err(loc)? - ); - self.add_completed_mut(false, false, true, body_ctx); + tracing::trace!("Mut function not processed"); } - } else { - tracing::trace!( - "Childless mut join function: {}", - func.name(self).into_expr_err(loc)? - ); - self.add_completed_mut(false, true, false, body_ctx); } - } else { - tracing::trace!("Mut function not processed"); } Ok(false) } - fn join_pure( + fn apply_pure( &mut self, arena: &mut RangeArena>, loc: Loc, @@ -233,7 +246,7 @@ pub trait FuncJoiner: .enumerate() .map(|(i, (_, ret_node))| { let mut new_var = ret_node.underlying(self).unwrap().clone(); - let new_name = format!("{}.{i}", func.name(self).unwrap()); + let new_name = format!("{}.{i}", func.loc_specified_name(self).unwrap()); new_var.name.clone_from(&new_name); new_var.display_name = new_name; if let Some(mut range) = new_var.ty.take_range() { @@ -267,7 +280,7 @@ pub trait FuncJoiner: let mut new_var = field.underlying(self).unwrap().clone(); let new_name = format!( "{}.{i}.{}", - func.name(self).unwrap(), + func.loc_specified_name(self).unwrap(), field.name(self).unwrap() ); new_var.name.clone_from(&new_name); @@ -379,12 +392,15 @@ pub trait FuncJoiner: } }); - target_ctx.underlying_mut(self).into_expr_err(loc)?.path = format!( + let new_path = format!( "{}.{}.resume{{ {} }}", target_ctx.path(self), resulting_edge.path(self), target_ctx.associated_fn_name(self).unwrap() ); + let underlying_mut = target_ctx.underlying_mut(self).into_expr_err(loc)?; + underlying_mut.path = new_path; + underlying_mut.applies.push(func); target_ctx .push_expr(ExprRet::Multi(rets), self) .into_expr_err(loc)?; @@ -409,7 +425,7 @@ pub trait FuncJoiner: .try_for_each(|(param, func_input)| { if let Some(name) = param.maybe_name(self).into_expr_err(loc)? { let mut new_cvar = func_input - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .underlying(self) .into_expr_err(loc)? .clone(); @@ -454,15 +470,15 @@ pub trait FuncJoiner: let new_min = r.range_min().into_owned().cast(r2.range_min().into_owned()); let new_max = r.range_max().into_owned().cast(r2.range_max().into_owned()); replacement - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .try_set_range_min(self, arena, new_min) .into_expr_err(loc)?; replacement - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .try_set_range_max(self, arena, new_max) .into_expr_err(loc)?; replacement - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .try_set_range_exclusions(self, r.exclusions) .into_expr_err(loc)?; } @@ -531,9 +547,9 @@ pub trait FuncJoiner: } } -impl JoinStatTracker for T where T: AnalyzerLike + GraphBackend {} +impl ApplyStatTracker for T where T: AnalyzerLike + GraphBackend {} -pub trait JoinStatTracker: AnalyzerLike { +pub trait ApplyStatTracker: AnalyzerLike { fn add_completed_pure( &mut self, completed: bool, @@ -546,12 +562,12 @@ pub trait JoinStatTracker: AnalyzerLike { match (no_children, forks) { (true, _) => { let num_vars = target_ctx.vars(self).len(); - let stats = self.join_stats_mut(); - stats.pure_no_children_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.pure_no_children_applies.num_applies += 1; if completed { - stats.pure_no_children_joins.completed_joins += 1; + stats.pure_no_children_applies.completed_applies += 1; } - stats.pure_no_children_joins.vars_reduced += num_vars; + stats.pure_no_children_applies.vars_reduced += num_vars; } (false, false) => { let mut parents = target_ctx.parent_list(self).unwrap(); @@ -561,18 +577,18 @@ pub trait JoinStatTracker: AnalyzerLike { acc += ctx.vars(self).len(); acc }); - let stats = self.join_stats_mut(); - stats.pure_children_no_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.pure_children_no_forks_applies.num_applies += 1; if completed { - stats.pure_children_no_forks_joins.completed_joins += 1; + stats.pure_children_no_forks_applies.completed_applies += 1; } - stats.pure_children_no_forks_joins.vars_reduced += vars_reduced; + stats.pure_children_no_forks_applies.vars_reduced += vars_reduced; } (false, true) => { - let stats = self.join_stats_mut(); - stats.pure_children_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.pure_children_forks_applies.num_applies += 1; if completed { - stats.pure_children_forks_joins.completed_joins += 1; + stats.pure_children_forks_applies.completed_applies += 1; } } } @@ -590,12 +606,12 @@ pub trait JoinStatTracker: AnalyzerLike { match (no_children, forks) { (true, _) => { let num_vars = target_ctx.vars(self).len(); - let stats = self.join_stats_mut(); - stats.view_no_children_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.view_no_children_applies.num_applies += 1; if completed { - stats.view_no_children_joins.completed_joins += 1; + stats.view_no_children_applies.completed_applies += 1; } - stats.view_no_children_joins.vars_reduced += num_vars; + stats.view_no_children_applies.vars_reduced += num_vars; } (false, false) => { let mut parents = target_ctx.parent_list(self).unwrap(); @@ -605,19 +621,19 @@ pub trait JoinStatTracker: AnalyzerLike { acc += ctx.vars(self).len(); acc }); - let stats = self.join_stats_mut(); - stats.view_children_no_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.view_children_no_forks_applies.num_applies += 1; if completed { - stats.view_children_no_forks_joins.completed_joins += 1; + stats.view_children_no_forks_applies.completed_applies += 1; } // parents is now: [body_ctx, ..., edges[0]] - stats.view_children_no_forks_joins.vars_reduced += vars_reduced; + stats.view_children_no_forks_applies.vars_reduced += vars_reduced; } (false, true) => { - let stats = self.join_stats_mut(); - stats.view_children_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.view_children_forks_applies.num_applies += 1; if completed { - stats.view_children_forks_joins.completed_joins += 1; + stats.view_children_forks_applies.completed_applies += 1; } } } @@ -635,25 +651,25 @@ pub trait JoinStatTracker: AnalyzerLike { match (no_children, forks) { (true, _) => { let num_vars = target_ctx.vars(self).len(); - let stats = self.join_stats_mut(); - stats.mut_no_children_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.mut_no_children_applies.num_applies += 1; if completed { - stats.mut_no_children_joins.completed_joins += 1; + stats.mut_no_children_applies.completed_applies += 1; } - stats.mut_no_children_joins.vars_reduced += num_vars; + stats.mut_no_children_applies.vars_reduced += num_vars; } (false, false) => { - let stats = self.join_stats_mut(); - stats.mut_children_no_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.mut_children_no_forks_applies.num_applies += 1; if completed { - stats.mut_children_no_forks_joins.completed_joins += 1; + stats.mut_children_no_forks_applies.completed_applies += 1; } } (false, true) => { - let stats = self.join_stats_mut(); - stats.mut_children_forks_joins.num_joins += 1; + let stats = self.apply_stats_mut(); + stats.mut_children_forks_applies.num_applies += 1; if completed { - stats.mut_children_forks_joins.completed_joins += 1; + stats.mut_children_forks_applies.completed_applies += 1; } } } diff --git a/crates/solc-expressions/src/func_call/func_caller.rs b/crates/solc-expressions/src/func_call/func_caller.rs index c7b19e1f..248de5c5 100644 --- a/crates/solc-expressions/src/func_call/func_caller.rs +++ b/crates/solc-expressions/src/func_call/func_caller.rs @@ -1,7 +1,7 @@ //! Traits & blanket implementations that facilitate performing various forms of function calls. use crate::{ - func_call::join::FuncJoiner, func_call::modifier::ModifierCaller, helper::CallerHelper, + func_call::apply::FuncApplier, func_call::modifier::ModifierCaller, helper::CallerHelper, internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller, namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExpressionParser, StatementParser, }; @@ -339,7 +339,9 @@ pub trait FuncCaller: ExprRet::Single(input_var) | ExprRet::SingleLiteral(input_var) => { // if we get a single var, we expect the func to only take a single // variable - let inputs = vec![ContextVarNode::from(input_var).latest_version(self)]; + let inputs = + vec![ContextVarNode::from(input_var) + .latest_version_or_inherited_in_ctx(ctx, self)]; self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { analyzer.func_call_inner( arena, @@ -371,7 +373,8 @@ pub trait FuncCaller: .iter() .map(|expr_ret| { let var = expr_ret.expect_single().into_expr_err(loc)?; - Ok(ContextVarNode::from(var).latest_version(self)) + Ok(ContextVarNode::from(var) + .latest_version_or_inherited_in_ctx(ctx, self)) }) .collect::, ExprErr>>()?; self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { @@ -398,6 +401,19 @@ pub trait FuncCaller: )) } } + ExprRet::Null => self.apply_to_edges(ctx, loc, arena, &|analyzer, arena, ctx, loc| { + analyzer.func_call_inner( + arena, + false, + ctx, + func, + loc, + &[], + ¶ms, + func_call_str, + &modifier_state, + ) + }), e => todo!("here: {:?}", e), } } @@ -417,7 +433,7 @@ pub trait FuncCaller: modifier_state: &Option, ) -> Result<(), ExprErr> { if !entry_call { - if let Ok(true) = self.join(arena, ctx, loc, func_node, params, inputs, &mut vec![]) { + if let Ok(true) = self.apply(arena, ctx, loc, func_node, params, inputs, &mut vec![]) { return Ok(()); } } diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs index 4e84ed86..aa25d418 100644 --- a/crates/solc-expressions/src/func_call/helper.rs +++ b/crates/solc-expressions/src/func_call/helper.rs @@ -38,14 +38,14 @@ pub trait CallerHelper: AnalyzerBackend + self.add_if_err(param.maybe_name(self).into_expr_err(loc))? { let res = input - .latest_version(self) + .latest_version_or_inherited_in_ctx(callee_ctx, self) .underlying(self) .into_expr_err(loc) .cloned(); let mut new_cvar = self.add_if_err(res)?; new_cvar.loc = Some(param.loc(self).unwrap()); new_cvar.name.clone_from(&name); - new_cvar.display_name = name; + new_cvar.display_name.clone_from(&name); new_cvar.is_tmp = false; new_cvar.storage = if let Some(StorageLocation::Storage(_)) = param.underlying(self).unwrap().storage @@ -59,7 +59,7 @@ pub trait CallerHelper: AnalyzerBackend + self.add_edge( node, - input.latest_version(self), + input.latest_version_or_inherited_in_ctx(callee_ctx, self), Edge::Context(ContextEdge::InputVariable), ); @@ -70,18 +70,58 @@ pub trait CallerHelper: AnalyzerBackend + } } - self.add_edge( - node, - input.latest_version(self), - Edge::Context(ContextEdge::InputVariable), - ); - 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) .unwrap(); } - let node = node.latest_version(self); + + let fields = input.struct_to_fields(self).ok()?; + if !fields.is_empty() { + // bring along struct fields + fields + .iter() + .try_for_each(|field| -> Result<(), ExprErr> { + let full_name = field.name(self).into_expr_err(loc)?; + let field_names = full_name.split('.').collect::>(); + let field_name = + field_names.get(1).ok_or(ExprErr::MemberAccessNotFound( + loc, + "Badly named struct field".to_string(), + ))?; + let mut new_field = field + .latest_version_or_inherited_in_ctx(callee_ctx, self) + .underlying(self) + .into_expr_err(loc)? + .clone(); + new_field.loc = Some(param.loc(self).unwrap()); + new_field.name = format!("{name}.{field_name}"); + new_field.display_name.clone_from(&new_field.name); + new_field.is_tmp = false; + new_field.storage = if let Some(StorageLocation::Storage(_)) = + field.underlying(self).unwrap().storage + { + new_field.storage + } else { + None + }; + + let field_node = ContextVarNode::from( + self.add_node(Node::ContextVar(new_field)), + ); + + self.add_edge( + field_node, + node, + Edge::Context(ContextEdge::AttrAccess("field")), + ); + + Ok(()) + }) + .ok()?; + } + + let node = node.latest_version_or_inherited_in_ctx(callee_ctx, self); if let (Some(r), Some(r2)) = (node.range(self).unwrap(), param.range(self).unwrap()) @@ -91,17 +131,17 @@ pub trait CallerHelper: AnalyzerBackend + let new_max = r.range_max().into_owned().cast(r2.range_max().into_owned()); let res = node - .latest_version(self) + .latest_version_or_inherited_in_ctx(callee_ctx, self) .try_set_range_min(self, arena, new_min) .into_expr_err(loc); self.add_if_err(res); let res = node - .latest_version(self) + .latest_version_or_inherited_in_ctx(callee_ctx, self) .try_set_range_max(self, arena, new_max) .into_expr_err(loc); self.add_if_err(res); let res = node - .latest_version(self) + .latest_version_or_inherited_in_ctx(callee_ctx, self) .try_set_range_exclusions(self, r.exclusions.clone()) .into_expr_err(loc); self.add_if_err(res); @@ -149,7 +189,7 @@ pub trait CallerHelper: AnalyzerBackend + ctx.push_expr(ret, analyzer).into_expr_err(loc) }) } else { - Ok(()) + ctx.push_expr(ExprRet::Null, self).into_expr_err(loc) } } @@ -481,8 +521,15 @@ pub trait CallerHelper: AnalyzerBackend + tmp_ret .underlying_mut(self) .into_expr_err(loc)? - .display_name = - format!("{}.{}", callee_ctx.associated_fn_name(self).unwrap(), i); + .display_name = format!( + "{}.{}", + callee_ctx + .associated_fn(self) + .unwrap() + .loc_specified_name(self) + .unwrap(), + i + ); ret_subctx.add_var(tmp_ret, self).into_expr_err(loc)?; self.add_edge(tmp_ret, ret_subctx, Edge::Context(ContextEdge::Variable)); Ok(ExprRet::Single(tmp_ret.into())) 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 0ba6e420..232e2adc 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 @@ -145,7 +145,12 @@ pub trait DynBuiltinCaller: AnalyzerBackend, ExprErr>>()?; // create the length variable - let _ = self.tmp_length(arena, acc.latest_version(self), ctx, loc); + let _ = self.tmp_length( + arena, + acc.latest_version_or_inherited_in_ctx(ctx, self), + ctx, + loc, + ); Ok(()) } diff --git a/crates/solc-expressions/src/func_call/mod.rs b/crates/solc-expressions/src/func_call/mod.rs index 2d8fcd1a..2f963567 100644 --- a/crates/solc-expressions/src/func_call/mod.rs +++ b/crates/solc-expressions/src/func_call/mod.rs @@ -1,7 +1,7 @@ +pub mod apply; pub mod func_caller; pub mod helper; pub mod internal_call; pub mod intrinsic_call; -pub mod join; pub mod modifier; pub mod namespaced_call; diff --git a/crates/solc-expressions/src/lib.rs b/crates/solc-expressions/src/lib.rs index 9d4ed05c..00ec7a14 100644 --- a/crates/solc-expressions/src/lib.rs +++ b/crates/solc-expressions/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::too_many_arguments)] + mod array; mod assign; mod bin_op; diff --git a/crates/solc-expressions/src/member_access/library_access.rs b/crates/solc-expressions/src/member_access/library_access.rs index 62a138e4..e2281ffa 100644 --- a/crates/solc-expressions/src/member_access/library_access.rs +++ b/crates/solc-expressions/src/member_access/library_access.rs @@ -40,6 +40,7 @@ pub trait LibraryAccess: AnalyzerBackend + /// Get all possible library functions fn possible_library_funcs(&mut self, ctx: ContextNode, ty: NodeIdx) -> BTreeSet { + tracing::trace!("looking for library functions of type: {:?}", self.node(ty)); let mut funcs: BTreeSet = BTreeSet::new(); if let Some(associated_contract) = ctx.maybe_associated_contract(self).unwrap() { // search for contract scoped `using` statements diff --git a/crates/solc-expressions/src/member_access/list_access.rs b/crates/solc-expressions/src/member_access/list_access.rs index 41ecd902..e8dd77ba 100644 --- a/crates/solc-expressions/src/member_access/list_access.rs +++ b/crates/solc-expressions/src/member_access/list_access.rs @@ -70,10 +70,18 @@ pub trait ListAccess: AnalyzerBackend + Si array: ContextVarNode, return_var: bool, ) -> Result, ExprErr> { - let next_arr = self.advance_var_in_ctx(array.latest_version(self), loc, ctx)?; + let next_arr = self.advance_var_in_ctx( + array.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + )?; // search for latest length if let Some(len_var) = next_arr.array_to_len_var(self) { - let len_node = self.advance_var_in_ctx(len_var.latest_version(self), loc, ctx)?; + let len_node = self.advance_var_in_ctx( + len_var.latest_version_or_inherited_in_ctx(ctx, self), + loc, + ctx, + )?; if !return_var { ctx.push_expr(ExprRet::Single(len_node.into()), self) .into_expr_err(loc)?; @@ -83,63 +91,6 @@ pub trait ListAccess: AnalyzerBackend + Si } } else { self.create_length(arena, ctx, loc, array, next_arr, return_var) - // no length variable, create one - // let name = format!("{}.length", array.name(self).into_expr_err(loc)?); - - // // Create the range from the current length or default to [0, uint256.max] - - // let len_min = Elem::from(next_arr) - // .get_length() - // .max(Elem::from(Concrete::from(U256::zero()))); - // let len_max = Elem::from(next_arr) - // .get_length() - // .min(Elem::from(Concrete::from(U256::MAX))); - // let range = SolcRange::new(len_min, len_max, vec![]); - - // let len_var = ContextVar { - // loc: Some(loc), - // name, - // display_name: array.display_name(self).into_expr_err(loc)? + ".length", - // storage: None, - // is_tmp: false, - // tmp_of: None, - // is_symbolic: true, - // is_return: false, - // ty: VarType::BuiltIn( - // BuiltInNode::from(self.builtin_or_add(Builtin::Uint(256))), - // Some(range), - // ), - // }; - // let len_node = ContextVarNode::from(self.add_node(Node::ContextVar(len_var))); - // self.add_edge( - // len_node, - // array, - // 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_next_arr = - // self.advance_var_in_ctx_forcible(array.latest_version(self), loc, ctx, true)?; - // let update_array_len = - // Elem::from(next_arr.latest_version(self)).set_length(len_node.into()); - - // // Update the array - // next_next_arr - // .set_range_min(self, update_array_len.clone()) - // .into_expr_err(loc)?; - // next_next_arr - // .set_range_max(self, update_array_len.clone()) - // .into_expr_err(loc)?; - - // if !return_var { - // ctx.push_expr(ExprRet::Single(len_node.into()), self) - // .into_expr_err(loc)?; - // Ok(None) - // } else { - // Ok(Some(len_node)) - // } } } @@ -189,10 +140,15 @@ pub trait ListAccess: AnalyzerBackend + Si 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(self), loc, ctx, true)?; + 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(self)).set_length(len_node.into()); + Elem::from(target_array.latest_version_or_inherited_in_ctx(ctx, self)) + .set_length(len_node.into()); // Update the array next_target_arr @@ -224,7 +180,7 @@ pub trait ListAccess: AnalyzerBackend + Si let name = format!("{}.length", arr.name(self).unwrap()); tracing::trace!("Length access: {}", name); if let Some(attr_var) = array_ctx.var_by_name_or_recurse(self, &name).unwrap() { - attr_var.latest_version(self) + attr_var.latest_version_or_inherited_in_ctx(array_ctx, self) } else { let range = if let Ok(Some(size)) = arr.ty(self).unwrap().maybe_array_size(self) { SolcRange::from(Concrete::from(size)) @@ -250,7 +206,11 @@ pub trait ListAccess: AnalyzerBackend + Si let len_node = self.add_node(Node::ContextVar(len_var)); let next_arr = self - .advance_var_in_ctx(arr.latest_version(self), loc, array_ctx) + .advance_var_in_ctx( + arr.latest_version_or_inherited_in_ctx(array_ctx, self), + loc, + array_ctx, + ) .unwrap(); if next_arr .underlying(self) diff --git a/crates/solc-expressions/src/member_access/member_trait.rs b/crates/solc-expressions/src/member_access/member_trait.rs index fd0de843..6c6967b8 100644 --- a/crates/solc-expressions/src/member_access/member_trait.rs +++ b/crates/solc-expressions/src/member_access/member_trait.rs @@ -142,7 +142,12 @@ pub trait MemberAccess: member_idx: NodeIdx, ) -> Result, ExprErr> { let res = match self.node(member_idx) { - Node::ContextVar(cvar) => match &cvar.ty { + Node::ContextVar(cvar) => { + tracing::trace!( + "looking for visible member functions of type: {:?}", + 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)?; @@ -178,6 +183,10 @@ pub trait MemberAccess: .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() @@ -190,7 +199,8 @@ pub trait MemberAccess: _ => unreachable!() } } - }, + } + } Node::Contract(_) => ContractNode::from(member_idx).funcs(self), Node::Concrete(_) | Node::Ty(_) diff --git a/crates/solc-expressions/src/member_access/struct_access.rs b/crates/solc-expressions/src/member_access/struct_access.rs index 65c73877..b8833f0f 100644 --- a/crates/solc-expressions/src/member_access/struct_access.rs +++ b/crates/solc-expressions/src/member_access/struct_access.rs @@ -28,12 +28,21 @@ pub trait StructAccess: ) -> Result { let name = format!( "{}.{}", - struct_node.name(self).into_expr_err(loc)?, + 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(attr_var) = ctx.var_by_name_or_recurse(self, &name).into_expr_err(loc)? { - Ok(ExprRet::Single(attr_var.latest_version(self).into())) + 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) { let cvar = if let Some(parent) = maybe_parent { parent @@ -52,8 +61,8 @@ pub trait StructAccess: ContextVarNode::from(member_idx).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)); + // 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)) } else { panic!("Couldn't create field variable"); diff --git a/crates/solc-expressions/src/pre_post_in_decrement.rs b/crates/solc-expressions/src/pre_post_in_decrement.rs index 8621893b..9550c32d 100644 --- a/crates/solc-expressions/src/pre_post_in_decrement.rs +++ b/crates/solc-expressions/src/pre_post_in_decrement.rs @@ -141,7 +141,7 @@ pub trait PrePostIncDecrement: self.match_in_de_crement(arena, ctx, pre, increment, loc, &ExprRet::Single(*var)) } ExprRet::Single(var) => { - let cvar = ContextVarNode::from(*var).latest_version(self); + 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()); @@ -160,8 +160,13 @@ pub trait PrePostIncDecrement: new_cvar .set_range_max(self, arena, elem + one) .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) - .into_expr_err(loc)?; + ctx.push_expr( + ExprRet::Single( + dup.latest_version_or_inherited_in_ctx(ctx, self).into(), + ), + self, + ) + .into_expr_err(loc)?; Ok(()) } else { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; @@ -177,8 +182,13 @@ pub trait PrePostIncDecrement: new_cvar .set_range_max(self, arena, elem + one) .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) - .into_expr_err(loc)?; + ctx.push_expr( + ExprRet::Single( + dup.latest_version_or_inherited_in_ctx(ctx, self).into(), + ), + self, + ) + .into_expr_err(loc)?; Ok(()) } } else if pre { @@ -194,8 +204,11 @@ pub trait PrePostIncDecrement: new_cvar .set_range_max(self, arena, elem - one) .into_expr_err(loc)?; - ctx.push_expr(ExprRet::Single(dup.latest_version(self).into()), self) - .into_expr_err(loc)?; + ctx.push_expr( + ExprRet::Single(dup.latest_version_or_inherited_in_ctx(ctx, self).into()), + self, + ) + .into_expr_err(loc)?; Ok(()) } else { let dup = cvar.as_tmp(loc, ctx, self).into_expr_err(loc)?; diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index dee22f09..6a953ab8 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -590,14 +590,9 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ctx.push_expr(lhs_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(other.loc())?, - ); - let rhs_paths = - ExprRet::Single(ContextVarNode::from(analyzer.add_node(tmp_true)).into()); + + 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, @@ -659,9 +654,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { - let lhs_cvar = ContextVarNode::from(*lhs).latest_version(self); + 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)?; - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let rhs_cvar = + 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)?; @@ -759,7 +756,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let mut tmp_cvar = None; if let Some(lhs_range) = new_lhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .range(self) .into_expr_err(loc)? { @@ -818,8 +815,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { )); } tracing::trace!("done range updating"); - new_rhs = new_rhs.latest_version(self); - new_lhs = new_lhs.latest_version(self); + new_rhs = new_rhs.latest_version_or_inherited_in_ctx(ctx, self); + new_lhs = new_lhs.latest_version_or_inherited_in_ctx(ctx, self); let rhs_display_name = new_rhs.display_name(self).into_expr_err(loc)?; let display_name = if rhs_display_name == "true" { @@ -948,18 +945,18 @@ 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); - } - } - } - } + // 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) } @@ -1027,7 +1024,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { fn update_nonconst_from_const( &mut self, arena: &mut RangeArena>, - _ctx: ContextNode, + ctx: ContextNode, loc: Loc, op: RangeOp, const_var: ContextVarNode, @@ -1038,12 +1035,12 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { match op { RangeOp::Eq => { // check that the constant is contained in the nonconst var range - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); let evaled_min = nonconst_range .evaled_range_min(self, arena) .into_expr_err(loc)?; if evaled_min.maybe_concrete().is_none() { - return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Eq). Min: {}", evaled_min.to_range_string(false, self, arena).s))); + return Err(ExprErr::BadRange(loc, format!("Expected to have a concrete range by now. This is likely a bug (update nonconst from const: Eq). {}.min: {}", nonconst_var.display_name(self).unwrap(), evaled_min.to_range_string(false, self, arena).s))); } if !nonconst_range.contains_elem(&elem, self, arena) { @@ -1060,7 +1057,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } RangeOp::Neq => { // check if contains - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // potentially add the const var as a range exclusion if let Some(Ordering::Equal) = nonconst_range @@ -1110,7 +1107,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Gt => { - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // if nonconst max is <= const, we can't make this true let max = nonconst_range @@ -1151,7 +1148,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Gte => { - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // if nonconst max is < const, we can't make this true if matches!( @@ -1174,7 +1171,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Lt => { - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // if nonconst min is >= const, we can't make this true let min = nonconst_range @@ -1204,7 +1201,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Lte => { - let elem = Elem::from(const_var.latest_version(self)); + let elem = Elem::from(const_var.latest_version_or_inherited_in_ctx(ctx, self)); // if nonconst min is > const, we can't make this true let min = nonconst_range @@ -1234,7 +1231,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { fn update_nonconst_from_nonconst( &mut self, arena: &mut RangeArena>, - _ctx: ContextNode, + ctx: ContextNode, loc: Loc, op: RangeOp, new_lhs: ContextVarNode, @@ -1307,7 +1304,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { return Ok(true); } - let rhs_elem = Elem::from(new_rhs.latest_version(self)); + let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); // just add as an exclusion let idx = arena.idx_or_upsert(rhs_elem, self); lhs_range.add_range_exclusion(idx); @@ -1315,7 +1312,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { .set_range_exclusions(self, lhs_range.exclusions) .into_expr_err(loc)?; - let lhs_elem = Elem::from(new_lhs.latest_version(self)); + let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); // just add as an exclusion let idx = arena.idx_or_upsert(lhs_elem, self); rhs_range.add_range_exclusion(idx); @@ -1325,8 +1322,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Gt => { - let rhs_elem = Elem::from(new_rhs.latest_version(self)); - let lhs_elem = Elem::from(new_lhs.latest_version(self)); + let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); + let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); // if lhs.max is <= rhs.min, we can't make this true let max = lhs_range.evaled_range_max(self, arena).into_expr_err(loc)?; @@ -1345,7 +1342,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // we add/sub one to the element because its strict > new_lhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_min( self, arena, @@ -1353,7 +1350,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) .into_expr_err(loc)?; new_rhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_max( self, arena, @@ -1367,8 +1364,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { // lhs >= rhs // lhs min is the max of current lhs_min and rhs_min - let rhs_elem = Elem::from(new_rhs.latest_version(self)); - let lhs_elem = Elem::from(new_lhs.latest_version(self)); + let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); + let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); // if lhs.max is < rhs.min, we can't make this true let max = lhs_range.evaled_range_max(self, arena).into_expr_err(loc)?; @@ -1378,12 +1375,12 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } let new_min = Elem::Expr(RangeExpr::new( - new_lhs.latest_version(self).into(), + new_lhs.latest_version_or_inherited_in_ctx(ctx, self).into(), RangeOp::Max, rhs_elem, )); let new_max = Elem::Expr(RangeExpr::new( - new_rhs.latest_version(self).into(), + new_rhs.latest_version_or_inherited_in_ctx(ctx, self).into(), RangeOp::Min, lhs_elem, )); @@ -1403,8 +1400,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Lt => { - let rhs_elem = Elem::from(new_rhs.latest_version(self)); - let lhs_elem = Elem::from(new_lhs.latest_version(self)); + let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); + let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)); // if lhs min is >= rhs.max, we can't make this true let min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; @@ -1426,7 +1423,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { let new_new_rhs = self.advance_var_in_curr_ctx(new_rhs, loc)?; new_new_lhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_max( self, arena, @@ -1434,7 +1431,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) .into_expr_err(loc)?; new_new_rhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_min( self, arena, @@ -1444,8 +1441,8 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(false) } RangeOp::Lte => { - let rhs_elem = Elem::from(new_rhs.latest_version(self)); - let lhs_elem = Elem::from(new_lhs.latest_version(self)) + let rhs_elem = Elem::from(new_rhs.latest_version_or_inherited_in_ctx(ctx, self)); + let lhs_elem = Elem::from(new_lhs.latest_version_or_inherited_in_ctx(ctx, self)) .max(rhs_range.range_min().into_owned()); // if nonconst min is > const, we can't make this true @@ -1458,7 +1455,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } new_lhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_max( self, arena, @@ -1466,11 +1463,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) .into_expr_err(loc)?; new_rhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_min(self, arena, lhs_elem.clone()) .into_expr_err(loc)?; new_rhs - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .set_range_max(self, arena, lhs_elem) .into_expr_err(loc)?; Ok(false) @@ -1479,47 +1476,6 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } } - fn uninvertable_range_recursion( - &mut self, - arena: &mut RangeArena>, - tmp_construction: TmpConstruction, - _new_lhs_core: ContextVarNode, - _rhs_cvar: ContextVarNode, - loc: Loc, - ctx: ContextNode, - ) { - if !tmp_construction.lhs.is_const(self, arena).unwrap() { - // widen to maximum range :( - let new_underlying_lhs = self - .advance_var_in_ctx(tmp_construction.lhs.latest_version(self), loc, ctx) - .unwrap(); - if let Some(lhs_range) = tmp_construction.lhs.ref_range(self).unwrap() { - if let Elem::Concrete(c) = lhs_range.evaled_range_min(self, arena).unwrap() { - new_underlying_lhs - .set_range_min( - self, - arena, - Elem::Concrete(RangeConcrete { - val: Concrete::min_of_type(&c.val).unwrap_or_else(|| c.val.clone()), - loc, - }), - ) - .unwrap(); - new_underlying_lhs - .set_range_max( - self, - arena, - Elem::Concrete(RangeConcrete { - val: Concrete::max_of_type(&c.val).unwrap_or(c.val), - loc, - }), - ) - .unwrap(); - } - } - } - } - /// Recursively updates the range for a fn range_recursion( &mut self, @@ -1559,8 +1515,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(tmp_construction.lhs.latest_version(self), loc)?; + let new_underlying_lhs = self.advance_var_in_curr_ctx( + tmp_construction + .lhs + .latest_version_or_inherited_in_ctx(ctx, self), + loc, + )?; if let Some(lhs_range) = new_underlying_lhs .underlying(self) .into_expr_err(loc)? @@ -1784,8 +1744,10 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { e => panic!("here {e:?}"), }; - let new_underlying_rhs = - self.advance_var_in_curr_ctx(rhs.latest_version(self), loc)?; + let new_underlying_rhs = self.advance_var_in_curr_ctx( + rhs.latest_version_or_inherited_in_ctx(ctx, self), + loc, + )?; if let Some(lhs_range) = new_underlying_rhs .underlying(self) .into_expr_err(loc)? diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs index 525c3dbf..59bf4972 100644 --- a/crates/solc-expressions/src/variable.rs +++ b/crates/solc-expressions/src/variable.rs @@ -5,9 +5,9 @@ use graph::{ nodes::{Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet, VarNode}, AnalyzerBackend, ContextEdge, Edge, Node, VarType, }; -use shared::{ExprErr, GraphError, IntoExprErr, RangeArena}; +use shared::{ExprErr, GraphError, IntoExprErr, NodeIdx, RangeArena}; -use solang_parser::pt::{Expression, Identifier, Loc, VariableDeclaration}; +use solang_parser::pt::{Expression, Identifier, Loc, StorageLocation, VariableDeclaration}; impl Variable for T where T: AnalyzerBackend + Sized {} /// Deals with variable retrieval, parsing, and versioning @@ -19,6 +19,7 @@ pub trait Variable: AnalyzerBackend + Size arena: &mut RangeArena>, ident: &Identifier, ctx: ContextNode, + location: Option, recursion_target: Option, ) -> Result<(), ExprErr> { tracing::trace!( @@ -35,7 +36,7 @@ pub trait Variable: AnalyzerBackend + Size // solang doesnt have `super` as a keyword if let Some(cvar) = ctx.var_by_name(self, &ident.name) { - let cvar = cvar.latest_version(self); + let cvar = cvar.latest_version_or_inherited_in_ctx(ctx, self); self.apply_to_edges( target_ctx, ident.loc, @@ -55,7 +56,7 @@ pub trait Variable: AnalyzerBackend + Size .into_expr_err(ident.loc)? { // check if we can inherit it - let cvar = cvar.latest_version(self); + let cvar = cvar.latest_version_or_inherited_in_ctx(ctx, self); self.apply_to_edges( target_ctx, ident.loc, @@ -74,8 +75,39 @@ pub trait Variable: AnalyzerBackend + Size // } } else if (self.env_variable(arena, ident, target_ctx)?).is_some() { Ok(()) - } else if let Some(idx) = self.user_types().get(&ident.name).cloned() { + } else if let Some(idxs) = self.user_types().get(&ident.name).cloned() { + let idx = if idxs.len() == 1 { + idxs[0] + } else { + // disambiguate by scope + let in_scope = if let Some(contract) = ctx + .maybe_associated_contract(self) + .into_expr_err(ident.loc)? + { + let mut all_storage_vars_tys = contract + .all_storage_vars(self) + .iter() + .map(|i| i.0.into()) + .collect::>(); + all_storage_vars_tys.sort(); + all_storage_vars_tys.dedup(); + all_storage_vars_tys + } else { + vec![] + }; + if let Some(idx) = self.disambiguate(ctx, ident.loc, idxs, in_scope, location) { + idx + } else { + return Err(ExprErr::ParseError( + ident.loc, + "Unable to disambiguate variable".to_string(), + )); + } + }; + + let mut is_contract_var = false; let const_var = if let Node::Var(_v) = self.node(idx) { + is_contract_var = true; VarNode::from(idx) .const_value(ident.loc, self) .into_expr_err(ident.loc)? @@ -118,6 +150,15 @@ pub trait Variable: AnalyzerBackend + Size target_ctx, Edge::Context(ContextEdge::Variable), ); + + if is_contract_var { + self.add_edge( + new_cvarnode, + idx, + Edge::Context(ContextEdge::ContractVariable), + ); + } + target_ctx .push_expr(ExprRet::Single(new_cvarnode), self) .into_expr_err(ident.loc)?; @@ -139,7 +180,8 @@ pub trait Variable: AnalyzerBackend + Size )) } else { let node = self.add_node(Node::Unresolved(ident.clone())); - self.user_types_mut().insert(ident.name.clone(), node); + let entry = self.user_types_mut().entry(ident.name.clone()).or_default(); + entry.push(node); target_ctx .push_expr(ExprRet::Single(node), self) .into_expr_err(ident.loc)?; @@ -147,9 +189,77 @@ 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 ExprRet::Single(lhs_idx) = maybe_lhs { + 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 + match location { + Some(StorageLocation::Storage(..)) => { + let mut idxs = idxs.clone(); + idxs.retain(|i| inscope_storage.contains(i)); + return match idxs.len() { + 1 => Some(idxs[0]), + 2 => { + // solidity is weird where if the current contract shadows a struct definition in a storage variable, + // the struct is presumed the correct type. + // ``` + // contract B { + // struct A {} + // } + // contract A is B { + // A a; + // function foo() external { + // // a is of type B.A, *not* Contract::A + // a = A(address(this)); + // } + // } + // ``` + match (self.node(idxs[0]), self.node(idxs[1])) { + (Node::Contract(..), Node::Struct(..)) => Some(idxs[1]), + (Node::Struct(..), Node::Contract(..)) => Some(idxs[0]), + _ => None, + } + } + _ => None, + }; + } + Some(StorageLocation::Memory(..)) | Some(StorageLocation::Calldata(..)) => idxs + .iter() + .find(|idx| matches!(self.node(**idx), Node::Struct(..))) + .copied(), + None => { + let t = &mut idxs; + ctx.keep_inscope_tys(t, self).ok()?; + if t.len() == 1 { + Some(t[0]) + } else { + None + } + } + } + } + fn get_tmp_variable(&mut self, name: &str, ctx: ContextNode) -> Option { let cvar = ctx.tmp_var_by_name(self, name)?; - Some(cvar.latest_version(self)) + Some(cvar.latest_version_or_inherited_in_ctx(ctx, self)) } fn get_unchanged_tmp_variable( @@ -163,8 +273,8 @@ pub trait Variable: AnalyzerBackend + Size }; if let Some(tmp) = var.tmp_of(self)? { - if tmp.lhs.latest_version(self) != tmp.lhs { - let latest = tmp.lhs.latest_version(self); + if tmp.lhs.latest_version_or_inherited_in_ctx(ctx, self) != tmp.lhs { + let latest = tmp.lhs.latest_version_or_inherited_in_ctx(ctx, self); let newest_min = latest.evaled_range_min(self, arena)?; let curr_min = tmp.lhs.evaled_range_min(self, arena)?; if newest_min != curr_min { @@ -178,8 +288,8 @@ pub trait Variable: AnalyzerBackend + Size } if let Some(rhs) = tmp.rhs { - if rhs.latest_version(self) != rhs { - let latest = rhs.latest_version(self); + if rhs.latest_version_or_inherited_in_ctx(ctx, self) != rhs { + let latest = rhs.latest_version_or_inherited_in_ctx(ctx, self); let newest_min = latest.evaled_range_min(self, arena)?; let curr_min = rhs.evaled_range_min(self, arena)?; if newest_min != curr_min { @@ -216,7 +326,8 @@ pub trait Variable: AnalyzerBackend + Size } (ExprRet::Single(ty), Some(ExprRet::SingleLiteral(rhs))) => { let ty = VarType::try_from_idx(self, *ty).expect("Not a known type"); - let rhs_cvar = ContextVarNode::from(*rhs).latest_version(self); + let rhs_cvar = + ContextVarNode::from(*rhs).latest_version_or_inherited_in_ctx(ctx, self); let res = rhs_cvar.literal_cast_from_ty(ty, self).into_expr_err(loc); let _ = self.add_if_err(res); self.match_var_def( @@ -370,6 +481,19 @@ pub trait Variable: AnalyzerBackend + Size cvar.display_name(self).unwrap() ); } + + let maybe_old_ctx = cvar_node.maybe_ctx(self); + if maybe_old_ctx.is_some() && maybe_old_ctx != Some(ctx) { + if let Some(cvar) = cvar_node.next_version_or_inherited_in_ctx(ctx, self) { + panic!( + "Not latest version of (inherited): {}, old context: {}, new context: {}", + cvar.display_name(self).unwrap(), + maybe_old_ctx.unwrap().path(self), + ctx.path(self), + ); + } + } + if let Some(child) = ctx.underlying(self).into_expr_err(loc)?.child { return Err(ExprErr::GraphError( loc, @@ -382,7 +506,7 @@ pub trait Variable: AnalyzerBackend + Size )); } let mut new_cvar = cvar_node - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .underlying(self) .into_expr_err(loc)? .clone(); @@ -393,7 +517,10 @@ pub trait Variable: AnalyzerBackend + Size if let Some(old_ctx) = cvar_node.maybe_ctx(self) { if !force { // get the previous version to remove and prevent spurious nodes - if let Some(prev) = cvar_node.latest_version(self).previous_version(self) { + if let Some(prev) = cvar_node + .latest_version_or_inherited_in_ctx(ctx, self) + .previous_version(self) + { let prev_version = prev.underlying(self).into_expr_err(loc)?; // check if there was no change between the previous version and the latest version if prev_version.eq_ignore_loc(&new_cvar) && old_ctx == ctx { @@ -407,6 +534,14 @@ pub trait Variable: AnalyzerBackend + Size new_cvar.loc = Some(loc); new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); if old_ctx != ctx { + tracing::trace!( + "moving var {} into new context: from {} to {}", + ContextVarNode::from(new_cvarnode) + .display_name(self) + .unwrap(), + old_ctx.path(self), + ctx.path(self), + ); ctx.add_var(new_cvarnode.into(), self).into_expr_err(loc)?; self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable)); self.add_edge( @@ -429,12 +564,21 @@ pub trait Variable: AnalyzerBackend + Size fn advance_var_in_forced_ctx( &mut self, - cvar_node: ContextVarNode, + mut cvar_node: ContextVarNode, loc: Loc, ctx: ContextNode, ) -> Result { + if cvar_node.maybe_ctx(self) != Some(ctx) { + if let Some(inherited) = cvar_node + .latest_version_or_inherited_in_ctx(ctx, self) + .next_version_or_inherited_in_ctx(ctx, self) + { + cvar_node = inherited.latest_version_or_inherited_in_ctx(ctx, self) + } + } + let mut new_cvar = cvar_node - .latest_version(self) + .latest_version_or_inherited_in_ctx(ctx, self) .underlying(self) .into_expr_err(loc)? .clone(); @@ -444,7 +588,10 @@ pub trait Variable: AnalyzerBackend + Size 'a: { if let Some(old_ctx) = cvar_node.maybe_ctx(self) { // get the previous version to remove and prevent spurious nodes - if let Some(prev) = cvar_node.latest_version(self).previous_version(self) { + if let Some(prev) = cvar_node + .latest_version_or_inherited_in_ctx(ctx, self) + .previous_version(self) + { let prev_version = prev.underlying(self).into_expr_err(loc)?; // check if there was no change between the previous version and the latest version if prev_version.eq_ignore_loc(&new_cvar) && old_ctx == ctx { @@ -458,6 +605,14 @@ pub trait Variable: AnalyzerBackend + Size // new_cvar.display_name = format!("{}_{}", new_cvar.name, cvar_node.prev_versions(self)); new_cvarnode = self.add_node(Node::ContextVar(new_cvar)); if old_ctx != ctx { + tracing::trace!( + "moving var {} into new context: from {} to {}", + ContextVarNode::from(new_cvarnode) + .display_name(self) + .unwrap(), + old_ctx.path(self), + ctx.path(self), + ); ctx.add_var(new_cvarnode.into(), self).into_expr_err(loc)?; self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable)); self.add_edge( diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs index dcaa8f74..6cc709c1 100644 --- a/crates/solc-expressions/src/yul/yul_builder.rs +++ b/crates/solc-expressions/src/yul/yul_builder.rs @@ -309,7 +309,7 @@ pub trait YulBuilder: 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)?; + 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)?) @@ -440,7 +440,7 @@ pub trait YulBuilder: let name = format!("{}.slot", lhs.name(self).unwrap()); tracing::trace!("Slot access: {}", name); if let Some(attr_var) = ctx.var_by_name_or_recurse(self, &name).unwrap() { - attr_var.latest_version(self) + attr_var.latest_version_or_inherited_in_ctx(ctx, self) } else { let slot_var = ContextVar { loc: Some(loc), diff --git a/crates/solc-expressions/src/yul/yul_funcs.rs b/crates/solc-expressions/src/yul/yul_funcs.rs index cb2d4e7c..82320d1b 100644 --- a/crates/solc-expressions/src/yul/yul_funcs.rs +++ b/crates/solc-expressions/src/yul/yul_funcs.rs @@ -441,7 +441,7 @@ pub trait YulFuncCaller: let vars = ctx.local_vars(self).clone(); vars.into_iter().try_for_each(|(_name, var)| { // widen to any max range - let latest_var = var.latest_version(self); + let latest_var = var.latest_version_or_inherited_in_ctx(ctx, self); if matches!( latest_var.underlying(self).into_expr_err(*loc)?.storage, Some(StorageLocation::Memory(_)) @@ -731,8 +731,12 @@ pub trait YulFuncCaller: var.ty.set_range(range).into_expr_err(loc)?; let node = self.add_node(Node::ContextVar(var)); self.add_edge(node, ctx, Edge::Context(ContextEdge::Return)); - ctx.add_return_node(loc, ContextVarNode::from(node).latest_version(self), self) - .into_expr_err(loc) + ctx.add_return_node( + loc, + ContextVarNode::from(node).latest_version_or_inherited_in_ctx(ctx, self), + self, + ) + .into_expr_err(loc) } ExprRet::Multi(sizes) => { sizes diff --git a/mini.sol b/mini.sol new file mode 100644 index 00000000..c1cc7602 --- /dev/null +++ b/mini.sol @@ -0,0 +1,172 @@ + +contract ComptrollerErrorReporter { + enum Error { + NO_ERROR, + UNAUTHORIZED, + COMPTROLLER_MISMATCH, + INSUFFICIENT_SHORTFALL, + INSUFFICIENT_LIQUIDITY, + INVALID_CLOSE_FACTOR, + INVALID_COLLATERAL_FACTOR, + INVALID_LIQUIDATION_INCENTIVE, + MARKET_NOT_ENTERED, // no longer possible + MARKET_NOT_LISTED, + MARKET_ALREADY_LISTED, + MATH_ERROR, + NONZERO_BORROW_BALANCE, + PRICE_ERROR, + REJECTION, + SNAPSHOT_ERROR, + TOO_MANY_ASSETS, + TOO_MUCH_REPAY + } + + enum FailureInfo { + ACCEPT_ADMIN_PENDING_ADMIN_CHECK, + ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK, + EXIT_MARKET_BALANCE_OWED, + EXIT_MARKET_REJECTION, + SET_CLOSE_FACTOR_OWNER_CHECK, + SET_CLOSE_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_OWNER_CHECK, + SET_COLLATERAL_FACTOR_NO_EXISTS, + SET_COLLATERAL_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_WITHOUT_PRICE, + SET_IMPLEMENTATION_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_VALIDATION, + SET_MAX_ASSETS_OWNER_CHECK, + SET_PENDING_ADMIN_OWNER_CHECK, + SET_PENDING_IMPLEMENTATION_OWNER_CHECK, + SET_PRICE_ORACLE_OWNER_CHECK, + SUPPORT_MARKET_EXISTS, + SUPPORT_MARKET_OWNER_CHECK, + SET_PAUSE_GUARDIAN_OWNER_CHECK + } + + function fail(Error err, FailureInfo info) internal returns (uint) { + emit Failure(uint(err), uint(info), 0); + + return uint(err); + } + +} + +contract CTokenStorage { + address payable public admin; + + +} + +contract ExponentialNoError { + struct Exp { + uint mantissa; + } + + function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa < right.mantissa; + } + +} + +abstract contract CToken is ExponentialNoError { + + +} + +contract UnitrollerAdminStorage { + address public admin; + + +} + +contract ComptrollerV1Storage is UnitrollerAdminStorage { + + +} + +contract ComptrollerV2Storage is ComptrollerV1Storage { + struct Market { + // Whether or not this market is listed + bool isListed; + + // Multiplier representing the most one can borrow against their collateral in this market. + // For instance, 0.9 to allow borrowing 90% of collateral value. + // Must be between 0 and 1, and stored as a mantissa. + uint collateralFactorMantissa; + + // Per-market mapping of "accounts in this asset" + mapping(address => bool) accountMembership; + + // Whether or not this market receives COMP + bool isComped; + } + + mapping(address => Market) public markets; + + +} + +contract ComptrollerV3Storage is ComptrollerV2Storage { + + +} + +contract ComptrollerV4Storage is ComptrollerV3Storage { + + +} + +contract ComptrollerV5Storage is ComptrollerV4Storage { + + +} + +contract ComptrollerV6Storage is ComptrollerV5Storage { + + +} + +contract ComptrollerV7Storage is ComptrollerV6Storage { + + +} + +contract Comptroller is ComptrollerV7Storage, ComptrollerErrorReporter, ExponentialNoError { + uint internal constant collateralFactorMaxMantissa = 0.9e18; + function _setCollateralFactor(CToken cToken, uint newCollateralFactorMantissa) external returns (uint) { + // Check caller is admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); + } + + // Verify market is listed + Market storage market = markets[address(cToken)]; + if (!market.isListed) { + return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); + } + + Exp memory newCollateralFactorExp = Exp({mantissa: newCollateralFactorMantissa}); + + // Check collateral factor <= 0.9 + Exp memory highLimit = Exp({mantissa: collateralFactorMaxMantissa}); + if (lessThanExp(highLimit, newCollateralFactorExp)) { + return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); + } + + // If collateral factor != 0, fail if price == 0 + if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(cToken) == 0) { + return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); + } + + // Set market's collateral factor to new collateral factor, remember old value + uint oldCollateralFactorMantissa = market.collateralFactorMantissa; + market.collateralFactorMantissa = newCollateralFactorMantissa; + + // Emit event with asset, old collateral factor, and new collateral factor + emit NewCollateralFactor(cToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); + + return uint(Error.NO_ERROR); + } + +} \ No newline at end of file