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                // `&mut c` is load-bearing: Cursor is Copy, so a by-value
100                // call left the caller's cursor pinned at the list start —
101                // EVERY handler parsed the first one's bytes and the whole
102                // list collapsed onto handler 0 (nested try/catch methods
103                // silently routed every catch to the first handler's code).
104                let h = Self::parse_handler(&mut c)?;
105                idx_by_off.insert(hoff, handlers.len());
106                handlers.push(h);
107            }
108
109            // Resolve each try's handler_off. The spec's offsets are from
110            // the list start (the uleb size included in the offset space);
111            // some writers count from after it — accept both, then the
112            // first handler.
113            for t in tries.iter_mut() {
114                let raw = t.handler_idx;
115                t.handler_idx = idx_by_off
116                    .get(&raw)
117                    .or_else(|| idx_by_off.get(&(raw + uleb_len)))
118                    .or_else(|| idx_by_off.get(&raw.saturating_sub(uleb_len)))
119                    .copied()
120                    .unwrap_or(0);
121            }
122        }
123
124        Some(CodeItem {
125            registers_size,
126            ins_size,
127            outs_size,
128            debug_info_off,
129            insns,
130            payloads,
131            tries,
132            handlers,
133        })
134    }
135
136    fn parse_handler(c: &mut Cursor<'_>) -> Option<CatchHandler> {
137        let sz = c.read_sleb128()?;
138        let mut h = CatchHandler {
139            catches: Vec::new(),
140            catch_all: None,
141        };
142        if sz != 0 {
143            for _ in 0..sz.unsigned_abs() {
144                let ty = c.read_uleb128()? as u32;
145                let addr = c.read_uleb128()? as u32;
146                h.catches.push((ty, addr));
147            }
148        }
149        if sz <= 0 {
150            let addr = c.read_uleb128()? as u32;
151            h.catch_all = Some(addr);
152        }
153        Some(h)
154    }
155
156    /// Handler for a try item (index-resolved, tolerating out-of-range).
157    pub fn handler_of(&self, t: &TryItem) -> Option<&CatchHandler> {
158        self.handlers.get(t.handler_idx)
159    }
160}