diff --git a/crates/graph/src/nodes/concrete.rs b/crates/graph/src/nodes/concrete.rs index 7006b569..f048c3f5 100644 --- a/crates/graph/src/nodes/concrete.rs +++ b/crates/graph/src/nodes/concrete.rs @@ -28,7 +28,7 @@ impl ConcreteNode { /// Creates a version of this concrete that is max size pub fn max_size(&self, analyzer: &mut impl AnalyzerBackend) -> Result { let c = self.underlying(analyzer)?.max_size(); - Ok(analyzer.add_node(Node::Concrete(c)).into()) + Ok(analyzer.add_node(c).into()) } /// Gets the internal type of the dynamic that backs this. Panics if this is not a dynamic concrete diff --git a/crates/graph/src/nodes/context/underlying.rs b/crates/graph/src/nodes/context/underlying.rs index 86ca74c4..29ed063d 100644 --- a/crates/graph/src/nodes/context/underlying.rs +++ b/crates/graph/src/nodes/context/underlying.rs @@ -7,7 +7,7 @@ use crate::{ AnalyzerBackend, ContextEdge, Edge, Node, }; -use shared::GraphError; +use shared::{GraphError, NodeIdx}; use solang_parser::pt::Loc; use std::collections::BTreeSet; @@ -408,7 +408,11 @@ impl Context { ))); } - let parent_fn = subctx_kind.parent_ctx().associated_fn(analyzer)?; + let parent_fn = if let Some(cont) = subctx_kind.continuation_of() { + cont.associated_fn(analyzer)? + } else { + subctx_kind.parent_ctx().associated_fn(analyzer)? + }; let modifier_state = if let Some(mstate) = modifier_state { Some(mstate) @@ -466,6 +470,37 @@ impl Context { Ok(ctx_node) } + pub fn add_fork_subctxs( + analyzer: &mut impl AnalyzerBackend, + parent_ctx: ContextNode, + loc: Loc, + ) -> Result<(ContextNode, ContextNode), GraphError> { + let true_subctx_kind = SubContextKind::new_fork(parent_ctx, true); + let true_subctx = Context::add_subctx(true_subctx_kind, loc, analyzer, None)?; + + let false_subctx_kind = SubContextKind::new_fork(parent_ctx, false); + let false_subctx = Context::add_subctx(false_subctx_kind, loc, analyzer, None)?; + + parent_ctx.set_child_fork(true_subctx, false_subctx, analyzer)?; + let ctx_fork = analyzer.add_node(Node::ContextFork); + analyzer.add_edge( + ctx_fork, + parent_ctx, + Edge::Context(ContextEdge::ContextFork), + ); + analyzer.add_edge( + NodeIdx::from(true_subctx.0), + ctx_fork, + Edge::Context(ContextEdge::Subcontext), + ); + analyzer.add_edge( + NodeIdx::from(false_subctx.0), + ctx_fork, + Edge::Context(ContextEdge::Subcontext), + ); + Ok((true_subctx, false_subctx)) + } + pub fn new_loop_subctx( parent_ctx: ContextNode, loc: Loc, diff --git a/crates/graph/src/nodes/context/variables.rs b/crates/graph/src/nodes/context/variables.rs index b81b5a32..b515c24e 100644 --- a/crates/graph/src/nodes/context/variables.rs +++ b/crates/graph/src/nodes/context/variables.rs @@ -24,13 +24,22 @@ impl ContextNode { } /// Debug print the stack - pub fn debug_expr_stack(&self, analyzer: &impl GraphBackend) -> Result<(), GraphError> { + pub fn debug_expr_stack_str(&self, analyzer: &impl GraphBackend) -> Result { let underlying_mut = self.underlying(analyzer)?; - underlying_mut - .expr_ret_stack - .iter() - .enumerate() - .for_each(|(i, elem)| println!("{i}. {}", elem.debug_str(analyzer))); + Ok(format!( + "[\n\t{}\n]", + underlying_mut + .expr_ret_stack + .iter() + .enumerate() + .map(|(i, elem)| format!("{i}. {}", elem.debug_str(analyzer))) + .collect::>() + .join("\n\t") + )) + } + + pub fn debug_expr_stack(&self, analyzer: &impl GraphBackend) -> Result<(), GraphError> { + println!("{}", self.debug_expr_stack_str(analyzer)?); Ok(()) } diff --git a/crates/graph/src/nodes/msg.rs b/crates/graph/src/nodes/msg.rs index a142e54c..550279b4 100644 --- a/crates/graph/src/nodes/msg.rs +++ b/crates/graph/src/nodes/msg.rs @@ -72,7 +72,7 @@ impl Msg { "data" => { if let Some(d) = self.data.clone() { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "msg.data".to_string()) + (analyzer.add_node(c), "msg.data".to_string()) } else { let b = Builtin::DynamicBytes; let node = analyzer.builtin_or_add(b); @@ -87,10 +87,7 @@ impl Msg { "sender" => { if let Some(d) = self.sender { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "msg.sender".to_string(), - ) + (analyzer.add_node(c), "msg.sender".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -104,7 +101,7 @@ impl Msg { "sig" => { if let Some(d) = self.sig { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "msg.sig".to_string()) + (analyzer.add_node(c), "msg.sig".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Bytes(4)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -118,10 +115,7 @@ impl Msg { "value" => { if let Some(d) = self.value { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "msg.value".to_string(), - ) + (analyzer.add_node(c), "msg.value".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(256)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -135,10 +129,7 @@ impl Msg { "origin" => { if let Some(d) = self.origin { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "tx.origin".to_string(), - ) + (analyzer.add_node(c), "tx.origin".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Address); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -152,10 +143,7 @@ impl Msg { "gasprice" => { if let Some(d) = self.gasprice { let c = Concrete::from(d); - ( - analyzer.add_node(Node::Concrete(c)), - "tx.gasprice".to_string(), - ) + (analyzer.add_node(c), "tx.gasprice".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; @@ -169,7 +157,7 @@ impl Msg { "gaslimit" => { if let Some(d) = self.gaslimit { let c = Concrete::from(d); - (analyzer.add_node(Node::Concrete(c)), "".to_string()) + (analyzer.add_node(c), "".to_string()) } else { let node = analyzer.builtin_or_add(Builtin::Uint(64)); let mut var = ContextVar::new_from_builtin(loc, node.into(), analyzer)?; diff --git a/crates/graph/src/nodes/var_ty.rs b/crates/graph/src/nodes/var_ty.rs index 42a4e948..3bd9c5ea 100644 --- a/crates/graph/src/nodes/var_ty.rs +++ b/crates/graph/src/nodes/var_ty.rs @@ -58,18 +58,14 @@ impl VarNode { self.underlying(analyzer)?.name.as_ref().unwrap().name ); let init = analyzer.parse_expr(arena, &expr, Some(parent)); + let underlying = self.underlying(analyzer)?; + let target_ty = VarType::try_from_idx(analyzer, underlying.ty).unwrap(); + let initer_ty = ContextVarNode::from(init).ty(analyzer)?.clone(); - let underlying = self.underlying(analyzer)?.clone(); let mut set = false; - if let Some(ty) = VarType::try_from_idx(analyzer, underlying.ty) { - if let Some(initer) = VarType::try_from_idx(analyzer, init) { - if let Some(initer) = initer.try_cast(&ty, analyzer)? { - if let Some(conc_idx) = initer.builtin_to_concrete_idx(analyzer, arena)? { - set = true; - self.underlying_mut(analyzer)?.initializer = Some(conc_idx); - } - } - } + if let Some(initer) = initer_ty.try_cast(&target_ty, analyzer)? { + set = true; + self.underlying_mut(analyzer)?.initializer = Some(initer.ty_idx()); } if !set { diff --git a/crates/graph/src/var_type.rs b/crates/graph/src/var_type.rs index 72ed4c8d..81d1be68 100644 --- a/crates/graph/src/var_type.rs +++ b/crates/graph/src/var_type.rs @@ -296,7 +296,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let b = to_bn.underlying(analyzer)?; if let Some(casted) = c.cast(b.clone()) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -306,7 +306,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let to_c = to_c.underlying(analyzer)?; if let Some(casted) = c.cast_from(to_c) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -334,7 +334,7 @@ impl VarType { let Some(conc) = min.val.cast(builtin.clone()) else { return Ok(None); }; - let conc_idx = analyzer.add_node(Node::Concrete(conc)); + let conc_idx = analyzer.add_node(conc); Ok(Some(conc_idx)) } else { Ok(None) @@ -380,7 +380,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let b = to_bn.underlying(analyzer)?; if let Some(casted) = c.literal_cast(b.clone()) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -390,7 +390,7 @@ impl VarType { let c = from_c.underlying(analyzer)?.clone(); let to_c = to_c.underlying(analyzer)?; if let Some(casted) = c.literal_cast_from(to_c) { - let node = analyzer.add_node(Node::Concrete(casted)); + let node = analyzer.add_node(casted); Ok(Some(Self::Concrete(node.into()))) } else { Ok(None) @@ -588,7 +588,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = val.0[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -616,7 +616,7 @@ impl VarType { // if let Some(idx) = val.0.node_idx() { // return Ok(idx.into()); // } else if let Some(c) = val.0.concrete() { - // let cnode = analyzer.add_node(Node::Concrete(c)); + // let cnode = analyzer.add_node(c); // return Ok(cnode.into()); // } // } @@ -640,7 +640,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = val.0[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -649,7 +649,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = elems[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } @@ -658,7 +658,7 @@ impl VarType { // let mut h = H256::default(); // h.0[0] = st.as_bytes()[idx.low_u32() as usize]; // let ret_val = Concrete::Bytes(1, h); - // let node = analyzer.add_node(Node::Concrete(ret_val)); + // let node = analyzer.add_node(ret_val); // return Ok(Some(node)); // } // } diff --git a/crates/pyrometer/src/analyzer.rs b/crates/pyrometer/src/analyzer.rs index 6e1b3e7f..dec1135f 100644 --- a/crates/pyrometer/src/analyzer.rs +++ b/crates/pyrometer/src/analyzer.rs @@ -364,7 +364,7 @@ impl Analyzer { expr: &Expression, parent: Option, ) -> Option { - tracing::trace!("Parsing required compile-time evaluation"); + tracing::trace!("Parsing required compile-time evaluation: {expr:?}, {parent:?}"); let ctx = if let Some(parent) = parent { let pf = Function { diff --git a/crates/pyrometer/src/analyzer_backend.rs b/crates/pyrometer/src/analyzer_backend.rs index abbe9e49..8cf7d4fe 100644 --- a/crates/pyrometer/src/analyzer_backend.rs +++ b/crates/pyrometer/src/analyzer_backend.rs @@ -561,7 +561,6 @@ impl AnalyzerLike for Analyzer { type ExprFlag = ExprFlag; fn push_expr(&mut self, flat: FlatExpr) { - tracing::trace!("pushing expression: {flat:?}"); self.flattened.push(flat); } diff --git a/crates/shared/src/flattened.rs b/crates/shared/src/flattened.rs index 66c454e2..b36e39d9 100644 --- a/crates/shared/src/flattened.rs +++ b/crates/shared/src/flattened.rs @@ -1,5 +1,5 @@ -use crate::StorageLocation; -use solang_parser::pt::{Expression, Loc, NamedArgument, Type}; +use crate::{FlatYulExpr, StorageLocation}; +use solang_parser::pt::{Expression, Loc, NamedArgument, Type, YulExpression}; #[derive(Debug, Clone, Copy)] pub enum ExprFlag { @@ -35,7 +35,6 @@ pub enum FlatExpr { ArrayTy(Loc), ArrayIndexAccess(Loc), ArraySlice(Loc), - Parenthesis(Loc), MemberAccess(Loc, &'static str), FunctionCall(Loc, usize), FunctionCallBlock(Loc), @@ -105,9 +104,23 @@ pub enum FlatExpr { AddressLiteral(Loc, &'static str), Variable(Loc, &'static str), ArrayLiteral(Loc), + + YulExpr(FlatYulExpr), } impl FlatExpr { + pub fn try_inv_cmp(&self) -> Option { + use FlatExpr::*; + Some(match self { + Less(loc) => MoreEqual(*loc), + More(loc) => LessEqual(*loc), + LessEqual(loc) => More(*loc), + MoreEqual(loc) => Less(*loc), + Equal(loc) => NotEqual(*loc), + NotEqual(loc) => Equal(*loc), + _ => Not(self.try_loc()?), + }) + } pub fn try_loc(&self) -> Option { use FlatExpr::*; match self { @@ -123,7 +136,6 @@ impl FlatExpr { | ArrayTy(loc, ..) | ArrayIndexAccess(loc, ..) | ArraySlice(loc, ..) - | Parenthesis(loc, ..) | MemberAccess(loc, ..) | FunctionCall(loc, ..) | FunctionCallBlock(loc, ..) @@ -181,8 +193,15 @@ impl FlatExpr { | Variable(loc, ..) | Requirement(loc, ..) | Super(loc, ..) + | YulExpr(FlatYulExpr::YulVariable(loc, ..)) + | YulExpr(FlatYulExpr::YulFuncCall(loc, ..)) + | YulExpr(FlatYulExpr::YulSuffixAccess(loc, ..)) | ArrayLiteral(loc, ..) => Some(*loc), - FunctionCallName(..) => None, + + FunctionCallName(..) + | YulExpr(FlatYulExpr::YulStartBlock) + | YulExpr(FlatYulExpr::YulEndBlock) + | YulExpr(FlatYulExpr::YulFunctionCallName(..)) => None, } } } @@ -197,6 +216,37 @@ impl From<&NamedArgument> for FlatExpr { } } +impl TryFrom<&YulExpression> for FlatExpr { + type Error = (); + fn try_from(expr: &YulExpression) -> Result { + use YulExpression::*; + let res = match expr { + BoolLiteral(loc, b, _unimpled_type) => FlatExpr::BoolLiteral(*loc, *b), + NumberLiteral(loc, int, exp, _unimpled_type) => FlatExpr::NumberLiteral( + *loc, + Box::leak(int.clone().into_boxed_str()), + Box::leak(exp.clone().into_boxed_str()), + None, + ), + HexNumberLiteral(loc, b, _unimpled_type) => { + FlatExpr::HexNumberLiteral(*loc, Box::leak(b.clone().into_boxed_str()), None) + } + HexStringLiteral(hexes, _unimpled_type) => { + let final_str = hexes.hex.clone(); + let loc = hexes.loc; + FlatExpr::HexLiteral(loc, string_to_static(final_str)) + } + StringLiteral(lits, _unimpled_type) => { + let final_str = lits.string.clone(); + let loc = lits.loc; + FlatExpr::StringLiteral(loc, string_to_static(final_str)) + } + other => FlatExpr::YulExpr(FlatYulExpr::try_from(other)?), + }; + Ok(res) + } +} + impl TryFrom<&Expression> for FlatExpr { type Error = (); fn try_from(expr: &Expression) -> Result { @@ -208,7 +258,6 @@ impl TryFrom<&Expression> for FlatExpr { ArraySubscript(loc, _, None) => FlatExpr::ArrayTy(*loc), ArraySubscript(loc, _, Some(_)) => FlatExpr::ArrayIndexAccess(*loc), ArraySlice(loc, ..) => FlatExpr::ArraySlice(*loc), - Parenthesis(loc, ..) => FlatExpr::Parenthesis(*loc), MemberAccess(loc, _, name) => { FlatExpr::MemberAccess(*loc, string_to_static(name.name.clone())) } @@ -222,12 +271,8 @@ impl TryFrom<&Expression> for FlatExpr { PreIncrement(loc, ..) => FlatExpr::PreIncrement(*loc), PreDecrement(loc, ..) => FlatExpr::PreDecrement(*loc), UnaryPlus(loc, ..) => FlatExpr::UnaryPlus(*loc), - // Power(loc, ..) => FlatExpr::Power(*loc), - // Multiply(loc, ..) => FlatExpr::Multiply(*loc), - // Divide(loc, ..) => FlatExpr::Divide(*loc), - // Modulo(loc, ..) => FlatExpr::Modulo(*loc), - // Add(loc, ..) => FlatExpr::Add(*loc), - // Subtract(loc, ..) => FlatExpr::Subtract(*loc), + Parenthesis(_, expr) => FlatExpr::try_from(&**expr)?, + Modulo(loc, _, _) => FlatExpr::Modulo(*loc), ShiftLeft(loc, ..) => FlatExpr::ShiftLeft(*loc), ShiftRight(loc, ..) => FlatExpr::ShiftRight(*loc), BitwiseAnd(loc, ..) => FlatExpr::BitwiseAnd(*loc), @@ -249,10 +294,6 @@ impl TryFrom<&Expression> for FlatExpr { AssignXor(loc, ..) => FlatExpr::AssignXor(*loc), AssignShiftLeft(loc, ..) => FlatExpr::AssignShiftLeft(*loc), AssignShiftRight(loc, ..) => FlatExpr::AssignShiftRight(*loc), - // AssignAdd(loc, ..) => FlatExpr::AssignAdd(*loc), - // AssignSubtract(loc, ..) => FlatExpr::AssignSubtract(*loc), - // AssignMultiply(loc, ..) => FlatExpr::AssignMultiply(*loc), - // AssignDivide(loc, ..) => FlatExpr::AssignDivide(*loc), AssignModulo(loc, ..) => FlatExpr::AssignModulo(*loc), Type(loc, ty) => { let ty_box = Box::new(ty.clone()); @@ -335,7 +376,16 @@ impl TryFrom<&Expression> for FlatExpr { } List(loc, params) => FlatExpr::List(*loc, params.len()), This(loc, ..) => FlatExpr::This(*loc), - _ => return Err(()), + + Power(_, _, _) + | Multiply(_, _, _) + | Divide(_, _, _) + | Add(_, _, _) + | Subtract(_, _, _) + | AssignAdd(_, _, _) + | AssignSubtract(_, _, _) + | AssignMultiply(_, _, _) + | AssignDivide(_, _, _) => return Err(()), }; Ok(res) } diff --git a/crates/shared/src/flattened_yul.rs b/crates/shared/src/flattened_yul.rs new file mode 100644 index 00000000..e6d4b86b --- /dev/null +++ b/crates/shared/src/flattened_yul.rs @@ -0,0 +1,135 @@ +use crate::{string_to_static, ExprErr}; + +use solang_parser::pt::{ + Identifier, Loc, YulExpression, YulFunctionCall, YulStatement, YulSwitchOptions, +}; + +#[derive(Debug, Clone, Copy)] +pub enum FlatYulExpr { + YulStartBlock, + YulVariable(Loc, &'static str), + YulFunctionCallName(usize), + YulFuncCall(Loc, usize), + YulSuffixAccess(Loc, &'static str), + YulEndBlock, +} + +impl TryFrom<&YulExpression> for FlatYulExpr { + type Error = (); + fn try_from(expr: &YulExpression) -> Result { + use YulExpression::*; + let res = match expr { + Variable(ident) => { + FlatYulExpr::YulVariable(ident.loc, string_to_static(ident.name.clone())) + } + FunctionCall(yul_func_call) => { + FlatYulExpr::YulFuncCall(yul_func_call.loc, yul_func_call.arguments.len()) + } + SuffixAccess(loc, _, ident) => { + FlatYulExpr::YulSuffixAccess(*loc, string_to_static(ident.name.clone())) + } + _ => return Err(()), + }; + Ok(res) + } +} + +#[derive(Clone, Debug)] +/// A yul-based if-else chain, which represents a switch statement +pub struct IfElseChain { + pub if_expr: YulExpression, + pub true_stmt: YulStatement, + pub next: Option, +} + +#[derive(Clone, Debug)] +/// Wrapper over a switch statement that denotes either another else statement or the default case +pub enum ElseOrDefault { + Else(Box), + Default(YulStatement), +} + +impl From for ElseOrDefault { + fn from(iec: IfElseChain) -> Self { + Self::Else(Box::new(iec)) + } +} + +impl IfElseChain { + pub fn from_child(ed: ElseOrDefault) -> Option { + match ed { + ElseOrDefault::Else(iec) => Some(*iec), + _ => None, + } + } +} + +impl From for ElseOrDefault { + fn from(default: YulSwitchOptions) -> Self { + match default { + YulSwitchOptions::Default(_loc, block) => { + ElseOrDefault::Default(YulStatement::Block(block)) + } + _ => unreachable!("case as default"), + } + } +} + +pub type SwitchInfo = ( + YulExpression, + Vec, + Option, +); + +impl IfElseChain { + pub fn from(loc: Loc, (condition, cases, default): SwitchInfo) -> Result { + let mut child: Option = default.map(|default| default.into()); + + cases.into_iter().rev().for_each(|case| { + let mut chain_part: IfElseChain = From::from((condition.clone(), case)); + if let Some(c) = child.take() { + chain_part.next = c.into(); + } + child = Some(chain_part.into()); + }); + let Some(child) = child else { + return Err(ExprErr::NoRhs( + loc, + "No cases or default found for switch statement".to_string(), + )); + }; + + let Some(iec) = IfElseChain::from_child(child) else { + return Err(ExprErr::NoRhs( + loc, + "No cases or default found for switch statement".to_string(), + )); + }; + Ok(iec) + } +} + +impl From<(YulExpression, YulSwitchOptions)> for IfElseChain { + fn from((condition, case): (YulExpression, YulSwitchOptions)) -> Self { + match case { + YulSwitchOptions::Case(loc, expr, stmt) => { + let if_expr = YulExpression::FunctionCall(Box::new(YulFunctionCall { + loc, + id: Identifier { + loc, + name: "eq".to_string(), + }, + arguments: vec![condition, expr], + })); + IfElseChain { + if_expr, + true_stmt: YulStatement::Block(stmt), + next: None, + } + } + YulSwitchOptions::Default(_loc, _block) => { + unreachable!("We shouldn't have a `default` case in cases - only in the `default` input parameter") + } + } + } +} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index b6abe097..967f75c9 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,6 +1,7 @@ mod analyzer_like; mod error; mod flattened; +mod flattened_yul; pub mod gas; mod graph_like; mod search; @@ -8,6 +9,7 @@ mod search; pub use analyzer_like::*; pub use error::*; pub use flattened::*; +pub use flattened_yul::*; pub use graph_like::*; pub use search::*; diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index f8632650..e4c5e357 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -315,7 +315,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ctx.push_expr(lhs, analyzer).into_expr_err(loc)?; return Ok(()); } - analyzer.bit_not_inner(arena, ctx, loc, lhs.flatten()) + analyzer.bit_not_inner(arena, ctx, lhs.flatten(), loc) }) } @@ -324,8 +324,8 @@ pub trait BinOp: AnalyzerBackend + Sized { &mut self, arena: &mut RangeArena>, ctx: ContextNode, - loc: Loc, lhs_expr: ExprRet, + loc: Loc, ) -> Result<(), ExprErr> { match lhs_expr { ExprRet::CtxKilled(kind) => { @@ -339,7 +339,7 @@ pub trait BinOp: AnalyzerBackend + Sized { ContextVarNode::from(lhs) .try_increase_size(self, arena) .into_expr_err(loc)?; - self.bit_not_inner(arena, ctx, loc, ExprRet::Single(lhs))?; + self.bit_not_inner(arena, ctx, ExprRet::Single(lhs), loc)?; Ok(()) } ExprRet::Single(lhs) => { diff --git a/crates/solc-expressions/src/context_builder/flattened.rs b/crates/solc-expressions/src/context_builder/flattened.rs index d7ff1c57..4de9187d 100644 --- a/crates/solc-expressions/src/context_builder/flattened.rs +++ b/crates/solc-expressions/src/context_builder/flattened.rs @@ -15,10 +15,12 @@ use graph::{ }; use shared::{ - post_to_site, string_to_static, ExprErr, ExprFlag, FlatExpr, GraphError, GraphLike, - IntoExprErr, NodeIdx, RangeArena, USE_DEBUG_SITE, + post_to_site, string_to_static, ElseOrDefault, ExprErr, ExprFlag, FlatExpr, FlatYulExpr, + GraphError, GraphLike, IfElseChain, IntoExprErr, NodeIdx, RangeArena, USE_DEBUG_SITE, +}; +use solang_parser::pt::{ + CodeLocation, Expression, Identifier, Loc, Statement, YulExpression, YulStatement, }; -use solang_parser::pt::{CodeLocation, Expression, Identifier, Loc, Statement}; impl Flatten for T where T: AnalyzerBackend + Sized + ExprTyParser @@ -48,7 +50,6 @@ pub trait Flatten: { fn traverse_statement(&mut self, stmt: &Statement, unchecked: Option) { use Statement::*; - tracing::trace!("traverse statement: {stmt:#?}"); match stmt { Block { loc: _, @@ -94,9 +95,17 @@ pub trait Flatten: // based on their size let start_len = self.expr_stack_mut().len(); self.traverse_expression(if_expr, unchecked); + let cmp = self.expr_stack_mut().pop().unwrap(); + // have it be a require statement + self.push_expr(FlatExpr::Requirement(*loc)); + self.push_expr(cmp); let true_cond = self.expr_stack_mut().drain(start_len..).collect::>(); + + // the false condition is the same as the true, but with the comparator inverted let mut false_cond = true_cond.clone(); - false_cond.push(FlatExpr::Not(if_expr.loc())); + if let Some(last) = false_cond.pop() { + false_cond.push(last.try_inv_cmp().unwrap_or(FlatExpr::Not(*loc))); + } let true_cond_delta = true_cond.len(); let false_cond_delta = false_cond.len(); @@ -156,7 +165,7 @@ pub trait Flatten: dialect: _, flags: _, block: yul_block, - } => {} + } => self.traverse_yul_statement(&YulStatement::Block(yul_block.clone())), Return(loc, maybe_ret_expr) => { if let Some(ret_expr) = maybe_ret_expr { self.traverse_expression(ret_expr, unchecked); @@ -172,10 +181,153 @@ pub trait Flatten: } } + fn traverse_yul_statement(&mut self, stmt: &YulStatement) { + use YulStatement::*; + match stmt { + Assign(loc, lhs, rhs) => {} + VariableDeclaration(loc, idents, maybe_assignment) => {} + If(loc, if_expr, true_stmt) => { + let iec = IfElseChain { + if_expr: if_expr.clone(), + true_stmt: YulStatement::Block(true_stmt.clone()), + next: None, + }; + self.traverse_yul_if_else(*loc, iec); + } + For(yul_for) => {} + Switch(solang_parser::pt::YulSwitch { + loc, + condition, + cases, + default, + }) => { + if let Some(iec) = self.add_if_err(IfElseChain::from( + *loc, + (condition.clone(), cases.clone(), default.clone()), + )) { + self.traverse_yul_if_else(*loc, iec); + } + } + Leave(loc) => { + self.push_expr(FlatExpr::Break(*loc)); + } + Break(loc) => { + self.push_expr(FlatExpr::Break(*loc)); + } + Continue(loc) => { + self.push_expr(FlatExpr::Continue(*loc)); + } + Block(block) => { + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulStartBlock)); + for statement in block.statements.iter() { + self.traverse_yul_statement(&statement); + } + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulEndBlock)); + } + FunctionDefinition(def) => { + todo!() + } + FunctionCall(call) => { + self.traverse_yul_expression(&YulExpression::FunctionCall(call.clone())); + } + Error(loc) => {} + } + } + + fn traverse_yul_if_else(&mut self, loc: Loc, iec: IfElseChain) { + let true_body = &iec.true_stmt; + let start_len = self.expr_stack_mut().len(); + self.traverse_yul_expression(&iec.if_expr); + self.push_expr(FlatExpr::NumberLiteral(loc, "0", "", None)); + // have it be a require statement + self.push_expr(FlatExpr::Requirement(loc)); + self.push_expr(FlatExpr::More(loc)); + let true_cond = self.expr_stack_mut().drain(start_len..).collect::>(); + + // the false condition is the same as the true, but with the comparator inverted + let mut false_cond = true_cond.clone(); + if let Some(last) = false_cond.pop() { + false_cond.push(last.try_inv_cmp().unwrap_or(FlatExpr::Not(loc))); + } + + let true_cond_delta = true_cond.len(); + let false_cond_delta = false_cond.len(); + + self.traverse_yul_statement(true_body); + let true_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let true_body_delta = true_body.len(); + + let (if_expr, false_body) = if let Some(next) = iec.next { + match next { + ElseOrDefault::Else(curr) => { + self.traverse_yul_if_else(loc, *curr); + } + ElseOrDefault::Default(false_body) => self.traverse_yul_statement(&false_body), + } + let false_body = self.expr_stack_mut().drain(start_len..).collect::>(); + let false_body_delta = false_body.len(); + ( + FlatExpr::If { + loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: false_body_delta, + }, + false_body, + ) + } else { + ( + FlatExpr::If { + loc, + true_cond: true_cond_delta, + false_cond: false_cond_delta, + true_body: true_body_delta, + false_body: 0, + }, + vec![], + ) + }; + + self.push_expr(if_expr); + let stack = self.expr_stack_mut(); + stack.extend(true_cond); + stack.extend(false_cond); + stack.extend(true_body); + stack.extend(false_body); + } + + fn traverse_yul_expression(&mut self, expr: &YulExpression) { + use YulExpression::*; + match expr { + FunctionCall(func_call) => { + func_call.arguments.iter().rev().for_each(|expr| { + self.traverse_yul_expression(expr); + }); + + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulFunctionCallName( + func_call.arguments.len(), + ))); + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulVariable( + func_call.id.loc, + string_to_static(func_call.id.name.clone()), + ))); + + self.push_expr(FlatExpr::YulExpr(FlatYulExpr::YulFuncCall( + expr.loc(), + func_call.arguments.len(), + ))); + } + SuffixAccess(_, member, _) => { + self.traverse_yul_expression(member); + self.push_expr(FlatExpr::try_from(expr).unwrap()); + } + _ => self.push_expr(FlatExpr::try_from(expr).unwrap()), + } + } + fn traverse_expression(&mut self, parent_expr: &Expression, unchecked: Option) { use Expression::*; - - tracing::trace!("traverse expression: {parent_expr:#?}"); match parent_expr { // literals NumberLiteral(..) @@ -189,12 +341,17 @@ pub trait Flatten: self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); } - Negate(_loc, expr) - | UnaryPlus(_loc, expr) + Negate(_loc, expr) => { + self.push_expr(FlatExpr::try_from(parent_expr).unwrap()); + self.traverse_expression(expr, unchecked); + } + + Parenthesis(_loc, expr) => self.traverse_expression(expr, unchecked), + + UnaryPlus(_loc, expr) | BitwiseNot(_loc, expr) | Not(_loc, expr) | Delete(_loc, expr) - | Parenthesis(_loc, expr) | PreIncrement(_loc, expr) | PostIncrement(_loc, expr) | PreDecrement(_loc, expr) @@ -506,6 +663,11 @@ pub trait Flatten: ctx.path(self), self.peek_expr_flag() ); + + tracing::trace!( + "current expr_ret stack: {}", + ctx.debug_expr_stack_str(self).unwrap() + ); match next { // Flag expressions FunctionCallName(n, is_super, named_args) => { @@ -542,7 +704,7 @@ pub trait Flatten: Equal(loc) | NotEqual(loc) | Less(loc) | More(loc) | LessEqual(loc) | MoreEqual(loc) | And(loc) | Or(loc) => self.interp_cmp(arena, ctx, loc, next), - If { .. } => self.interp_if(arena, ctx, stack, next), + If { .. } => self.interp_if(arena, ctx, stack, next, parse_idx), Continue(loc) | Break(loc) => Err(ExprErr::Todo( loc, @@ -570,8 +732,9 @@ pub trait Flatten: | ShiftRight(loc, ..) | BitwiseAnd(loc, ..) | BitwiseXor(loc, ..) - | BitwiseOr(loc, ..) - | BitwiseNot(loc, ..) => self.interp_op(arena, ctx, next, loc, false), + | BitwiseOr(loc, ..) => self.interp_op(arena, ctx, next, loc, false), + + BitwiseNot(loc, ..) => self.interp_bit_not(arena, ctx, next), AssignAdd(loc, ..) | AssignSubtract(loc, ..) @@ -584,7 +747,6 @@ pub trait Flatten: | AssignShiftLeft(loc, ..) | AssignShiftRight(loc, ..) => self.interp_op(arena, ctx, next, loc, true), - Parenthesis(_) => todo!(), Super(..) => unreachable!(), MemberAccess(..) => self.interp_member_access(arena, ctx, next), @@ -608,6 +770,12 @@ pub trait Flatten: List(_, _) => self.interp_list(ctx, stack, next, parse_idx), Parameter(_, _, _) => Ok(()), Null(loc) => ctx.push_expr(ExprRet::Null, self).into_expr_err(loc), + YulExpr(FlatYulExpr::YulStartBlock) => Ok(()), + YulExpr(FlatYulExpr::YulVariable(..)) => Ok(()), + YulExpr(FlatYulExpr::YulFunctionCallName(..)) => Ok(()), + YulExpr(FlatYulExpr::YulFuncCall(..)) => Ok(()), + YulExpr(FlatYulExpr::YulSuffixAccess(..)) => Ok(()), + YulExpr(FlatYulExpr::YulEndBlock) => Ok(()), }?; if matches!(self.peek_expr_flag(), Some(ExprFlag::Requirement)) @@ -627,6 +795,24 @@ pub trait Flatten: } } + fn interp_bit_not( + &mut self, + arena: &mut RangeArena>, + ctx: ContextNode, + next: FlatExpr, + ) -> Result<(), ExprErr> { + let FlatExpr::BitwiseNot(loc) = next else { + unreachable!() + }; + + let to_not = ctx + .pop_n_latest_exprs(1, loc, self) + .into_expr_err(loc)? + .swap_remove(0); + + self.bit_not_inner(arena, ctx, to_not, loc) + } + fn interp_list( &mut self, ctx: ContextNode, @@ -714,6 +900,7 @@ pub trait Flatten: ctx: ContextNode, stack: &[FlatExpr], if_expr: FlatExpr, + parse_idx: usize, ) -> Result<(), ExprErr> { let FlatExpr::If { loc, @@ -726,63 +913,81 @@ pub trait Flatten: unreachable!() }; - let true_subctx_kind = SubContextKind::new_fork(ctx, true); - let true_subctx = - Context::add_subctx(true_subctx_kind, loc, self, None).into_expr_err(loc)?; + let (true_subctx, false_subctx) = + Context::add_fork_subctxs(self, ctx, loc).into_expr_err(loc)?; - let false_subctx_kind = SubContextKind::new_fork(ctx, false); - let false_subctx = - Context::add_subctx(false_subctx_kind, loc, self, None).into_expr_err(loc)?; - ctx.set_child_fork(true_subctx, false_subctx, self) - .into_expr_err(loc)?; - let ctx_fork = self.add_node(Node::ContextFork); - self.add_edge(ctx_fork, ctx, Edge::Context(ContextEdge::ContextFork)); - self.add_edge( - NodeIdx::from(true_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - self.add_edge( - NodeIdx::from(false_subctx.0), - ctx_fork, - Edge::Context(ContextEdge::Subcontext), - ); - - // parse the true condition expressions - for i in 0..true_cond { + // Parse the true condition expressions then skip the + // false condition expressions, thus resulting in the + // true_subctx parse_idx being the start of true body + for _ in 0..true_cond { self.interpret_step(arena, true_subctx, loc, stack)?; } - // skip the false condition expressions self.modify_edges(true_subctx, loc, &|analyzer, true_subctx| { true_subctx.skip_n_exprs(false_cond, analyzer); Ok(()) })?; - // skip the true condition expressions + // Skip the true condition expressions then parse the false + // condition expressions false_subctx.skip_n_exprs(true_cond, self); - // parse the false condition expressions for _ in 0..false_cond { self.interpret_step(arena, false_subctx, loc, stack)?; } - // todo: the kill check - for _ in 0..true_body { - self.interpret_step(arena, true_subctx, loc, stack)?; - } - // skip the false body expressions - self.modify_edges(true_subctx, loc, &|analyzer, true_subctx| { - true_subctx.skip_n_exprs(false_body, analyzer); - Ok(()) - })?; + let true_killed = true_subctx.is_killed(self).into_expr_err(loc)? + || true_subctx.unreachable(self, arena).into_expr_err(loc)?; + let false_killed = false_subctx.is_killed(self).into_expr_err(loc)? + || false_subctx.unreachable(self, arena).into_expr_err(loc)?; - // skip the true body expressions - self.modify_edges(false_subctx, loc, &|analyzer, false_subctx| { - false_subctx.skip_n_exprs(true_body, analyzer); - Ok(()) - })?; - // parse the false body expressions - for i in 0..false_body { - self.interpret_step(arena, false_subctx, loc, stack)?; + match (true_killed, false_killed) { + (true, true) => { + // both have been killed, delete the child and dont process the bodies + ctx.delete_child(self).into_expr_err(loc)?; + } + (true, false) => { + // the true context has been killed, delete child, process the false fork expression + // in the parent context and parse the false body + ctx.delete_child(self).into_expr_err(loc)?; + + // point the parse index of the parent ctx to the false body + ctx.underlying_mut(self).unwrap().parse_idx = + parse_idx + true_cond + false_cond + true_body; + for _ in 0..false_body { + self.interpret_step(arena, ctx, loc, stack)?; + } + } + (false, true) => { + // the false context has been killed, delete child, process the true fork expression + // in the parent context and parse the true body + ctx.delete_child(self).into_expr_err(loc)?; + + // point the parse index of the parent ctx to the true body + ctx.underlying_mut(self).unwrap().parse_idx = parse_idx + true_cond + false_cond; + for _ in 0..true_body { + self.interpret_step(arena, ctx, loc, stack)?; + } + } + (false, false) => { + // both branches are reachable. process each body + for _ in 0..true_body { + self.interpret_step(arena, true_subctx, loc, stack)?; + } + // skip the false body expressions + self.modify_edges(true_subctx, loc, &|analyzer, true_subctx| { + true_subctx.skip_n_exprs(false_body, analyzer); + Ok(()) + })?; + + // skip the true body expressions + self.modify_edges(false_subctx, loc, &|analyzer, false_subctx| { + false_subctx.skip_n_exprs(true_body, analyzer); + Ok(()) + })?; + // parse the false body expressions + for i in 0..false_body { + self.interpret_step(arena, false_subctx, loc, stack)?; + } + } } Ok(()) } diff --git a/crates/solc-expressions/src/func_call/internal_call.rs b/crates/solc-expressions/src/func_call/internal_call.rs index 78c5e46f..06b3595a 100644 --- a/crates/solc-expressions/src/func_call/internal_call.rs +++ b/crates/solc-expressions/src/func_call/internal_call.rs @@ -271,7 +271,8 @@ pub trait InternalFuncCaller: _ => { let stack = &ctx.underlying(self)?.expr_ret_stack; let len = stack.len(); - let inputs = &stack[len - num_inputs - 1..]; + let mut inputs = stack[len.saturating_sub(num_inputs)..].to_vec(); + inputs.reverse(); let resizeables: Vec<_> = inputs .iter() .map(|input| input.expect_single().ok()) @@ -301,7 +302,7 @@ pub trait InternalFuncCaller: }) .collect(); - let inputs = ExprRet::Multi(inputs.to_vec()); + let inputs = ExprRet::Multi(inputs); Ok(self.disambiguate_fn_call( arena, &name, diff --git a/crates/solc-expressions/src/yul/yul_builder.rs b/crates/solc-expressions/src/yul/yul_builder.rs index 10c7b2c7..aa7fefd5 100644 --- a/crates/solc-expressions/src/yul/yul_builder.rs +++ b/crates/solc-expressions/src/yul/yul_builder.rs @@ -301,8 +301,8 @@ pub trait YulBuilder: // use YulExpression::*; // match expr { // BoolLiteral(loc, b, _) => self.bool_literal(ctx, *loc, *b), - // NumberLiteral(loc, int, expr, unit) => { - // self.number_literal(ctx, *loc, int, expr, false, unit) + // NumberLiteral(loc, int, exp, unit) => { + // self.number_literal(ctx, *loc, int, exp, false, unit) // } // HexNumberLiteral(loc, b, _unit) => self.hex_num_literal(ctx, *loc, b, false), // HexStringLiteral(lit, _) => self.hex_literals(ctx, &[lit.clone()]),