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}