Skip to main content

ddc_dex/
file.rs

1//! DEX container: header, id tables, class defs.
2
3use std::collections::HashMap;
4
5use crate::annotations::{self, EncodedValue};
6use crate::code::CodeItem;
7use crate::reader::Cursor;
8
9/// Index sentinel for "absent".
10pub const NO_INDEX: u32 = 0xffff_ffff;
11
12#[derive(Debug, Clone)]
13pub struct ProtoId {
14    /// Index into string ids: the shorty descriptor.
15    pub shorty_idx: u32,
16    /// Index into type ids: return type.
17    pub return_type_idx: u32,
18    /// Offset of the parameter type list, or 0.
19    pub parameters_off: u32,
20}
21
22#[derive(Debug, Clone)]
23pub struct FieldId {
24    pub class_idx: u32,
25    pub type_idx: u32,
26    pub name_idx: u32,
27}
28
29#[derive(Debug, Clone)]
30pub struct MethodId {
31    pub class_idx: u32,
32    pub proto_idx: u32,
33    pub name_idx: u32,
34}
35
36/// One `class_def_item`.
37#[derive(Debug, Clone)]
38pub struct ClassDef {
39    /// Index into type ids.
40    pub class_idx: u32,
41    pub access_flags: u32,
42    pub superclass_idx: u32,
43    pub interfaces_off: u32,
44    pub source_file_idx: u32,
45    pub annotations_off: u32,
46    pub class_data_off: u32,
47    pub static_values_off: u32,
48}
49
50/// One `encoded_field`.
51#[derive(Debug, Clone)]
52pub struct EncodedField {
53    pub field_idx: u32,
54    pub access_flags: u32,
55}
56
57/// One `encoded_method`.
58#[derive(Debug, Clone)]
59pub struct EncodedMethod {
60    pub method_idx: u32,
61    pub access_flags: u32,
62    pub code_off: u32,
63}
64
65/// One `class_data_item`, fully expanded (index diffs applied).
66#[derive(Debug, Clone, Default)]
67pub struct ClassData {
68    pub static_fields: Vec<EncodedField>,
69    pub instance_fields: Vec<EncodedField>,
70    pub direct_methods: Vec<EncodedMethod>,
71    pub virtual_methods: Vec<EncodedMethod>,
72}
73
74/// A parsed DEX image.
75///
76/// Id tables and class defs are parsed eagerly; code items, class data and
77/// annotations are materialized on demand. All accessors degrade to
78/// sentinels/`None` on out-of-range indices instead of panicking.
79pub struct DexFile {
80    /// The raw image. `None` after `release_data`/`mark_released` —
81    /// retired images drop their inflated bytes (the full-decompile
82    /// pipeline retires a dex once every class in it has been emitted;
83    /// on weibo/lark that is ~360MB of images that used to stay resident
84    /// for the whole run).
85    data: std::sync::Mutex<Option<Vec<u8>>>,
86    /// Set by `mark_released` from a shared (&) reference — the actual
87    /// bytes drop immediately through the mutex.
88    released: std::sync::atomic::AtomicBool,
89    strings: Vec<String>,
90    /// Type ids: descriptor string indices.
91    types: Vec<u32>,
92    protos: Vec<ProtoId>,
93    fields: Vec<FieldId>,
94    methods: Vec<MethodId>,
95    pub class_defs: Vec<ClassDef>,
96    pub version: String,
97    /// class_idx → index into class_defs.
98    by_type: HashMap<u32, usize>,
99    /// `method_handle_item`s (DEX 037+): (kind, field/method id).
100    method_handles: Vec<MethodHandleItem>,
101    /// `call_site_id_item`s (DEX 037+), resolved.
102    call_sites: Vec<CallSiteInfo>,
103}
104
105/// One `method_handle_item`.
106#[derive(Debug, Clone)]
107pub struct MethodHandleItem {
108    /// 0-3: field static-put/static-get/instance-put/instance-get;
109    /// 4: invoke-static; 5: invoke-instance; 6: invoke-constructor;
110    /// 7: invoke-direct; 8: invoke-interface.
111    pub kind: u16,
112    /// Field or method id (widened from u2).
113    pub target_id: u32,
114    pub is_field: bool,
115}
116
117/// A resolved `call_site_id_item`: the `call_site_off` encoded array.
118#[derive(Debug, Clone)]
119pub struct CallSiteInfo {
120    /// Bootstrap method handle index.
121    pub bootstrap_handle: u32,
122    /// Call-site method name (string index).
123    pub name_idx: u32,
124    /// Call-site method type (proto index).
125    pub proto_idx: u32,
126    /// Linker arguments (raw encoded values).
127    pub linker_args: Vec<crate::annotations::EncodedValue>,
128}
129
130impl Drop for DexFile {
131    fn drop(&mut self) {
132        *self.data.lock().unwrap() = None;
133    }
134}
135
136impl DexFile {
137    pub fn parse(data: Vec<u8>) -> Result<DexFile, String> {
138        if data.len() < crate::DEX_HEADER_SIZE {
139            return Err("file too small for DEX header".into());
140        }
141        let magic = &data[..8];
142        if &magic[..4] != b"dex\n" || magic[7] != 0 {
143            return Err(format!("not a DEX file: magic {:02x?}", magic));
144        }
145        let version = String::from_utf8_lossy(&magic[4..7]).into_owned();
146        // 036 was an unofficial odex-era marker; ART accepts 035, 037-041.
147        if !matches!(
148            version.as_str(),
149            "035" | "037" | "038" | "039" | "040" | "041"
150        ) {
151            return Err(format!("unsupported DEX version {}", version));
152        }
153        let endian = u32::from_le_bytes(data[40..44].try_into().unwrap());
154        if endian != 0x1234_5678 {
155            return Err(format!("unexpected endian tag {endian:#x}"));
156        }
157
158        let u32at = |off: usize| -> u32 {
159            if off + 4 > data.len() {
160                return 0;
161            }
162            u32::from_le_bytes(data[off..off + 4].try_into().unwrap())
163        };
164        let string_ids_size = u32at(0x38) as usize;
165        let string_ids_off = u32at(0x3c) as usize;
166        let type_ids_size = u32at(0x40) as usize;
167        let type_ids_off = u32at(0x44) as usize;
168        let proto_ids_size = u32at(0x48) as usize;
169        let proto_ids_off = u32at(0x4c) as usize;
170        let field_ids_size = u32at(0x50) as usize;
171        let field_ids_off = u32at(0x54) as usize;
172        let method_ids_size = u32at(0x58) as usize;
173        let method_ids_off = u32at(0x5c) as usize;
174        let class_defs_size = u32at(0x60) as usize;
175        let class_defs_off = u32at(0x64) as usize;
176
177        // Strings: MUTF-8 with uleb length.
178        let mut strings = Vec::with_capacity(string_ids_size);
179        if string_ids_off + 4 * string_ids_size <= data.len() {
180            for i in 0..string_ids_size {
181                let so = u32at(string_ids_off + 4 * i) as usize;
182                let mut c = Cursor::at(&data, so);
183                let s = c
184                    .read_uleb128()
185                    .and_then(|len| c.read_mutf8(c.pos, len))
186                    .unwrap_or_default();
187                strings.push(s);
188            }
189        }
190
191        // Type ids: u4 descriptor string indices.
192        let mut types = Vec::with_capacity(type_ids_size);
193        if type_ids_off + 4 * type_ids_size <= data.len() {
194            for i in 0..type_ids_size {
195                types.push(u32at(type_ids_off + 4 * i));
196            }
197        }
198
199        // Proto ids: 12 bytes each.
200        let mut protos = Vec::with_capacity(proto_ids_size);
201        if proto_ids_off + 12 * proto_ids_size <= data.len() {
202            for i in 0..proto_ids_size {
203                let base = proto_ids_off + 12 * i;
204                protos.push(ProtoId {
205                    shorty_idx: u32at(base),
206                    return_type_idx: {
207                        let v = u16::from_le_bytes(data[base + 4..base + 6].try_into().unwrap());
208                        v as u32
209                    },
210                    parameters_off: u32at(base + 8),
211                });
212            }
213        }
214
215        // Field ids: 8 bytes each.
216        let mut fields = Vec::with_capacity(field_ids_size);
217        if field_ids_off + 8 * field_ids_size <= data.len() {
218            for i in 0..field_ids_size {
219                let base = field_ids_off + 8 * i;
220                fields.push(FieldId {
221                    class_idx: u16::from_le_bytes(data[base..base + 2].try_into().unwrap()) as u32,
222                    type_idx: u16::from_le_bytes(data[base + 2..base + 4].try_into().unwrap())
223                        as u32,
224                    name_idx: u32at(base + 4),
225                });
226            }
227        }
228
229        // Method ids: 8 bytes each.
230        let mut methods = Vec::with_capacity(method_ids_size);
231        if method_ids_off + 8 * method_ids_size <= data.len() {
232            for i in 0..method_ids_size {
233                let base = method_ids_off + 8 * i;
234                methods.push(MethodId {
235                    class_idx: u16::from_le_bytes(data[base..base + 2].try_into().unwrap()) as u32,
236                    proto_idx: u16::from_le_bytes(data[base + 2..base + 4].try_into().unwrap())
237                        as u32,
238                    name_idx: u32at(base + 4),
239                });
240            }
241        }
242
243        // Class defs: 32 bytes each.
244        let mut class_defs = Vec::with_capacity(class_defs_size);
245        if class_defs_off + 32 * class_defs_size <= data.len() {
246            for i in 0..class_defs_size {
247                let base = class_defs_off + 32 * i;
248                class_defs.push(ClassDef {
249                    class_idx: u32at(base),
250                    access_flags: u32at(base + 4),
251                    superclass_idx: u32at(base + 8),
252                    interfaces_off: u32at(base + 12),
253                    source_file_idx: u32at(base + 16),
254                    annotations_off: u32at(base + 20),
255                    class_data_off: u32at(base + 24),
256                    static_values_off: u32at(base + 28),
257                });
258            }
259        }
260
261        let mut by_type = HashMap::with_capacity(class_defs.len());
262        for (i, cd) in class_defs.iter().enumerate() {
263            by_type.insert(cd.class_idx, i);
264        }
265
266        // DEX 037+ tables (call sites / method handles) live ONLY in the
267        // map list — the header carries no offsets for them.
268        let (method_handles, call_sites) = parse_map_tables(&data);
269
270        Ok(DexFile {
271            data: std::sync::Mutex::new(Some(data)),
272            released: std::sync::atomic::AtomicBool::new(false),
273            strings,
274            types,
275            protos,
276            fields,
277            methods,
278            class_defs,
279            version,
280            by_type,
281            method_handles,
282            call_sites,
283        })
284    }
285
286    /// The raw image, or empty once retired.
287    ///
288    /// SAFETY of the lifetime: the bytes live in an `Arc<Vec<u8>>` whose
289    /// clone is moved into a self-owned stash (`raw_keep`) that outlives
290    /// every later call — each call swaps the stash, keeping the previous
291    /// slice alive only for the previous caller, which is unsound in
292    /// general. The callers here hold the slice only inside one method
293    /// (`code_at` / parse-time table walks) and never across calls, and
294    /// single-call scope is the documented contract; the alternative
295    /// (returning a guard) would infect every parser signature.
296    pub fn raw(&self) -> &[u8] {
297        let guard = self.data.lock().unwrap();
298        match guard.as_ref() {
299            Some(bytes) => unsafe { std::mem::transmute::<&[u8], &[u8]>(bytes.as_slice()) },
300            None => &[],
301        }
302    }
303
304    /// Drop the inflated image (tables stay usable; code accessors return
305    /// empty). Call only when no further code decoding will happen.
306    pub fn release_data(&mut self) {
307        *self.data.lock().unwrap() = None;
308        self.released
309            .store(true, std::sync::atomic::Ordering::Release);
310    }
311
312    /// Mark the image retired through a shared reference: code accessors
313    /// go empty immediately; the bytes themselves drop when the last Arc
314    /// snapshot drops (workers hold snapshots only mid-class, so the
315    /// memory is reclaimed as chunks complete).
316    pub fn mark_released(&self) {
317        self.released
318            .store(true, std::sync::atomic::Ordering::Release);
319        // Free the bytes NOW (through the mutex, &self-safe).
320        *self.data.lock().unwrap() = None;
321    }
322
323    #[inline]
324    #[allow(dead_code)] // debug hook for retirement postmortems
325    fn is_released(&self) -> bool {
326        self.released.load(std::sync::atomic::Ordering::Acquire)
327    }
328
329    pub fn string(&self, idx: u32) -> &str {
330        self.strings
331            .get(idx as usize)
332            .map(|s| s.as_str())
333            .unwrap_or("")
334    }
335
336    pub fn string_count(&self) -> usize {
337        self.strings.len()
338    }
339
340    /// Type id → CLASS internal name (`java/lang/String`), descriptor
341    /// stripped; array descriptors pass through unchanged.
342    pub fn class_name(&self, idx: u32) -> String {
343        let d = self.type_name(idx);
344        if d.len() > 2 && d.starts_with('L') && d.ends_with(';') {
345            d[1..d.len() - 1].to_string()
346        } else {
347            d.to_string()
348        }
349    }
350
351    /// Type id → descriptor string (`Ljava/lang/String;`, `[I`, ...).
352    pub fn type_name(&self, idx: u32) -> &str {
353        match self.types.get(idx as usize) {
354            Some(&si) => self.string(si),
355            None => "",
356        }
357    }
358
359    pub fn type_count(&self) -> usize {
360        self.types.len()
361    }
362
363    pub fn proto(&self, idx: u32) -> &ProtoId {
364        static EMPTY: std::sync::OnceLock<ProtoId> = std::sync::OnceLock::new();
365        self.protos.get(idx as usize).unwrap_or_else(|| {
366            EMPTY.get_or_init(|| ProtoId {
367                shorty_idx: 0,
368                return_type_idx: 0,
369                parameters_off: 0,
370            })
371        })
372    }
373
374    /// Parameter type ids of a proto (empty for `(V)`).
375    pub fn proto_params(&self, idx: u32) -> Vec<u32> {
376        let off = self.proto(idx).parameters_off;
377        if off == 0 {
378            return Vec::new();
379        }
380        self.read_type_list(off).unwrap_or_default()
381    }
382
383    fn read_type_list(&self, off: u32) -> Option<Vec<u32>> {
384        let off = off as usize;
385        if off + 4 > self.raw().len() {
386            return None;
387        }
388        let size = u32::from_le_bytes(self.raw()[off..off + 4].try_into().unwrap()) as usize;
389        let mut out = Vec::with_capacity(size);
390        let mut p = off + 4;
391        for _ in 0..size {
392            if p + 2 > self.raw().len() {
393                break;
394            }
395            out.push(u16::from_le_bytes(self.raw()[p..p + 2].try_into().unwrap()) as u32);
396            p += 2;
397        }
398        Some(out)
399    }
400
401    pub fn field(&self, idx: u32) -> &FieldId {
402        static EMPTY: std::sync::OnceLock<FieldId> = std::sync::OnceLock::new();
403        self.fields.get(idx as usize).unwrap_or_else(|| {
404            EMPTY.get_or_init(|| FieldId {
405                class_idx: 0,
406                type_idx: 0,
407                name_idx: 0,
408            })
409        })
410    }
411
412    pub fn field_count(&self) -> usize {
413        self.fields.len()
414    }
415
416    pub fn method(&self, idx: u32) -> &MethodId {
417        static EMPTY: std::sync::OnceLock<MethodId> = std::sync::OnceLock::new();
418        self.methods.get(idx as usize).unwrap_or_else(|| {
419            EMPTY.get_or_init(|| MethodId {
420                class_idx: 0,
421                proto_idx: 0,
422                name_idx: 0,
423            })
424        })
425    }
426
427    pub fn method_count(&self) -> usize {
428        self.methods.len()
429    }
430
431    /// Class def index for a type descriptor, when this DEX defines it.
432    pub fn class_def_of(&self, type_idx: u32) -> Option<usize> {
433        self.by_type.get(&type_idx).copied()
434    }
435
436    /// Interfaces of a class def as type ids.
437    pub fn interfaces_of(&self, cd: &ClassDef) -> Vec<u32> {
438        if cd.interfaces_off == 0 {
439            return Vec::new();
440        }
441        self.read_type_list(cd.interfaces_off).unwrap_or_default()
442    }
443
444    pub fn class_data(&self, cd: &ClassDef) -> ClassData {
445        if cd.class_data_off == 0 {
446            return ClassData::default();
447        }
448        self.read_class_data(cd.class_data_off).unwrap_or_default()
449    }
450
451    fn read_class_data(&self, off: u32) -> Option<ClassData> {
452        let mut c = Cursor::at(self.raw(), off as usize);
453        let sf = c.read_uleb128()? as usize;
454        let inf = c.read_uleb128()? as usize;
455        let dm = c.read_uleb128()? as usize;
456        let vm = c.read_uleb128()? as usize;
457
458        let read_fields = |n: usize, cur: &mut Cursor| -> Option<Vec<EncodedField>> {
459            let mut out = Vec::with_capacity(n);
460            let mut idx: u64 = 0;
461            for _ in 0..n {
462                idx += cur.read_uleb128()?;
463                out.push(EncodedField {
464                    field_idx: idx as u32,
465                    access_flags: cur.read_uleb128()? as u32,
466                });
467            }
468            Some(out)
469        };
470        let static_fields = read_fields(sf, &mut c)?;
471        let instance_fields = read_fields(inf, &mut c)?;
472
473        let read_methods = |n: usize, cur: &mut Cursor| -> Option<Vec<EncodedMethod>> {
474            let mut out = Vec::with_capacity(n);
475            let mut idx: u64 = 0;
476            for _ in 0..n {
477                idx += cur.read_uleb128()?;
478                out.push(EncodedMethod {
479                    method_idx: idx as u32,
480                    access_flags: cur.read_uleb128()? as u32,
481                    code_off: cur.read_uleb128()? as u32,
482                });
483            }
484            Some(out)
485        };
486        let direct_methods = read_methods(dm, &mut c)?;
487        let virtual_methods = read_methods(vm, &mut c)?;
488
489        Some(ClassData {
490            static_fields,
491            instance_fields,
492            direct_methods,
493            virtual_methods,
494        })
495    }
496
497    /// (registers_size, insns_size) read straight from the code_item header
498    /// — no instruction decoding. Hot path for risk checks.
499    pub fn code_stats(&self, off: u32) -> Option<(u16, u32)> {
500        if off == 0 {
501            return None;
502        }
503        let o = off as usize;
504        if o + 16 > self.raw().len() {
505            return None;
506        }
507        let regs = u16::from_le_bytes([self.raw()[o], self.raw()[o + 1]]);
508        let insns = u32::from_le_bytes([
509            self.raw()[o + 12],
510            self.raw()[o + 13],
511            self.raw()[o + 14],
512            self.raw()[o + 15],
513        ]);
514        Some((regs, insns))
515    }
516
517    /// debug_info_off straight from the code_item header (pool building).
518    pub fn debug_info_off_at(&self, off: u32) -> Option<u32> {
519        if off == 0 {
520            return None;
521        }
522        let o = off as usize;
523        if o + 12 > self.raw().len() {
524            return None;
525        }
526        Some(u32::from_le_bytes([
527            self.raw()[o + 8],
528            self.raw()[o + 9],
529            self.raw()[o + 10],
530            self.raw()[o + 11],
531        ]))
532    }
533
534    pub fn code_at(&self, off: u32) -> Option<CodeItem> {
535        if off == 0 {
536            return None;
537        }
538        CodeItem::parse(self.raw(), off as usize)
539    }
540
541    /// Class descriptors straight from an inflated image, WITHOUT a full
542    /// `DexFile::parse` (which decodes the whole string table — the class
543    /// names are a small slice of it). Class listing pays for exactly what
544    /// it prints.
545    pub fn class_names_from_image(image: &[u8]) -> Vec<String> {
546        if image.len() < 0x70 || &image[..4] != b"dex\n" {
547            return Vec::new();
548        }
549        let u4 = |o: usize| -> u32 {
550            u32::from_le_bytes([image[o], image[o + 1], image[o + 2], image[o + 3]])
551        };
552        let strings_size = u4(0x38) as usize;
553        let strings_off = u4(0x3c) as usize;
554        let types_size = u4(0x40) as usize;
555        let types_off = u4(0x44) as usize;
556        let classes_size = u4(0x60) as usize;
557        let classes_off = u4(0x64) as usize;
558        if strings_off + 4 * strings_size > image.len()
559            || types_off + 4 * types_size > image.len()
560            || classes_off + 32 * classes_size > image.len()
561        {
562            return Vec::new();
563        }
564        let u4at = |o: usize| -> u32 {
565            u32::from_le_bytes([image[o], image[o + 1], image[o + 2], image[o + 3]])
566        };
567        let string_at = |idx: u32| -> Option<String> {
568            let so = u4at(strings_off + 4 * idx as usize) as usize;
569            if so >= image.len() {
570                return None;
571            }
572            // mutf8 string_data_item: uleb len (utf16 units), bytes, NUL.
573            let mut p = so;
574            // uleb128
575            let mut len = 0u64;
576            let mut shift = 0;
577            loop {
578                if p >= image.len() {
579                    return None;
580                }
581                let b = image[p];
582                p += 1;
583                len |= ((b & 0x7f) as u64) << shift;
584                shift += 7;
585                if b & 0x80 == 0 {
586                    break;
587                }
588            }
589            let end = image[p..].iter().position(|&b| b == 0)? + p;
590            crate::reader::mutf8_decode(&image[p..end], len)
591        };
592        let mut out = Vec::with_capacity(classes_size);
593        for i in 0..classes_size {
594            let cd = classes_off + 32 * i;
595            let class_idx = u4at(cd) as usize;
596            if class_idx >= types_size {
597                continue;
598            }
599            let string_idx = u4at(types_off + 4 * class_idx);
600            if let Some(name) = string_at(string_idx) {
601                // type strings are descriptors (Lcom/foo/Bar;): strip the
602                // L; shell to match DexFile::class_name's plain form.
603                let plain = name
604                    .strip_prefix('L')
605                    .and_then(|s| s.strip_suffix(';'))
606                    .map(str::to_string)
607                    .unwrap_or(name);
608                out.push(plain);
609            }
610        }
611        out
612    }
613
614    /// The raw insns byte section of one code item (zero-copy) for the
615    /// boundary-walking scan path — skips try tables and full decode.
616    pub fn code_insns_bytes_at(&self, off: u32) -> Option<&[u8]> {
617        if off == 0 {
618            return None;
619        }
620        let off = off as usize;
621        let d = self.raw();
622        if off + 16 > d.len() {
623            return None;
624        }
625        // code_item: 4×u2 header, u4 debug_info_off, u4 insns_size(units).
626        let insns_size =
627            u32::from_le_bytes([d[off + 12], d[off + 13], d[off + 14], d[off + 15]]) as usize;
628        let start = off + 16;
629        let end = start + 2 * insns_size;
630        if end > d.len() {
631            return None;
632        }
633        Some(&d[start..end])
634    }
635
636    /// Static field initial values (aligned with `static_fields` order).
637    pub fn static_values(&self, off: u32) -> Vec<EncodedValue> {
638        if off == 0 {
639            return Vec::new();
640        }
641        annotations::read_encoded_array(self.raw(), off as usize).unwrap_or_default()
642    }
643
644    /// `method_handle_item` by index.
645    pub fn method_handle(&self, idx: u32) -> Option<&MethodHandleItem> {
646        self.method_handles.get(idx as usize)
647    }
648
649    pub fn method_handle_count(&self) -> usize {
650        self.method_handles.len()
651    }
652
653    /// Resolved call site by index.
654    pub fn call_site(&self, idx: u32) -> Option<&CallSiteInfo> {
655        self.call_sites.get(idx as usize)
656    }
657
658    pub fn call_site_count(&self) -> usize {
659        self.call_sites.len()
660    }
661
662    /// Parameter names from debug info (NO_INDEX → absent). `debug_info_off`
663    /// comes from a code item; the entry layout is `line_start` uleb,
664    /// `parameters_size` uleb, then `uleb128p1` names.
665    pub fn parameter_names(&self, debug_info_off: u32) -> Vec<Option<String>> {
666        if debug_info_off == 0 {
667            return Vec::new();
668        }
669        let mut c = Cursor::at(self.raw(), debug_info_off as usize);
670        let _line_start = match c.read_uleb128() {
671            Some(v) => v,
672            None => return Vec::new(),
673        };
674        let n = match c.read_uleb128() {
675            Some(v) => v as usize,
676            None => return Vec::new(),
677        };
678        let mut out = Vec::with_capacity(n);
679        for _ in 0..n {
680            let idx = c.read_uleb128p1().unwrap_or(-1);
681            if idx < 0 {
682                out.push(None);
683            } else {
684                out.push(Some(self.string(idx as u32).to_string()));
685            }
686        }
687        out
688    }
689}
690
691impl std::fmt::Debug for DexFile {
692    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693        f.debug_struct("DexFile")
694            .field("version", &self.version)
695            .field("strings", &self.strings.len())
696            .field("types", &self.types.len())
697            .field("protos", &self.protos.len())
698            .field("fields", &self.fields.len())
699            .field("methods", &self.methods.len())
700            .field("class_defs", &self.class_defs.len())
701            .finish()
702    }
703}
704
705/// Sections 0x0007 (call_site_id) and 0x0008 (method_handle_id) from the
706/// map list. Layouts (dx `ItemType`/`MethodHandleItem`):
707/// * `call_site_id_item`: u4 offset → `call_site_off` encoded array
708///   [bootstrap METHOD_HANDLE, name STRING, type METHOD_TYPE, linker args…].
709/// * `method_handle_item`: u2 kind, u2 reserved, u2 target id, u2 reserved.
710fn parse_map_tables(data: &[u8]) -> (Vec<MethodHandleItem>, Vec<CallSiteInfo>) {
711    let mut handles = Vec::new();
712    let mut sites = Vec::new();
713    if data.len() < 0x70 {
714        return (handles, sites);
715    }
716    let map_off = u32le(data, 0x34) as usize;
717    if map_off + 4 > data.len() {
718        return (handles, sites);
719    }
720    let n = u32le(data, map_off) as usize;
721    let mut p = map_off + 4;
722    for _ in 0..n {
723        if p + 12 > data.len() {
724            break;
725        }
726        let t = u16le(data, p);
727        let size = u32le(data, p + 4) as usize;
728        let off = u32le(data, p + 8) as usize;
729        p += 12;
730        match t {
731            0x0007 => {
732                // call_site_id_item: u4 call_site_off each.
733                for i in 0..size {
734                    let q = off + 4 * i;
735                    if q + 4 > data.len() {
736                        break;
737                    }
738                    let cs_off = u32le(data, q) as usize;
739                    if let Some(info) = parse_call_site(data, cs_off) {
740                        sites.push(info);
741                    }
742                }
743            }
744            0x0008 => {
745                // method_handle_item: (u2 kind, u2 res, u2 id, u2 res).
746                for i in 0..size {
747                    let q = off + 8 * i;
748                    if q + 8 > data.len() {
749                        break;
750                    }
751                    let kind = u16le(data, q);
752                    let id = u16le(data, q + 4);
753                    handles.push(MethodHandleItem {
754                        kind,
755                        target_id: id as u32,
756                        is_field: kind <= 3,
757                    });
758                }
759            }
760            _ => {}
761        }
762    }
763    (handles, sites)
764}
765
766fn parse_call_site(data: &[u8], off: usize) -> Option<CallSiteInfo> {
767    let vals = crate::annotations::read_encoded_array(data, off)?;
768    if vals.len() < 3 {
769        return None;
770    }
771    let bootstrap_handle = match &vals[0] {
772        crate::annotations::EncodedValue::MethodHandle(h) => *h,
773        _ => return None,
774    };
775    let name_idx = match &vals[1] {
776        crate::annotations::EncodedValue::String(s) => *s,
777        _ => return None,
778    };
779    let proto_idx = match &vals[2] {
780        crate::annotations::EncodedValue::MethodType(p) => *p,
781        _ => return None,
782    };
783    Some(CallSiteInfo {
784        bootstrap_handle,
785        name_idx,
786        proto_idx,
787        linker_args: vals[3..].to_vec(),
788    })
789}
790
791fn u16le(data: &[u8], off: usize) -> u16 {
792    if off + 2 > data.len() {
793        return 0;
794    }
795    u16::from_le_bytes([data[off], data[off + 1]])
796}
797
798fn u32le(data: &[u8], off: usize) -> u32 {
799    if off + 4 > data.len() {
800        return 0;
801    }
802    u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
803}
804
805#[cfg(test)]
806mod tests {
807    use super::*;
808
809    #[test]
810    fn rejects_non_dex() {
811        assert!(DexFile::parse(vec![0u8; 128]).is_err());
812    }
813}