Skip to main content

ddc_dec/
cfg.rs

1//! Basic-block splitting and CFG construction for Dalvik code units.
2//!
3//! Mirrors jcdc's `block.rs` but for a register machine: leaders come from
4//! branch targets, post-branch pcs, try boundaries and handler entries.
5//! Payload pseudo-instructions are never leaders (they are data).
6
7use ddc_dex::code::CodeItem;
8use ddc_dex::insn::{Insn, InsnKind, Payload};
9
10#[derive(Debug, Clone)]
11pub struct Block {
12    pub id: usize,
13    /// First code unit (inclusive).
14    pub start: u32,
15    /// End code unit (exclusive).
16    pub end: u32,
17    /// Instruction INDEX range `[lo, hi)` into the code item's linear
18    /// `insns` stream (blocks are contiguous pc ranges of a linear decode —
19    /// the previous per-block `Vec<Insn>` CLONE was a top allocation cost).
20    pub ins_lo: usize,
21    pub ins_hi: usize,
22    /// Successors. Conditional: `[fallthrough, taken]`; switch: default
23    /// first, then key order.
24    pub succ: Vec<usize>,
25    pub pred: Vec<usize>,
26    /// Exception range indices whose handler this block is.
27    pub handlers: Vec<usize>,
28}
29
30#[derive(Debug, Clone)]
31pub struct ExcRange {
32    /// Code-unit range `[start, end)`.
33    pub start: u32,
34    pub end: u32,
35    /// Handler entry code unit.
36    pub handler: u32,
37    /// `None` = catch-all.
38    pub catch_type: Option<String>,
39}
40
41pub struct DexCfg {
42    pub blocks: Vec<Block>,
43    pub entry: usize,
44    pub exc_ranges: Vec<ExcRange>,
45    /// The method's linear decode stream, moved in from the `CodeItem`.
46    /// Blocks reference it via `ins_lo..ins_hi` index ranges.
47    pub insns: Vec<Insn>,
48    /// Total code units (pc of one-past-the-last instruction).
49    pub code_units: u32,
50    /// Block start pcs sorted for `block_at`.
51    starts: Vec<u32>,
52}
53
54impl DexCfg {
55    /// Build the CFG for one method body. `type_name` resolves type ids for
56    /// catch types. Takes the `CodeItem` by `&mut` and MOVES its `insns`
57    /// stream into the CFG — blocks then index into it instead of each
58    /// owning a deep clone (the old per-block `Vec<Insn>` copies summed to a
59    /// second full decode of every method; on weibo that was tens of
60    /// millions of instruction clones).
61    pub fn build(code: &mut CodeItem, type_name: &dyn Fn(u32) -> String) -> DexCfg {
62        let insns = std::mem::take(&mut code.insns);
63        let code_units: u32 = insns.last().map(|last| last.pc + last.size).unwrap_or(0);
64
65        // 1. Leaders.
66        let mut leaders: Vec<u32> = vec![0];
67        for ins in insns.iter() {
68            let next = ins.pc + ins.size;
69            if ins.is_branch() && next <= code_units {
70                leaders.push(next);
71            }
72            for t in insn_targets(ins) {
73                leaders.push(t);
74            }
75        }
76        // Try boundaries and handler entries.
77        let mut ranges: Vec<ExcRange> = Vec::new();
78        for t in &code.tries {
79            let end = t.start_addr.saturating_add(t.insn_count);
80            if t.start_addr >= end || t.start_addr >= code_units {
81                continue;
82            }
83            leaders.push(t.start_addr);
84            if end < code_units {
85                leaders.push(end);
86            }
87            if let Some(h) = code.handler_of(t) {
88                for (ty, addr) in &h.catches {
89                    if *addr < code_units {
90                        leaders.push(*addr);
91                        ranges.push(ExcRange {
92                            start: t.start_addr,
93                            end,
94                            handler: *addr,
95                            catch_type: Some(type_name(*ty)),
96                        });
97                    }
98                }
99                if let Some(addr) = h.catch_all {
100                    if addr < code_units {
101                        leaders.push(addr);
102                        ranges.push(ExcRange {
103                            start: t.start_addr,
104                            end,
105                            handler: addr,
106                            catch_type: None,
107                        });
108                    }
109                }
110            }
111        }
112        leaders.retain(|&l| l < code_units || (l == 0 && code_units == 0));
113        leaders.sort_unstable();
114        leaders.dedup();
115        if leaders.is_empty() {
116            leaders.push(0);
117        }
118
119        // 2. pc → block id (last leader <= pc).
120        let block_of_pc = |p: u32| -> Option<usize> {
121            let i = leaders.partition_point(|&l| l <= p);
122            if i == 0 {
123                None
124            } else {
125                Some(i - 1)
126            }
127        };
128
129        // 3. Blocks as index ranges over the linear instruction stream.
130        // (pc-sorted insns; a block's instructions are exactly the stream
131        // slice whose pcs fall in [start, end).)
132        let mut blocks: Vec<Block> = Vec::with_capacity(leaders.len());
133        for (id, &start) in leaders.iter().enumerate() {
134            let end = leaders
135                .get(id + 1)
136                .copied()
137                .unwrap_or(code_units)
138                .max(start);
139            let lo = insns.partition_point(|i| i.pc < start);
140            let hi = insns.partition_point(|i| i.pc < end);
141            blocks.push(Block {
142                id,
143                start,
144                end,
145                ins_lo: lo,
146                ins_hi: hi,
147                succ: Vec::new(),
148                pred: Vec::new(),
149                handlers: Vec::new(),
150            });
151        }
152
153        // 4. Successors.
154        for b in blocks.iter_mut() {
155            if b.ins_hi <= b.ins_lo {
156                continue;
157            }
158            let last = &insns[b.ins_hi - 1];
159            match &last.kind {
160                InsnKind::Goto { target } => {
161                    if let Some(tb) = block_of_pc(*target) {
162                        b.succ.push(tb);
163                    }
164                }
165                InsnKind::If { target, .. } => {
166                    let next_pc = last.pc + last.size;
167                    if let Some(f) = block_of_pc(next_pc) {
168                        b.succ.push(f);
169                    }
170                    if let Some(tb) = block_of_pc(*target) {
171                        b.succ.push(tb);
172                    }
173                }
174                InsnKind::PackedSwitch { payload_pc, .. }
175                | InsnKind::SparseSwitch { payload_pc, .. } => {
176                    let next_pc = last.pc + last.size;
177                    if let Some(f) = block_of_pc(next_pc) {
178                        b.succ.push(f); // default arm first
179                    }
180                    for t in switch_targets(code.payloads.get(payload_pc), last.pc) {
181                        if let Some(tb) = block_of_pc(t) {
182                            if !b.succ.contains(&tb) {
183                                b.succ.push(tb);
184                            }
185                        }
186                    }
187                }
188                InsnKind::ReturnVoid | InsnKind::Return { .. } | InsnKind::Throw { .. } => {}
189                _ => {
190                    let next_pc = last.pc + last.size;
191                    if next_pc < code_units {
192                        if let Some(f) = block_of_pc(next_pc) {
193                            b.succ.push(f);
194                        }
195                    }
196                }
197            }
198        }
199        for b in blocks.iter_mut() {
200            let mut seen = Vec::new();
201            b.succ.retain(|s| {
202                if seen.contains(s) {
203                    false
204                } else {
205                    seen.push(*s);
206                    true
207                }
208            });
209        }
210
211        // 5. Predecessors + handler back-references (jcdc semantics: a block
212        // is protected when its FIRST offset lies in [start, end)).
213        for b in 0..blocks.len() {
214            let succ = blocks[b].succ.clone();
215            for s in succ {
216                if s < blocks.len() {
217                    blocks[s].pred.push(b);
218                }
219            }
220        }
221        for (ri, r) in ranges.iter().enumerate() {
222            if let Some(h) = blocks.iter().position(|b| b.start == r.handler) {
223                blocks[h].handlers.push(ri);
224            }
225        }
226        for b in blocks.iter_mut() {
227            b.pred.sort_unstable();
228            b.pred.dedup();
229            b.handlers.sort_unstable();
230            b.handlers.dedup();
231        }
232
233        let mut starts = leaders;
234        starts.sort_unstable();
235        DexCfg {
236            blocks,
237            entry: 0,
238            exc_ranges: ranges,
239            insns,
240            code_units,
241            starts,
242        }
243    }
244
245    /// The block's instructions (slice of the linear decode stream).
246    pub fn block_ins(&self, b: &Block) -> &[Insn] {
247        &self.insns[b.ins_lo..b.ins_hi]
248    }
249
250    /// The block's last instruction.
251    pub fn block_last(&self, b: &Block) -> Option<&Insn> {
252        if b.ins_hi > b.ins_lo {
253            self.insns.get(b.ins_hi - 1)
254        } else {
255            None
256        }
257    }
258
259    pub fn block_at(&self, p: u32) -> Option<usize> {
260        let i = self.starts.partition_point(|&l| l <= p);
261        if i == 0 {
262            None
263        } else {
264            Some(i - 1)
265        }
266    }
267
268    /// Exception edges `(range idx, from block, to block)` derived from the
269    /// protected-region rule (first offset inside `[start, end)`, has code).
270    pub fn exc_edges(&self) -> Vec<(usize, usize, usize)> {
271        let mut out = Vec::new();
272        for (ri, r) in self.exc_ranges.iter().enumerate() {
273            if let Some(h) = self.blocks.iter().position(|b| b.start == r.handler) {
274                for b in &self.blocks {
275                    if b.start >= r.start && b.start < r.end && b.ins_hi > b.ins_lo {
276                        out.push((ri, b.id, h));
277                    }
278                }
279            }
280        }
281        out
282    }
283
284    /// Machine-neutral view for the shared structurer (ids preserved).
285    pub fn to_core(&self) -> jdc_core::cfg::Cfg {
286        let blocks = self
287            .blocks
288            .iter()
289            .map(|b| jdc_core::cfg::Block {
290                id: b.id,
291                start: b.start,
292                end: b.end,
293                ins_len: (b.ins_hi - b.ins_lo) as u32,
294                succ: b.succ.clone(),
295                pred: b.pred.clone(),
296                handlers: b.handlers.iter().map(|&h| h as u32).collect(),
297            })
298            .collect();
299        let ranges = self
300            .exc_ranges
301            .iter()
302            .map(|r| jdc_core::cfg::ExcRange {
303                start: r.start,
304                end: r.end,
305                handler: r.handler,
306                catch_type: r.catch_type.clone(),
307            })
308            .collect();
309        let edges = self
310            .exc_edges()
311            .into_iter()
312            .map(|(range, from, to)| jdc_core::cfg::ExcEdge { range, from, to })
313            .collect();
314        jdc_core::cfg::Cfg::from_parts(blocks, self.entry, ranges, edges)
315    }
316}
317
318/// Absolute branch targets of one instruction.
319fn insn_targets(ins: &Insn) -> Vec<u32> {
320    match &ins.kind {
321        InsnKind::Goto { target } => vec![*target],
322        InsnKind::If { target, .. } => vec![*target],
323        // Payload locations are data, not control targets.
324        _ => Vec::new(),
325    }
326}
327
328/// Absolute case targets of a switch from its payload.
329pub fn switch_targets(payload: Option<&Payload>, switch_pc: u32) -> Vec<u32> {
330    match payload {
331        Some(Payload::Packed { targets, .. }) => targets
332            .iter()
333            .map(|t| (switch_pc as i64 + *t as i64) as u32)
334            .collect(),
335        Some(Payload::Sparse { pairs }) => pairs
336            .iter()
337            .map(|(_, t)| (switch_pc as i64 + *t as i64) as u32)
338            .collect(),
339        _ => Vec::new(),
340    }
341}
342
343/// Switch keys for `Term::Switch` construction.
344pub fn switch_keys(payload: Option<&Payload>) -> SwitchKeys {
345    match payload {
346        Some(Payload::Packed { first_key, targets }) => SwitchKeys::Table {
347            low: *first_key,
348            targets: targets.clone(),
349        },
350        Some(Payload::Sparse { pairs }) => SwitchKeys::Lookup {
351            pairs: pairs.clone(),
352        },
353        _ => SwitchKeys::None,
354    }
355}
356
357pub enum SwitchKeys {
358    Table { low: i32, targets: Vec<i32> },
359    Lookup { pairs: Vec<(i32, i32)> },
360    None,
361}