diff --git a/translator/eth/src/bytecode/flow_graph/builder.rs b/translator/eth/src/bytecode/flow_graph/builder.rs deleted file mode 100644 index 46d8cf91..00000000 --- a/translator/eth/src/bytecode/flow_graph/builder.rs +++ /dev/null @@ -1,449 +0,0 @@ -#![allow(dead_code)] - -use std::collections::{HashMap, VecDeque}; -use std::usize; - -use anyhow::Error; -use primitive_types::U256; - -use crate::bytecode::block::InstructionBlock; -use crate::bytecode::flow_graph::flow::Flow; -use crate::bytecode::flow_graph::mapper::map_flow; -use crate::bytecode::tracing::tracer::{FlowTrace, Tracer}; -use crate::{Offset, OpCode}; - -pub struct FlowBuilder<'a> { - call_stack: Vec, - block: Offset, - blocks: &'a HashMap, - flow_trace: FlowTrace, -} - -impl<'a> FlowBuilder<'a> { - pub fn new(blocks: &'a HashMap) -> Result, Error> { - Ok(FlowBuilder { - call_stack: Vec::new(), - block: Default::default(), - blocks, - flow_trace: Tracer::new(blocks).trace()?, - }) - } - - pub fn make_flow(&mut self) -> Flow { - let mut cnd_branches = Vec::new(); - - let mut branch_stack: Vec = Vec::new(); - let mut root = Vec::::new(); - - self.block = Offset::default(); - 'pc: loop { - let next = self.exec_block(); - - for branch in branch_stack.iter_mut() { - branch.push_block(self.block); - } - - if branch_stack.is_empty() && !cnd_branches.is_empty() { - root.push(self.block); - } - - match next { - Next::Jmp(next) => { - self.block = next; - } - Next::Stop => 'branch: loop { - if let Some(branching) = branch_stack.pop() { - let branching = branching.set_end(self.block); - self.call_stack = branching.stack().clone(); - if let Some(if_el) = branching.complete() { - cnd_branches.push(if_el); - continue; - } else { - self.block = branching.false_br(); - branch_stack.push(branching); - break 'branch; - } - } else { - break 'pc; - } - }, - Next::Cnd(true_br, false_br) => { - let loop_br = branch_stack - .iter_mut() - .filter(|b| b.jmp().block == self.block) - .find(|b| self.flow_trace.loops.contains_key(&b.jmp().block)) - .map(|b| { - b.set_loop(); - true - }) - .unwrap_or_default(); - - if loop_br { - for branch in branch_stack.iter_mut() { - branch.pop_block(); - } - - 'branch: loop { - if let Some(branching) = branch_stack.pop() { - let branching = branching.set_end(self.block); - self.call_stack = branching.stack().clone(); - if let Some(if_el) = branching.complete() { - cnd_branches.push(if_el); - continue; - } else { - self.block = branching.false_br(); - branch_stack.push(branching); - break 'branch; - } - } else { - break 'pc; - } - } - } else { - branch_stack.push(BranchingState::new( - self.block, - true_br, - false_br, - self.call_stack.clone(), - )); - self.block = true_br; - } - } - } - } - - if root.is_empty() { - map_flow(cnd_branches) - } else { - Flow::Sequence( - root.into_iter() - .map(Flow::Block) - .chain([map_flow(cnd_branches)]) - .collect(), - ) - } - } - - pub fn flow_trace(self) -> FlowTrace { - self.flow_trace - } - - fn pop_stack(&mut self, count: usize) -> Vec { - let mut res = Vec::with_capacity(count); - for _ in 0..count { - res.push(self.call_stack.pop().unwrap_or_default()); - } - res - } - - fn push_stack(&mut self, to_push: Vec) { - self.call_stack.extend(to_push.into_iter().rev()); - } - - fn exec_block(&mut self) -> Next { - if let Some(block) = self.blocks.get(&self.block) { - for inst in block.iter() { - let mut ops = self.pop_stack(inst.pops()); - match &inst.1 { - OpCode::Jump => return Next::Jmp(ops.remove(0)), - OpCode::JumpIf => { - let jmp = ops.remove(0); - return Next::Cnd(jmp, inst.next()); - } - OpCode::Return | OpCode::Stop | OpCode::Revert | OpCode::SelfDestruct => { - return Next::Stop; - } - OpCode::Dup(_) => { - let new_item = ops[ops.len() - 1]; - ops.insert(0, new_item); - self.push_stack(ops); - } - OpCode::Swap(_) => { - let last_index = ops.len() - 1; - ops.swap(0, last_index); - self.push_stack(ops); - } - OpCode::Push(val) => { - let val = U256::from(val.as_slice()); - if val <= U256::from(u32::MAX) { - self.push_stack(vec![Offset::from(val)]); - } else { - self.push_stack(vec![Offset::default()]); - } - } - _ => { - let pushes = inst.pushes(); - if pushes > 0 { - self.push_stack((0..pushes).map(|_| Offset::default()).collect()); - } - } - } - } - block - .last() - .map(|last| Next::Jmp(last.next())) - .unwrap_or(Next::Stop) - } else { - Next::Stop - } - } -} - -#[derive(Clone, Debug)] -enum BranchingState { - JumpIf { - jmp: CndJmp, - stack: Vec, - true_br: Branch, - }, - TrueBranch { - jmp: CndJmp, - true_br: Branch, - false_br: Branch, - stack: Vec, - }, - IF { - jmp: CndJmp, - true_br: Branch, - false_br: Branch, - stack: Vec, - }, -} - -impl BranchingState { - pub fn new(block: Offset, true_br: Offset, false_br: Offset, stack: Vec) -> Self { - BranchingState::JumpIf { - jmp: CndJmp { - block, - true_br, - false_br, - }, - stack, - true_br: Branch::default(), - } - } - - pub fn set_loop(&mut self) { - match self { - BranchingState::JumpIf { true_br, .. } => { - true_br.set_loop(true); - } - BranchingState::TrueBranch { false_br, .. } => { - false_br.set_loop(true); - } - BranchingState::IF { .. } => {} - } - } - - pub fn set_end(self, end: Offset) -> BranchingState { - match self { - BranchingState::JumpIf { - jmp, - stack, - mut true_br, - } => { - true_br.set_end(end); - BranchingState::TrueBranch { - jmp, - true_br, - false_br: Default::default(), - stack, - } - } - BranchingState::TrueBranch { - jmp, - true_br, - mut false_br, - stack, - } => { - false_br.set_end(end); - BranchingState::IF { - jmp, - true_br, - false_br, - stack, - } - } - BranchingState::IF { .. } => self, - } - } - - pub fn complete(&self) -> Option { - match self.clone() { - BranchingState::IF { - jmp, - true_br, - false_br, - stack: _, - } => Some(CndBranch { - jmp, - true_br, - false_br, - }), - _ => None, - } - } - - pub fn stack(&self) -> &Vec { - match self { - BranchingState::JumpIf { stack, .. } => stack, - BranchingState::TrueBranch { stack, .. } => stack, - BranchingState::IF { stack, .. } => stack, - } - } - - pub fn false_br(&self) -> Offset { - match self { - BranchingState::JumpIf { jmp, .. } => jmp.false_br, - BranchingState::TrueBranch { jmp, .. } => jmp.false_br, - BranchingState::IF { jmp, .. } => jmp.false_br, - } - } - - pub fn push_block(&mut self, block_id: Offset) { - match self { - BranchingState::JumpIf { true_br, .. } => { - true_br.blocks.push(block_id); - } - BranchingState::TrueBranch { false_br, .. } => { - false_br.blocks.push(block_id); - } - BranchingState::IF { .. } => {} - } - } - - pub fn pop_block(&mut self) { - match self { - BranchingState::JumpIf { true_br, .. } => { - true_br.blocks.pop(); - } - BranchingState::TrueBranch { false_br, .. } => { - false_br.blocks.pop(); - } - BranchingState::IF { .. } => {} - } - } - - pub fn jmp(&self) -> &CndJmp { - match self { - BranchingState::JumpIf { jmp, .. } => jmp, - BranchingState::TrueBranch { jmp, .. } => jmp, - BranchingState::IF { jmp, .. } => jmp, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct CndJmp { - pub block: Offset, - pub true_br: Offset, - pub false_br: Offset, -} - -#[derive(Debug, Clone)] -pub struct CndBranch { - pub jmp: CndJmp, - pub true_br: Branch, - pub false_br: Branch, -} - -#[derive(Debug, Clone, Default)] -pub struct Branch { - pub end: Offset, - pub blocks: Vec, - pub continue_blocks: Option, - pub is_loop: bool, -} - -#[derive(Debug, Clone, Default)] -pub struct Continue { - pub loop_head: Offset, - pub continue_block: Offset, -} - -impl Branch { - pub fn set_end(&mut self, end: Offset) { - self.end = end; - } - - pub fn set_loop(&mut self, is_loop: bool) { - self.is_loop = is_loop; - let len = self.blocks.len(); - let loop_head = self.blocks[len - 1]; - let continue_block = self.blocks[len - 2]; - self.continue_blocks = Some(Continue { - loop_head, - continue_block, - }); - } -} - -impl CndBranch { - pub fn take_common_fail(&mut self) -> VecDeque { - let mut tail = VecDeque::new(); - - loop { - let last_true_br = self.true_br.blocks.last(); - let last_false_br = self.false_br.blocks.last(); - - if let (Some(true_br), Some(false_br)) = (last_true_br, last_false_br) { - if *true_br == *false_br { - tail.push_front(*true_br); - self.true_br.blocks.pop(); - self.false_br.blocks.pop(); - } else { - break; - } - } else { - break; - } - } - tail - } - - pub fn is_subset(&self, other: &[Offset]) -> bool { - let mut idx = 0; - if self.block() != other[idx] { - return false; - } - idx += 1; - - fn is_subset_branch(br: &Branch, other: &[Offset]) -> bool { - for (idx, block) in br.blocks.iter().enumerate() { - if *block != other[idx] { - return false; - } - } - true - } - - if is_subset_branch(&self.true_br, &other[idx..]) { - idx += self.true_br.blocks.len(); - } else { - return false; - } - - is_subset_branch(&self.false_br, &other[idx..]) - } -} - -#[derive(Clone, Copy, Debug)] -enum Next { - Jmp(Offset), - Stop, - Cnd(Offset, Offset), -} - -impl CndBranch { - pub fn block(&self) -> Offset { - self.jmp.block - } - - pub fn len(&self) -> usize { - self.true_br.blocks.len() + self.false_br.blocks.len() + 1 - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} diff --git a/translator/eth/src/bytecode/flow_graph/debug.rs b/translator/eth/src/bytecode/flow_graph/debug.rs deleted file mode 100644 index 11330cf4..00000000 --- a/translator/eth/src/bytecode/flow_graph/debug.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::fmt::Write; - -use anyhow::Error; -use log::log_enabled; -use log::Level; - -use crate::bytecode::flow_graph::flow::Flow; -use crate::bytecode::flow_graph::LoopBr; - -pub fn log_flow(flow: &Flow) { - if log_enabled!(Level::Trace) { - let mut s = String::new(); - if let Err(err) = print_flow(&mut s, flow, 0) { - log::warn!("Failed to print flow {}", err); - } else { - log::info!("\n{}", s); - } - } -} - -fn print_flows(buf: &mut W, vec: &[Flow], width: usize) -> Result<(), Error> { - for flow in vec { - print_flow(buf, flow, width)?; - } - Ok(()) -} - -fn print_flow(buf: &mut W, flow: &Flow, width: usize) -> Result<(), Error> { - match flow { - Flow::Block(seq) => { - writeln!(buf, "{:width$}0x{}", " ", seq)?; - } - Flow::Loop(loop_) => { - writeln!(buf, "{:width$}'{}: loop {{", " ", loop_.jmp.block)?; - writeln!( - buf, - "{:width$}if ({}) {{", - " ", - loop_.jmp.block, - width = width + 4 - )?; - match &loop_.br { - LoopBr::TrueBr(br) => { - print_flow(buf, br, width + 8)?; - writeln!(buf, "{:width$}}} else {{", " ", width = width + 4)?; - writeln!(buf, "{:width$} break;", " ", width = width + 4)?; - } - LoopBr::FalseBr(br) => { - writeln!(buf, "{:width$} break;", " ", width = width + 4)?; - writeln!(buf, "{:width$}}} else {{", " ", width = width + 4)?; - print_flow(buf, br, width + 8)?; - } - } - writeln!(buf, "{:width$}}}", " ", width = width + 4)?; - writeln!(buf, "{:width$}}}", " ")?; - } - Flow::IF(if_) => { - writeln!(buf, "{:width$}if ({}) {{", " ", if_.jmp.block)?; - print_flow(buf, &if_.true_br, width + 4)?; - writeln!(buf, "{:width$}}} else {{", " ")?; - print_flow(buf, &if_.false_br, width + 4)?; - writeln!(buf, "{:width$}}}", " ")?; - } - Flow::Sequence(flow) => { - print_flows(buf, flow, width)?; - } - Flow::Continue(block) => { - writeln!(buf, "{:width$}continue {};", " ", block)?; - } - } - Ok(()) -} diff --git a/translator/eth/src/bytecode/flow_graph/flow.rs b/translator/eth/src/bytecode/flow_graph/flow.rs deleted file mode 100644 index cfb70ab9..00000000 --- a/translator/eth/src/bytecode/flow_graph/flow.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::bytecode::flow_graph::builder::CndJmp; -use crate::Offset; - -#[derive(Debug, Clone)] -pub enum Flow { - Continue(Offset), - Block(Offset), - Loop(LoopFlow), - IF(IfFlow), - Sequence(Vec), -} - -#[derive(Debug, Clone)] -pub struct LoopFlow { - pub jmp: CndJmp, - pub br: LoopBr, -} - -impl LoopFlow { - pub fn break_block(&self) -> Offset { - if self.br.is_true_br_loop() { - self.jmp.false_br - } else { - self.jmp.true_br - } - } -} - -#[derive(Debug, Clone)] -pub enum LoopBr { - TrueBr(Box), - FalseBr(Box), -} - -impl LoopBr { - pub fn is_true_br_loop(&self) -> bool { - matches!(self, LoopBr::TrueBr(_)) - } - - pub fn flow(&self) -> &Flow { - match self { - LoopBr::TrueBr(flow) => flow, - LoopBr::FalseBr(flow) => flow, - } - } -} - -#[derive(Debug, Clone)] -pub struct IfFlow { - pub jmp: CndJmp, - pub true_br: Box, - pub false_br: Box, -} diff --git a/translator/eth/src/bytecode/flow_graph/mapper.rs b/translator/eth/src/bytecode/flow_graph/mapper.rs deleted file mode 100644 index 66a16e6d..00000000 --- a/translator/eth/src/bytecode/flow_graph/mapper.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::collections::BTreeMap; - -use crate::bytecode::flow_graph::builder::CndBranch; -use crate::bytecode::flow_graph::debug::log_flow; -use crate::bytecode::flow_graph::flow::{Flow, IfFlow, LoopFlow}; -use crate::bytecode::flow_graph::LoopBr; -use crate::Offset; - -#[derive(Debug, Clone)] -struct Mapper { - //blocks: BTreeMap, - elements: Vec, - /// continue -> loop head - loop_map: BTreeMap, -} - -impl Mapper { - pub fn new(elements: Vec) -> Mapper { - // let blocks = elements - // .into_iter() - // .map(|flow| (flow.block(), flow)) - // .collect::>(); - Mapper { - //blocks, - elements, - loop_map: Default::default(), - } - } - - pub fn make_flow(mut self) -> Flow { - let element = self - .elements - .iter() - .find(|cnd| cnd.block() == Offset::default()) - .cloned(); - - let first_block = if let Some(element) = element { - element - } else { - return Flow::Sequence(vec![]); - }; - - let flow = self.map_branch(first_block); - log_flow(&flow); - flow - } - - fn update_loop_map(&mut self, block: &mut CndBranch) { - if let Some(ctn) = block.true_br.continue_blocks.take() { - self.loop_map.insert(ctn.continue_block, ctn.loop_head); - } - - if let Some(ctn) = block.false_br.continue_blocks.take() { - self.loop_map.insert(ctn.continue_block, ctn.loop_head); - } - } - - fn map_branch(&mut self, mut block: CndBranch) -> Flow { - let mut seq = vec![]; - self.update_loop_map(&mut block); - match (block.true_br.is_loop, block.false_br.is_loop) { - (true, true) => { - unreachable!("Loop in two branches") - } - (true, false) => { - seq.push(Flow::Loop(LoopFlow { - jmp: block.jmp, - br: LoopBr::TrueBr(Box::new(Flow::Sequence( - self.map_block(&block.true_br.blocks), - ))), - })); - if !block.false_br.blocks.is_empty() { - seq.extend(self.map_block(&block.false_br.blocks)); - } - } - (false, true) => { - seq.push(Flow::Loop(LoopFlow { - jmp: block.jmp, - br: LoopBr::FalseBr(Box::new(Flow::Sequence( - self.map_block(&block.false_br.blocks), - ))), - })); - if !block.true_br.blocks.is_empty() { - seq.extend(self.map_block(&block.true_br.blocks)); - } - } - (false, false) => { - //IF - // let common_tail = block.take_common_fail().into_iter().collect::>(); - - seq.push(Flow::IF(IfFlow { - jmp: block.jmp, - true_br: Box::new(Flow::Sequence(self.map_block(&block.true_br.blocks))), - false_br: Box::new(Flow::Sequence(self.map_block(&block.false_br.blocks))), - })); - // if !common_tail.is_empty() { - // seq.extend(self.map_block(&common_tail)); - // } - } - } - Flow::Sequence(seq) - } - - fn map_block(&mut self, blocks: &[Offset]) -> Vec { - let mut seq = Vec::new(); - if blocks.is_empty() { - return seq; - } - - let mut index = 0; - loop { - if blocks.len() <= index { - break; - } - - let block = blocks[index]; - if let Some(element) = self.find_element(&blocks[index..]) { - index += element.len(); - seq.push(self.map_branch(element.clone())); - } else { - index += 1; - seq.push(Flow::Block(block)); - if let Some(block) = self.loop_map.get(&block) { - seq.push(Flow::Continue(*block)); - } - } - } - seq - } - - fn find_element(&self, blocks: &[Offset]) -> Option<&CndBranch> { - self.elements - .iter() - .filter(|cnd| cnd.block() == blocks[0]) - .find(|cnd| cnd.is_subset(blocks)) - } -} - -pub fn map_flow(elements: Vec) -> Flow { - let mapper = Mapper::new(elements); - mapper.make_flow() -} diff --git a/translator/eth/src/bytecode/flow_graph/mod.rs b/translator/eth/src/bytecode/flow_graph/mod.rs deleted file mode 100644 index e69a0092..00000000 --- a/translator/eth/src/bytecode/flow_graph/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub use builder::FlowBuilder; -pub use flow::*; - -pub mod builder; -mod debug; -mod flow; -mod mapper; diff --git a/translator/eth/src/bytecode/hir/context.rs b/translator/eth/src/bytecode/hir/context.rs index 3aef066f..afffe55b 100644 --- a/translator/eth/src/bytecode/hir/context.rs +++ b/translator/eth/src/bytecode/hir/context.rs @@ -18,6 +18,7 @@ pub struct Context<'a> { pub stack: Stack, pub vars: Vars, jmp_id: usize, + pub private_func: bool, } impl<'a> Context<'a> { @@ -39,6 +40,7 @@ impl<'a> Context<'a> { vars: Default::default(), loc: Loc::new(0u128, 0u128, ()), jmp_id: 0, + private_func: false, } } diff --git a/translator/eth/src/bytecode/hir/debug.rs b/translator/eth/src/bytecode/hir/debug.rs index 04441f24..0843b107 100644 --- a/translator/eth/src/bytecode/hir/debug.rs +++ b/translator/eth/src/bytecode/hir/debug.rs @@ -185,6 +185,16 @@ pub fn print_stmt(buf: &mut B, stmt: &Loc) -> Result<(), Error> print_expr(buf, cnd)?; writeln!(buf, "\nBrTrue {};", true_br)?; } + Stmt::CallLocal(label, expr) => { + write!(buf, "call_local {}(", label)?; + for (i, arg) in expr.iter().enumerate() { + if i > 0 { + write!(buf, ", ")?; + } + print_expr(buf, arg)?; + } + writeln!(buf, ");")?; + } } Ok(()) } diff --git a/translator/eth/src/bytecode/hir/func.rs b/translator/eth/src/bytecode/hir/func.rs new file mode 100644 index 00000000..14b7478b --- /dev/null +++ b/translator/eth/src/bytecode/hir/func.rs @@ -0,0 +1,8 @@ +use crate::{Hir, Offset}; + +pub struct PrivateFunc { + pub name: Offset, + // pub params: Vec, + // pub ret: Vec, + pub body: Hir, +} diff --git a/translator/eth/src/bytecode/hir/ir.rs b/translator/eth/src/bytecode/hir/ir.rs index 00ab5bd2..af8d442e 100644 --- a/translator/eth/src/bytecode/hir/ir.rs +++ b/translator/eth/src/bytecode/hir/ir.rs @@ -45,6 +45,7 @@ pub enum Stmt { }, BrunchTrue(Expr, Label), Brunch(Label), + CallLocal(Label, Vec), } pub type Expr = Loc<_Expr>; diff --git a/translator/eth/src/bytecode/hir/mod.rs b/translator/eth/src/bytecode/hir/mod.rs index a3c85b84..d0f5fba0 100644 --- a/translator/eth/src/bytecode/hir/mod.rs +++ b/translator/eth/src/bytecode/hir/mod.rs @@ -4,7 +4,10 @@ use crate::bytecode::hir::executor::{ExecutionResult, InstructionHandler}; use crate::bytecode::hir::ir::{Expr, Label, VarId, _Expr}; use crate::bytecode::hir::vars::Vars; -use crate::bytecode::tracing::tracer::{FlowTrace, Tracer}; +use crate::bytecode::hir::func::PrivateFunc; +use crate::bytecode::loc::Loc; +use crate::bytecode::tracing::tracer::{FlowTrace, Func, Tracer}; +use crate::bytecode::types::EthType; use crate::{Flags, Function, Hir, Offset, OpCode}; use anyhow::{anyhow, ensure, Context as ErrorContext, Error}; use primitive_types::U256; @@ -13,6 +16,7 @@ use std::collections::{BTreeMap, HashMap}; pub mod context; pub mod debug; pub mod executor; +pub mod func; pub mod ir; pub mod stack; pub mod vars; @@ -21,25 +25,75 @@ pub struct HirBuilder { contract: HashMap, flags: Flags, flow: FlowTrace, + contract_address: U256, + code_size: u128, } impl HirBuilder { - pub fn new(contract: HashMap, flags: Flags) -> Result { + pub fn new( + contract: HashMap, + flags: Flags, + contract_address: U256, + code_size: u128, + ) -> Result { let flow = Tracer::new(&contract).trace()?; Ok(Self { contract, flags, flow, + contract_address, + code_size, }) } - pub fn translate_fun( - &self, - fun: &Function, - contract_address: U256, - code_size: u128, - ) -> Result { - let mut ctx = Context::new(fun, contract_address, code_size, self.flags); + pub fn translate_module_base(&self) -> Result, Error> { + let mut funcs = HashMap::new(); + for (offset, fun) in &self.flow.funcs { + funcs.insert(*offset, self.translate_private_fun(fun)?); + } + + Ok(funcs) + } + + fn translate_private_fun(&self, fun: &Func) -> Result { + let def = Function { + hash: Default::default(), + name: fun.name(), + eth_input: vec![], + native_input: fun.input.iter().map(|v| EthType::U256).collect(), + eth_output: vec![], + native_output: fun.output.iter().map(|v| EthType::U256).collect(), + }; + let mut ctx = Context::new( + &def, + self.contract_address, + self.code_size, + Flags { + native_input: true, + native_output: true, + hidden_output: false, + u128_io: false, + }, + ); + ctx.private_func = true; + let mut ir = Hir::default(); + + let fn_loc = Loc::new(fun.entry_point.0, fun.entry_point.0, ()); + for (idx, _) in fun.input.iter().enumerate() { + ctx.stack + .push(fn_loc.wrap(_Expr::Args(Box::new(fn_loc.wrap(_Expr::Val(idx.into())))))); + } + + self.translate_blocks(Offset::default(), &mut ir, &mut ctx)?; + println!("fun: {:?}", fun); + let mut s = String::new(); + ir.print(&mut s)?; + println!("{}", s); + todo!() + } + + pub fn translate_public_fun(&self, fun: &Function) -> Result { + let mut ctx = Context::new(fun, self.contract_address, self.code_size, self.flags); let mut ir = Hir::default(); self.translate_blocks(Offset::default(), &mut ir, &mut ctx)?; Ok(ir) diff --git a/translator/eth/src/bytecode/mir/ir/debug.rs b/translator/eth/src/bytecode/mir/ir/debug.rs index 08db4288..d21727a5 100644 --- a/translator/eth/src/bytecode/mir/ir/debug.rs +++ b/translator/eth/src/bytecode/mir/ir/debug.rs @@ -136,6 +136,21 @@ impl Display for Statement { Statement::Br(l) => { write!(f, "goto '{};", l) } + Statement::CallLocal { + name, + storage, + memory, + context, + } => { + write!(f, "{}.CallLocal({}, {}, [", storage, memory, name)?; + for (i, var) in context.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", var)?; + } + write!(f, "]);") + } } } } diff --git a/translator/eth/src/bytecode/mir/ir/statement.rs b/translator/eth/src/bytecode/mir/ir/statement.rs index 6121c740..ba224412 100644 --- a/translator/eth/src/bytecode/mir/ir/statement.rs +++ b/translator/eth/src/bytecode/mir/ir/statement.rs @@ -36,6 +36,12 @@ pub enum Statement { Label(Label), BrTrue(Loc, Label), Br(Label), + CallLocal { + name: Label, + storage: Variable, + memory: Variable, + context: Vec>, + }, } impl Statement { diff --git a/translator/eth/src/bytecode/mir/translation/mod.rs b/translator/eth/src/bytecode/mir/translation/mod.rs index 6f51f77a..d61253ca 100644 --- a/translator/eth/src/bytecode/mir/translation/mod.rs +++ b/translator/eth/src/bytecode/mir/translation/mod.rs @@ -163,6 +163,7 @@ impl<'a> MirTranslator<'a> { Stmt::Brunch(label) => { self.mir.push(Statement::Br(label).loc(self.loc)); } + Stmt::CallLocal(label, params) => {} } } Ok(()) diff --git a/translator/eth/src/bytecode/mod.rs b/translator/eth/src/bytecode/mod.rs index e0d6cf75..b33ee99a 100644 --- a/translator/eth/src/bytecode/mod.rs +++ b/translator/eth/src/bytecode/mod.rs @@ -1,5 +1,4 @@ pub mod block; -pub mod flow_graph; pub mod hir; pub mod instruction; pub mod loc; diff --git a/translator/eth/src/bytecode/tracing/exec.rs b/translator/eth/src/bytecode/tracing/exec.rs index 79ed773f..2036f937 100644 --- a/translator/eth/src/bytecode/tracing/exec.rs +++ b/translator/eth/src/bytecode/tracing/exec.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::mem; use anyhow::{anyhow, Error}; @@ -11,7 +10,7 @@ pub struct Executor { call_stack: Vec, pub path: Vec, negative_stack_seq: usize, - negative_item_used: HashSet, + negative_stack: Vec, } impl Executor { @@ -20,10 +19,12 @@ impl Executor { for _ in 0..count { res.push(self.call_stack.pop().unwrap_or_else(|| { self.negative_stack_seq += 1; - StackItem::Negative { + let item = StackItem::Negative { id: self.negative_stack_seq, offset, - } + }; + self.negative_stack.push(item.clone()); + item })); } res @@ -33,21 +34,16 @@ impl Executor { self.call_stack.extend(to_push.into_iter().rev()); } - pub fn exec_one(&mut self, block: &InstructionBlock) -> BlockResult { - let next = self.exec(block); - BlockResult { - next, - input: self.negative_item_used.clone(), - output: mem::take(&mut self.call_stack), - } + pub fn negative_stack(&self) -> &[StackItem] { + &self.negative_stack } - pub fn negative_item_used(&self) -> &HashSet { - &self.negative_item_used + pub fn call_stack(&self) -> &[StackItem] { + &self.call_stack } - pub fn call_stack(&self) -> &Vec { - &self.call_stack + pub fn into_io(self) -> (Vec, Vec) { + (self.negative_stack, self.call_stack) } pub fn exec(&mut self, block: &InstructionBlock) -> Next { @@ -99,12 +95,6 @@ impl Executor { } OpCode::Pop => {} _ => { - for op in ops { - if op.is_negative() { - self.negative_item_used.insert(op); - } - } - let pushes = inst.pushes(); if pushes > 0 { self.push_stack( @@ -193,6 +183,6 @@ pub enum Next { #[derive(Clone, Debug)] pub struct BlockResult { pub next: Next, - pub input: HashSet, + pub input: Vec, pub output: Vec, } diff --git a/translator/eth/src/bytecode/tracing/tracer.rs b/translator/eth/src/bytecode/tracing/tracer.rs index 6fdb4049..911c2d4e 100644 --- a/translator/eth/src/bytecode/tracing/tracer.rs +++ b/translator/eth/src/bytecode/tracing/tracer.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use std::mem; -use anyhow::{anyhow, Context as ErrContext, Error}; +use anyhow::{anyhow, Error}; use crate::bytecode::block::InstructionBlock; use crate::bytecode::tracing::exec::{Executor, Next, StackItem}; @@ -21,10 +22,9 @@ impl<'a> Tracer<'a> { } pub fn trace(&mut self) -> Result { - let io = self.calculate_io()?; let loops = self.clone().find_loops()?; let funcs = self.clone().find_funcs(&loops); - Ok(FlowTrace { io, funcs, loops }) + Ok(FlowTrace { funcs, loops }) } fn next_block(block: &InstructionBlock) -> Offset { @@ -44,10 +44,6 @@ impl<'a> Tracer<'a> { continue; } - if block.len() < 2 { - continue; - } - let call_addr = if let Some(inst) = block.get(block.len() - 2) { if let OpCode::Push(vec) = &inst.1 { let val = U256::from(vec.as_slice()); @@ -62,6 +58,8 @@ impl<'a> Tracer<'a> { let func = funcs.entry(call_addr).or_insert_with(|| Func { entry_point: call_addr, calls: Default::default(), + input: vec![], + output: vec![], }); func.calls.insert( *id, @@ -74,13 +72,102 @@ impl<'a> Tracer<'a> { funcs .into_iter() - .filter(|(id, fun)| self.check_func(id, fun, loops)) + .filter(|(_, fun)| fun.calls.len() > 1) + .filter(|(_, fun)| !loops.contains_key(&fun.entry_point)) + .filter_map(|(offset, mut fun)| { + for call in &fun.calls { + match self.calc_func_io(call.1, loops) { + Ok((input, output)) => { + if fun.input.is_empty() && fun.output.is_empty() { + fun.input = input; + fun.output = output; + } else { + if fun.input != input || fun.output != output { + return None; + } + } + } + Err(err) => { + log::warn!("Failed to calculate function IO: {}", err); + return None; + } + } + } + + Some((offset, fun)) + }) .collect() } - fn check_func(&self, _id: &Offset, _fun: &Func, _loops: &HashMap) -> bool { - //todo filter out functions that are not really functions) - true + fn calc_func_io( + &self, + call: &Call, + loops: &HashMap, + ) -> Result<(Vec, Vec), Error> { + let mut block_id = call.entry_point; + let ret = call.return_point; + + let mut br_stack = Vec::new(); + + let mut io_states = Vec::new(); + let mut visited_loops = HashSet::new(); + let mut entry_exec = Executor::default(); + let mut fun_exec = Executor::default(); + + loop { + let block = self.blocks.get(&block_id).ok_or_else(|| { + anyhow!( + "Block with id {} not found. Blocks: {:?}", + block_id, + self.blocks + ) + })?; + let next = entry_exec.exec(block); + + if call.entry_point != block_id { + fun_exec.exec(block); + } + + match next { + Next::Jmp(val) => { + let jmp = val.as_positive()?; + if jmp == ret { + io_states.push(fun_exec); + return self.branch_io_to_fun_io(io_states); + } + if loops.contains_key(&jmp) { + if !visited_loops.insert(jmp) { + if let Some((br, entry_br_exec, mut func_br_exec)) = br_stack.pop() { + block_id = br; + entry_exec = entry_br_exec; + mem::swap(&mut fun_exec, &mut func_br_exec); + io_states.push(func_br_exec); + continue; + } else { + return Err(anyhow!("Not a function:{}", call.entry_point)); + } + } + } + block_id = jmp; + } + Next::Stop => { + if let Some((br, entry_br_exec, mut func_br_exec)) = br_stack.pop() { + block_id = br; + entry_exec = entry_br_exec; + mem::swap(&mut fun_exec, &mut func_br_exec); + io_states.push(func_br_exec); + } else { + return Err(anyhow!("Not a function:{}", call.entry_point)); + } + } + Next::Cnd(true_br, false_br) => { + let true_br = true_br.as_positive()?; + let false_br = false_br.as_positive()?; + br_stack.push((false_br, entry_exec.clone(), fun_exec.clone())); + block_id = true_br; + } + } + } } fn find_loops(&mut self) -> Result, Error> { @@ -196,75 +283,26 @@ impl<'a> Tracer<'a> { } } - fn calculate_io(&self) -> Result, Error> { - let mut io: HashMap = HashMap::new(); - for (id, block) in self.blocks { - let mut exec = Executor::default(); - let res = exec.exec_one(block); - - let outputs = res.output.into_iter().map(|i| (i.offset(), i)).collect(); - - let inputs = res - .input - .into_iter() - .map(|i| { - i.as_negative() - .ok_or_else(|| anyhow!("Invalid input: {:?}. Block:{}", i, id)) - }) - .collect::>()?; - - io.insert(*id, BlockIO { inputs, outputs }); - } - Ok(io) - } - - pub fn fill_io(&self, lp: &mut Loop, loops: &HashMap) -> Result<(), Error> { - let mut exec = Executor::default(); - let mut block_id = lp.root; - let exit = lp.loop_exit; - - let mut ctx: Vec = vec![]; - loop { - let block = self.blocks.get(&block_id).unwrap(); - let next = exec.exec(block); - match next { - Next::Jmp(jmp) => { - let jmp = jmp.as_positive().context("Invalid jmp")?; - if lp.root == jmp { - lp.loop_ctx = LoopCtx::new(block_id, &exec); - break; - } - - if let Some(_lp) = loops.get(&jmp) { - todo!("Loop inside loop"); - } else { - block_id = jmp; - } - } - Next::Stop => { - if let Some(ctx) = ctx.pop() { - exec = ctx.executor; - block_id = ctx.false_br; - } - } - Next::Cnd(true_br, false_br) => { - let true_br = true_br.as_positive().context("Invalid true branch")?; - let false_br = false_br.as_positive().context("Invalid false branch")?; - - if true_br == exit { - block_id = false_br; - continue; - } - - ctx.push(Context { - executor: exec.clone(), - false_br, - }); - block_id = true_br; - } + fn branch_io_to_fun_io( + &self, + mut executors: Vec, + ) -> Result<(Vec, Vec), Error> { + let exec = executors + .pop() + .ok_or_else(|| anyhow!("Empty function io"))?; + let (mut input, mut output) = exec.into_io(); + + for exec in executors { + let (i, o) = exec.into_io(); + if i.len() > input.len() { + input = i; + } + if o.len() > output.len() { + output = o; } } - Ok(()) + + return Ok((input, output)); } } @@ -272,28 +310,31 @@ impl<'a> Tracer<'a> { pub struct LoopCtx { pub block: Offset, pub output: Vec, - pub input: HashSet, + pub input: Vec, } impl LoopCtx { pub fn new(block: Offset, exec: &Executor) -> Self { LoopCtx { block, - output: exec.call_stack().clone(), - input: exec.negative_item_used().clone(), + output: exec.call_stack().to_vec(), + input: exec.negative_stack().to_vec(), } } } -pub struct Context { - pub executor: Executor, - pub false_br: Offset, -} - #[derive(Debug)] pub struct Func { pub entry_point: Offset, pub calls: HashMap, + pub input: Vec, + pub output: Vec, +} + +impl Func { + pub fn name(&self) -> String { + format!("func_{}", self.entry_point) + } } #[derive(Debug)] @@ -323,7 +364,6 @@ pub struct Loop { #[derive(Debug)] pub struct FlowTrace { - pub io: HashMap, pub funcs: HashMap, pub loops: HashMap, } diff --git a/translator/eth/src/lib.rs b/translator/eth/src/lib.rs index 666a4b83..a6cb1311 100644 --- a/translator/eth/src/lib.rs +++ b/translator/eth/src/lib.rs @@ -50,26 +50,18 @@ pub fn transpile_program( .map(|block| (block.start, block)) .collect::>(); - let hir = HirBuilder::new(contract, flags)?; + let hir = HirBuilder::new(contract, flags, contract_addr, contract_code_len as u128)?; + hir.translate_module_base()?; let functions = abi .functions() .iter() - .map(|(hash, fun)| { - translate_function(&hir, fun, contract_addr, contract_code_len as u128, flags) - .map(|mir| (*hash, mir)) - }) + .map(|(hash, fun)| translate_function(&hir, fun, flags).map(|mir| (*hash, mir))) .collect::, _>>()?; Program::new(constructor, functions, abi) } -pub fn translate_function( - hir: &HirBuilder, - fun: &Function, - contract_addr: U256, - code_size: u128, - flags: Flags, -) -> Result { - let hir = hir.translate_fun(fun, contract_addr, code_size)?; +pub fn translate_function(hir: &HirBuilder, fun: &Function, flags: Flags) -> Result { + let hir = hir.translate_public_fun(fun)?; let mut buff = String::new(); hir.print(&mut buff)?; trace!("{}", buff); diff --git a/translator/mv/src/translator/mod.rs b/translator/mv/src/translator/mod.rs index f661b787..160cde33 100644 --- a/translator/mv/src/translator/mod.rs +++ b/translator/mv/src/translator/mod.rs @@ -242,6 +242,9 @@ impl MvIrTranslator { Statement::Br(goto) => { self.code.jmp(*goto, false); } + Statement::CallLocal { .. } => { + todo!("CallLocal"); + } } } diff --git a/translator/test_infra/sol/private_functions.sol b/translator/test_infra/sol/private_functions.sol new file mode 100644 index 00000000..fbe88191 --- /dev/null +++ b/translator/test_infra/sol/private_functions.sol @@ -0,0 +1,46 @@ +pragma solidity ^0.8.0; + +contract private_functions { + function test_1(bool a, bool b, bool c) public pure returns (uint128){ + if (a) { + if (b) { + if (c) { + return on_first_case(); + } + return on_second_case(); + } else { + return on_third_case(); + } + } + + return default_case(); + } + + function on_first_case() private pure returns (uint128) { + uint128 a = 10; + uint128 b = 2; + uint128 c = 3; + return a + b + c; + } + + function on_second_case() private pure returns (uint128) { + uint128 a = 100; + uint128 b = 20; + uint128 c = 30; + return a + b + c; + } + + function on_third_case() private pure returns (uint128) { + uint128 a = 1000; + uint128 b = 200; + uint128 c = 300; + return a + b + c; + } + + function default_case() private pure returns (uint128) { + uint128 a = 10000; + uint128 b = 2000; + uint128 c = 3000; + return a + b + c; + } +} diff --git a/translator/test_infra/tests/private_functions.rs b/translator/test_infra/tests/private_functions.rs new file mode 100644 index 00000000..e459558a --- /dev/null +++ b/translator/test_infra/tests/private_functions.rs @@ -0,0 +1,35 @@ +use crate::testssol::env::executor::MoveExecutor; +use crate::testssol::{make_move_module, sol_path}; +use eth::compile::build_sol; +use eth::Flags; +use test_infra::init_log; + +mod testssol; + +#[test] +pub fn test_private_functions() { + init_log(); + let evm = build_sol(sol_path().join("private_functions.sol")).unwrap(); + let bytecode = make_move_module( + &format!("0x42::{}", evm.name()), + evm.contract().bin(), + "", + evm.contract().abi(), + Flags::default(), + ) + .unwrap(); + let mut vm = MoveExecutor::new( + serde_json::from_str(evm.contract().abi()).unwrap(), + Flags::default(), + ); + vm.deploy("0x42", bytecode); + + vm.run("0x42::private_functions::constructor", "0x42", None) + .unwrap(); + + // let res = vm + // .run("0x42::PrivateFunctions::test", "0x42", None) + // .unwrap() + // .to_result_str(); + // assert_eq!("Uint(10)", res); +}