From d186ee18d8f381359b60a9225b31fd4141e1b3e6 Mon Sep 17 00:00:00 2001 From: Jessie Zijia Xu Date: Wed, 3 Jul 2024 16:07:05 -0700 Subject: [PATCH] parent 9e567c88dcd12a2369bf795ea2810a11377c6f4e author Jessie Zijia Xu 1720048025 -0700 committer Jessie Xu 1724974035 -0700 early return bug & for loop fixes --- README.md | 1 + crates/forge_analyzer/src/definitions.rs | 139 ++++++++-- crates/forge_analyzer/src/ir.rs | 327 ++++++++++++++++++++++- crates/forge_analyzer/src/pretty.rs | 85 +++++- crates/fsrt/src/main.rs | 9 + test-apps/basic/package.json | 4 +- test-apps/basic/src/index.tsx | 293 +++++++++++++++++--- 7 files changed, 792 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index f8280d65..f8bf940b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Arguments: Options: -d, --debug --dump-ir Dump the IR for the specified function. + -dt, --dump-dt Dump the Dominator Tree for the specified app -f, --function A specific function to scan. Must be an entrypoint specified in `manifest.yml` -h, --help Print help information -V, --version Print version information diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 497ef9fa..a32cd1a7 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -909,6 +909,11 @@ impl<'cx> FunctionAnalyzer<'cx> { self.body.set_terminator(self.block, term); } + #[inline] + fn get_curr_terminator(&mut self) -> Option { + self.body.get_terminator(self.block) + } + fn as_intrinsic(&self, callee: &[PropPath], first_arg: Option<&Expr>) -> Option { fn is_storage_read(prop: &JsWord) -> bool { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" @@ -1051,7 +1056,9 @@ impl<'cx> FunctionAnalyzer<'cx> { /// Sets the current block to `block` and returns the previous block. #[inline] fn goto_block(&mut self, block: BasicBlockId) -> BasicBlockId { - self.set_curr_terminator(Terminator::Goto(block)); + if self.get_curr_terminator().is_none() { + self.set_curr_terminator(Terminator::Goto(block)); + } mem::replace(&mut self.block, block) } @@ -1217,7 +1224,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { - // debug!("in da lower call"); let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { if self.res.is_imported_from(id, "@forge/ui").map_or( @@ -1243,6 +1249,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Fn(FnExpr { ident: _, function }) => { if let Some(body) = &function.body { self.lower_stmts(&body.stmts); + return Operand::UNDEF; } } @@ -1530,6 +1537,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .push_tmp(self.block, Rvalue::Unary(op.into(), arg), None); Operand::with_var(tmp) } + Expr::Update(UpdateExpr { op, prefix, arg, .. }) => { @@ -1626,10 +1634,12 @@ impl<'cx> FunctionAnalyzer<'cx> { }) => { let cond = self.lower_expr(test, None); let curr = self.block; - let rest = self.body.new_block(); - let cons_block = self.body.new_block(); - let alt_block = self.body.new_block(); + let [temp1, temp2, temp3] = self.body.new_blocks(); + let rest = self.body.new_blockbuilder(); + let cons_block = self.body.new_blockbuilder(); + let alt_block = self.body.new_blockbuilder(); self.set_curr_terminator(Terminator::If { + // TODO: COME BACK TO? cond, cons: cons_block, alt: alt_block, @@ -1637,6 +1647,7 @@ impl<'cx> FunctionAnalyzer<'cx> { self.block = cons_block; let cons = self.lower_expr(cons, None); let cons_phi = self.body.push_tmp(self.block, Rvalue::Read(cons), None); + self.set_curr_terminator(Terminator::Goto(rest)); self.block = alt_block; let alt = self.lower_expr(alt, None); @@ -1650,6 +1661,7 @@ impl<'cx> FunctionAnalyzer<'cx> { ); Operand::with_var(phi) } + Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), Expr::New(NewExpr { callee, args, .. }) => { if let Expr::Ident(ident) = &**callee { @@ -1750,12 +1762,28 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + // Lowers all statements from the given `stmts`. + // If a return is encounted, return early to prevent + // unreachable statements that come afterwards from being lowered. fn lower_stmts(&mut self, stmts: &[Stmt]) { for stmt in stmts { self.lower_stmt(stmt); + if let Stmt::Return(_) = stmt { + return; + } } } + // Lowers a single statement by pushing corresponding instruction(s)/expression(s) + // onto self.body.blockbuilders[block]. + // + // Corresponding instruction(s)/expression(s) are initially placed in + // self.body.blockbuilders[block] and transferred to self.body.blocks[block] + // once the block's terminator gets set. + // + // B/c of this, an empty block is pushed to self.body.blocks whenever + // a new block is added to self.body.blockbuilders, to ensure space is allocated + // on self.body.blocks for all blocks when instructions are moved. fn lower_stmt(&mut self, n: &Stmt) { match n { Stmt::Block(BlockStmt { stmts, .. }) => self.lower_stmts(stmts), @@ -1764,6 +1792,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Stmt::With(WithStmt { obj, body, .. }) => { let opnd = self.lower_expr(obj, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); + self.lower_stmt(body); } Stmt::Return(ReturnStmt { arg, .. }) => { @@ -1777,17 +1806,29 @@ impl<'cx> FunctionAnalyzer<'cx> { Stmt::Labeled(LabeledStmt { label, body, .. }) => { self.lower_stmt(body); } + // TODO: Lower Break and Continue Stmt::Break(BreakStmt { label, .. }) => {} Stmt::Continue(ContinueStmt { label, .. }) => {} Stmt::If(IfStmt { test, cons, alt, .. }) => { - let [cons_block, cont] = self.body.new_blocks(); + // Adds two blocks to the body: + // - cons_block: block to store insts that run if the test condition of the Stmt::If is true + // - cont: block to store insts that run after the Stmt::If + let [temp1, temp2] = self.body.new_blocks(); + let [cons_block, cont] = self.body.new_blockbuilders(); + + // If an alt block (`else` case) is present, add another block to the body let alt_block = if let Some(alt) = alt { - let alt_block = self.body.new_block(); + let temp3 = self.body.new_block(); + let alt_block = self.body.new_blockbuilder(); let old_block = mem::replace(&mut self.block, alt_block); self.lower_stmt(alt); - self.set_curr_terminator(Terminator::Goto(cont)); + + if self.get_curr_terminator().is_none() { + self.set_curr_terminator(Terminator::Goto(cont)); + } + self.block = old_block; alt_block } else { @@ -1801,6 +1842,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }); self.block = cons_block; self.lower_stmt(cons); + self.goto_block(cont); } Stmt::Switch(SwitchStmt { @@ -1829,7 +1871,8 @@ impl<'cx> FunctionAnalyzer<'cx> { } } Stmt::While(WhileStmt { test, body, .. }) => { - let [check, cont, body_id] = self.body.new_blocks(); + let [temp1, temp2, temp3] = self.body.new_blocks(); + let [check, cont, body_id] = self.body.new_blockbuilders(); self.set_curr_terminator(Terminator::Goto(check)); self.block = check; let cond = self.lower_expr(test, None); @@ -1844,7 +1887,8 @@ impl<'cx> FunctionAnalyzer<'cx> { self.block = cont; } Stmt::DoWhile(DoWhileStmt { test, body, .. }) => { - let [check, cont, body_id] = self.body.new_blocks(); + let [temp1, temp2, temp3] = self.body.new_blocks(); + let [check, cont, body_id] = self.body.new_blockbuilders(); self.set_curr_terminator(Terminator::Goto(body_id)); self.block = body_id; self.lower_stmt(body); @@ -1874,7 +1918,8 @@ impl<'cx> FunctionAnalyzer<'cx> { } None => {} } - let [check, cont, body_id] = self.body.new_blocks(); + let [temp1, temp2, temp3] = self.body.new_blocks(); + let [check, cont, body_id] = self.body.new_blockbuilders(); self.goto_block(check); if let Some(test) = test { let cond = self.lower_expr(test, None); @@ -2233,7 +2278,18 @@ impl Visit for FunctionCollector<'_> { }; if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); - let body = analyzer.body; + let mut body = analyzer.body; + + let mut blocks_to_update: Vec = Vec::new(); + for (id, block) in body.blocks.iter_enumerated() { + if !block.set_term_called { + blocks_to_update.push(id); + } + } + for id in blocks_to_update { + body.set_terminator(id, Terminator::Ret); + } + *self.res.def_mut(*owner).expect_body() = body; } } @@ -2280,7 +2336,18 @@ impl Visit for FunctionCollector<'_> { }; if let Some(BlockStmt { stmts, .. }) = &n.function.body { analyzer.lower_stmts(stmts); - let body = analyzer.body; + let mut body = analyzer.body; + + let mut blocks_to_update: Vec = Vec::new(); + for (id, block) in body.blocks.iter_enumerated() { + if !block.set_term_called { + blocks_to_update.push(id); + } + } + for id in blocks_to_update { + body.set_terminator(id, Terminator::Ret); + } + *self.res.def_mut(*owner).expect_body() = body; } } @@ -2341,7 +2408,19 @@ impl Visit for FunctionCollector<'_> { .push_inst(analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); } } - *self.res.def_mut(owner).expect_body() = analyzer.body; + let mut body = analyzer.body; + + let mut blocks_to_update: Vec = Vec::new(); + for (id, block) in body.blocks.iter_enumerated() { + if !block.set_term_called { + blocks_to_update.push(id); + } + } + for id in blocks_to_update { + body.set_terminator(id, Terminator::Ret); + } + + *self.res.def_mut(owner).expect_body() = body; self.parent = old_parent; } @@ -2449,6 +2528,10 @@ impl Visit for FunctionCollector<'_> { analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd)), ); + + analyzer + .body + .set_terminator(analyzer.block, Terminator::Ret); *self.res.def_mut(owner).expect_body() = analyzer.body; self.parent = old_parent; } @@ -2576,7 +2659,21 @@ impl FunctionCollector<'_> { }; if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); - let body = analyzer.body; + let mut body = analyzer.body; + + let mut blocks_to_update: Vec = Vec::new(); + for (id, block) in body.blocks.iter_enumerated() { + if !block.set_term_called { + blocks_to_update.push(id); + } + } + + // Ensures that instructions of all blocks from body.blockbuilders + // are moved to body.blocks + for id in blocks_to_update { + body.set_terminator(id, Terminator::Ret); + } + *self.res.def_mut(owner).expect_body() = body; } } @@ -3111,7 +3208,17 @@ impl Visit for GlobalCollector<'_> { } } analyzer.lower_stmts(all_module_items.as_slice()); - let body = analyzer.body; + let mut body = analyzer.body; + + let mut blocks_to_update: Vec = Vec::new(); + for (id, block) in body.blocks.iter_enumerated() { + if !block.set_term_called { + blocks_to_update.push(id); + } + } + for id in blocks_to_update { + body.set_terminator(id, Terminator::Ret); + } *self.res.def_mut(owner).expect_body() = body; } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 6e2e979e..eaad7477 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -16,6 +16,7 @@ use std::slice; use forge_utils::create_newtype; use forge_utils::FxHashMap; +use petgraph::algo::dominators; use smallvec::smallvec; use smallvec::smallvec_inline; use smallvec::SmallVec; @@ -43,7 +44,7 @@ use crate::interp::ProjectionVec; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); create_newtype! { - pub struct BasicBlockId(u32); + pub struct BasicBlockId(pub u32); } #[derive(Clone, Debug)] @@ -52,9 +53,8 @@ pub struct BranchTargets { branch: SmallVec<[BasicBlockId; 2]>, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub enum Terminator { - #[default] Ret, Goto(BasicBlockId), Throw, @@ -104,10 +104,17 @@ pub enum Rvalue { Template(Template), } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct BasicBlock { pub insts: Vec, pub term: Terminator, + pub set_term_called: bool, // represents whether or not we've + // moved over its corresponding BasicBlockBuilder +} + +#[derive(Clone, Debug, Default)] +pub struct BasicBlockBuilder { + pub insts: Vec, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -141,6 +148,8 @@ pub struct Body { pub def_id_to_vars: FxHashMap, pub class_instantiations: HashMap, predecessors: OnceCell>>, + pub dominator_tree: OnceCell, + pub blockbuilders: TiVec, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -233,6 +242,12 @@ create_newtype! { pub struct VarId(pub u32); } +#[derive(Clone, Debug, Hash, Default)] +pub struct DomTree { + pub idom: Vec, // maybe change to BasicBlockId later + pub frontiers: Vec>, +} + #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Variable { pub(crate) base: Base, @@ -284,6 +299,17 @@ impl BasicBlock { } } +#[derive(Clone, Debug, Copy)] +struct Arc { + // represents an edge + v: u32, // destination vertex of an arc/edge; the u32 represents BasicBlockId + next: Option, // index of the next arc in the list of arcs; None if this is the last arc + // TODO: should v be a BasicBlockId or a usize? or u32 +} + +const N: usize = 100000; // max number of nodes/bbs +const M: usize = 500000; // max number of edges + impl Body { #[inline] fn new() -> Self { @@ -291,12 +317,19 @@ impl Body { Self { vars: local_vars, owner: None, - blocks: vec![BasicBlock::default()].into(), + blocks: vec![BasicBlock { + insts: Vec::new(), + term: Terminator::Ret, + set_term_called: false, + }] + .into(), values: FxHashMap::default(), class_instantiations: Default::default(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), predecessors: Default::default(), + dominator_tree: Default::default(), + blockbuilders: vec![BasicBlockBuilder { insts: Vec::new() }].into(), } } @@ -318,6 +351,11 @@ impl Body { self.vars.iter_enumerated() } + #[inline] + pub(crate) fn iter_cfg_enumerated(&self) -> impl Iterator { + self.build_cfg_vec().into_iter() + } + pub(crate) fn iter_block_keys( &self, ) -> impl ExactSizeIterator + DoubleEndedIterator + '_ { @@ -331,6 +369,14 @@ impl Body { self.blocks.iter_enumerated() } + #[inline] + pub(crate) fn iter_blockbuilders_enumerated( + &self, + ) -> impl ExactSizeIterator + DoubleEndedIterator + { + self.blockbuilders.iter_enumerated() + } + #[inline] pub(crate) fn owner(&self) -> Option { self.owner @@ -377,18 +423,232 @@ impl Body { #[inline] pub(crate) fn new_block(&mut self) -> BasicBlockId { - self.blocks.push_and_get_key(BasicBlock::default()) + self.new_block_with_terminator(Terminator::Ret) } pub(crate) fn new_blocks(&mut self) -> [BasicBlockId; NUM] { array::from_fn(|_| self.new_block()) } + #[inline] + pub(crate) fn new_blockbuilder(&mut self) -> BasicBlockId { + self.blockbuilders + .push_and_get_key(BasicBlockBuilder::default()) + } + + pub(crate) fn new_blockbuilders(&mut self) -> [BasicBlockId; NUM] { + array::from_fn(|_| self.new_blockbuilder()) + } + #[inline] pub(crate) fn new_block_with_terminator(&mut self, term: Terminator) -> BasicBlockId { self.blocks.push_and_get_key(BasicBlock { + insts: Vec::new(), term, - ..Default::default() + set_term_called: false, + }) + } + + // for building up the incoming/outgoing vectors + // returns a vector in format of: [(from, to), ...] + fn build_cfg_vec(&self) -> Vec<(u32, u32)> { + // the u32's represent BasicBlockIds + let mut edges = vec![]; + for (bb_id, block) in self.iter_blocks_enumerated() { + match block.successors() { + Successors::Return => {} + Successors::One(s) => edges.push((bb_id.0, s.0)), + Successors::Two(s1, s2) => { + edges.push((bb_id.0, s1.0)); + edges.push((bb_id.0, s2.0)); + } + } + } + edges + } + + // using semi-nca algo from https://maskray.me/blog/2020-12-11-dominator-tree + fn build_dom_tree(&self, cfg: &Vec<(u32, u32)>) -> Vec { + // declare vars + let mut outgoing = vec![None; N]; // e + let mut incoming = vec![None; N]; // ee + + let mut pool: Vec = Vec::new(); + + // build graph; main() fn from the algo + // u->v :: from->to + for &(u, v) in cfg { + // add edge 'u->v' to u's outgoing edges + pool.push(Arc { + v, + next: outgoing[u as usize], + }); + outgoing[u as usize] = Some(pool.len() - 1); + + // add edge 'u->v' to v's incoming edges + pool.push(Arc { + v: u, + next: incoming[v as usize], + }); + incoming[v as usize] = Some(pool.len() - 1); + } + + // semi-nca algo + let mut tick = 0; + let mut dfn: Vec = vec![-1; N]; + let mut rdfn = vec![0; N]; + let mut uf = vec![0; N]; + let mut sdom = vec![0; N]; + let mut best: Vec = vec![0; N]; + let mut idom = vec![-1; N]; + + // call dfs + Self::dfs( + 0, + &mut tick, + &mut dfn, + &mut rdfn, + &mut uf, + &mut outgoing, + &pool, + ); + + // iota equivalent + for (i, value) in best.iter_mut().enumerate() { + *value = i as i32; + } + + for i in (1..tick).rev() { + let v = rdfn[i as usize]; // values of rdfn match up with the indicies + let mut u; + sdom[v as usize] = v; // essentially sdom values match rdfn values (?) + + let mut a = incoming[v as usize]; + while let Some(_arc_index) = a { + // let arc = &pool[arc_index]; + u = pool[a.unwrap()].v; + if dfn[u as usize] != -1 { + Self::eval(u.try_into().unwrap(), i as i32, &dfn, &mut best, &mut uf); + if dfn[best[u as usize] as usize] < dfn[sdom[v as usize] as usize] { + sdom[v as usize] = best[u as usize]; + } + } + a = pool[a.unwrap()].next; + } + + best[v as usize] = sdom[v as usize]; + idom[v as usize] = uf[v as usize]; + } + + for i in 1..tick { + let v = rdfn[i as usize]; + while dfn[idom[v as usize] as usize] > dfn[sdom[v as usize] as usize] { + idom[v as usize] = idom[idom[v as usize] as usize]; + } + } + idom + } + + // dfs; helper for semi-nca algo in build_dom_tree() + fn dfs( + u: usize, + tick: &mut u32, + dfn: &mut Vec, + rdfn: &mut Vec, + uf: &mut Vec, + outgoing: &mut Vec>, + pool: &Vec, + ) { + dfn[u] = *tick as i32; + rdfn[*tick as usize] = u as i32; + *tick += 1; + + let mut a = outgoing[u]; + while let Some(arc_index) = a { + let arc = &pool[arc_index]; + let v = arc.v; + if dfn[v as usize] < 0 { + uf[v as usize] = u as i32; + Self::dfs(v as usize, tick, dfn, rdfn, uf, outgoing, pool); + } + a = arc.next; + } + } + + // eval; helper for semi-nca algo in build_dom_tree() + fn eval(v: usize, cur: i32, dfn: &Vec, best: &mut Vec, uf: &mut Vec) -> i32 { + if dfn[v] <= cur { + return v.try_into().unwrap(); + } + let u = uf[v]; + let r = Self::eval(u.try_into().unwrap(), cur, dfn, best, uf); + if dfn[best[u as usize] as usize] < dfn[best[v] as usize] { + best[v] = best[u as usize]; + } + uf[v] = r; + r + } + + // returns true if a dominates b; false otherwise + // a dominates b if every path from entry -> b must go through a; [entry...a...b] + pub(crate) fn dominates(&self, a: BasicBlockId, b: BasicBlockId) -> bool { + let dom_tree = self.dominator_tree(); + let idom = &dom_tree.idom; + + let a = a.0 as i32; + let b = b.0 as i32; + + let mut val = b as usize; + while idom[val] != -1 { + if idom[val] == a { + return true; + } + val = idom[val] as usize; + } + false + } + + // returns an iterator over the dominance frontier of a basic block + pub(crate) fn dominance_frontier( + &self, + b: BasicBlockId, + ) -> impl Iterator + '_ { + let dom_tree = self.dominator_tree(); + let ret_frontier = dom_tree.frontiers[b.0 as usize].clone(); + ret_frontier.into_iter() + } + + fn build_dom_frontier(&self, idom: &[i32]) -> Vec> { + let mut frontiers: Vec> = Vec::new(); + for _ in 0..self.blocks.len() { + frontiers.push(Vec::new()); + } + + // algorithm from https://en.wikipedia.org/wiki/Static_single-assignment_form#Computing_minimal_SSA_using_dominance_frontiers + // which references: https://www.cs.tufts.edu/comp/150FP/archive/keith-cooper/dom14.pdf + for (id, _) in self.iter_blocks_enumerated() { + if self.predecessors(id).len() >= 2 { + for pred in self.predecessors(id) { + let mut runner = pred.0; + while runner != idom[id.0 as usize] as u32 { + frontiers[runner as usize].push(id); + runner = idom[runner as usize] as u32; + } + } + } + } + frontiers + } + + pub(crate) fn dominator_tree(&self) -> &DomTree { + self.dominator_tree.get_or_init(|| { + let cfg = self.build_cfg_vec(); + let dom_tree = self.build_dom_tree(&cfg); + let dom_frontier = self.build_dom_frontier(&dom_tree); + DomTree { + idom: dom_tree, + frontiers: dom_frontier, + } }) } @@ -398,7 +658,7 @@ impl Body { for (bb, block) in self.iter_blocks_enumerated() { match block.successors() { Successors::Return => {} - Successors::One(s) => preds[s].push(bb), + Successors::One(s) => preds[s].push(bb), // pushes self's block on the predecessor list of the successor block Successors::Two(s1, s2) => { preds[s1].push(bb); preds[s2].push(bb); @@ -409,14 +669,37 @@ impl Body { })[block] } + // Moves all instructions of a given block from body.blockbuilders[bb] + // to body.blocks[bb], and sets its terminator. + // + // TODO: Returning the old terminator may not be necessary #[inline] pub(crate) fn set_terminator(&mut self, bb: BasicBlockId, term: Terminator) -> Terminator { - mem::replace(&mut self.blocks[bb].term, term) + let builder_insts = std::mem::take(&mut self.blockbuilders[bb].insts); + + let block = BasicBlock { + insts: builder_insts, + term, + set_term_called: true, + }; + + let old_block = mem::replace(&mut self.blocks[bb], block); + old_block.term + } + + // Returns the terminator of a given block if it has been set, otherwise returns None. + #[inline] + pub(crate) fn get_terminator(&mut self, bb: BasicBlockId) -> Option { + if self.blocks[bb].set_term_called { + Some(self.blocks[bb].term.clone()) + } else { + None + } } #[inline] pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { - self.blocks[bb].insts.push(inst); + self.blockbuilders[bb].insts.push(inst); } pub(crate) fn resolve_prop(&mut self, bb: BasicBlockId, opnd: Operand) -> Projection { @@ -506,12 +789,12 @@ impl Body { #[inline] pub(crate) fn push_assign(&mut self, bb: BasicBlockId, var: Variable, val: Rvalue) { - self.blocks[bb].insts.push(Inst::Assign(var, val)); + self.blockbuilders[bb].insts.push(Inst::Assign(var, val)); } #[inline] pub(crate) fn push_expr(&mut self, bb: BasicBlockId, val: Rvalue) { - self.blocks[bb].insts.push(Inst::Expr(val)); + self.blockbuilders[bb].insts.push(Inst::Expr(val)); } #[inline] @@ -636,6 +919,17 @@ impl<'a> IntoIterator for &'a BasicBlock { } } +impl<'a> IntoIterator for &'a BasicBlockBuilder { + type Item = &'a Inst; + + type IntoIter = slice::Iter<'a, Inst>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.insts.iter() + } +} + impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -718,6 +1012,15 @@ impl fmt::Display for BasicBlock { } } +impl fmt::Display for BasicBlockBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for inst in &self.insts { + writeln!(f, " {inst}")?; + } + write!(f, " ") + } +} + impl fmt::Display for UnOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { diff --git a/crates/forge_analyzer/src/pretty.rs b/crates/forge_analyzer/src/pretty.rs index aa4b112d..efcccdb9 100644 --- a/crates/forge_analyzer/src/pretty.rs +++ b/crates/forge_analyzer/src/pretty.rs @@ -2,7 +2,7 @@ use std::io::{self, Write}; use crate::{ definitions::Environment, - ir::{Body, VarKind}, + ir::{BasicBlockId, Body, VarKind}, }; impl Environment { @@ -26,6 +26,27 @@ impl Environment { tracing::error!("Error dumping IR: {e}"); } } + + pub fn dump_tree(&self, output: &mut dyn Write, func_name: &str) { + let Some(body_map) = self + .resolver + .names + .iter_enumerated() + .find_map(|(id, name)| { + if *func_name == *name { + self.def_ref(id).to_body() + } else { + None + } + }) + else { + eprintln!("No function named {func_name}"); + return; + }; + if let Err(e) = dump_dom_tree(output, self, body_map) { + tracing::error!("Error dumping IR: {e}"); + } + } } pub fn dump_ir(output: &mut dyn Write, env: &Environment, body: &Body) -> io::Result<()> { @@ -51,5 +72,67 @@ pub fn dump_ir(output: &mut dyn Write, env: &Environment, body: &Body) -> io::Re for (id, block) in body.iter_blocks_enumerated() { writeln!(output, "{id}:\n{block}")?; } + + writeln!(output)?; + dump_dom_tree(output, env, body)?; + + Ok(()) +} + +pub fn dump_dom_tree(output: &mut dyn Write, _env: &Environment, body: &Body) -> io::Result<()> { + writeln!(output, "----------------------------------------------")?; + writeln!(output, "Checking Control Flow Graph\n")?; + + for (a, b) in body.iter_cfg_enumerated() { + writeln!(output, "Edge: ({a}, {b})")?; + } + + writeln!(output, "----------------------------------------------")?; + let num_blocks = body.blocks.len(); + writeln!( + output, + "Dominator Tree idom: {:?}", + &body.dominator_tree().idom[..num_blocks] + )?; + + writeln!(output, "Idoms in format of ")?; + for (block, idom) in body.dominator_tree().idom[..num_blocks].iter().enumerate() { + writeln!(output, "{}: {:?}", block, idom)?; + } + + writeln!(output, "----------------------------------------------")?; + for (block, _) in body.dominator_tree().idom[..num_blocks].iter().enumerate() { + write!(output, "Blocks that bb{} Dominates: ", block)?; + for i in 0..num_blocks { + if body.dominates(BasicBlockId(block as u32), BasicBlockId(i as u32)) { + write!(output, "{}, ", i)?; + } + } + writeln!(output)?; + } + + writeln!(output, "----------------------------------------------")?; + for (block, _) in body.dominator_tree().idom[..num_blocks].iter().enumerate() { + write!(output, "bb{}'s Dominators: ", block)?; + for i in 0..num_blocks { + if body.dominates(BasicBlockId(i as u32), BasicBlockId(block as u32)) { + write!(output, "{}, ", i)?; + } + } + writeln!(output)?; + } + + writeln!(output, "----------------------------------------------")?; + writeln!(output, "Dominance Frontiers:")?; + for (block, _) in body.iter_blocks_enumerated() { + write!(output, "bb{}: ", block.0)?; + let dominance_frontier = body.dominance_frontier(block); + + for frontier_block in dominance_frontier { + write!(output, "{}, ", frontier_block.0)?; + } + writeln!(output)?; + } + Ok(()) } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 07c92ca4..6749b4a6 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -46,6 +46,10 @@ pub struct Args { #[arg(long)] dump_ir: Option, + /// Dump the Dominator Tree for specified file + #[arg(long)] + dump_dt: Option, + /// A specific function to scan. Must be an entrypoint specified in `manifest.yml` #[arg(short, long)] function: Option, @@ -171,6 +175,11 @@ pub(crate) fn scan_directory<'a>( std::process::exit(0); } + if let Some(func) = opts.dump_dt.as_ref() { + proj.env.dump_tree(&mut std::io::stdout().lock(), func); + std::process::exit(0); + } + let permissions = permissions_declared.into_iter().collect::>(); let (jira_permission_resolver, jira_regex_map) = get_permission_resolver_jira(); diff --git a/test-apps/basic/package.json b/test-apps/basic/package.json index 36ceb84b..122f4e80 100644 --- a/test-apps/basic/package.json +++ b/test-apps/basic/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "@forge/api": "3.2.0", - "@forge/ui": "1.11.0" + "@forge/ui": "1.11.0", + "atlassian-jwt": "^2.0.3", + "jsonwebtoken": "^9.0.2" } } diff --git a/test-apps/basic/src/index.tsx b/test-apps/basic/src/index.tsx index 34db10d6..8ee8d4ce 100644 --- a/test-apps/basic/src/index.tsx +++ b/test-apps/basic/src/index.tsx @@ -1,51 +1,272 @@ -import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui'; -import api, { route, fetch } from '@forge/api'; -import { testFn } from './test'; +function reassign() { + let a = 3; + let b = 4; + a = 1; +} -const foo = () => { - const res = api.asApp().requestConfluence(route`/rest/api/3/test`); - test_function("hi") - return res; -}; +function retimmed() { + return +} -let test_function = (word) => { - console.log(word); - let test_var = "test_var"; +function reassign2() { + let a = 3; + let b = 4; + a = 1; + return } -const App = () => { +function updatetest() { + let i = 0; + i++; +} - let testObjectOther = { - someFunction(): any { - let a = "b"; - } +function updatetest2() { + let i = 0; + i = i + 1; +} + +// function foo(a) { +// let b = a - 20; +// let c = b + 1; +// b = 10; +// return b * 10; +// } + +// function reassign_expr() { +// let a = 4; +// let b = "7"; +// b = "7" + a; +// let c = a + b; +// } + +// w/ control flow +function newfunc() { + let a = 3; + if (a > 2) { + a = 1; + let b = a; + } + let c = a; +} + +function newfunc2() { + let a = 3; + if (a > 2) { + a = 1; + let b = a; + } else { + a = 2; + let b = a; + } + let c = a; +} + +function compnewfunc2() { + let a = Math.random(); + let b = 0; + if (a >= 0.5) { + if (a > 0.5) { + a = a + 1; + } else { + a = a + 2; } + b = a + 3; + } + let c = a; +} - let testObject = { - someFunction() { - const res = api.asApp().requestConfluence(route`/rest/api/3/test`); - test_function("hi") - return res; +function compnewfunc3() { + let a = Math.random(); // bb0 + let b = 0; + if (a >= 0.5) { + if (a > 0.5) { // bb1 + a = a + 1; // bb4 + } else { + a = a + 2; // bb6 } + b = a + 3; // bb5 + } else { // bb3 + b = a + 4; } + let c = a; // bb2 +} - let value = "value" +function forloop() { + let a = 0; + for (let i = 0; i < 10; i = i + 1) { + a = a + i; + } + return a; +} - let h = { headers: { authorization: "test" } } - h.headers.authorization = process.env.SECRET - h.headers.authorization = `test ${value}` +function whileloop() { + let a = 0; + let i = 0; + while (i < 10) { + a = a + i; // bb3 + i = i + 1; + } + return a; +} +function nestedforloop() { + let a = 0; + for (let i = 0; i < 10; i = i + 1) { + for (let j = 0; j < 10; j = j + 1) { + a = a + i + j; + } + } + return a; +} - fetch("url", h) +function nestedwhileloop() { + let a = 0; + let i = 0; // bb0 + while (i < 10) { // bb1 + let j = 0; // bb3 + while (j < 10) { // bb4 + a = a + i + j; // bb6 + j = j + 1; + } + i = i + 1; // bb5 + } + return a; // bb2 +} - foo(); - test_function("test_word"); - testFn(); - return ( - - Hello world! - - ); -}; +function whileandcond() { + let a = 0; + let i = 0; // bb0 + while (i < 10) { // bb1 + if (i > 5) { // bb3 + a = a + i; // bb4 + } + i++; // bb5 + } + return a; // bb2 +} + +function whileandcond2() { + let a = 0; + let i = 0; // bb0 + while (i < 10) { // bb1 + if (i > 5) { // bb3 + a = a + 1; // bb4 + } else { + a = a + 2; // bb6 + } + i++; // bb5 + } + return a; // bb2 +} + +function whileincond() { + let a = 0; + let b = Math.random(); // bb0 + if (b < 0.5) { // bb1 + while (a < 2) { // bb4 + a = a + b; // bb6 + } + // bb5 + } else { + a = 1; // bb3 + } + return a; // bb2 +} +function earlyretforloop() { + for (let i = 0; i < 10; i = i + 1) { + let a = Math.random(); + if (a > 0.5) { + return; + } + } +} + +function earlyretwhileloop() { + let i = 0; // bb0 + while (i < 10) { // bb1 + let a = Math.random(); // bb3 + if (a > 0.5) { + return; // bb4 + } + i = i + 1; // bb5 + } + // bb2 +} + +// early return in `if` only +function earlyret() { + let a = 0; + if (a > 5) { // bb0 + a = 1; + return a; // bb1 + } + let b = 4; + return a; // bb2 +} + +// early return in `else` only +function earlyret2() { + let a = 0; + if (a > 5) { + a = 1; + } else { + a = 2; + return a; + let c = 3; + } + let b = 4; + return a; +} + +// early return in 'if' and 'else' +function earlyret3() { + let a = 0; + if (a > 5) { + a = 1; + return a; + } else { + a = 2; + return a; + } + let b = 4; // a bb still gets created, it's just + // never jumped to. check if this is OK? + // assuming that this is how compilers work + // to detect unreachable code + return a; +} + +// function earlyretloop() { +// let a = 0; +// let i = 0; +// while (i < 10) { +// if (i > 5) { +// return 1; +// } +// i = i + 1; +// } +// } + +function loopbreak() { + let a = 0; + let i = 0; // bb0 + while (i < 10) { // bb1 + if (i > 5) { // bb3 + a = 1; // bb4 + break; + } + i = i + 1; // bb5 + } + return a; // bb2 +} -export const run = render(} />); +// ignore below for now +// function nestedforloop() { +// let a = 0; // bb0 +// for (let i = 0; i < 10; i++) { // i = 0 is in bb0; cond in bb1; i++ in bb4 +// for (let j = 0; j < 10; j++) { // j = 0 in bb3; cond in bb4 +// a = a + i + j; //bb6 +// } +// let b = 3; +// } +// return a; // bb2 +// }