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<std::sync::Arc<str>>,
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) -> std::sync::Arc<str>) -> 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            // Switch CASE targets are control-flow leaders too. Ordinary
76            // switches get their case heads for free (each case ends in
77            // `goto break`, and a branch's fall-through is a leader) —
78            // but the weixin protobuf `op()` shape ends every case in a
79            // direct `return`, so NOTHING else marks the heads: all case
80            // bodies fused into one block, every switch edge resolved to
81            // that single block, and the structurer emitted empty
82            // `case 1..6: break;` with the bodies linearized after the
83            // switch (returns dropped — semantics change). The payload
84            // LOCATION is data; the offsets INSIDE it are targets.
85            match &ins.kind {
86                InsnKind::PackedSwitch { payload_pc, .. }
87                | InsnKind::SparseSwitch { payload_pc, .. } => {
88                    for t in switch_targets(code.payloads.get(payload_pc), ins.pc) {
89                        leaders.push(t);
90                    }
91                }
92                _ => {}
93            }
94        }
95        // Try boundaries and handler entries.
96        let mut ranges: Vec<ExcRange> = Vec::new();
97        for t in &code.tries {
98            let end = t.start_addr.saturating_add(t.insn_count);
99            if t.start_addr >= end || t.start_addr >= code_units {
100                continue;
101            }
102            leaders.push(t.start_addr);
103            if end < code_units {
104                leaders.push(end);
105            }
106            if let Some(h) = code.handler_of(t) {
107                for (ty, addr) in &h.catches {
108                    if *addr < code_units {
109                        leaders.push(*addr);
110                        ranges.push(ExcRange {
111                            start: t.start_addr,
112                            end,
113                            handler: *addr,
114                            catch_type: Some(type_name(*ty)),
115                        });
116                    }
117                }
118                if let Some(addr) = h.catch_all {
119                    if addr < code_units {
120                        leaders.push(addr);
121                        ranges.push(ExcRange {
122                            start: t.start_addr,
123                            end,
124                            handler: addr,
125                            catch_type: None,
126                        });
127                    }
128                }
129            }
130        }
131        leaders.retain(|&l| l < code_units || (l == 0 && code_units == 0));
132        leaders.sort_unstable();
133        leaders.dedup();
134        if leaders.is_empty() {
135            leaders.push(0);
136        }
137
138        // 2. pc → block id (last leader <= pc).
139        let block_of_pc = |p: u32| -> Option<usize> {
140            let i = leaders.partition_point(|&l| l <= p);
141            if i == 0 {
142                None
143            } else {
144                Some(i - 1)
145            }
146        };
147
148        // 3. Blocks as index ranges over the linear instruction stream.
149        // (pc-sorted insns; a block's instructions are exactly the stream
150        // slice whose pcs fall in [start, end).)
151        let mut blocks: Vec<Block> = Vec::with_capacity(leaders.len());
152        for (id, &start) in leaders.iter().enumerate() {
153            let end = leaders
154                .get(id + 1)
155                .copied()
156                .unwrap_or(code_units)
157                .max(start);
158            let lo = insns.partition_point(|i| i.pc < start);
159            let hi = insns.partition_point(|i| i.pc < end);
160            blocks.push(Block {
161                id,
162                start,
163                end,
164                ins_lo: lo,
165                ins_hi: hi,
166                succ: Vec::new(),
167                pred: Vec::new(),
168                handlers: Vec::new(),
169            });
170        }
171
172        // 4. Successors.
173        for b in blocks.iter_mut() {
174            if b.ins_hi <= b.ins_lo {
175                continue;
176            }
177            let last = &insns[b.ins_hi - 1];
178            match &last.kind {
179                InsnKind::Goto { target } => {
180                    if let Some(tb) = block_of_pc(*target) {
181                        b.succ.push(tb);
182                    }
183                }
184                InsnKind::If { target, .. } => {
185                    let next_pc = last.pc + last.size;
186                    if let Some(f) = block_of_pc(next_pc) {
187                        b.succ.push(f);
188                    }
189                    if let Some(tb) = block_of_pc(*target) {
190                        b.succ.push(tb);
191                    }
192                }
193                InsnKind::PackedSwitch { payload_pc, .. }
194                | InsnKind::SparseSwitch { payload_pc, .. } => {
195                    let next_pc = last.pc + last.size;
196                    if let Some(f) = block_of_pc(next_pc) {
197                        b.succ.push(f); // default arm first
198                    }
199                    for t in switch_targets(code.payloads.get(payload_pc), last.pc) {
200                        if let Some(tb) = block_of_pc(t) {
201                            if !b.succ.contains(&tb) {
202                                b.succ.push(tb);
203                            }
204                        }
205                    }
206                }
207                InsnKind::ReturnVoid | InsnKind::Return { .. } | InsnKind::Throw { .. } => {}
208                _ => {
209                    let next_pc = last.pc + last.size;
210                    if next_pc < code_units {
211                        if let Some(f) = block_of_pc(next_pc) {
212                            b.succ.push(f);
213                        }
214                    }
215                }
216            }
217        }
218        for b in blocks.iter_mut() {
219            let mut seen = Vec::new();
220            b.succ.retain(|s| {
221                if seen.contains(s) {
222                    false
223                } else {
224                    seen.push(*s);
225                    true
226                }
227            });
228        }
229
230        // 5. Predecessors + handler back-references (jcdc semantics: a block
231        // is protected when its FIRST offset lies in [start, end)).
232        for b in 0..blocks.len() {
233            let succ = blocks[b].succ.clone();
234            for s in succ {
235                if s < blocks.len() {
236                    blocks[s].pred.push(b);
237                }
238            }
239        }
240        for (ri, r) in ranges.iter().enumerate() {
241            if let Some(h) = blocks.iter().position(|b| b.start == r.handler) {
242                blocks[h].handlers.push(ri);
243            }
244        }
245        for b in blocks.iter_mut() {
246            b.pred.sort_unstable();
247            b.pred.dedup();
248            b.handlers.sort_unstable();
249            b.handlers.dedup();
250        }
251
252        let mut starts = leaders;
253        starts.sort_unstable();
254        DexCfg {
255            blocks,
256            entry: 0,
257            exc_ranges: ranges,
258            insns,
259            code_units,
260            starts,
261        }
262    }
263
264    /// The block's instructions (slice of the linear decode stream).
265    pub fn block_ins(&self, b: &Block) -> &[Insn] {
266        &self.insns[b.ins_lo..b.ins_hi]
267    }
268
269    /// The block's last instruction.
270    pub fn block_last(&self, b: &Block) -> Option<&Insn> {
271        if b.ins_hi > b.ins_lo {
272            self.insns.get(b.ins_hi - 1)
273        } else {
274            None
275        }
276    }
277
278    pub fn block_at(&self, p: u32) -> Option<usize> {
279        let i = self.starts.partition_point(|&l| l <= p);
280        if i == 0 {
281            None
282        } else {
283            Some(i - 1)
284        }
285    }
286
287    /// Exception edges `(range idx, from block, to block)` derived from the
288    /// protected-region rule (first offset inside `[start, end)`, has code).
289    pub fn exc_edges(&self) -> Vec<(usize, usize, usize)> {
290        let mut out = Vec::new();
291        for (ri, r) in self.exc_ranges.iter().enumerate() {
292            if let Some(h) = self.blocks.iter().position(|b| b.start == r.handler) {
293                for b in &self.blocks {
294                    if b.start >= r.start && b.start < r.end && b.ins_hi > b.ins_lo {
295                        out.push((ri, b.id, h));
296                    }
297                }
298            }
299        }
300        out
301    }
302
303    /// Machine-neutral view for the shared structurer (ids preserved).
304    pub fn to_core(&self) -> jdc_core::cfg::Cfg {
305        let blocks = self
306            .blocks
307            .iter()
308            .map(|b| jdc_core::cfg::Block {
309                id: b.id,
310                start: b.start,
311                end: b.end,
312                ins_len: (b.ins_hi - b.ins_lo) as u32,
313                succ: b.succ.clone(),
314                pred: b.pred.clone(),
315                handlers: b.handlers.iter().map(|&h| h as u32).collect(),
316            })
317            .collect();
318        let ranges = self
319            .exc_ranges
320            .iter()
321            .map(|r| jdc_core::cfg::ExcRange {
322                start: r.start,
323                end: r.end,
324                handler: r.handler,
325                catch_type: r.catch_type.clone(),
326            })
327            .collect();
328        let edges = self
329            .exc_edges()
330            .into_iter()
331            .map(|(range, from, to)| jdc_core::cfg::ExcEdge { range, from, to })
332            .collect();
333        jdc_core::cfg::Cfg::from_parts(blocks, self.entry, ranges, edges)
334    }
335}
336
337/// Absolute branch targets of one instruction.
338fn insn_targets(ins: &Insn) -> Vec<u32> {
339    match &ins.kind {
340        InsnKind::Goto { target } => vec![*target],
341        InsnKind::If { target, .. } => vec![*target],
342        // Payload locations are data, not control targets.
343        _ => Vec::new(),
344    }
345}
346
347/// Absolute case targets of a switch from its payload.
348pub fn switch_targets(payload: Option<&Payload>, switch_pc: u32) -> Vec<u32> {
349    match payload {
350        Some(Payload::Packed { targets, .. }) => targets
351            .iter()
352            .map(|t| (switch_pc as i64 + *t as i64) as u32)
353            .collect(),
354        Some(Payload::Sparse { pairs }) => pairs
355            .iter()
356            .map(|(_, t)| (switch_pc as i64 + *t as i64) as u32)
357            .collect(),
358        _ => Vec::new(),
359    }
360}
361
362/// Switch keys for `Term::Switch` construction.
363pub fn switch_keys(payload: Option<&Payload>) -> SwitchKeys {
364    match payload {
365        Some(Payload::Packed { first_key, targets }) => SwitchKeys::Table {
366            low: *first_key,
367            targets: targets.clone(),
368        },
369        Some(Payload::Sparse { pairs }) => SwitchKeys::Lookup {
370            pairs: pairs.clone(),
371        },
372        _ => SwitchKeys::None,
373    }
374}
375
376pub enum SwitchKeys {
377    Table { low: i32, targets: Vec<i32> },
378    Lookup { pairs: Vec<(i32, i32)> },
379    None,
380}