Skip to main content

ddc_dex/
code.rs

1//! `code_item`: register frame, insns, try table and catch handlers.
2
3use std::collections::HashMap;
4
5use crate::insn::{decode_all, Insn, Payload};
6use crate::reader::Cursor;
7
8/// One decoded try region.
9#[derive(Debug, Clone)]
10pub struct TryItem {
11    /// First code unit covered (inclusive).
12    pub start_addr: u32,
13    /// Number of 16-bit code units covered.
14    pub insn_count: u32,
15    /// Index into [`CodeItem::handlers`].
16    pub handler_idx: usize,
17}
18
19/// One `encoded_catch_handler`.
20#[derive(Debug, Clone)]
21pub struct CatchHandler {
22    /// `(type id, handler address)` pairs, in priority order.
23    pub catches: Vec<(u32, u32)>,
24    /// Catch-all (`finally`) handler address.
25    pub catch_all: Option<u32>,
26}
27
28/// A decoded `code_item`.
29#[derive(Debug, Clone)]
30pub struct CodeItem {
31    pub registers_size: u16,
32    /// Incoming argument registers (the LAST `ins_size` registers).
33    pub ins_size: u16,
34    pub outs_size: u16,
35    pub debug_info_off: u32,
36    /// Linearly decoded instructions (payload bodies are NOT included —
37    /// they live in `payloads`).
38    pub insns: Vec<Insn>,
39    /// Payload pseudo-instructions keyed by their code-unit offset.
40    pub payloads: HashMap<u32, Payload>,
41    pub tries: Vec<TryItem>,
42    pub handlers: Vec<CatchHandler>,
43}
44
45impl CodeItem {
46    pub fn parse(data: &[u8], off: usize) -> Option<CodeItem> {
47        let mut c = Cursor::at(data, off);
48        let registers_size = c.u2()?;
49        let ins_size = c.u2()?;
50        let outs_size = c.u2()?;
51        let tries_size = c.u2()?;
52        let debug_info_off = c.u4()?;
53        let insns_size = c.u4()? as usize; // in 16-bit code units
54        let insns_off = c.pos;
55        if insns_off + 2 * insns_size > data.len() {
56            return None;
57        }
58        let insns_end = insns_off + 2 * insns_size;
59        let (insns, payloads) = decode_all(&data[insns_off..insns_end]);
60
61        // Optional 4-byte alignment padding before the try table.
62        let mut p = insns_end;
63        if tries_size != 0 && insns_size % 2 == 1 {
64            p += 2;
65        }
66
67        let mut tries = Vec::with_capacity(tries_size as usize);
68        let mut c = Cursor::at(data, p);
69        for _ in 0..tries_size {
70            let start_addr = c.u4()?;
71            let insn_count = c.u2()? as u32;
72            // Raw byte offset from the start of the handler list; resolved
73            // below once the list has been walked.
74            let handler_off = c.u2()? as usize;
75            tries.push(TryItem {
76                start_addr,
77                insn_count,
78                handler_idx: handler_off,
79            });
80        }
81
82        // encoded_catch_handler_list: uleb size, then handlers back to
83        // back. Present ONLY when tries exist (spec 8.3.1): the
84        // unconditional read used to consume whatever bytes followed the
85        // item — harmless inside a live image, fatal for exact-sized
86        // snapshot slices (parse bailed at the bound → accessor inlining
87        // silently off corpus-wide → 187 un-inlined `g.l.i(..)` shadow
88        // refs in reqable's flutter g.java alone).
89        let mut handlers: Vec<CatchHandler> = Vec::new();
90        if tries_size != 0 {
91            let list_off = c.pos;
92            let n_handlers = c.read_uleb128()? as usize;
93            let uleb_len = c.pos - list_off;
94            handlers.reserve(n_handlers);
95            // handler byte offset (from list start) → handler index.
96            let mut idx_by_off: HashMap<usize, usize> = HashMap::new();
97            for _ in 0..n_handlers {
98                let hoff = c.pos - list_off;
99                let h = Self::parse_handler(c)?;
100                idx_by_off.insert(hoff, handlers.len());
101                handlers.push(h);
102            }
103
104            // Resolve each try's handler_off. The spec's offsets are from
105            // the list start (the uleb size included in the offset space);
106            // some writers count from after it — accept both, then the
107            // first handler.
108            for t in tries.iter_mut() {
109                let raw = t.handler_idx;
110                t.handler_idx = idx_by_off
111                    .get(&raw)
112                    .or_else(|| idx_by_off.get(&(raw + uleb_len)))
113                    .or_else(|| idx_by_off.get(&raw.saturating_sub(uleb_len)))
114                    .copied()
115                    .unwrap_or(0);
116            }
117        }
118
119        Some(CodeItem {
120            registers_size,
121            ins_size,
122            outs_size,
123            debug_info_off,
124            insns,
125            payloads,
126            tries,
127            handlers,
128        })
129    }
130
131    fn parse_handler(mut c: Cursor<'_>) -> Option<CatchHandler> {
132        let sz = c.read_sleb128()?;
133        let mut h = CatchHandler {
134            catches: Vec::new(),
135            catch_all: None,
136        };
137        if sz != 0 {
138            for _ in 0..sz.unsigned_abs() {
139                let ty = c.read_uleb128()? as u32;
140                let addr = c.read_uleb128()? as u32;
141                h.catches.push((ty, addr));
142            }
143        }
144        if sz <= 0 {
145            let addr = c.read_uleb128()? as u32;
146            h.catch_all = Some(addr);
147        }
148        Some(h)
149    }
150
151    /// Handler for a try item (index-resolved, tolerating out-of-range).
152    pub fn handler_of(&self, t: &TryItem) -> Option<&CatchHandler> {
153        self.handlers.get(t.handler_idx)
154    }
155}