diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs index b603e39b..2045d818 100644 --- a/crates/solc-expressions/src/array.rs +++ b/crates/solc-expressions/src/array.rs @@ -12,6 +12,7 @@ use solang_parser::{ }; impl Array for T where T: AnalyzerBackend + Sized {} +/// Handles arrays pub trait Array: AnalyzerBackend + Sized { /// Gets the array type #[tracing::instrument(level = "trace", skip_all)] diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index 712cfe7b..5f855b1b 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -13,6 +13,7 @@ use ethers_core::types::{I256, U256}; use solang_parser::pt::{Expression, Loc}; impl BinOp for T where T: AnalyzerBackend + Sized {} +/// Handles binary operations (`+`, `-`, `/`, etc.) pub trait BinOp: AnalyzerBackend + Sized { /// Evaluate and execute a binary operation expression #[tracing::instrument(level = "trace", skip_all)] diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs index 771e54e4..948e34a2 100644 --- a/crates/solc-expressions/src/cmp.rs +++ b/crates/solc-expressions/src/cmp.rs @@ -13,6 +13,7 @@ use solang_parser::pt::{Expression, Loc}; use std::cmp::Ordering; impl Cmp for T where T: AnalyzerBackend + Sized {} +/// Handles comparator operations, i.e: `!` pub trait Cmp: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn not(&mut self, loc: Loc, lhs_expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { diff --git a/crates/solc-expressions/src/cond_op.rs b/crates/solc-expressions/src/cond_op.rs index 00ac50e9..f96e3307 100644 --- a/crates/solc-expressions/src/cond_op.rs +++ b/crates/solc-expressions/src/cond_op.rs @@ -11,8 +11,10 @@ use solang_parser::pt::{Expression, Loc, Statement}; impl CondOp for T where T: AnalyzerBackend + Require + Sized {} +/// Handles conditional operations, like `if .. else ..` and ternary operations pub trait CondOp: AnalyzerBackend + Require + Sized { #[tracing::instrument(level = "trace", skip_all)] + /// Handles a conditional operation like `if .. else ..` fn cond_op_stmt( &mut self, loc: Loc, @@ -126,6 +128,7 @@ pub trait CondOp: AnalyzerBackend + Requir }) } + /// Handles a conditional expression like `if .. else ..` /// When we have a conditional operator, we create a fork in the context. One side of the fork is /// if the expression is true, the other is if it is false. #[tracing::instrument(level = "trace", skip_all)] diff --git a/crates/solc-expressions/src/context_builder/mod.rs b/crates/solc-expressions/src/context_builder/mod.rs index 0f97cd16..c959f033 100644 --- a/crates/solc-expressions/src/context_builder/mod.rs +++ b/crates/solc-expressions/src/context_builder/mod.rs @@ -1,5 +1,6 @@ +//! Trait and blanket implementation for the core parsing loop use crate::{ - func_call::FuncCaller, loops::Looper, yul::YulBuilder, ExprErr, ExprParser, IntoExprErr, + func_call::{func_caller::FuncCaller, modifier::ModifierCaller}, loops::Looper, yul::YulBuilder, ExprErr, ExprParser, IntoExprErr, }; use graph::{ @@ -24,9 +25,11 @@ impl ContextBuilder for T where { } +/// Dispatcher for building up a context of a function pub trait ContextBuilder: AnalyzerBackend + Sized + ExprParser { + /// Performs setup for parsing a solidity statement fn parse_ctx_statement( &mut self, stmt: &Statement, @@ -61,6 +64,7 @@ pub trait ContextBuilder: } #[tracing::instrument(level = "trace", skip_all)] + /// Performs parsing of a solidity statement fn parse_ctx_stmt_inner( &mut self, stmt: &Statement, @@ -525,6 +529,7 @@ pub trait ContextBuilder: } } + /// TODO: rename this. Sometimes we dont want to kill a context if we hit an error fn widen_if_limit_hit(&mut self, ctx: ContextNode, maybe_err: Result<(), ExprErr>) -> bool { match maybe_err { Err(ExprErr::FunctionCallBlockTodo(_, _s)) => { @@ -549,6 +554,7 @@ pub trait ContextBuilder: } } + /// Match on the [`ExprRet`]s of a return statement and performs the return fn return_match(&mut self, ctx: ContextNode, loc: &Loc, paths: &ExprRet) { match paths { ExprRet::CtxKilled(kind) => { @@ -582,6 +588,7 @@ pub trait ContextBuilder: } } + /// Match on the [`ExprRet`]s of a variable definition fn match_var_def( &mut self, ctx: ContextNode, @@ -702,6 +709,7 @@ pub trait ContextBuilder: } } + /// Perform setup for parsing an expression fn parse_ctx_expr(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { if !ctx.killed_or_ret(self).unwrap() { let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; @@ -719,6 +727,7 @@ pub trait ContextBuilder: } #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self).replace('.', "\n\t.")))] + /// Perform parsing of an expression fn parse_ctx_expr_inner(&mut self, expr: &Expression, ctx: ContextNode) -> Result<(), ExprErr> { use Expression::*; // println!( @@ -1128,6 +1137,7 @@ pub trait ContextBuilder: } } + /// Match on the [`ExprRet`]s of a pre-or-post in/decrement and performs it fn match_in_de_crement( &mut self, ctx: ContextNode, @@ -1217,6 +1227,7 @@ pub trait ContextBuilder: } #[tracing::instrument(level = "trace", skip_all)] + /// Parse an assignment expression fn assign_exprs( &mut self, loc: Loc, @@ -1255,6 +1266,7 @@ pub trait ContextBuilder: }) } + /// Match on the [`ExprRet`]s of an assignment expression fn match_assign_sides( &mut self, ctx: ContextNode, @@ -1312,6 +1324,7 @@ pub trait ContextBuilder: } } + /// Perform an assignment fn assign( &mut self, loc: Loc, @@ -1442,6 +1455,8 @@ pub trait ContextBuilder: } #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] + /// Creates a newer version of a variable in the context. It may or may not actually + /// create this new variable depending on if there are two successively identical version. fn advance_var_in_ctx( &mut self, cvar_node: ContextVarNode, @@ -1452,6 +1467,8 @@ pub trait ContextBuilder: } #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] + /// Creates a new version of a variable in a context. Takes an additional parameter + /// denoting whether or not to force the creation, skipping an optimization. fn advance_var_in_ctx_forcible( &mut self, cvar_node: ContextVarNode, @@ -1526,6 +1543,7 @@ pub trait ContextBuilder: Ok(ContextVarNode::from(new_cvarnode)) } + /// Creates a new version of a variable in it's current context fn advance_var_in_curr_ctx( &mut self, cvar_node: ContextVarNode, @@ -1554,6 +1572,7 @@ pub trait ContextBuilder: Ok(ContextVarNode::from(new_cvarnode)) } + /// fn advance_var_underlying(&mut self, cvar_node: ContextVarNode, loc: Loc) -> &mut ContextVar { assert_eq!(None, cvar_node.next_version(self)); let mut new_cvar = cvar_node @@ -1569,6 +1588,9 @@ pub trait ContextBuilder: .unwrap() } + /// Apply an expression or statement to all *live* edges of a context. This is used everywhere + /// to ensure we only ever update *live* contexts. If a context has a subcontext, we *never* + /// want to update the original context. We only ever want to operate on the latest edges. fn apply_to_edges( &mut self, ctx: ContextNode, @@ -1602,6 +1624,8 @@ pub trait ContextBuilder: } } + /// The inverse of [`apply_to_edges`], used only for modifiers because modifiers have extremely weird + /// dynamics. fn take_from_edge( &mut self, ctx: ContextNode, diff --git a/crates/solc-expressions/src/env.rs b/crates/solc-expressions/src/env.rs index e203455e..54060832 100644 --- a/crates/solc-expressions/src/env.rs +++ b/crates/solc-expressions/src/env.rs @@ -1,4 +1,4 @@ -use crate::{func_call::FuncCaller, ExprErr, IntoExprErr}; +use crate::{func_call::helper::CallerHelper, func_call::modifier::ModifierCaller, ExprErr, IntoExprErr}; use graph::{ nodes::{Builtin, Concrete, ContextNode, ContextVar, ExprRet}, @@ -9,6 +9,7 @@ use shared::StorageLocation; use solang_parser::pt::{Expression, Identifier, Loc}; impl Env for T where T: AnalyzerBackend + Sized {} +/// Handles environment based things like `msg`, `block`, etc. pub trait Env: AnalyzerBackend + Sized { fn env_variable( &mut self, diff --git a/crates/solc-expressions/src/func_call/func_caller.rs b/crates/solc-expressions/src/func_call/func_caller.rs new file mode 100644 index 00000000..8b06d943 --- /dev/null +++ b/crates/solc-expressions/src/func_call/func_caller.rs @@ -0,0 +1,380 @@ +//! Traits & blanket implementations that facilitate performing various forms of function calls. + +use crate::{ + func_call::modifier::ModifierCaller, + internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller, + namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExprErr, IntoExprErr, + helper::CallerHelper, +}; + +use graph::{ + nodes::{ + Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + FunctionParamNode, ModifierState, + }, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, +}; +use shared::{NodeIdx}; + +use solang_parser::pt::{Expression, Loc, NamedArgument}; + +use std::collections::BTreeMap; + +impl FuncCaller for T where + T: AnalyzerBackend + Sized + GraphBackend + CallerHelper +{ +} +/// A trait for calling a function +pub trait FuncCaller: + GraphBackend + AnalyzerBackend + Sized +{ + #[tracing::instrument(level = "trace", skip_all)] + /// Perform a function call with named inputs + fn named_fn_call_expr( + &mut self, + ctx: ContextNode, + loc: &Loc, + func_expr: &Expression, + input_exprs: &[NamedArgument], + ) -> Result<(), ExprErr> { + use solang_parser::pt::Expression::*; + match func_expr { + MemberAccess(loc, member_expr, ident) => { + self.call_name_spaced_named_func(ctx, loc, member_expr, ident, input_exprs) + } + Variable(ident) => self.call_internal_named_func(ctx, loc, ident, input_exprs), + e => Err(ExprErr::IntrinsicNamedArgs( + *loc, + format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"), + )), + } + } + #[tracing::instrument(level = "trace", skip_all)] + /// Perform a function call + fn fn_call_expr( + &mut self, + ctx: ContextNode, + loc: &Loc, + func_expr: &Expression, + input_exprs: &[Expression], + ) -> Result<(), ExprErr> { + use solang_parser::pt::Expression::*; + match func_expr { + MemberAccess(loc, member_expr, ident) => { + self.call_name_spaced_func(ctx, loc, member_expr, ident, input_exprs) + } + Variable(ident) => self.call_internal_func(ctx, loc, ident, func_expr, input_exprs), + _ => { + self.parse_ctx_expr(func_expr, ctx)?; + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoLhs( + loc, + "Function call to nonexistent function".to_string(), + )); + }; + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret) + }) + } + } + } + + /// Perform an intrinsic function call + fn match_intrinsic_fallback( + &mut self, + ctx: ContextNode, + loc: &Loc, + input_exprs: &[Expression], + ret: ExprRet, + ) -> Result<(), ExprErr> { + match ret { + ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => { + self.intrinsic_func_call(loc, input_exprs, func_idx, ctx) + } + ExprRet::Multi(inner) => inner + .into_iter() + .try_for_each(|ret| self.match_intrinsic_fallback(ctx, loc, input_exprs, ret)), + ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc), + ExprRet::Null => Ok(()), + } + } + + /// Setups up storage variables for a function call and calls it + fn setup_fn_call( + &mut self, + loc: &Loc, + inputs: &ExprRet, + func_idx: NodeIdx, + ctx: ContextNode, + func_call_str: Option<&str>, + ) -> Result<(), ExprErr> { + // if we have a single match thats our function + let var = match ContextVar::maybe_from_user_ty(self, *loc, func_idx) { + Some(v) => v, + None => panic!( + "Could not create context variable from user type: {:?}", + self.node(func_idx) + ), + }; + + let new_cvarnode = self.add_node(Node::ContextVar(var)); + ctx.add_var(new_cvarnode.into(), self).into_expr_err(*loc)?; + self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable)); + if let Some(func_node) = ContextVarNode::from(new_cvarnode) + .ty(self) + .into_expr_err(*loc)? + .func_node(self) + { + self.func_call(ctx, *loc, inputs, func_node, func_call_str, None) + } else { + unreachable!() + } + } + + /// Matches the input kinds and performs the call + fn func_call( + &mut self, + ctx: ContextNode, + loc: Loc, + input_paths: &ExprRet, + func: FunctionNode, + func_call_str: Option<&str>, + modifier_state: Option, + ) -> Result<(), ExprErr> { + let params = func.params(self); + let input_paths = input_paths.clone().flatten(); + if input_paths.has_killed() { + return ctx + .kill(self, loc, input_paths.killed_kind().unwrap()) + .into_expr_err(loc); + } + match input_paths { + ExprRet::Single(input_var) | ExprRet::SingleLiteral(input_var) => { + // if we get a single var, we expect the func to only take a single + // variable + self.func_call_inner( + false, + ctx, + func, + loc, + vec![ContextVarNode::from(input_var).latest_version(self)], + params, + func_call_str, + modifier_state, + ) + } + ExprRet::Multi(ref inputs) => { + if ExprRet::Multi(inputs.to_vec()).flatten().has_killed() { + return ctx + .kill( + self, + loc, + ExprRet::Multi(inputs.to_vec()).killed_kind().unwrap(), + ) + .into_expr_err(loc); + } + // check if the inputs length matchs func params length + // if they do, check that none are forks + if inputs.len() == params.len() { + let input_vars = inputs + .iter() + .map(|expr_ret| { + let var = expr_ret.expect_single().into_expr_err(loc)?; + Ok(ContextVarNode::from(var).latest_version(self)) + }) + .collect::, ExprErr>>()?; + self.func_call_inner( + false, + ctx, + func, + loc, + input_vars, + params, + func_call_str, + modifier_state, + ) + } else { + Err(ExprErr::InvalidFunctionInput( + loc, + format!( + "Length mismatch: {inputs:?} {params:?}, inputs as vars: {}, ctx: {}", + ExprRet::Multi(inputs.to_vec()).debug_str(self), + ctx.path(self) + ), + )) + } + } + e => todo!("here: {:?}", e), + } + } + + /// Checks if there are any modifiers and executes them prior to executing the function + #[tracing::instrument(level = "trace", skip_all)] + fn func_call_inner( + &mut self, + entry_call: bool, + ctx: ContextNode, + func_node: FunctionNode, + loc: Loc, + inputs: Vec, + params: Vec, + func_call_str: Option<&str>, + modifier_state: Option, + ) -> Result<(), ExprErr> { + // pseudocode: + // 1. Create context for the call + // 2. Check for modifiers + // 3. Call modifier 0, then 1, then 2, ... then N. + // 4. Call this function + // 5. Finish modifier N.. then 2, then 1, then 0 + let callee_ctx = if entry_call { + ctx + } else { + self.create_call_ctx(ctx, loc, func_node, modifier_state)? + }; + + // TODO: implement joining + // if !entry_call { + // let mapping = params + // .iter() + // .zip(inputs.iter()) + // .map(|(param, input)| (*input, *param)) + // .collect::>(); + // ctx.join(func_node, &mapping, self); + // } + + // handle remapping of variable names and bringing variables into the new context + let renamed_inputs = + self.map_inputs_to_params(loc, entry_call, params, inputs, callee_ctx)?; + + // begin modifier handling by making sure modifiers were set + if !func_node.modifiers_set(self).into_expr_err(loc)? { + self.set_modifiers(func_node, ctx)?; + } + + // get modifiers + let mods = func_node.modifiers(self); + self.apply_to_edges(callee_ctx, loc, &|analyzer, callee_ctx, loc| { + if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state { + // we are iterating through modifiers + if mod_state.num + 1 < mods.len() { + // use the next modifier + let mut mstate = mod_state.clone(); + mstate.num += 1; + analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, mstate) + } else { + // out of modifiers, execute the actual function call + analyzer.execute_call_inner( + loc, + ctx, + callee_ctx, + func_node, + &renamed_inputs, + func_call_str, + ) + } + } else if !mods.is_empty() { + // we have modifiers and havent executed them, start the process of executing them + let state = + ModifierState::new(0, loc, func_node, callee_ctx, ctx, renamed_inputs.clone()); + analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, state) + } else { + // no modifiers, just execute the function + analyzer.execute_call_inner( + loc, + ctx, + callee_ctx, + func_node, + &renamed_inputs, + func_call_str, + ) + } + }) + } + + + /// Actually executes the function + #[tracing::instrument(level = "trace", skip_all)] + fn execute_call_inner( + &mut self, + loc: Loc, + caller_ctx: ContextNode, + callee_ctx: ContextNode, + func_node: FunctionNode, + _renamed_inputs: &BTreeMap, + func_call_str: Option<&str>, + ) -> Result<(), ExprErr> { + if let Some(body) = func_node.underlying(self).into_expr_err(loc)?.body.clone() { + // add return nodes into the subctx + func_node + .returns(self) + .collect::>() + .into_iter() + .for_each(|ret| { + if let Some(var) = ContextVar::maybe_new_from_func_ret( + self, + ret.underlying(self).unwrap().clone(), + ) { + let cvar = self.add_node(Node::ContextVar(var)); + callee_ctx.add_var(cvar.into(), self).unwrap(); + self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); + } + }); + + self.parse_ctx_statement(&body, false, Some(callee_ctx)); + self.ctx_rets(loc, caller_ctx, callee_ctx) + } else { + let ret_ctx = Context::new_subctx( + callee_ctx, + Some(caller_ctx), + loc, + None, + None, + false, + self, + caller_ctx + .underlying(self) + .into_expr_err(loc)? + .modifier_state + .clone(), + ) + .unwrap(); + let ret_subctx = ContextNode::from(self.add_node(Node::Context(ret_ctx))); + self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue)); + + let res = callee_ctx + .set_child_call(ret_subctx, self) + .into_expr_err(loc); + let _ = self.add_if_err(res); + self.apply_to_edges(callee_ctx, loc, &|analyzer, ctx, loc| { + func_node + .returns(analyzer) + .collect::>() + .into_iter() + .try_for_each(|ret| { + let underlying = ret.underlying(analyzer).unwrap(); + let mut var = + ContextVar::new_from_func_ret(ctx, analyzer, underlying.clone()) + .unwrap() + .expect("No type for return variable?"); + if let Some(func_call) = &func_call_str { + var.name = + format!("{}_{}", func_call, callee_ctx.new_tmp(analyzer).unwrap()); + var.display_name = func_call.to_string(); + } + let node = analyzer.add_node(Node::ContextVar(var)); + ctx.add_var(node.into(), analyzer).into_expr_err(loc)?; + analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); + analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Return)); + ctx.push_expr(ExprRet::Single(node), analyzer) + .into_expr_err(loc)?; + Ok(()) + }) + }) + } + } +} diff --git a/crates/solc-expressions/src/func_call/helper.rs b/crates/solc-expressions/src/func_call/helper.rs new file mode 100644 index 00000000..fe1e0dcd --- /dev/null +++ b/crates/solc-expressions/src/func_call/helper.rs @@ -0,0 +1,585 @@ +//! Helper traits & blanket implementations that help facilitate performing function calls. +use crate::{ + ContextBuilder, ExprErr, IntoExprErr, +}; + +use graph::{ + nodes::{ + CallFork, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, + FunctionParamNode, FunctionReturnNode, ModifierState, + }, + AnalyzerBackend, ContextEdge, Edge, Node, Range, VarType, +}; +use shared::{NodeIdx, StorageLocation}; + +use solang_parser::pt::{Expression, Loc}; + +use std::{ + cell::RefCell, + collections::BTreeMap, + rc::Rc +}; + +impl CallerHelper for T where T: AnalyzerBackend + Sized {} +/// Helper trait for performing function calls +pub trait CallerHelper: AnalyzerBackend + Sized { + /// Maps inputs to function parameters such that if there is a renaming i.e. `a(uint256 x)` is called via `a(y)`, + /// we map `y -> x` for future lookups + fn map_inputs_to_params( + &mut self, + loc: Loc, + entry_call: bool, + params: Vec, + inputs: Vec, + callee_ctx: ContextNode, + ) -> Result, ExprErr> { + Ok(params + .iter() + .zip(inputs.iter()) + .filter_map(|(param, input)| { + if !entry_call { + if let Some(name) = + self.add_if_err(param.maybe_name(self).into_expr_err(loc))? + { + let res = input + .latest_version(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 = name.clone(); + new_cvar.display_name = name; + new_cvar.is_tmp = false; + new_cvar.storage = if let Some(StorageLocation::Storage(_)) = + param.underlying(self).unwrap().storage + { + new_cvar.storage + } else { + None + }; + + if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) + { + let ty = new_cvar.ty.clone(); + if !ty.ty_eq(¶m_ty, self).unwrap() { + if let Some(new_ty) = ty.try_cast(¶m_ty, self).unwrap() { + new_cvar.ty = new_ty; + } + } + } + + let node = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar))); + self.add_edge( + node, + input.latest_version(self), + Edge::Context(ContextEdge::InputVariable), + ); + + if let (Some(r), Some(r2)) = + (node.range(self).unwrap(), param.range(self).unwrap()) + { + 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()); + let res = node.try_set_range_min(self, new_min).into_expr_err(loc); + self.add_if_err(res); + let res = node.try_set_range_max(self, new_max).into_expr_err(loc); + self.add_if_err(res); + let res = node + .try_set_range_exclusions(self, r.exclusions) + .into_expr_err(loc); + self.add_if_err(res); + } + callee_ctx.add_var(node, self).unwrap(); + self.add_edge(node, callee_ctx, Edge::Context(ContextEdge::Variable)); + Some((*input, node)) + } else { + None + } + } else { + None + } + }) + .collect::>()) + } + + #[tracing::instrument(level = "trace", skip_all)] + /// Parses input expressions into [`ExprRet`]s and adds them to the expr ret stack + fn parse_inputs( + &mut self, + ctx: ContextNode, + loc: Loc, + inputs: &[Expression], + ) -> Result<(), ExprErr> { + let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() { + Rc::new(RefCell::new(true)) + } else { + Rc::new(RefCell::new(false)) + }; + + inputs.iter().try_for_each(|input| { + self.parse_ctx_expr(input, ctx)?; + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoLhs( + loc, + "Inputs did not have left hand sides".to_string(), + )); + }; + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + if *append.borrow() { + ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc) + } else { + *append.borrow_mut() = true; + ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc) + } + }) + })?; + if !inputs.is_empty() { + self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { + return Err(ExprErr::NoLhs( + loc, + "Inputs did not have left hand sides".to_string(), + )); + }; + ctx.push_expr(ret, analyzer).into_expr_err(loc) + }) + } else { + Ok(()) + } + } + + /// Creates a new context for a call + fn create_call_ctx( + &mut self, + curr_ctx: ContextNode, + loc: Loc, + func_node: FunctionNode, + modifier_state: Option, + ) -> Result { + let fn_ext = curr_ctx.is_fn_ext(func_node, self).into_expr_err(loc)?; + let ctx = Context::new_subctx( + curr_ctx, + None, + loc, + None, + Some(func_node), + fn_ext, + self, + modifier_state, + ) + .into_expr_err(loc)?; + let callee_ctx = ContextNode::from(self.add_node(Node::Context(ctx))); + curr_ctx + .set_child_call(callee_ctx, self) + .into_expr_err(loc)?; + let ctx_fork = self.add_node(Node::FunctionCall); + self.add_edge(ctx_fork, curr_ctx, Edge::Context(ContextEdge::Subcontext)); + self.add_edge(ctx_fork, func_node, Edge::Context(ContextEdge::Call)); + self.add_edge( + NodeIdx::from(callee_ctx.0), + ctx_fork, + Edge::Context(ContextEdge::Subcontext), + ); + Ok(callee_ctx) + } + + /// Disambiguates a function call by their inputs (length & type) + fn disambiguate_fn_call( + &mut self, + fn_name: &str, + literals: Vec, + input_paths: &ExprRet, + funcs: &[FunctionNode], + ) -> Option { + let input_paths = input_paths.clone().flatten(); + // try to find the function based on naive signature + // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could + // be a function that takes an int256 but we evaled as uint256) + let fn_sig = format!("{}{}", fn_name, input_paths.try_as_func_input_str(self)); + if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) { + return Some(*func); + } + + // filter by input len + let inputs = input_paths.as_flat_vec(); + let funcs: Vec<&FunctionNode> = funcs + .iter() + .filter(|func| func.params(self).len() == inputs.len()) + .collect(); + + if funcs.len() == 1 { + return Some(*funcs[0]); + } + + if !literals.iter().any(|i| *i) { + None + } else { + let funcs = funcs + .iter() + .filter(|func| { + let params = func.params(self); + params + .iter() + .zip(&inputs) + .enumerate() + .all(|(i, (param, input))| { + let param_ty = VarType::try_from_idx(self, (*param).into()).unwrap(); + let input_ty = ContextVarNode::from(*input).ty(self).unwrap(); + if param_ty.ty_eq(input_ty, self).unwrap() { + true + } else if literals[i] { + let possibilities = ContextVarNode::from(*input) + .ty(self) + .unwrap() + .possible_builtins_from_ty_inf(self); + let param_ty = param.ty(self).unwrap(); + match self.node(param_ty) { + Node::Builtin(b) => possibilities.contains(b), + _ => false, + } + } else { + false + } + }) + }) + .collect::>(); + if funcs.len() == 1 { + Some(**funcs[0]) + } else { + // this would be invalid solidity, likely the user needs to perform a cast + None + } + } + } + + /// Handle returns for a function call + fn ctx_rets( + &mut self, + loc: Loc, + caller_ctx: ContextNode, + callee_ctx: ContextNode, + ) -> Result<(), ExprErr> { + tracing::trace!( + "Handling function call return for: {}, {}, depth: {:?}, {:?}", + caller_ctx.path(self), + callee_ctx.path(self), + caller_ctx.depth(self), + callee_ctx.depth(self), + ); + match callee_ctx.underlying(self).into_expr_err(loc)?.child { + Some(CallFork::Fork(w1, w2)) => { + self.ctx_rets(loc, caller_ctx, w1)?; + self.ctx_rets(loc, caller_ctx, w2)?; + Ok(()) + } + Some(CallFork::Call(c)) + if c.underlying(self).into_expr_err(loc)?.depth + >= caller_ctx.underlying(self).into_expr_err(loc)?.depth => + { + // follow rabbit hole + self.ctx_rets(loc, caller_ctx, c)?; + Ok(()) + } + _ => { + if callee_ctx.is_killed(self).into_expr_err(loc)? { + return Ok(()); + } + let callee_depth = callee_ctx.underlying(self).into_expr_err(loc)?.depth; + let caller_depth = caller_ctx.underlying(self).into_expr_err(loc)?.depth; + if callee_depth != caller_depth { + let ctx = Context::new_subctx( + callee_ctx, + Some(caller_ctx), + loc, + None, + None, + false, + self, + caller_ctx + .underlying(self) + .into_expr_err(loc)? + .modifier_state + .clone(), + ) + .unwrap(); + let ret_subctx = ContextNode::from(self.add_node(Node::Context(ctx))); + self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue)); + + let res = callee_ctx + .set_child_call(ret_subctx, self) + .into_expr_err(loc); + let _ = self.add_if_err(res); + + let mut rets = callee_ctx.underlying(self).unwrap().ret.clone(); + + if rets.is_empty() { + let func_rets: Vec = callee_ctx + .associated_fn(self) + .into_expr_err(loc)? + .returns(self) + .collect(); + func_rets + .iter() + .filter_map(|ret| { + let n: String = ret.maybe_name(self).ok()??; + let ret_loc: Loc = ret.loc(self).ok()?; + Some((n, ret_loc)) + }) + .collect::>() + .into_iter() + .try_for_each(|(name, ret_loc)| { + if let Some(cvar) = callee_ctx + .var_by_name_or_recurse(self, &name) + .into_expr_err(loc)? + { + let cvar = cvar.latest_version(self); + // let ret_loc = ret.loc(self).into_expr_err(loc)?; + callee_ctx + .add_return_node(ret_loc, cvar, self) + .into_expr_err(loc)?; + self.add_edge( + cvar, + callee_ctx, + Edge::Context(ContextEdge::Return), + ); + } + Ok(()) + })?; + + // add unnamed rets + func_rets + .into_iter() + .filter(|ret| ret.maybe_name(self).unwrap().is_none()) + .collect::>() + .iter() + .try_for_each(|ret| { + let ret_loc = ret.loc(self).into_expr_err(loc)?; + let cvar = ContextVar::new_from_func_ret( + callee_ctx, + self, + ret.underlying(self).into_expr_err(loc)?.clone(), + ) + .into_expr_err(loc)? + .unwrap(); + let cvar = + ContextVarNode::from(self.add_node(Node::ContextVar(cvar))); + callee_ctx.add_var(cvar, self).into_expr_err(loc)?; + self.add_edge( + cvar, + callee_ctx, + Edge::Context(ContextEdge::Variable), + ); + callee_ctx + .add_return_node(ret_loc, cvar, self) + .into_expr_err(loc)?; + self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Return)); + Ok(()) + })?; + rets = callee_ctx.underlying(self).unwrap().ret.clone(); + } + + let handle_rets = rets.iter().all(|(_, node)| node.is_some()); + if handle_rets { + let ret = rets + .into_iter() + .enumerate() + .map(|(i, (_, node))| { + let tmp_ret = node + .unwrap() + .as_tmp( + callee_ctx.underlying(self).unwrap().loc, + ret_subctx, + self, + ) + .unwrap(); + tmp_ret.underlying_mut(self).into_expr_err(loc)?.is_return = true; + tmp_ret + .underlying_mut(self) + .into_expr_err(loc)? + .display_name = format!( + "{}.{}", + callee_ctx.associated_fn_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())) + }) + .collect::>()?; + ret_subctx + .push_expr(ExprRet::Multi(ret), self) + .into_expr_err(loc)?; + } + Ok(()) + } else { + let mut rets = callee_ctx.underlying(self).unwrap().ret.clone(); + + if rets.is_empty() { + callee_ctx + .associated_fn(self) + .into_expr_err(loc)? + .returns(self) + .filter_map(|ret| { + let n: String = ret.maybe_name(self).ok()??; + let ret_loc: Loc = ret.loc(self).ok()?; + Some((n, ret_loc)) + }) + .collect::>() + .into_iter() + .try_for_each(|(name, ret_loc)| { + if let Some(cvar) = callee_ctx + .var_by_name_or_recurse(self, &name) + .into_expr_err(loc)? + { + let cvar = cvar.latest_version(self); + // let ret_loc = ret.loc(self).into_expr_err(loc)?; + callee_ctx + .add_return_node(ret_loc, cvar, self) + .into_expr_err(loc)?; + self.add_edge( + cvar, + callee_ctx, + Edge::Context(ContextEdge::Return), + ); + } + Ok(()) + })?; + rets = callee_ctx.underlying(self).unwrap().ret.clone(); + } + if rets.iter().all(|(_, node)| node.is_some()) { + callee_ctx + .push_expr( + ExprRet::Multi( + rets.iter() + .map(|(_, node)| ExprRet::Single((node.unwrap()).into())) + .collect(), + ), + self, + ) + .into_expr_err(loc) + } else { + Ok(()) + } + } + } + } + } + + /// Inherit the input changes from a function call + fn inherit_input_changes( + &mut self, + loc: Loc, + to_ctx: ContextNode, + from_ctx: ContextNode, + renamed_inputs: &BTreeMap, + ) -> Result<(), ExprErr> { + if to_ctx != from_ctx { + self.apply_to_edges(to_ctx, loc, &|analyzer, to_ctx, loc| { + renamed_inputs + .iter() + .try_for_each(|(input_var, updated_var)| { + let new_input = analyzer.advance_var_in_ctx( + input_var.latest_version(analyzer), + loc, + to_ctx, + )?; + let latest_updated = updated_var.latest_version(analyzer); + if let Some(updated_var_range) = + latest_updated.range(analyzer).into_expr_err(loc)? + { + let res = new_input + .set_range_min(analyzer, updated_var_range.range_min().into_owned()) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + let res = new_input + .set_range_max(analyzer, updated_var_range.range_max().into_owned()) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + let res = new_input + .set_range_exclusions( + analyzer, + updated_var_range.range_exclusions(), + ) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + } + Ok(()) + }) + })?; + } + Ok(()) + } + + /// Inherit the input changes from a function call + fn modifier_inherit_return(&mut self, mod_ctx: ContextNode, fn_ctx: ContextNode) { + let ret = fn_ctx.underlying(self).unwrap().ret.clone(); + mod_ctx.underlying_mut(self).unwrap().ret = ret; + } + + /// Inherit the storage changes from a function call + fn inherit_storage_changes( + &mut self, + loc: Loc, + inheritor_ctx: ContextNode, + grantor_ctx: ContextNode, + ) -> Result<(), ExprErr> { + if inheritor_ctx != grantor_ctx { + return self.apply_to_edges(inheritor_ctx, loc, &|analyzer, inheritor_ctx, loc| { + let vars = grantor_ctx.local_vars(analyzer).clone(); + vars.iter().try_for_each(|(name, old_var)| { + let var = old_var.latest_version(analyzer); + let underlying = var.underlying(analyzer).into_expr_err(loc)?; + if var.is_storage(analyzer).into_expr_err(loc)? { + if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { + let inheritor_var = inheritor_var.latest_version(analyzer); + if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? { + let new_inheritor_var = analyzer + .advance_var_in_ctx( + inheritor_var, + underlying.loc.expect("No loc for val change"), + inheritor_ctx, + ) + .unwrap(); + let _ = new_inheritor_var + .set_range_min(analyzer, r.range_min().into_owned()); + let _ = new_inheritor_var + .set_range_max(analyzer, r.range_max().into_owned()); + let _ = new_inheritor_var + .set_range_exclusions(analyzer, r.range_exclusions()); + } + } else { + let new_in_inheritor = + analyzer.add_node(Node::ContextVar(underlying.clone())); + inheritor_ctx + .add_var(new_in_inheritor.into(), analyzer) + .into_expr_err(loc)?; + analyzer.add_edge( + new_in_inheritor, + inheritor_ctx, + Edge::Context(ContextEdge::Variable), + ); + analyzer.add_edge( + new_in_inheritor, + var, + Edge::Context(ContextEdge::InheritedVariable), + ); + } + } + Ok(()) + }) + }); + } + Ok(()) + } +} \ No newline at end of file diff --git a/crates/solc-expressions/src/func_call/internal_call.rs b/crates/solc-expressions/src/func_call/internal_call.rs index e9a417a1..d7e615f0 100644 --- a/crates/solc-expressions/src/func_call/internal_call.rs +++ b/crates/solc-expressions/src/func_call/internal_call.rs @@ -1,4 +1,6 @@ -use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr}; +//! Traits & blanket implementations that facilitate performing locally scoped function calls. + +use crate::{func_call::func_caller::FuncCaller, helper::CallerHelper, ContextBuilder, ExprErr, IntoExprErr}; use graph::{ nodes::{Builtin, Concrete, ContextNode, ContextVar, ContextVarNode, ExprRet}, @@ -8,13 +10,15 @@ use graph::{ use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument}; impl InternalFuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + T: AnalyzerBackend + Sized + GraphBackend + CallerHelper { } +/// A trait for performing internally scoped function calls (i.e. *NOT* `MyContract.func(...)`) pub trait InternalFuncCaller: - AnalyzerBackend + Sized + GraphBackend + AnalyzerBackend + Sized + GraphBackend + CallerHelper { #[tracing::instrument(level = "trace", skip_all)] + /// Perform a named function call fn call_internal_named_func( &mut self, ctx: ContextNode, @@ -186,7 +190,7 @@ pub trait InternalFuncCaller: tracing::trace!("function call: {}(..)", ident.name); // It is a function call, check if we have the ident in scope let funcs = ctx.visible_funcs(self).into_expr_err(*loc)?; - // println!("visible funcs: {:#?}", funcs.iter().map(|f| f.name(self).unwrap()).collect::>()); + // filter down all funcs to those that match let possible_funcs = funcs .iter() @@ -198,89 +202,88 @@ pub trait InternalFuncCaller: .copied() .collect::>(); - if possible_funcs.is_empty() { - // this is a builtin, cast, or unknown function? - self.parse_ctx_expr(func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let ret = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - let ret = ret.flatten(); - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret) - }) - } else if possible_funcs.len() == 1 { - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let inputs = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - let inputs = inputs.flatten(); - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.setup_fn_call(&ident.loc, &inputs, (possible_funcs[0]).into(), ctx, None) - }) - } else { - // this is the annoying case due to function overloading & type inference on number literals - self.parse_inputs(ctx, *loc, input_exprs)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let inputs = ctx - .pop_expr_latest(loc, analyzer) - .into_expr_err(loc)? - .unwrap_or_else(|| ExprRet::Multi(vec![])); - let inputs = inputs.flatten(); - if matches!(inputs, ExprRet::CtxKilled(_)) { - ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; - return Ok(()); - } - let resizeables: Vec<_> = inputs.as_flat_vec() - .iter() - .map(|idx| { - match VarType::try_from_idx(analyzer, *idx) { - Some(VarType::BuiltIn(bn, _)) => { - matches!(analyzer.node(bn), Node::Builtin(Builtin::Uint(_)) | Node::Builtin(Builtin::Int(_)) | Node::Builtin(Builtin::Bytes(_))) - // match analyzer.node(bn) { - // Node::Builtin(Builtin::Uint(s)) if s < &256 => true, - // Node::Builtin(Builtin::Int(s)) if s < &256 => true, - // Node::Builtin(Builtin::Bytes(s)) if s < &32 => true, - // _ => false - // } - } - Some(VarType::Concrete(c)) => { - matches!(analyzer.node(c), Node::Concrete(Concrete::Uint(_, _)) | Node::Concrete(Concrete::Int(_, _)) | Node::Concrete(Concrete::Bytes(_, _))) + match possible_funcs.len() { + 0 => { + // this is a builtin, cast, or unknown function + self.parse_ctx_expr(func_expr, ctx)?; + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let ret = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + .unwrap_or_else(|| ExprRet::Multi(vec![])); + let ret = ret.flatten(); + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret) + }) + } + 1 => { + // there is only a single possible function + self.parse_inputs(ctx, *loc, input_exprs)?; + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let inputs = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + .unwrap_or_else(|| ExprRet::Multi(vec![])); + let inputs = inputs.flatten(); + if matches!(inputs, ExprRet::CtxKilled(_)) { + ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; + return Ok(()); + } + analyzer.setup_fn_call(&ident.loc, &inputs, (possible_funcs[0]).into(), ctx, None) + }) + } + _ => { + // this is the annoying case due to function overloading & type inference on number literals + self.parse_inputs(ctx, *loc, input_exprs)?; + self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let inputs = ctx + .pop_expr_latest(loc, analyzer) + .into_expr_err(loc)? + .unwrap_or_else(|| ExprRet::Multi(vec![])); + let inputs = inputs.flatten(); + if matches!(inputs, ExprRet::CtxKilled(_)) { + ctx.push_expr(inputs, analyzer).into_expr_err(loc)?; + return Ok(()); + } + let resizeables: Vec<_> = inputs.as_flat_vec() + .iter() + .map(|idx| { + match VarType::try_from_idx(analyzer, *idx) { + Some(VarType::BuiltIn(bn, _)) => { + matches!(analyzer.node(bn), Node::Builtin(Builtin::Uint(_)) | Node::Builtin(Builtin::Int(_)) | Node::Builtin(Builtin::Bytes(_))) + } + Some(VarType::Concrete(c)) => { + matches!(analyzer.node(c), Node::Concrete(Concrete::Uint(_, _)) | Node::Concrete(Concrete::Int(_, _)) | Node::Concrete(Concrete::Bytes(_, _))) + } + _ => false } - _ => false - } - }) - .collect(); - if let Some(func) = analyzer.disambiguate_fn_call( - &ident.name, - resizeables, - &inputs, - &possible_funcs, - ) { - analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None) - } else { - Err(ExprErr::FunctionNotFound( - loc, - format!( - "Could not disambiguate function, default input types: {}, possible functions: {:#?}", - inputs.try_as_func_input_str(analyzer), - possible_funcs - .iter() - .map(|i| i.name(analyzer).unwrap()) - .collect::>() - ), - )) - } - }) + }) + .collect(); + if let Some(func) = analyzer.disambiguate_fn_call( + &ident.name, + resizeables, + &inputs, + &possible_funcs, + ) { + analyzer.setup_fn_call(&loc, &inputs, func.into(), ctx, None) + } else { + Err(ExprErr::FunctionNotFound( + loc, + format!( + "Could not disambiguate function, default input types: {}, possible functions: {:#?}", + inputs.try_as_func_input_str(analyzer), + possible_funcs + .iter() + .map(|i| i.name(analyzer).unwrap()) + .collect::>() + ), + )) + } + }) + } } } } diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs index 3307fb04..d2507e2e 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/abi.rs @@ -8,7 +8,10 @@ use graph::{ use solang_parser::pt::{Expression, Loc}; impl AbiCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling abi-namespaced intrinsic functions pub trait AbiCaller: AnalyzerBackend + Sized { + /// Perform an `abi.<..>` function call fn abi_call( &mut self, func_name: String, @@ -91,7 +94,7 @@ pub trait AbiCaller: AnalyzerBackend + Siz | "abi.encodeCall" | "abi.encodeWithSignature" | "abi.encodeWithSelector" => { - // currently we dont support concrete abi encoding, TODO + // TODO: Support concrete abi encoding let bn = self.builtin_or_add(Builtin::DynamicBytes); let cvar = ContextVar::new_from_builtin(loc, bn.into(), self).into_expr_err(loc)?; let node = self.add_node(Node::ContextVar(cvar)); diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs index 67fbf8d1..8303d01d 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/address.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/address.rs @@ -8,7 +8,10 @@ use graph::{ use solang_parser::pt::{Expression, Loc}; impl AddressCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling address-based intrinsic functions pub trait AddressCaller: AnalyzerBackend + Sized { + /// Perform an `address.<..>` function call fn address_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs index 58a93628..8a898252 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/array.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/array.rs @@ -10,7 +10,10 @@ use ethers_core::types::U256; use solang_parser::pt::{Expression, Loc}; impl ArrayCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling array-based intrinsic functions pub trait ArrayCaller: AnalyzerBackend + Sized { + /// Perform an `array.<..>` function call fn array_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs index b29f08c1..67e9ea18 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/block.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/block.rs @@ -8,7 +8,10 @@ use graph::{ use solang_parser::pt::{Expression, Loc}; impl BlockCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling block-based intrinsic functions pub trait BlockCaller: AnalyzerBackend + Sized { + /// Perform a `block` function call fn block_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs index 86bed531..71735a84 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/constructors.rs @@ -1,4 +1,4 @@ -use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr}; +use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper}; use graph::{ elem::*, @@ -10,10 +10,13 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Loc}; impl ConstructorCaller for T where - T: AnalyzerBackend + Sized + T: AnalyzerBackend + Sized + CallerHelper { } -pub trait ConstructorCaller: AnalyzerBackend + Sized { + +/// Trait for constructing compound types like contracts, structs and arrays +pub trait ConstructorCaller: AnalyzerBackend + Sized + CallerHelper { + /// Construct an array fn construct_array( &mut self, func_idx: NodeIdx, @@ -97,6 +100,7 @@ pub trait ConstructorCaller: AnalyzerBackend DynBuiltinCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling dynamic builtin-based intrinsic functions, like `concat` pub trait DynBuiltinCaller: AnalyzerBackend + Sized { + /// Perform a dynamic builtin type's builtin function call fn dyn_builtin_call( &mut self, func_name: String, @@ -30,6 +33,7 @@ pub trait DynBuiltinCaller: AnalyzerBackend CallerParts for T where + TypesCaller + ConstructorCaller + MsgCaller + + CallerHelper { } @@ -46,6 +49,8 @@ impl IntrinsicFuncCaller for T where T: AnalyzerBackend + Sized + CallerParts { } + +/// Perform calls to intrinsic functions like `abi.encode`, `array.push`, `require`, and constructors etc. pub trait IntrinsicFuncCaller: AnalyzerBackend + Sized + CallerParts { diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs index 0345a08a..9a33e6da 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/mod.rs @@ -1,3 +1,4 @@ +//! Traits & blanket implementations that facilitate performing intrinsic function calls. mod abi; mod address; mod array; diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs index cf3f371f..d2b3f1d5 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/msg.rs @@ -8,7 +8,10 @@ use graph::{ use solang_parser::pt::{Expression, Loc}; impl MsgCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling msg-based intrinsic functions, like `gasleft` pub trait MsgCaller: AnalyzerBackend + Sized { + /// Perform a msg's builtin function call, like `gasleft()` fn msg_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs index 64c7f09f..bfe38af9 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/precompile.rs @@ -1,4 +1,4 @@ -use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr}; +use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper}; use graph::{ nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, @@ -8,9 +8,12 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Loc}; -impl PrecompileCaller for T where T: AnalyzerBackend + Sized +impl PrecompileCaller for T where T: AnalyzerBackend + Sized + CallerHelper {} -pub trait PrecompileCaller: AnalyzerBackend + Sized { + +/// Trait for calling precompile intrinsic functions, like `ecrecover` +pub trait PrecompileCaller: AnalyzerBackend + Sized + CallerHelper { + /// Perform a precompile's function call, like `ecrecover` fn precompile_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs index e8fd3f11..acbf38ce 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/solidity.rs @@ -1,4 +1,4 @@ -use crate::{require::Require, ContextBuilder, ExprErr, FuncCaller, IntoExprErr}; +use crate::{require::Require, ContextBuilder, ExprErr, func_call::helper::CallerHelper, IntoExprErr}; use graph::{ nodes::{Builtin, ContextNode, ContextVar, ExprRet}, @@ -7,8 +7,11 @@ use graph::{ use solang_parser::pt::{Expression, Loc}; -impl SolidityCaller for T where T: AnalyzerBackend + Sized {} -pub trait SolidityCaller: AnalyzerBackend + Sized { +impl SolidityCaller for T where T: AnalyzerBackend + Sized + CallerHelper {} + +/// Trait for calling solidity's intrinsic functions, like `keccak256` +pub trait SolidityCaller: AnalyzerBackend + Sized + CallerHelper { + /// Perform a solidity intrinsic function call, like `keccak256` fn solidity_call( &mut self, func_name: String, diff --git a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs index ee5b421c..82875d94 100644 --- a/crates/solc-expressions/src/func_call/intrinsic_call/types.rs +++ b/crates/solc-expressions/src/func_call/intrinsic_call/types.rs @@ -1,4 +1,4 @@ -use crate::{ContextBuilder, ExprErr, FuncCaller, IntoExprErr}; +use crate::{ContextBuilder, ExprErr, IntoExprErr, func_call::helper::CallerHelper}; use graph::{ elem::*, @@ -10,7 +10,10 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Loc}; impl TypesCaller for T where T: AnalyzerBackend + Sized {} + +/// Trait for calling type-based intrinsic functions, like `wrap` pub trait TypesCaller: AnalyzerBackend + Sized { + /// Perform a type-based intrinsic function call, like `wrap` fn types_call( &mut self, func_name: String, @@ -116,6 +119,7 @@ pub trait TypesCaller: AnalyzerBackend + S } } + /// Perform a cast of a type fn cast( &mut self, ty: Builtin, diff --git a/crates/solc-expressions/src/func_call/mod.rs b/crates/solc-expressions/src/func_call/mod.rs index 33c82738..a4ea9278 100644 --- a/crates/solc-expressions/src/func_call/mod.rs +++ b/crates/solc-expressions/src/func_call/mod.rs @@ -1,1197 +1,6 @@ -use crate::{ - internal_call::InternalFuncCaller, intrinsic_call::IntrinsicFuncCaller, - namespaced_call::NameSpaceFuncCaller, ContextBuilder, ExprErr, IntoExprErr, -}; - -use graph::{ - nodes::{ - CallFork, Context, ContextNode, ContextVar, ContextVarNode, ExprRet, FunctionNode, - FunctionParamNode, FunctionReturnNode, ModifierState, - }, - AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, Range, VarType, -}; -use shared::{NodeIdx, StorageLocation}; - -use solang_parser::helpers::CodeLocation; -use solang_parser::pt::{Expression, Loc, NamedArgument}; -use std::cell::RefCell; -use std::collections::BTreeMap; -use std::rc::Rc; - +pub mod helper; pub mod internal_call; pub mod intrinsic_call; pub mod modifier; pub mod namespaced_call; - -impl FuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend -{ -} -pub trait FuncCaller: - GraphBackend + AnalyzerBackend + Sized -{ - #[tracing::instrument(level = "trace", skip_all)] - fn named_fn_call_expr( - &mut self, - ctx: ContextNode, - loc: &Loc, - func_expr: &Expression, - input_exprs: &[NamedArgument], - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - match func_expr { - MemberAccess(loc, member_expr, ident) => { - self.call_name_spaced_named_func(ctx, loc, member_expr, ident, input_exprs) - } - Variable(ident) => self.call_internal_named_func(ctx, loc, ident, input_exprs), - e => Err(ExprErr::IntrinsicNamedArgs( - *loc, - format!("Cannot call intrinsic functions with named arguments. Call: {e:?}"), - )), - } - } - #[tracing::instrument(level = "trace", skip_all)] - fn fn_call_expr( - &mut self, - ctx: ContextNode, - loc: &Loc, - func_expr: &Expression, - input_exprs: &[Expression], - ) -> Result<(), ExprErr> { - use solang_parser::pt::Expression::*; - match func_expr { - MemberAccess(loc, member_expr, ident) => { - self.call_name_spaced_func(ctx, loc, member_expr, ident, input_exprs) - } - Variable(ident) => self.call_internal_func(ctx, loc, ident, func_expr, input_exprs), - _ => { - self.parse_ctx_expr(func_expr, ctx)?; - self.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Function call to nonexistent function".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - analyzer.match_intrinsic_fallback(ctx, &loc, input_exprs, ret) - }) - } - } - } - - fn match_intrinsic_fallback( - &mut self, - ctx: ContextNode, - loc: &Loc, - input_exprs: &[Expression], - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret { - ExprRet::Single(func_idx) | ExprRet::SingleLiteral(func_idx) => { - self.intrinsic_func_call(loc, input_exprs, func_idx, ctx) - } - ExprRet::Multi(inner) => inner - .into_iter() - .try_for_each(|ret| self.match_intrinsic_fallback(ctx, loc, input_exprs, ret)), - ExprRet::CtxKilled(kind) => ctx.kill(self, *loc, kind).into_expr_err(*loc), - ExprRet::Null => Ok(()), - } - } - - /// Disambiguates a function call by their inputs (length & type) - fn disambiguate_fn_call( - &mut self, - fn_name: &str, - literals: Vec, - input_paths: &ExprRet, - funcs: &[FunctionNode], - ) -> Option { - let input_paths = input_paths.clone().flatten(); - // try to find the function based on naive signature - // This doesnt do type inference on NumberLiterals (i.e. 100 could be uintX or intX, and there could - // be a function that takes an int256 but we evaled as uint256) - let fn_sig = format!("{}{}", fn_name, input_paths.try_as_func_input_str(self)); - if let Some(func) = funcs.iter().find(|func| func.name(self).unwrap() == fn_sig) { - return Some(*func); - } - - // filter by input len - let inputs = input_paths.as_flat_vec(); - let funcs: Vec<&FunctionNode> = funcs - .iter() - .filter(|func| func.params(self).len() == inputs.len()) - .collect(); - - if funcs.len() == 1 { - return Some(*funcs[0]); - } - - if !literals.iter().any(|i| *i) { - None - } else { - let funcs = funcs - .iter() - .filter(|func| { - let params = func.params(self); - params - .iter() - .zip(&inputs) - .enumerate() - .all(|(i, (param, input))| { - let param_ty = VarType::try_from_idx(self, (*param).into()).unwrap(); - let input_ty = ContextVarNode::from(*input).ty(self).unwrap(); - if param_ty.ty_eq(input_ty, self).unwrap() { - true - } else if literals[i] { - let possibilities = ContextVarNode::from(*input) - .ty(self) - .unwrap() - .possible_builtins_from_ty_inf(self); - let param_ty = param.ty(self).unwrap(); - match self.node(param_ty) { - Node::Builtin(b) => possibilities.contains(b), - _ => false, - } - } else { - false - } - }) - }) - .collect::>(); - if funcs.len() == 1 { - Some(**funcs[0]) - } else { - // this would be invalid solidity, likely the user needs to perform a cast - None - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - fn parse_inputs( - &mut self, - ctx: ContextNode, - loc: Loc, - inputs: &[Expression], - ) -> Result<(), ExprErr> { - let append = if ctx.underlying(self).into_expr_err(loc)?.tmp_expr.is_empty() { - Rc::new(RefCell::new(true)) - } else { - Rc::new(RefCell::new(false)) - }; - - inputs.iter().try_for_each(|input| { - self.parse_ctx_expr(input, ctx)?; - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(ret) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - if *append.borrow() { - ctx.append_tmp_expr(ret, analyzer).into_expr_err(loc) - } else { - *append.borrow_mut() = true; - ctx.push_tmp_expr(ret, analyzer).into_expr_err(loc) - } - }) - })?; - if !inputs.is_empty() { - self.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(ret) = ctx.pop_tmp_expr(loc, analyzer).into_expr_err(loc)? else { - return Err(ExprErr::NoLhs( - loc, - "Inputs did not have left hand sides".to_string(), - )); - }; - ctx.push_expr(ret, analyzer).into_expr_err(loc) - }) - } else { - Ok(()) - } - } - - /// Setups up storage variables for a function call and calls it - fn setup_fn_call( - &mut self, - loc: &Loc, - inputs: &ExprRet, - func_idx: NodeIdx, - ctx: ContextNode, - func_call_str: Option<&str>, - ) -> Result<(), ExprErr> { - // if we have a single match thats our function - let var = match ContextVar::maybe_from_user_ty(self, *loc, func_idx) { - Some(v) => v, - None => panic!( - "Could not create context variable from user type: {:?}", - self.node(func_idx) - ), - }; - - let new_cvarnode = self.add_node(Node::ContextVar(var)); - ctx.add_var(new_cvarnode.into(), self).into_expr_err(*loc)?; - self.add_edge(new_cvarnode, ctx, Edge::Context(ContextEdge::Variable)); - if let Some(func_node) = ContextVarNode::from(new_cvarnode) - .ty(self) - .into_expr_err(*loc)? - .func_node(self) - { - self.func_call(ctx, *loc, inputs, func_node, func_call_str, None) - } else { - unreachable!() - } - } - - /// Matches the input kinds and performs the call - fn func_call( - &mut self, - ctx: ContextNode, - loc: Loc, - input_paths: &ExprRet, - func: FunctionNode, - func_call_str: Option<&str>, - modifier_state: Option, - ) -> Result<(), ExprErr> { - let params = func.params(self); - let input_paths = input_paths.clone().flatten(); - if input_paths.has_killed() { - return ctx - .kill(self, loc, input_paths.killed_kind().unwrap()) - .into_expr_err(loc); - } - match input_paths { - ExprRet::Single(input_var) | ExprRet::SingleLiteral(input_var) => { - // if we get a single var, we expect the func to only take a single - // variable - self.func_call_inner( - false, - ctx, - func, - loc, - vec![ContextVarNode::from(input_var).latest_version(self)], - params, - func_call_str, - modifier_state, - ) - } - ExprRet::Multi(ref inputs) => { - if ExprRet::Multi(inputs.to_vec()).flatten().has_killed() { - return ctx - .kill( - self, - loc, - ExprRet::Multi(inputs.to_vec()).killed_kind().unwrap(), - ) - .into_expr_err(loc); - } - // check if the inputs length matchs func params length - // if they do, check that none are forks - if inputs.len() == params.len() { - let input_vars = inputs - .iter() - .map(|expr_ret| { - let var = expr_ret.expect_single().into_expr_err(loc)?; - Ok(ContextVarNode::from(var).latest_version(self)) - }) - .collect::, ExprErr>>()?; - self.func_call_inner( - false, - ctx, - func, - loc, - input_vars, - params, - func_call_str, - modifier_state, - ) - } else { - Err(ExprErr::InvalidFunctionInput( - loc, - format!( - "Length mismatch: {inputs:?} {params:?}, inputs as vars: {}, ctx: {}", - ExprRet::Multi(inputs.to_vec()).debug_str(self), - ctx.path(self) - ), - )) - } - } - e => todo!("here: {:?}", e), - } - } - - fn create_call_ctx( - &mut self, - curr_ctx: ContextNode, - loc: Loc, - func_node: FunctionNode, - modifier_state: Option, - ) -> Result { - let fn_ext = curr_ctx.is_fn_ext(func_node, self).into_expr_err(loc)?; - let ctx = Context::new_subctx( - curr_ctx, - None, - loc, - None, - Some(func_node), - fn_ext, - self, - modifier_state, - ) - .into_expr_err(loc)?; - let callee_ctx = ContextNode::from(self.add_node(Node::Context(ctx))); - curr_ctx - .set_child_call(callee_ctx, self) - .into_expr_err(loc)?; - let ctx_fork = self.add_node(Node::FunctionCall); - self.add_edge(ctx_fork, curr_ctx, Edge::Context(ContextEdge::Subcontext)); - self.add_edge(ctx_fork, func_node, Edge::Context(ContextEdge::Call)); - self.add_edge( - NodeIdx::from(callee_ctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - Ok(callee_ctx) - } - - /// Maps inputs to function parameters such that if there is a renaming i.e. `a(uint256 x)` is called via `a(y)`, - /// we map `y -> x` for future lookups - fn map_inputs_to_params( - &mut self, - loc: Loc, - entry_call: bool, - params: Vec, - inputs: Vec, - callee_ctx: ContextNode, - ) -> Result, ExprErr> { - Ok(params - .iter() - .zip(inputs.iter()) - .filter_map(|(param, input)| { - if !entry_call { - if let Some(name) = - self.add_if_err(param.maybe_name(self).into_expr_err(loc))? - { - let res = input - .latest_version(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 = name.clone(); - new_cvar.display_name = name; - new_cvar.is_tmp = false; - new_cvar.storage = if let Some(StorageLocation::Storage(_)) = - param.underlying(self).unwrap().storage - { - new_cvar.storage - } else { - None - }; - - if let Some(param_ty) = VarType::try_from_idx(self, param.ty(self).unwrap()) - { - let ty = new_cvar.ty.clone(); - if !ty.ty_eq(¶m_ty, self).unwrap() { - if let Some(new_ty) = ty.try_cast(¶m_ty, self).unwrap() { - new_cvar.ty = new_ty; - } - } - } - - let node = ContextVarNode::from(self.add_node(Node::ContextVar(new_cvar))); - self.add_edge( - node, - input.latest_version(self), - Edge::Context(ContextEdge::InputVariable), - ); - - if let (Some(r), Some(r2)) = - (node.range(self).unwrap(), param.range(self).unwrap()) - { - 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()); - let res = node.try_set_range_min(self, new_min).into_expr_err(loc); - self.add_if_err(res); - let res = node.try_set_range_max(self, new_max).into_expr_err(loc); - self.add_if_err(res); - let res = node - .try_set_range_exclusions(self, r.exclusions) - .into_expr_err(loc); - self.add_if_err(res); - } - callee_ctx.add_var(node, self).unwrap(); - self.add_edge(node, callee_ctx, Edge::Context(ContextEdge::Variable)); - Some((*input, node)) - } else { - None - } - } else { - None - } - }) - .collect::>()) - } - - /// Checks if there are any modifiers and executes them prior to executing the function - #[tracing::instrument(level = "trace", skip_all)] - fn func_call_inner( - &mut self, - entry_call: bool, - ctx: ContextNode, - func_node: FunctionNode, - loc: Loc, - inputs: Vec, - params: Vec, - func_call_str: Option<&str>, - modifier_state: Option, - ) -> Result<(), ExprErr> { - // pseudocode: - // 1. Create context for the call - // 2. Check for modifiers - // 3. Call modifier 0, then 1, then 2, ... then N. - // 4. Call this function - // 5. Finish modifier N.. then 2, then 1, then 0 - let callee_ctx = if entry_call { - ctx - } else { - self.create_call_ctx(ctx, loc, func_node, modifier_state)? - }; - - // TODO: implement joining - // if !entry_call { - // let mapping = params - // .iter() - // .zip(inputs.iter()) - // .map(|(param, input)| (*input, *param)) - // .collect::>(); - // ctx.join(func_node, &mapping, self); - // } - - // handle remapping of variable names and bringing variables into the new context - let renamed_inputs = - self.map_inputs_to_params(loc, entry_call, params, inputs, callee_ctx)?; - - // begin modifier handling by making sure modifiers were set - if !func_node.modifiers_set(self).into_expr_err(loc)? { - self.set_modifiers(func_node, ctx)?; - } - - // get modifiers - let mods = func_node.modifiers(self); - self.apply_to_edges(callee_ctx, loc, &|analyzer, callee_ctx, loc| { - if let Some(mod_state) = &ctx.underlying(analyzer).into_expr_err(loc)?.modifier_state { - // we are iterating through modifiers - if mod_state.num + 1 < mods.len() { - // use the next modifier - let mut mstate = mod_state.clone(); - mstate.num += 1; - analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, mstate) - } else { - // out of modifiers, execute the actual function call - analyzer.execute_call_inner( - loc, - ctx, - callee_ctx, - func_node, - &renamed_inputs, - func_call_str, - ) - } - } else if !mods.is_empty() { - // we have modifiers and havent executed them, start the process of executing them - let state = - ModifierState::new(0, loc, func_node, callee_ctx, ctx, renamed_inputs.clone()); - analyzer.call_modifier_for_fn(loc, callee_ctx, func_node, state) - } else { - // no modifiers, just execute the function - analyzer.execute_call_inner( - loc, - ctx, - callee_ctx, - func_node, - &renamed_inputs, - func_call_str, - ) - } - }) - } - - /// Actually executes the function - #[tracing::instrument(level = "trace", skip_all)] - fn execute_call_inner( - &mut self, - loc: Loc, - caller_ctx: ContextNode, - callee_ctx: ContextNode, - func_node: FunctionNode, - _renamed_inputs: &BTreeMap, - func_call_str: Option<&str>, - ) -> Result<(), ExprErr> { - if let Some(body) = func_node.underlying(self).into_expr_err(loc)?.body.clone() { - // add return nodes into the subctx - func_node - .returns(self) - .collect::>() - .into_iter() - .for_each(|ret| { - if let Some(var) = ContextVar::maybe_new_from_func_ret( - self, - ret.underlying(self).unwrap().clone(), - ) { - let cvar = self.add_node(Node::ContextVar(var)); - callee_ctx.add_var(cvar.into(), self).unwrap(); - self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Variable)); - } - }); - - self.parse_ctx_statement(&body, false, Some(callee_ctx)); - self.ctx_rets(loc, caller_ctx, callee_ctx) - } else { - let ret_ctx = Context::new_subctx( - callee_ctx, - Some(caller_ctx), - loc, - None, - None, - false, - self, - caller_ctx - .underlying(self) - .into_expr_err(loc)? - .modifier_state - .clone(), - ) - .unwrap(); - let ret_subctx = ContextNode::from(self.add_node(Node::Context(ret_ctx))); - self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue)); - - let res = callee_ctx - .set_child_call(ret_subctx, self) - .into_expr_err(loc); - let _ = self.add_if_err(res); - self.apply_to_edges(callee_ctx, loc, &|analyzer, ctx, loc| { - func_node - .returns(analyzer) - .collect::>() - .into_iter() - .try_for_each(|ret| { - let underlying = ret.underlying(analyzer).unwrap(); - let mut var = - ContextVar::new_from_func_ret(ctx, analyzer, underlying.clone()) - .unwrap() - .expect("No type for return variable?"); - if let Some(func_call) = &func_call_str { - var.name = - format!("{}_{}", func_call, callee_ctx.new_tmp(analyzer).unwrap()); - var.display_name = func_call.to_string(); - } - let node = analyzer.add_node(Node::ContextVar(var)); - ctx.add_var(node.into(), analyzer).into_expr_err(loc)?; - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Variable)); - analyzer.add_edge(node, ctx, Edge::Context(ContextEdge::Return)); - ctx.push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc)?; - Ok(()) - }) - }) - } - } - - fn ctx_rets( - &mut self, - loc: Loc, - caller_ctx: ContextNode, - callee_ctx: ContextNode, - ) -> Result<(), ExprErr> { - tracing::trace!( - "Handling function call return for: {}, {}, depth: {:?}, {:?}", - caller_ctx.path(self), - callee_ctx.path(self), - caller_ctx.depth(self), - callee_ctx.depth(self), - ); - match callee_ctx.underlying(self).into_expr_err(loc)?.child { - Some(CallFork::Fork(w1, w2)) => { - self.ctx_rets(loc, caller_ctx, w1)?; - self.ctx_rets(loc, caller_ctx, w2)?; - Ok(()) - } - Some(CallFork::Call(c)) - if c.underlying(self).into_expr_err(loc)?.depth - >= caller_ctx.underlying(self).into_expr_err(loc)?.depth => - { - // follow rabbit hole - self.ctx_rets(loc, caller_ctx, c)?; - Ok(()) - } - _ => { - if callee_ctx.is_killed(self).into_expr_err(loc)? { - return Ok(()); - } - let callee_depth = callee_ctx.underlying(self).into_expr_err(loc)?.depth; - let caller_depth = caller_ctx.underlying(self).into_expr_err(loc)?.depth; - if callee_depth != caller_depth { - let ctx = Context::new_subctx( - callee_ctx, - Some(caller_ctx), - loc, - None, - None, - false, - self, - caller_ctx - .underlying(self) - .into_expr_err(loc)? - .modifier_state - .clone(), - ) - .unwrap(); - let ret_subctx = ContextNode::from(self.add_node(Node::Context(ctx))); - self.add_edge(ret_subctx, caller_ctx, Edge::Context(ContextEdge::Continue)); - - let res = callee_ctx - .set_child_call(ret_subctx, self) - .into_expr_err(loc); - let _ = self.add_if_err(res); - - let mut rets = callee_ctx.underlying(self).unwrap().ret.clone(); - - if rets.is_empty() { - let func_rets: Vec = callee_ctx - .associated_fn(self) - .into_expr_err(loc)? - .returns(self) - .collect(); - func_rets - .iter() - .filter_map(|ret| { - let n: String = ret.maybe_name(self).ok()??; - let ret_loc: Loc = ret.loc(self).ok()?; - Some((n, ret_loc)) - }) - .collect::>() - .into_iter() - .try_for_each(|(name, ret_loc)| { - if let Some(cvar) = callee_ctx - .var_by_name_or_recurse(self, &name) - .into_expr_err(loc)? - { - let cvar = cvar.latest_version(self); - // let ret_loc = ret.loc(self).into_expr_err(loc)?; - callee_ctx - .add_return_node(ret_loc, cvar, self) - .into_expr_err(loc)?; - self.add_edge( - cvar, - callee_ctx, - Edge::Context(ContextEdge::Return), - ); - } - Ok(()) - })?; - - // add unnamed rets - func_rets - .into_iter() - .filter(|ret| ret.maybe_name(self).unwrap().is_none()) - .collect::>() - .iter() - .try_for_each(|ret| { - let ret_loc = ret.loc(self).into_expr_err(loc)?; - let cvar = ContextVar::new_from_func_ret( - callee_ctx, - self, - ret.underlying(self).into_expr_err(loc)?.clone(), - ) - .into_expr_err(loc)? - .unwrap(); - let cvar = - ContextVarNode::from(self.add_node(Node::ContextVar(cvar))); - callee_ctx.add_var(cvar, self).into_expr_err(loc)?; - self.add_edge( - cvar, - callee_ctx, - Edge::Context(ContextEdge::Variable), - ); - callee_ctx - .add_return_node(ret_loc, cvar, self) - .into_expr_err(loc)?; - self.add_edge(cvar, callee_ctx, Edge::Context(ContextEdge::Return)); - Ok(()) - })?; - rets = callee_ctx.underlying(self).unwrap().ret.clone(); - } - - let handle_rets = rets.iter().all(|(_, node)| node.is_some()); - if handle_rets { - let ret = rets - .into_iter() - .enumerate() - .map(|(i, (_, node))| { - let tmp_ret = node - .unwrap() - .as_tmp( - callee_ctx.underlying(self).unwrap().loc, - ret_subctx, - self, - ) - .unwrap(); - tmp_ret.underlying_mut(self).into_expr_err(loc)?.is_return = true; - tmp_ret - .underlying_mut(self) - .into_expr_err(loc)? - .display_name = format!( - "{}.{}", - callee_ctx.associated_fn_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())) - }) - .collect::>()?; - ret_subctx - .push_expr(ExprRet::Multi(ret), self) - .into_expr_err(loc)?; - } - Ok(()) - } else { - let mut rets = callee_ctx.underlying(self).unwrap().ret.clone(); - - if rets.is_empty() { - callee_ctx - .associated_fn(self) - .into_expr_err(loc)? - .returns(self) - .filter_map(|ret| { - let n: String = ret.maybe_name(self).ok()??; - let ret_loc: Loc = ret.loc(self).ok()?; - Some((n, ret_loc)) - }) - .collect::>() - .into_iter() - .try_for_each(|(name, ret_loc)| { - if let Some(cvar) = callee_ctx - .var_by_name_or_recurse(self, &name) - .into_expr_err(loc)? - { - let cvar = cvar.latest_version(self); - // let ret_loc = ret.loc(self).into_expr_err(loc)?; - callee_ctx - .add_return_node(ret_loc, cvar, self) - .into_expr_err(loc)?; - self.add_edge( - cvar, - callee_ctx, - Edge::Context(ContextEdge::Return), - ); - } - Ok(()) - })?; - rets = callee_ctx.underlying(self).unwrap().ret.clone(); - } - if rets.iter().all(|(_, node)| node.is_some()) { - callee_ctx - .push_expr( - ExprRet::Multi( - rets.iter() - .map(|(_, node)| ExprRet::Single((node.unwrap()).into())) - .collect(), - ), - self, - ) - .into_expr_err(loc) - } else { - Ok(()) - } - } - } - } - } - - /// Calls a modifier for a function - #[tracing::instrument(level = "trace", skip_all)] - fn call_modifier_for_fn( - &mut self, - loc: Loc, - func_ctx: ContextNode, - func_node: FunctionNode, - mod_state: ModifierState, - ) -> Result<(), ExprErr> { - let mod_node = func_node.modifiers(self)[mod_state.num]; - tracing::trace!( - "calling modifier {} for func {}", - mod_node.name(self).into_expr_err(loc)?, - func_node.name(self).into_expr_err(loc)? - ); - - let input_exprs = func_node - .modifier_input_vars(mod_state.num, self) - .into_expr_err(loc)?; - - input_exprs - .iter() - .try_for_each(|expr| self.parse_ctx_expr(expr, func_ctx))?; - self.apply_to_edges(func_ctx, loc, &|analyzer, ctx, loc| { - let input_paths = if input_exprs.is_empty() { - ExprRet::Multi(vec![]) - } else { - let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - format!("No inputs to modifier, expected: {}", input_exprs.len()), - )); - }; - - if matches!(input_paths, ExprRet::CtxKilled(_)) { - ctx.push_expr(input_paths, analyzer).into_expr_err(loc)?; - return Ok(()); - } - input_paths - }; - - analyzer.func_call( - ctx, - loc, - &input_paths, - mod_node, - None, - Some(mod_state.clone()), - ) - }) - } - - /// Resumes the parent function of a modifier - #[tracing::instrument(level = "trace", skip_all)] - fn resume_from_modifier( - &mut self, - ctx: ContextNode, - modifier_state: ModifierState, - ) -> Result<(), ExprErr> { - let mods = modifier_state.parent_fn.modifiers(self); - self.apply_to_edges(ctx, modifier_state.loc, &|analyzer, ctx, loc| { - if modifier_state.num + 1 < mods.len() { - // use the next modifier - let mut mstate = modifier_state.clone(); - mstate.num += 1; - - let loc = mods[mstate.num] - .underlying(analyzer) - .into_expr_err(mstate.loc)? - .loc; - - let pctx = Context::new_subctx( - ctx, - Some(modifier_state.parent_ctx), - loc, - None, - None, - false, - analyzer, - Some(modifier_state.clone()), - ) - .unwrap(); - let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); - - analyzer.add_edge( - new_parent_subctx, - modifier_state.parent_ctx, - Edge::Context(ContextEdge::Continue), - ); - ctx.set_child_call(new_parent_subctx, analyzer) - .into_expr_err(modifier_state.loc)?; - - analyzer.call_modifier_for_fn( - mods[mstate.num] - .underlying(analyzer) - .into_expr_err(mstate.loc)? - .loc, - new_parent_subctx, - mstate.parent_fn, - mstate, - )?; - Ok(()) - } else { - let pctx = Context::new_subctx( - ctx, - Some(modifier_state.parent_ctx), - modifier_state.loc, - None, - None, - false, - analyzer, - None, - ) - .unwrap(); - let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); - - analyzer.add_edge( - new_parent_subctx, - modifier_state.parent_ctx, - Edge::Context(ContextEdge::Continue), - ); - ctx.set_child_call(new_parent_subctx, analyzer) - .into_expr_err(modifier_state.loc)?; - - // actually execute the parent function - analyzer.execute_call_inner( - modifier_state.loc, - ctx, - new_parent_subctx, - modifier_state.parent_fn, - &modifier_state.renamed_inputs, - None, - )?; - - fn inherit_return_from_call( - analyzer: &mut (impl GraphBackend + AnalyzerBackend), - loc: Loc, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - let mctx = - Context::new_subctx(ctx, Some(ctx), loc, None, None, false, analyzer, None) - .unwrap(); - let modifier_after_subctx = - ContextNode::from(analyzer.add_node(Node::Context(mctx))); - - ctx.set_child_call(modifier_after_subctx, analyzer) - .into_expr_err(loc)?; - analyzer.add_edge( - modifier_after_subctx, - ctx, - Edge::Context(ContextEdge::Continue), - ); - - let ret = ctx.underlying(analyzer).unwrap().ret.clone(); - modifier_after_subctx.underlying_mut(analyzer).unwrap().ret = ret; - Ok(()) - } - - analyzer.apply_to_edges(new_parent_subctx, loc, &|analyzer, ctx, _loc| { - inherit_return_from_call(analyzer, modifier_state.loc, ctx) - }) - - // if edges.is_empty() { - // inherit_return_from_call(analyzer, modifier_state.loc, new_parent_subctx)?; - // } else { - // edges.iter().try_for_each(|i| { - // inherit_return_from_call(analyzer, modifier_state.loc, *i)?; - // Ok(()) - // })?; - // } - // Ok(()) - } - }) - } - - /// Inherit the input changes from a function call - fn inherit_input_changes( - &mut self, - loc: Loc, - to_ctx: ContextNode, - from_ctx: ContextNode, - renamed_inputs: &BTreeMap, - ) -> Result<(), ExprErr> { - if to_ctx != from_ctx { - self.apply_to_edges(to_ctx, loc, &|analyzer, to_ctx, loc| { - renamed_inputs - .iter() - .try_for_each(|(input_var, updated_var)| { - let new_input = analyzer.advance_var_in_ctx( - input_var.latest_version(analyzer), - loc, - to_ctx, - )?; - let latest_updated = updated_var.latest_version(analyzer); - if let Some(updated_var_range) = - latest_updated.range(analyzer).into_expr_err(loc)? - { - let res = new_input - .set_range_min(analyzer, updated_var_range.range_min().into_owned()) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_input - .set_range_max(analyzer, updated_var_range.range_max().into_owned()) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_input - .set_range_exclusions( - analyzer, - updated_var_range.range_exclusions(), - ) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } - Ok(()) - }) - })?; - } - Ok(()) - } - - /// Inherit the input changes from a function call - fn modifier_inherit_return(&mut self, mod_ctx: ContextNode, fn_ctx: ContextNode) { - let ret = fn_ctx.underlying(self).unwrap().ret.clone(); - mod_ctx.underlying_mut(self).unwrap().ret = ret; - } - - /// Inherit the storage changes from a function call - fn inherit_storage_changes( - &mut self, - loc: Loc, - inheritor_ctx: ContextNode, - grantor_ctx: ContextNode, - ) -> Result<(), ExprErr> { - if inheritor_ctx != grantor_ctx { - return self.apply_to_edges(inheritor_ctx, loc, &|analyzer, inheritor_ctx, loc| { - let vars = grantor_ctx.local_vars(analyzer).clone(); - vars.iter().try_for_each(|(name, old_var)| { - let var = old_var.latest_version(analyzer); - let underlying = var.underlying(analyzer).into_expr_err(loc)?; - if var.is_storage(analyzer).into_expr_err(loc)? { - if let Some(inheritor_var) = inheritor_ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = underlying.ty.range(analyzer).into_expr_err(loc)? { - let new_inheritor_var = analyzer - .advance_var_in_ctx( - inheritor_var, - underlying.loc.expect("No loc for val change"), - inheritor_ctx, - ) - .unwrap(); - let _ = new_inheritor_var - .set_range_min(analyzer, r.range_min().into_owned()); - let _ = new_inheritor_var - .set_range_max(analyzer, r.range_max().into_owned()); - let _ = new_inheritor_var - .set_range_exclusions(analyzer, r.range_exclusions()); - } - } else { - let new_in_inheritor = - analyzer.add_node(Node::ContextVar(underlying.clone())); - inheritor_ctx - .add_var(new_in_inheritor.into(), analyzer) - .into_expr_err(loc)?; - analyzer.add_edge( - new_in_inheritor, - inheritor_ctx, - Edge::Context(ContextEdge::Variable), - ); - analyzer.add_edge( - new_in_inheritor, - var, - Edge::Context(ContextEdge::InheritedVariable), - ); - } - } - Ok(()) - }) - }); - } - Ok(()) - } - - fn modifiers( - &mut self, - ctx: ContextNode, - func: FunctionNode, - ) -> Result, ExprErr> { - use std::fmt::Write; - let binding = func.underlying(self).unwrap().clone(); - let modifiers = binding.modifiers_as_base(); - if modifiers.is_empty() { - Ok(vec![]) - } else { - let res = modifiers - .iter() - .map(|modifier| { - assert_eq!(modifier.name.identifiers.len(), 1); - // construct arg string for function selector - let mut mod_name = format!("{}", modifier.name.identifiers[0]); - if let Some(args) = &modifier.args { - let args_str = args - .iter() - .map(|expr| { - let mctx = Context::new_subctx( - ctx, - None, - Loc::Implicit, - None, - None, - false, - self, - None, - ) - .into_expr_err(Loc::Implicit)?; - let callee_ctx = - ContextNode::from(self.add_node(Node::Context(mctx))); - let _res = ctx.set_child_call(callee_ctx, self); - self.parse_ctx_expr(expr, callee_ctx)?; - let f: Vec = - self.take_from_edge(ctx, expr.loc(), &|analyzer, ctx, loc| { - if let Some(ret) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - { - Ok(ret.try_as_func_input_str(analyzer)) - } else { - Err(ExprErr::ParseError( - loc, - "Bad modifier parse".to_string(), - )) - } - })?; - - ctx.delete_child(self).into_expr_err(expr.loc())?; - Ok(f.first().unwrap().clone()) - }) - .collect::, ExprErr>>()? - .join(", "); - let _ = write!(mod_name, "{args_str}"); - } else { - let _ = write!(mod_name, "()"); - } - let _ = write!(mod_name, ""); - let found: Option = ctx - .visible_modifiers(self) - .unwrap() - .iter() - .find(|modifier| modifier.name(self).unwrap() == mod_name) - .copied(); - Ok(found) - }) - .collect::>, ExprErr>>()? - .into_iter() - .flatten() - .collect::>(); - Ok(res) - } - } - - fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> { - let modifiers = self.modifiers(ctx, func)?; - modifiers - .iter() - .enumerate() - .for_each(|(i, modifier)| self.add_edge(*modifier, func, Edge::FuncModifier(i))); - func.underlying_mut(self).unwrap().modifiers_set = true; - Ok(()) - } -} +pub mod func_caller; diff --git a/crates/solc-expressions/src/func_call/modifier.rs b/crates/solc-expressions/src/func_call/modifier.rs index a6d56a05..bec4128e 100644 --- a/crates/solc-expressions/src/func_call/modifier.rs +++ b/crates/solc-expressions/src/func_call/modifier.rs @@ -1,31 +1,288 @@ -use crate::{ExprErr, FuncCaller, IntoExprErr}; +//! Traits & blanket implementations that facilitate performing modifier function calls. + +use crate::helper::CallerHelper; +use crate::{ + ContextBuilder, ExprErr, IntoExprErr, func_caller::FuncCaller +}; use graph::{ - nodes::{ContextNode, ExprRet, FunctionNode}, - AnalyzerBackend, GraphBackend, + nodes::{ + Context, ContextNode, ExprRet, FunctionNode, ModifierState, + }, + AnalyzerBackend, ContextEdge, Edge, GraphBackend, Node, }; -use solang_parser::pt::{Expression, Loc}; +use solang_parser::pt::{Expression, Loc, CodeLocation}; impl ModifierCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + T: AnalyzerBackend + Sized + GraphBackend + FuncCaller + CallerHelper { } +/// A trait for dealing with modifier calls pub trait ModifierCaller: - GraphBackend + AnalyzerBackend + Sized + GraphBackend + AnalyzerBackend + Sized + FuncCaller + CallerHelper { - fn handle_modifiers( + /// Calls a modifier for a function + #[tracing::instrument(level = "trace", skip_all)] + fn call_modifier_for_fn( &mut self, - ctx: ContextNode, loc: Loc, - _input_paths: &ExprRet, + func_ctx: ContextNode, + func_node: FunctionNode, + mod_state: ModifierState, + ) -> Result<(), ExprErr> { + let mod_node = func_node.modifiers(self)[mod_state.num]; + tracing::trace!( + "calling modifier {} for func {}", + mod_node.name(self).into_expr_err(loc)?, + func_node.name(self).into_expr_err(loc)? + ); + + let input_exprs = func_node + .modifier_input_vars(mod_state.num, self) + .into_expr_err(loc)?; + + input_exprs + .iter() + .try_for_each(|expr| self.parse_ctx_expr(expr, func_ctx))?; + self.apply_to_edges(func_ctx, loc, &|analyzer, ctx, loc| { + let input_paths = if input_exprs.is_empty() { + ExprRet::Multi(vec![]) + } else { + let Some(input_paths) = ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + format!("No inputs to modifier, expected: {}", input_exprs.len()), + )); + }; + + if matches!(input_paths, ExprRet::CtxKilled(_)) { + ctx.push_expr(input_paths, analyzer).into_expr_err(loc)?; + return Ok(()); + } + input_paths + }; + + analyzer.func_call( + ctx, + loc, + &input_paths, + mod_node, + None, + Some(mod_state.clone()), + ) + }) + } + + /// Resumes the parent function of a modifier + #[tracing::instrument(level = "trace", skip_all)] + fn resume_from_modifier( + &mut self, + ctx: ContextNode, + modifier_state: ModifierState, + ) -> Result<(), ExprErr> { + let mods = modifier_state.parent_fn.modifiers(self); + self.apply_to_edges(ctx, modifier_state.loc, &|analyzer, ctx, loc| { + if modifier_state.num + 1 < mods.len() { + // use the next modifier + let mut mstate = modifier_state.clone(); + mstate.num += 1; + + let loc = mods[mstate.num] + .underlying(analyzer) + .into_expr_err(mstate.loc)? + .loc; + + let pctx = Context::new_subctx( + ctx, + Some(modifier_state.parent_ctx), + loc, + None, + None, + false, + analyzer, + Some(modifier_state.clone()), + ) + .unwrap(); + let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); + + analyzer.add_edge( + new_parent_subctx, + modifier_state.parent_ctx, + Edge::Context(ContextEdge::Continue), + ); + ctx.set_child_call(new_parent_subctx, analyzer) + .into_expr_err(modifier_state.loc)?; + + analyzer.call_modifier_for_fn( + mods[mstate.num] + .underlying(analyzer) + .into_expr_err(mstate.loc)? + .loc, + new_parent_subctx, + mstate.parent_fn, + mstate, + )?; + Ok(()) + } else { + let pctx = Context::new_subctx( + ctx, + Some(modifier_state.parent_ctx), + modifier_state.loc, + None, + None, + false, + analyzer, + None, + ) + .unwrap(); + let new_parent_subctx = ContextNode::from(analyzer.add_node(Node::Context(pctx))); + + analyzer.add_edge( + new_parent_subctx, + modifier_state.parent_ctx, + Edge::Context(ContextEdge::Continue), + ); + ctx.set_child_call(new_parent_subctx, analyzer) + .into_expr_err(modifier_state.loc)?; + + // actually execute the parent function + analyzer.execute_call_inner( + modifier_state.loc, + ctx, + new_parent_subctx, + modifier_state.parent_fn, + &modifier_state.renamed_inputs, + None, + )?; + + fn inherit_return_from_call( + analyzer: &mut (impl GraphBackend + AnalyzerBackend), + loc: Loc, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + let mctx = + Context::new_subctx(ctx, Some(ctx), loc, None, None, false, analyzer, None) + .unwrap(); + let modifier_after_subctx = + ContextNode::from(analyzer.add_node(Node::Context(mctx))); + + ctx.set_child_call(modifier_after_subctx, analyzer) + .into_expr_err(loc)?; + analyzer.add_edge( + modifier_after_subctx, + ctx, + Edge::Context(ContextEdge::Continue), + ); + + let ret = ctx.underlying(analyzer).unwrap().ret.clone(); + modifier_after_subctx.underlying_mut(analyzer).unwrap().ret = ret; + Ok(()) + } + + analyzer.apply_to_edges(new_parent_subctx, loc, &|analyzer, ctx, _loc| { + inherit_return_from_call(analyzer, modifier_state.loc, ctx) + }) + + // if edges.is_empty() { + // inherit_return_from_call(analyzer, modifier_state.loc, new_parent_subctx)?; + // } else { + // edges.iter().try_for_each(|i| { + // inherit_return_from_call(analyzer, modifier_state.loc, *i)?; + // Ok(()) + // })?; + // } + // Ok(()) + } + }) + } + + /// Gets the modifiers for a function + fn modifiers( + &mut self, + ctx: ContextNode, func: FunctionNode, - _func_call_str: Option, - ) -> Result { - if !func.modifiers_set(self).into_expr_err(loc)? { - self.set_modifiers(func, ctx)?; + ) -> Result, ExprErr> { + use std::fmt::Write; + let binding = func.underlying(self).unwrap().clone(); + let modifiers = binding.modifiers_as_base(); + if modifiers.is_empty() { + Ok(vec![]) + } else { + let res = modifiers + .iter() + .map(|modifier| { + assert_eq!(modifier.name.identifiers.len(), 1); + // construct arg string for function selector + let mut mod_name = format!("{}", modifier.name.identifiers[0]); + if let Some(args) = &modifier.args { + let args_str = args + .iter() + .map(|expr| { + let mctx = Context::new_subctx( + ctx, + None, + Loc::Implicit, + None, + None, + false, + self, + None, + ) + .into_expr_err(Loc::Implicit)?; + let callee_ctx = + ContextNode::from(self.add_node(Node::Context(mctx))); + let _res = ctx.set_child_call(callee_ctx, self); + self.parse_ctx_expr(expr, callee_ctx)?; + let f: Vec = + self.take_from_edge(ctx, expr.loc(), &|analyzer, ctx, loc| { + if let Some(ret) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + { + Ok(ret.try_as_func_input_str(analyzer)) + } else { + Err(ExprErr::ParseError( + loc, + "Bad modifier parse".to_string(), + )) + } + })?; + + ctx.delete_child(self).into_expr_err(expr.loc())?; + Ok(f.first().unwrap().clone()) + }) + .collect::, ExprErr>>()? + .join(", "); + let _ = write!(mod_name, "{args_str}"); + } else { + let _ = write!(mod_name, "()"); + } + let _ = write!(mod_name, ""); + let found: Option = ctx + .visible_modifiers(self) + .unwrap() + .iter() + .find(|modifier| modifier.name(self).unwrap() == mod_name) + .copied(); + Ok(found) + }) + .collect::>, ExprErr>>()? + .into_iter() + .flatten() + .collect::>(); + Ok(res) } + } - todo!() + /// Sets the modifiers for a function + fn set_modifiers(&mut self, func: FunctionNode, ctx: ContextNode) -> Result<(), ExprErr> { + let modifiers = self.modifiers(ctx, func)?; + modifiers + .iter() + .enumerate() + .for_each(|(i, modifier)| self.add_edge(*modifier, func, Edge::FuncModifier(i))); + func.underlying_mut(self).unwrap().modifiers_set = true; + Ok(()) } } diff --git a/crates/solc-expressions/src/func_call/namespaced_call.rs b/crates/solc-expressions/src/func_call/namespaced_call.rs index 1086c7d9..2c2778c2 100644 --- a/crates/solc-expressions/src/func_call/namespaced_call.rs +++ b/crates/solc-expressions/src/func_call/namespaced_call.rs @@ -1,6 +1,9 @@ +//! Traits & blanket implementations that facilitate performing namespaced function calls. + use crate::{ - intrinsic_call::IntrinsicFuncCaller, member_access::MemberAccess, ContextBuilder, ExprErr, - FuncCaller, IntoExprErr, + func_call::helper::CallerHelper, + func_call::func_caller::FuncCaller, + intrinsic_call::IntrinsicFuncCaller, member_access::MemberAccess, ContextBuilder, ExprErr, IntoExprErr, }; use graph::{ @@ -13,13 +16,15 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Identifier, Loc, NamedArgument}; impl NameSpaceFuncCaller for T where - T: AnalyzerBackend + Sized + GraphBackend + T: AnalyzerBackend + Sized + GraphBackend + CallerHelper { } +/// A trait for performing namespaced function calls (i.e. `MyContract.myFunc(...)`) pub trait NameSpaceFuncCaller: - AnalyzerBackend + Sized + GraphBackend + AnalyzerBackend + Sized + GraphBackend + CallerHelper { #[tracing::instrument(level = "trace", skip_all)] + /// TODO: Call a namedspaced named input function, i.e. `MyContract.myFunc({a: 1, b: 2})` fn call_name_spaced_named_func( &mut self, ctx: ContextNode, @@ -33,6 +38,7 @@ pub trait NameSpaceFuncCaller: } #[tracing::instrument(level = "trace", skip_all)] + /// Call a namedspaced function, i.e. `MyContract.myFunc(...)` fn call_name_spaced_func( &mut self, ctx: ContextNode, @@ -153,6 +159,7 @@ pub trait NameSpaceFuncCaller: }) } + /// Match the expression return for getting the member node fn match_namespaced_member( &mut self, ctx: ContextNode, @@ -178,6 +185,7 @@ pub trait NameSpaceFuncCaller: } #[tracing::instrument(level = "trace", skip_all)] + /// Actually perform the namespaced function call fn call_name_spaced_func_inner( &mut self, ctx: ContextNode, diff --git a/crates/solc-expressions/src/lib.rs b/crates/solc-expressions/src/lib.rs index b3e4ace9..2e903258 100644 --- a/crates/solc-expressions/src/lib.rs +++ b/crates/solc-expressions/src/lib.rs @@ -13,7 +13,7 @@ mod loops; mod member_access; mod require; mod variable; -mod yul; +pub mod yul; pub use array::*; pub use bin_op::*; @@ -28,8 +28,8 @@ pub use loops::*; pub use member_access::*; pub use require::*; pub use variable::*; -pub use yul::*; +/// Supertrait for parsing expressions pub trait ExprParser: BinOp + Require + Variable + Literal + Array + MemberAccess + Cmp + CondOp + List + Env { @@ -39,7 +39,9 @@ impl ExprParser for T where { } +/// Convert some error into an expression error by attaching a code source location pub trait IntoExprErr { + /// Convert into a ExprErr fn into_expr_err(self, loc: Loc) -> Result; } @@ -53,6 +55,7 @@ impl IntoExprErr for Result { } #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] +/// An error that arose from the analyzer when interpreting expressions and statements pub enum ExprErr { ParseError(Loc, String), NoLhs(Loc, String), @@ -81,12 +84,14 @@ pub enum ExprErr { } impl ExprErr { + /// Convert from a graph error pub fn from_graph_err(loc: Loc, graph_err: graph::GraphError) -> Self { Self::GraphError(loc, graph_err) } } impl ExprErr { + /// Get the code source location of the error pub fn loc(&self) -> Loc { use ExprErr::*; match self { @@ -115,6 +120,7 @@ impl ExprErr { } } + /// Get the error message pub fn msg(&self) -> &str { use ExprErr::*; match self { @@ -152,6 +158,7 @@ impl ExprErr { } } + /// Get the top-level report message pub fn report_msg(&self) -> &str { use ExprErr::*; match self { diff --git a/crates/solc-expressions/src/list.rs b/crates/solc-expressions/src/list.rs index a3af89d9..39af7787 100644 --- a/crates/solc-expressions/src/list.rs +++ b/crates/solc-expressions/src/list.rs @@ -8,7 +8,7 @@ use graph::{ use solang_parser::pt::{Expression, Loc, Parameter, ParameterList}; impl List for T where T: AnalyzerBackend + Sized {} - +/// Dealing with list parsing and operations pub trait List: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] fn list(&mut self, ctx: ContextNode, loc: Loc, params: &ParameterList) -> Result<(), ExprErr> { diff --git a/crates/solc-expressions/src/literal.rs b/crates/solc-expressions/src/literal.rs index 242834ae..de1d0837 100644 --- a/crates/solc-expressions/src/literal.rs +++ b/crates/solc-expressions/src/literal.rs @@ -13,6 +13,7 @@ use std::str::FromStr; impl Literal for T where T: AnalyzerBackend + Sized {} +/// Dealing with literal expression and parsing them into nodes pub trait Literal: AnalyzerBackend + Sized { fn number_literal( &mut self, diff --git a/crates/solc-expressions/src/loops.rs b/crates/solc-expressions/src/loops.rs index c7aec51a..bb02b142 100644 --- a/crates/solc-expressions/src/loops.rs +++ b/crates/solc-expressions/src/loops.rs @@ -11,10 +11,13 @@ impl Looper for T where T: AnalyzerBackend + Sized + GraphBackend { } + +/// Dealing with loops pub trait Looper: GraphBackend + AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] + /// Handles a for loop. Needs improvement fn for_loop( &mut self, loc: Loc, @@ -38,6 +41,7 @@ pub trait Looper: } } + /// Resets all variables referenced in the loop because we don't elegantly handle loops fn reset_vars(&mut self, loc: Loc, ctx: ContextNode, body: &Statement) -> Result<(), ExprErr> { let og_ctx = ctx; let sctx = Context::new_subctx(ctx, None, loc, None, None, false, self, None) @@ -81,6 +85,7 @@ pub trait Looper: }) } + /// Handles a while-loop fn while_loop( &mut self, loc: Loc, diff --git a/crates/solc-expressions/src/member_access/builtin_access.rs b/crates/solc-expressions/src/member_access/builtin_access.rs index b901b282..04ad8278 100644 --- a/crates/solc-expressions/src/member_access/builtin_access.rs +++ b/crates/solc-expressions/src/member_access/builtin_access.rs @@ -12,9 +12,12 @@ impl BuiltinAccess for T where T: LibraryAccess + AnalyzerBackend + Sized { } + +/// Trait for performing member access on builtin types pub trait BuiltinAccess: LibraryAccess + AnalyzerBackend + Sized { + /// Perform member access on builtin types fn builtin_member_access( &mut self, loc: Loc, diff --git a/crates/solc-expressions/src/member_access/contract_access.rs b/crates/solc-expressions/src/member_access/contract_access.rs index 28945a58..860558a8 100644 --- a/crates/solc-expressions/src/member_access/contract_access.rs +++ b/crates/solc-expressions/src/member_access/contract_access.rs @@ -9,7 +9,10 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Identifier, Loc}; impl ContractAccess for T where T: AnalyzerBackend + Sized {} + +/// Trait for performing member access on a Contract pub trait ContractAccess: AnalyzerBackend + Sized { + /// Perform member access on a contract fn contract_member_access( &mut self, member_idx: NodeIdx, diff --git a/crates/solc-expressions/src/member_access/enum_access.rs b/crates/solc-expressions/src/member_access/enum_access.rs index 435ce8f9..c373e772 100644 --- a/crates/solc-expressions/src/member_access/enum_access.rs +++ b/crates/solc-expressions/src/member_access/enum_access.rs @@ -12,9 +12,12 @@ impl EnumAccess for T where T: LibraryAccess + AnalyzerBackend + Sized { } + +/// Trait for performing member access on an enum pub trait EnumAccess: LibraryAccess + AnalyzerBackend + Sized { + /// Perform member access on an enum fn enum_member_access( &mut self, _member_idx: NodeIdx, diff --git a/crates/solc-expressions/src/member_access/library_access.rs b/crates/solc-expressions/src/member_access/library_access.rs index 4a5d234d..164b5f0b 100644 --- a/crates/solc-expressions/src/member_access/library_access.rs +++ b/crates/solc-expressions/src/member_access/library_access.rs @@ -12,7 +12,10 @@ use solang_parser::pt::{Expression, Identifier}; use std::collections::BTreeSet; impl LibraryAccess for T where T: AnalyzerBackend + Sized {} + +/// Trait for getting library functions for a type pub trait LibraryAccess: AnalyzerBackend + Sized { + /// Search for a library function by name fn library_func_search( &mut self, ctx: ContextNode, @@ -37,6 +40,7 @@ pub trait LibraryAccess: AnalyzerBackend + }) } + /// Get all possible library functions fn possible_library_funcs(&mut self, ctx: ContextNode, ty: NodeIdx) -> BTreeSet { let mut funcs: BTreeSet = BTreeSet::new(); if let Some(associated_contract) = ctx.maybe_associated_contract(self).unwrap() { diff --git a/crates/solc-expressions/src/member_access/list_access.rs b/crates/solc-expressions/src/member_access/list_access.rs index 3923db87..79657c65 100644 --- a/crates/solc-expressions/src/member_access/list_access.rs +++ b/crates/solc-expressions/src/member_access/list_access.rs @@ -10,8 +10,10 @@ use shared::NodeIdx; use solang_parser::pt::{Expression, Identifier, Loc}; impl ListAccess for T where T: AnalyzerBackend + Sized {} +/// Handles list/array member access (indices, length, etc) pub trait ListAccess: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] + /// Access an index of a list/array fn index_access( &mut self, loc: Loc, @@ -35,6 +37,7 @@ pub trait ListAccess: AnalyzerBackend + Si } #[tracing::instrument(level = "trace", skip_all)] + /// Match on the [`ExprRet`] of a index access expression fn match_index_access( &mut self, index_paths: &ExprRet, @@ -91,6 +94,7 @@ pub trait ListAccess: AnalyzerBackend + Si } #[tracing::instrument(level = "trace", skip_all)] + /// Get the length member of an array/list fn length( &mut self, loc: Loc, @@ -114,6 +118,7 @@ pub trait ListAccess: AnalyzerBackend + Si } #[tracing::instrument(level = "trace", skip_all)] + /// Get the length member of an array/list and create it as a temporary variable fn tmp_length( &mut self, arr: ContextVarNode, @@ -188,6 +193,7 @@ pub trait ListAccess: AnalyzerBackend + Si } #[tracing::instrument(level = "trace", skip_all)] + /// Get the length member of an array/list fn match_length( &mut self, ctx: ContextNode, diff --git a/crates/solc-expressions/src/member_access/member_trait.rs b/crates/solc-expressions/src/member_access/member_trait.rs index 370114ff..fd8c7129 100644 --- a/crates/solc-expressions/src/member_access/member_trait.rs +++ b/crates/solc-expressions/src/member_access/member_trait.rs @@ -18,6 +18,8 @@ impl MemberAccessParts for T where T: BuiltinAccess + ContractAccess + EnumAccess + ListAccess + StructAccess { } + +/// Supertrait that coalesces various member access traits pub trait MemberAccessParts: BuiltinAccess + ContractAccess + EnumAccess + ListAccess + StructAccess { @@ -27,85 +29,12 @@ impl MemberAccess for T where T: MemberAccessParts + AnalyzerBackend + Sized { } + +/// Toplevel trait for performing member access. Utilizes other `..Access` traits pub trait MemberAccess: MemberAccessParts + AnalyzerBackend + Sized { - fn visible_member_funcs( - &mut self, - ctx: ContextNode, - loc: Loc, - member_idx: NodeIdx, - ) -> Result, ExprErr> { - let res = match self.node(member_idx) { - Node::ContextVar(cvar) => match &cvar.ty { - VarType::User(TypeNode::Contract(con_node), _) => { - let mut funcs = con_node.linearized_functions(self); - self - .possible_library_funcs(ctx, con_node.0.into()) - .into_iter() - .for_each(|func| { - let name = func.name(self).unwrap(); - funcs.entry(name).or_insert(func); - }); - funcs.values().copied().collect() - }, - VarType::BuiltIn(bn, _) => self - .possible_library_funcs(ctx, bn.0.into()) - .into_iter() - .collect::>(), - VarType::Concrete(cnode) => { - let b = cnode.underlying(self).unwrap().as_builtin(); - let bn = self.builtin_or_add(b); - self.possible_library_funcs(ctx, bn) - .into_iter() - .collect::>() - } - VarType::User(TypeNode::Struct(sn), _) => self - .possible_library_funcs(ctx, sn.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Enum(en), _) => self - .possible_library_funcs(ctx, en.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Ty(ty), _) => self - .possible_library_funcs(ctx, ty.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Func(func_node), _) => self - .possible_library_funcs(ctx, func_node.0.into()) - .into_iter() - .collect::>(), - VarType::User(TypeNode::Unresolved(n), _) => { - match self.node(*n) { - Node::Unresolved(ident) => { - return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name))) - } - _ => unreachable!() - } - } - }, - Node::Contract(_) => ContractNode::from(member_idx).funcs(self), - Node::Concrete(_) - | Node::Ty(_) - | Node::Struct(_) - | Node::Function(_) - | Node::Enum(_) - | Node::Builtin(_) => self - .possible_library_funcs(ctx, member_idx) - .into_iter() - .collect::>(), - e => { - return Err(ExprErr::MemberAccessNotFound( - loc, - format!("This type cannot have member functions: {:?}", e), - )) - } - }; - Ok(res) - } - - /// Gets the array type + /// Entry function for perform a member access #[tracing::instrument(level = "trace", skip_all)] fn member_access( &mut self, @@ -135,6 +64,7 @@ pub trait MemberAccess: }) } + /// Match on [`ExprRet`]s and call the member access for each fn match_member( &mut self, ctx: ContextNode, @@ -156,6 +86,7 @@ pub trait MemberAccess: } } + /// Perform the member access fn member_access_inner( &mut self, loc: Loc, @@ -201,6 +132,83 @@ pub trait MemberAccess: } } + /// Get visible functions for this member + fn visible_member_funcs( + &mut self, + ctx: ContextNode, + loc: Loc, + member_idx: NodeIdx, + ) -> Result, ExprErr> { + let res = match self.node(member_idx) { + Node::ContextVar(cvar) => match &cvar.ty { + VarType::User(TypeNode::Contract(con_node), _) => { + let mut funcs = con_node.linearized_functions(self); + self + .possible_library_funcs(ctx, con_node.0.into()) + .into_iter() + .for_each(|func| { + let name = func.name(self).unwrap(); + funcs.entry(name).or_insert(func); + }); + funcs.values().copied().collect() + }, + VarType::BuiltIn(bn, _) => self + .possible_library_funcs(ctx, bn.0.into()) + .into_iter() + .collect::>(), + VarType::Concrete(cnode) => { + let b = cnode.underlying(self).unwrap().as_builtin(); + let bn = self.builtin_or_add(b); + self.possible_library_funcs(ctx, bn) + .into_iter() + .collect::>() + } + VarType::User(TypeNode::Struct(sn), _) => self + .possible_library_funcs(ctx, sn.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Enum(en), _) => self + .possible_library_funcs(ctx, en.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Ty(ty), _) => self + .possible_library_funcs(ctx, ty.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Func(func_node), _) => self + .possible_library_funcs(ctx, func_node.0.into()) + .into_iter() + .collect::>(), + VarType::User(TypeNode::Unresolved(n), _) => { + match self.node(*n) { + Node::Unresolved(ident) => { + return Err(ExprErr::Unresolved(loc, format!("The type \"{}\" is currently unresolved but should have been resolved by now. This is a bug.", ident.name))) + } + _ => unreachable!() + } + } + }, + Node::Contract(_) => ContractNode::from(member_idx).funcs(self), + Node::Concrete(_) + | Node::Ty(_) + | Node::Struct(_) + | Node::Function(_) + | Node::Enum(_) + | Node::Builtin(_) => self + .possible_library_funcs(ctx, member_idx) + .into_iter() + .collect::>(), + e => { + return Err(ExprErr::MemberAccessNotFound( + loc, + format!("This type cannot have member functions: {:?}", e), + )) + } + }; + Ok(res) + } + + /// Perform member access for a variable type fn member_access_var_ty( &mut self, cvar: ContextVar, @@ -251,6 +259,7 @@ pub trait MemberAccess: } } + /// Perform a `TyNode` member access fn ty_member_access( &mut self, _member_idx: NodeIdx, diff --git a/crates/solc-expressions/src/member_access/mod.rs b/crates/solc-expressions/src/member_access/mod.rs index f86ca279..59766091 100644 --- a/crates/solc-expressions/src/member_access/mod.rs +++ b/crates/solc-expressions/src/member_access/mod.rs @@ -1,3 +1,6 @@ +//! This module consists of traits & blanket implementations that facilitate performing member access operations +//! like `MyStruct.field` or `MyContract.myFunc` + mod builtin_access; mod contract_access; mod enum_access; diff --git a/crates/solc-expressions/src/member_access/struct_access.rs b/crates/solc-expressions/src/member_access/struct_access.rs index a17eed14..41d252a5 100644 --- a/crates/solc-expressions/src/member_access/struct_access.rs +++ b/crates/solc-expressions/src/member_access/struct_access.rs @@ -12,9 +12,11 @@ impl StructAccess for T where T: LibraryAccess + AnalyzerBackend + Sized { } +/// Trait for performing member accesses on Structs pub trait StructAccess: LibraryAccess + AnalyzerBackend + Sized { + /// Perform member access on a struct fn struct_member_access( &mut self, member_idx: NodeIdx, diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index 01fc710e..e51f0b9e 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -19,6 +19,8 @@ use solang_parser::{ use std::cmp::Ordering; impl Require for T where T: Variable + BinOp + Sized + AnalyzerBackend {} + +/// Deals with require and assert statements, as well as adjusts bounds for variables pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { /// Inverts a comparator expression fn inverse_expr(&self, expr: Expression) -> Expression { @@ -582,6 +584,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { } } + /// Do matching on [`ExprRet`]s to actually perform the require statement evaluation fn handle_require_inner( &mut self, ctx: ContextNode, diff --git a/crates/solc-expressions/src/variable.rs b/crates/solc-expressions/src/variable.rs index ba91ba49..f4640931 100644 --- a/crates/solc-expressions/src/variable.rs +++ b/crates/solc-expressions/src/variable.rs @@ -8,9 +8,10 @@ use graph::{ use solang_parser::pt::{Expression, Identifier}; impl Variable for T where T: AnalyzerBackend + Sized {} - +/// Deals with variable retrieval pub trait Variable: AnalyzerBackend + Sized { #[tracing::instrument(level = "trace", skip_all)] + /// Get a variable based on an identifier fn variable( &mut self, ident: &Identifier, diff --git a/crates/solc-expressions/src/yul/mod.rs b/crates/solc-expressions/src/yul/mod.rs index a6eb5228..aa6f8d1a 100644 --- a/crates/solc-expressions/src/yul/mod.rs +++ b/crates/solc-expressions/src/yul/mod.rs @@ -1,380 +1,8 @@ -use crate::{ContextBuilder, ExprErr, ExprParser, IntoExprErr}; - -use graph::{ - nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, - AnalyzerBackend, ContextEdge, Edge, Node, VarType, -}; - -use solang_parser::{ - helpers::CodeLocation, - pt::{Expression, Loc, YulExpression, YulFor, YulStatement, YulSwitch}, -}; +//! Traits and blanket implementations for parsing with yul statements and expressions +mod yul_builder; mod yul_cond_op; -pub use yul_cond_op::*; - mod yul_funcs; -pub use yul_funcs::*; - -impl YulBuilder for T where - T: AnalyzerBackend + Sized + ExprParser -{ -} -pub trait YulBuilder: - AnalyzerBackend + Sized + ExprParser -{ - #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] - fn parse_ctx_yul_statement(&mut self, stmt: &YulStatement, ctx: ContextNode) - where - Self: Sized, - { - if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) { - return; - } - if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) { - if live_edges.is_empty() { - self.parse_ctx_yul_stmt_inner(stmt, ctx) - } else { - live_edges.iter().for_each(|fork_ctx| { - self.parse_ctx_yul_stmt_inner(stmt, *fork_ctx); - }); - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - fn parse_ctx_yul_stmt_inner(&mut self, stmt: &YulStatement, ctx: ContextNode) - where - Self: Sized, - { - use YulStatement::*; - // println!("ctx: {}, yul stmt: {:?}", ctx.path(self), stmt); - - let res = ctx - .pop_expr_latest(stmt.loc(), self) - .into_expr_err(stmt.loc()); - let _ = self.add_if_err(res); - - if ctx.is_killed(self).unwrap() { - return; - } - let ret = self.apply_to_edges(ctx, stmt.loc(), &|analyzer, ctx, _loc| { - match stmt { - Assign(loc, yul_exprs, yul_expr) => { - match yul_exprs - .iter() - .try_for_each(|expr| analyzer.parse_ctx_yul_expr(expr, ctx)) - { - Ok(()) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(lhs_side) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoLhs( - loc, - "No left hand side assignments in yul block".to_string(), - )); - }; - if matches!(lhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; - analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { - let Some(rhs_side) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No right hand side assignments in yul block".to_string(), - )); - }; - - if matches!(rhs_side, ExprRet::CtxKilled(_)) { - ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_assign_sides(ctx, loc, &lhs_side, &rhs_side) - }) - }), - Err(e) => Err(e), - } - } - VariableDeclaration(loc, yul_idents, maybe_yul_expr) => { - let nodes = yul_idents - .iter() - .map(|ident| { - let b_ty = analyzer.builtin_or_add(Builtin::Uint(256)); - let var = ContextVar { - loc: Some(ident.loc), - name: ident.id.name.clone(), - display_name: ident.id.name.clone(), - storage: None, - is_tmp: false, - tmp_of: None, - is_symbolic: true, - is_return: false, - ty: VarType::try_from_idx(analyzer, b_ty).unwrap(), - }; - let cvar = - ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); - ctx.add_var(cvar, analyzer).unwrap(); - analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); - analyzer.advance_var_in_ctx(cvar, *loc, ctx).unwrap() - }) - .collect::>(); - - if let Some(yul_expr) = maybe_yul_expr { - analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; - analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let Some(ret) = - ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? - else { - return Err(ExprErr::NoRhs( - loc, - "No right hand side assignments in yul block".to_string(), - )); - }; - - if matches!(ret, ExprRet::CtxKilled(_)) { - ctx.push_expr(ret, analyzer).into_expr_err(loc)?; - return Ok(()); - } - - analyzer.match_assign_yul(ctx, loc, &nodes, ret) - }) - } else { - Ok(()) - } - } - If(loc, yul_expr, yul_block) => { - analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - let ret = analyzer.yul_cond_op_stmt(loc, yul_expr, yul_block, ctx); - let _ = analyzer.add_if_err(ret); - Ok(()) - }) - } - For(YulFor { - loc, - init_block: _, - condition: _, - post_block: _, - execution_block: _, - }) => { - let sctx = - Context::new_subctx(ctx, None, *loc, None, None, false, analyzer, None) - .into_expr_err(*loc)?; - let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx))); - ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?; - analyzer.apply_to_edges(subctx, *loc, &|analyzer, subctx, loc| { - let vars = subctx.local_vars(analyzer).clone(); - vars.iter().for_each(|(name, var)| { - // widen to max range - if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) { - let inheritor_var = inheritor_var.latest_version(analyzer); - if let Some(r) = var - .underlying(analyzer) - .unwrap() - .ty - .default_range(analyzer) - .unwrap() - { - let new_inheritor_var = analyzer - .advance_var_in_ctx(inheritor_var, loc, ctx) - .unwrap(); - let res = new_inheritor_var - .set_range_min(analyzer, r.min) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - let res = new_inheritor_var - .set_range_max(analyzer, r.max) - .into_expr_err(loc); - let _ = analyzer.add_if_err(res); - } - } - }); - Ok(()) - }) - } - Switch(YulSwitch { - loc, - condition, - cases, - default, - }) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { - analyzer.yul_switch_stmt( - loc, - condition.clone(), - cases.to_vec(), - default.clone(), - ctx, - ) - }), - Leave(loc) => Err(ExprErr::Todo( - *loc, - "Yul `leave` statements are not currently supported".to_string(), - )), - Break(loc) => Err(ExprErr::Todo( - *loc, - "Yul `break` statements are not currently supported".to_string(), - )), - Continue(loc) => Err(ExprErr::Todo( - *loc, - "Yul `continue` statements are not currently supported".to_string(), - )), - Block(yul_block) => { - yul_block - .statements - .iter() - .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(stmt, ctx)); - Ok(()) - } - FunctionDefinition(yul_func_def) => Err(ExprErr::Todo( - yul_func_def.loc(), - "Yul `function` defintions are not currently supported".to_string(), - )), - FunctionCall(yul_func_call) => analyzer.yul_func_call(yul_func_call, ctx), - Error(loc) => Err(ExprErr::ParseError( - *loc, - "Could not parse this yul statement".to_string(), - )), - } - }); - let _ = self.add_if_err(ret); - } - - #[tracing::instrument(level = "trace", skip_all)] - fn parse_ctx_yul_expr( - &mut self, - expr: &YulExpression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - tracing::trace!("Parsing yul expression: {expr:?}"); - - let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; - if edges.is_empty() { - self.parse_ctx_yul_expr_inner(expr, ctx) - } else { - edges - .iter() - .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(expr, *fork_ctx))?; - Ok(()) - } - } - - fn parse_ctx_yul_expr_inner( - &mut self, - expr: &YulExpression, - ctx: ContextNode, - ) -> Result<(), ExprErr> { - use YulExpression::*; - match expr { - BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b), - NumberLiteral(loc, int, expr, _unit) => { - self.number_literal(ctx, *loc, int, expr, false) - } - HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), - HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]), - StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string), - Variable(ident) => { - self.variable(ident, ctx, None)?; - self.apply_to_edges(ctx, ident.loc, &|analyzer, edge_ctx, loc| { - if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { - if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?) - .is_memory(analyzer) - .into_expr_err(loc)? - { - // its a memory based variable, push a uint instead - let b = Builtin::Uint(256); - let var = ContextVar::new_from_builtin( - loc, - analyzer.builtin_or_add(b).into(), - analyzer, - ) - .into_expr_err(loc)?; - let node = analyzer.add_node(Node::ContextVar(var)); - edge_ctx - .push_expr(ExprRet::Single(node), analyzer) - .into_expr_err(loc) - } else { - edge_ctx.push_expr(ret, analyzer).into_expr_err(loc) - } - } else { - Err(ExprErr::Unresolved( - ident.loc, - format!("Could not find variable with name: {}", ident.name), - )) - } - }) - } - FunctionCall(yul_func_call) => self.yul_func_call(yul_func_call, ctx), - SuffixAccess(_loc, _yul_member_expr, _ident) => Err(ExprErr::Todo( - expr.loc(), - "Yul member access not yet supported".to_string(), - )), - } - } - - fn match_assign_yul( - &mut self, - _ctx: ContextNode, - loc: Loc, - nodes: &[ContextVarNode], - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret { - s @ ExprRet::Single(_) | s @ ExprRet::SingleLiteral(_) => { - self.match_assign_yul_inner(loc, &nodes[0], s)?; - } - ExprRet::Multi(inner) => { - if inner.len() == nodes.len() { - inner - .into_iter() - .zip(nodes.iter()) - .map(|(ret, node)| self.match_assign_yul_inner(loc, node, ret)) - .collect::, ExprErr>>()?; - } else { - return Err(ExprErr::Todo( - loc, - format!("Differing number of assignees and assignors in yul expression, assignors: {}, assignees: {}", nodes.len(), inner.len()), - )); - }; - } - ExprRet::CtxKilled(_kind) => {} - ExprRet::Null => {} - } - - Ok(()) - } - - fn match_assign_yul_inner( - &mut self, - loc: Loc, - node: &ContextVarNode, - ret: ExprRet, - ) -> Result<(), ExprErr> { - match ret.flatten() { - ExprRet::Single(idx) | ExprRet::SingleLiteral(idx) => { - let assign = ContextVarNode::from(idx); - let assign_ty = assign.underlying(self).into_expr_err(loc)?.ty.clone(); - if assign_ty.is_dyn(self).into_expr_err(loc)? { - let b_ty = self.builtin_or_add(Builtin::Bytes(32)); - node.underlying_mut(self).into_expr_err(loc)?.ty = - VarType::try_from_idx(self, b_ty).unwrap(); - } else { - node.underlying_mut(self).into_expr_err(loc)?.ty = assign_ty; - } - } - ExprRet::Multi(_inner) => { - return Err(ExprErr::Todo( - loc, - "Multi in single assignment yul expression is unhandled".to_string(), - )) - } - ExprRet::CtxKilled(..) => {} - ExprRet::Null => {} - } - Ok(()) - } -} +pub use yul_builder::*; +pub use yul_cond_op::*; +pub use yul_funcs::*; \ No newline at end of file diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs new file mode 100644 index 00000000..652ed9b2 --- /dev/null +++ b/crates/solc-expressions/src/yul/yul_builder.rs @@ -0,0 +1,388 @@ +//! Trait and blanket implementation for parsing yul-based statements and expressions + +use crate::{ + ContextBuilder, ExprErr, ExprParser, IntoExprErr, + yul::YulFuncCaller, + yul::YulCondOp +}; + +use graph::{ + nodes::{Builtin, Context, ContextNode, ContextVar, ContextVarNode, ExprRet}, + AnalyzerBackend, ContextEdge, Edge, Node, VarType, +}; + +use solang_parser::{ + helpers::CodeLocation, + pt::{Expression, Loc, YulExpression, YulFor, YulStatement, YulSwitch}, +}; + + +impl YulBuilder for T where + T: AnalyzerBackend + Sized + ExprParser +{ +} +/// Trait that processes Yul statements and expressions +pub trait YulBuilder: + AnalyzerBackend + Sized + ExprParser +{ + #[tracing::instrument(level = "trace", skip_all, fields(ctx = %ctx.path(self)))] + /// Parse a yul statement + fn parse_ctx_yul_statement(&mut self, stmt: &YulStatement, ctx: ContextNode) + where + Self: Sized, + { + if let Some(true) = self.add_if_err(ctx.is_ended(self).into_expr_err(stmt.loc())) { + return; + } + if let Some(live_edges) = self.add_if_err(ctx.live_edges(self).into_expr_err(stmt.loc())) { + if live_edges.is_empty() { + self.parse_ctx_yul_stmt_inner(stmt, ctx) + } else { + live_edges.iter().for_each(|fork_ctx| { + self.parse_ctx_yul_stmt_inner(stmt, *fork_ctx); + }); + } + } + } + + #[tracing::instrument(level = "trace", skip_all)] + /// After doing some setup in `parse_ctx_yul_statement`, actually parse a yul statement + fn parse_ctx_yul_stmt_inner(&mut self, stmt: &YulStatement, ctx: ContextNode) + where + Self: Sized, + { + use YulStatement::*; + // println!("ctx: {}, yul stmt: {:?}", ctx.path(self), stmt); + + let res = ctx + .pop_expr_latest(stmt.loc(), self) + .into_expr_err(stmt.loc()); + let _ = self.add_if_err(res); + + if ctx.is_killed(self).unwrap() { + return; + } + let ret = self.apply_to_edges(ctx, stmt.loc(), &|analyzer, ctx, _loc| { + match stmt { + Assign(loc, yul_exprs, yul_expr) => { + match yul_exprs + .iter() + .try_for_each(|expr| analyzer.parse_ctx_yul_expr(expr, ctx)) + { + Ok(()) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let Some(lhs_side) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoLhs( + loc, + "No left hand side assignments in yul block".to_string(), + )); + }; + if matches!(lhs_side, ExprRet::CtxKilled(_)) { + ctx.push_expr(lhs_side, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; + analyzer.apply_to_edges(ctx, loc, &|analyzer, ctx, loc| { + let Some(rhs_side) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "No right hand side assignments in yul block".to_string(), + )); + }; + + if matches!(rhs_side, ExprRet::CtxKilled(_)) { + ctx.push_expr(rhs_side, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + analyzer.match_assign_sides(ctx, loc, &lhs_side, &rhs_side) + }) + }), + Err(e) => Err(e), + } + } + VariableDeclaration(loc, yul_idents, maybe_yul_expr) => { + let nodes = yul_idents + .iter() + .map(|ident| { + let b_ty = analyzer.builtin_or_add(Builtin::Uint(256)); + let var = ContextVar { + loc: Some(ident.loc), + name: ident.id.name.clone(), + display_name: ident.id.name.clone(), + storage: None, + is_tmp: false, + tmp_of: None, + is_symbolic: true, + is_return: false, + ty: VarType::try_from_idx(analyzer, b_ty).unwrap(), + }; + let cvar = + ContextVarNode::from(analyzer.add_node(Node::ContextVar(var))); + ctx.add_var(cvar, analyzer).unwrap(); + analyzer.add_edge(cvar, ctx, Edge::Context(ContextEdge::Variable)); + analyzer.advance_var_in_ctx(cvar, *loc, ctx).unwrap() + }) + .collect::>(); + + if let Some(yul_expr) = maybe_yul_expr { + analyzer.parse_ctx_yul_expr(yul_expr, ctx)?; + analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let Some(ret) = + ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? + else { + return Err(ExprErr::NoRhs( + loc, + "No right hand side assignments in yul block".to_string(), + )); + }; + + if matches!(ret, ExprRet::CtxKilled(_)) { + ctx.push_expr(ret, analyzer).into_expr_err(loc)?; + return Ok(()); + } + + analyzer.match_assign_yul(ctx, loc, &nodes, ret) + }) + } else { + Ok(()) + } + } + If(loc, yul_expr, yul_block) => { + analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + let ret = analyzer.yul_cond_op_stmt(loc, yul_expr, yul_block, ctx); + let _ = analyzer.add_if_err(ret); + Ok(()) + }) + } + For(YulFor { + loc, + init_block: _, + condition: _, + post_block: _, + execution_block: _, + }) => { + let sctx = + Context::new_subctx(ctx, None, *loc, None, None, false, analyzer, None) + .into_expr_err(*loc)?; + let subctx = ContextNode::from(analyzer.add_node(Node::Context(sctx))); + ctx.set_child_call(subctx, analyzer).into_expr_err(*loc)?; + analyzer.apply_to_edges(subctx, *loc, &|analyzer, subctx, loc| { + let vars = subctx.local_vars(analyzer).clone(); + vars.iter().for_each(|(name, var)| { + // widen to max range + if let Some(inheritor_var) = ctx.var_by_name(analyzer, name) { + let inheritor_var = inheritor_var.latest_version(analyzer); + if let Some(r) = var + .underlying(analyzer) + .unwrap() + .ty + .default_range(analyzer) + .unwrap() + { + let new_inheritor_var = analyzer + .advance_var_in_ctx(inheritor_var, loc, ctx) + .unwrap(); + let res = new_inheritor_var + .set_range_min(analyzer, r.min) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + let res = new_inheritor_var + .set_range_max(analyzer, r.max) + .into_expr_err(loc); + let _ = analyzer.add_if_err(res); + } + } + }); + Ok(()) + }) + } + Switch(YulSwitch { + loc, + condition, + cases, + default, + }) => analyzer.apply_to_edges(ctx, *loc, &|analyzer, ctx, loc| { + analyzer.yul_switch_stmt( + loc, + condition.clone(), + cases.to_vec(), + default.clone(), + ctx, + ) + }), + Leave(loc) => Err(ExprErr::Todo( + *loc, + "Yul `leave` statements are not currently supported".to_string(), + )), + Break(loc) => Err(ExprErr::Todo( + *loc, + "Yul `break` statements are not currently supported".to_string(), + )), + Continue(loc) => Err(ExprErr::Todo( + *loc, + "Yul `continue` statements are not currently supported".to_string(), + )), + Block(yul_block) => { + yul_block + .statements + .iter() + .for_each(|stmt| analyzer.parse_ctx_yul_stmt_inner(stmt, ctx)); + Ok(()) + } + FunctionDefinition(yul_func_def) => Err(ExprErr::Todo( + yul_func_def.loc(), + "Yul `function` defintions are not currently supported".to_string(), + )), + FunctionCall(yul_func_call) => analyzer.yul_func_call(yul_func_call, ctx), + Error(loc) => Err(ExprErr::ParseError( + *loc, + "Could not parse this yul statement".to_string(), + )), + } + }); + let _ = self.add_if_err(ret); + } + + #[tracing::instrument(level = "trace", skip_all)] + /// Parse a yul expression + fn parse_ctx_yul_expr( + &mut self, + expr: &YulExpression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + tracing::trace!("Parsing yul expression: {expr:?}"); + + let edges = ctx.live_edges(self).into_expr_err(expr.loc())?; + if edges.is_empty() { + self.parse_ctx_yul_expr_inner(expr, ctx) + } else { + edges + .iter() + .try_for_each(|fork_ctx| self.parse_ctx_yul_expr(expr, *fork_ctx))?; + Ok(()) + } + } + + /// After performing some setup in `parse_ctx_yul_expr`, actually parse the yul expression + fn parse_ctx_yul_expr_inner( + &mut self, + expr: &YulExpression, + ctx: ContextNode, + ) -> Result<(), ExprErr> { + use YulExpression::*; + match expr { + BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b), + NumberLiteral(loc, int, expr, _unit) => { + self.number_literal(ctx, *loc, int, expr, false) + } + HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), + HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]), + StringLiteral(lit, _) => self.string_literal(ctx, lit.loc, &lit.string), + Variable(ident) => { + self.variable(ident, ctx, None)?; + self.apply_to_edges(ctx, ident.loc, &|analyzer, edge_ctx, loc| { + if let Some(ret) = edge_ctx.pop_expr_latest(loc, analyzer).into_expr_err(loc)? { + if ContextVarNode::from(ret.expect_single().into_expr_err(loc)?) + .is_memory(analyzer) + .into_expr_err(loc)? + { + // its a memory based variable, push a uint instead + let b = Builtin::Uint(256); + let var = ContextVar::new_from_builtin( + loc, + analyzer.builtin_or_add(b).into(), + analyzer, + ) + .into_expr_err(loc)?; + let node = analyzer.add_node(Node::ContextVar(var)); + edge_ctx + .push_expr(ExprRet::Single(node), analyzer) + .into_expr_err(loc) + } else { + edge_ctx.push_expr(ret, analyzer).into_expr_err(loc) + } + } else { + Err(ExprErr::Unresolved( + ident.loc, + format!("Could not find variable with name: {}", ident.name), + )) + } + }) + } + FunctionCall(yul_func_call) => self.yul_func_call(yul_func_call, ctx), + SuffixAccess(_loc, _yul_member_expr, _ident) => Err(ExprErr::Todo( + expr.loc(), + "Yul member access not yet supported".to_string(), + )), + } + } + + /// Match [`ExprRet`] from the sides of an `YulAssign` to perform the assignment + fn match_assign_yul( + &mut self, + _ctx: ContextNode, + loc: Loc, + nodes: &[ContextVarNode], + ret: ExprRet, + ) -> Result<(), ExprErr> { + match ret { + s @ ExprRet::Single(_) | s @ ExprRet::SingleLiteral(_) => { + self.match_assign_yul_inner(loc, &nodes[0], s)?; + } + ExprRet::Multi(inner) => { + if inner.len() == nodes.len() { + inner + .into_iter() + .zip(nodes.iter()) + .map(|(ret, node)| self.match_assign_yul_inner(loc, node, ret)) + .collect::, ExprErr>>()?; + } else { + return Err(ExprErr::Todo( + loc, + format!("Differing number of assignees and assignors in yul expression, assignors: {}, assignees: {}", nodes.len(), inner.len()), + )); + }; + } + ExprRet::CtxKilled(_kind) => {} + ExprRet::Null => {} + } + + Ok(()) + } + + /// Perform the actual yul assignment + fn match_assign_yul_inner( + &mut self, + loc: Loc, + node: &ContextVarNode, + ret: ExprRet, + ) -> Result<(), ExprErr> { + match ret.flatten() { + ExprRet::Single(idx) | ExprRet::SingleLiteral(idx) => { + let assign = ContextVarNode::from(idx); + let assign_ty = assign.underlying(self).into_expr_err(loc)?.ty.clone(); + if assign_ty.is_dyn(self).into_expr_err(loc)? { + let b_ty = self.builtin_or_add(Builtin::Bytes(32)); + node.underlying_mut(self).into_expr_err(loc)?.ty = + VarType::try_from_idx(self, b_ty).unwrap(); + } else { + node.underlying_mut(self).into_expr_err(loc)?.ty = assign_ty; + } + } + ExprRet::Multi(_inner) => { + return Err(ExprErr::Todo( + loc, + "Multi in single assignment yul expression is unhandled".to_string(), + )) + } + ExprRet::CtxKilled(..) => {} + ExprRet::Null => {} + } + Ok(()) + } +} diff --git a/crates/solc-expressions/src/yul/yul_cond_op.rs b/crates/solc-expressions/src/yul/yul_cond_op.rs index 8faf7127..7f5f74a2 100644 --- a/crates/solc-expressions/src/yul/yul_cond_op.rs +++ b/crates/solc-expressions/src/yul/yul_cond_op.rs @@ -1,4 +1,4 @@ -use crate::{require::Require, ContextBuilder, ExprErr, IntoExprErr, YulBuilder}; +use crate::{require::Require, ContextBuilder, ExprErr, IntoExprErr, yul::YulBuilder}; use graph::{ elem::*, @@ -17,10 +17,13 @@ impl YulCondOp for T where T: AnalyzerBackend + Require + Sized { } + +/// Trait for dealing with conditional operations in yul pub trait YulCondOp: AnalyzerBackend + Require + Sized { #[tracing::instrument(level = "trace", skip_all)] + /// Handle a yul conditional operation statement fn yul_cond_op_stmt( &mut self, loc: Loc, @@ -98,6 +101,7 @@ pub trait YulCondOp: } #[tracing::instrument(level = "trace", skip_all)] + /// Handle a yul if-else fn yul_if_else( &mut self, loc: Loc, @@ -172,6 +176,7 @@ pub trait YulCondOp: }) } + /// Helper for the `true` evaluation of a yul conditional fn match_yul_true( &mut self, ctx: ContextNode, @@ -214,6 +219,7 @@ pub trait YulCondOp: Ok(()) } + /// Helper for the `false` evaluation of a yul conditional fn match_yul_false( &mut self, ctx: ContextNode, @@ -258,6 +264,7 @@ pub trait YulCondOp: } #[tracing::instrument(level = "trace", skip_all)] + /// Handle a yul swithc statement by converting it into an if-else chain fn yul_switch_stmt( &mut self, loc: Loc, @@ -274,6 +281,7 @@ pub trait YulCondOp: } #[derive(Clone, Debug)] +/// A yul-based if-else chain, which represents a switch statement pub struct IfElseChain { pub if_expr: YulExpression, pub true_stmt: YulStatement, @@ -281,6 +289,7 @@ pub struct IfElseChain { } #[derive(Clone, Debug)] +/// Wrapper over a switch statement that denotes either another else statement or the default case pub enum ElseOrDefault { Else(Box), Default(YulStatement), diff --git a/crates/solc-expressions/src/yul/yul_funcs.rs b/crates/solc-expressions/src/yul/yul_funcs.rs index 0d28f98e..922a8f89 100644 --- a/crates/solc-expressions/src/yul/yul_funcs.rs +++ b/crates/solc-expressions/src/yul/yul_funcs.rs @@ -1,4 +1,4 @@ -use crate::{BinOp, Cmp, ContextBuilder, Env, ExprErr, IntoExprErr, YulBuilder}; +use crate::{BinOp, Cmp, ContextBuilder, Env, ExprErr, IntoExprErr, yul::YulBuilder}; use graph::{ elem::*,