Skip to main content

asc_smali/
lib.rs

1//! baksmali-style disassembly of one class.
2//!
3//! This is the built-in "decompile" backend: the rebuilt
4//! single-class DEX is rendered as smali so the output is always available
5//! without an external Java decompiler. The rebuilt DEX itself is a valid
6//! file that external tools (jadx, baksmali, dexdump) can consume as well.
7
8mod format;
9mod insn;
10
11use asc_dex::annotations::{AnnotationsDirectory, parse_annotation_set, parse_annotation_set_ref_list};
12use asc_dex::class_data::{EncodedField, EncodedMethod};
13use asc_dex::debug_info::DebugInfo;
14use asc_dex::encoded::{AnnotationItem, EncodedValue, parse_annotation_item, parse_array};
15use asc_dex::{ClassDef, Dex, NO_INDEX, access};
16use std::collections::HashMap;
17use std::fmt::Write as _;
18use std::rc::Rc;
19
20pub use format::{escape_string, field_signature, method_signature, proto_descriptor};
21
22/// Why a class or method could not be disassembled.
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum SmaliError {
26    /// The DEX is malformed where the listing needs it.
27    #[error(transparent)]
28    Dex(#[from] asc_dex::DexError),
29    /// No class definition with this descriptor in the DEX.
30    #[error("class {0} not found")]
31    ClassNotFound(String),
32    /// A method signature (as given) without its name, parentheses or return type.
33    #[error("method signature must include the name, parameter descriptor and return type")]
34    BadMethodSignature(String),
35    /// The method (its signature) is not defined by the class or DEX.
36    #[error("method not found or signature is not defined in the target DEX: {0}")]
37    MethodNotFound(String),
38    /// The listing grew past 64 times the DEX plus 1 MiB: items shared by
39    /// many methods (code, annotations, a long string named over and over)
40    /// would make a small file expand without bound.
41    #[error("listing exceeds {0} bytes, far more than the DEX could hold")]
42    TooLarge(usize),
43}
44
45/// The most text a listing of a class of `dex` may run to: 64 times the DEX
46/// plus 1 MiB, beyond any real class.
47pub(crate) fn listing_limit(dex: &Dex<'_>) -> usize {
48    dex.buf().len().saturating_mul(64).saturating_add(1 << 20)
49}
50
51/// [`SmaliError::TooLarge`] once `out` has grown past `limit`.
52pub(crate) fn check_size(out: &str, limit: usize) -> Result<()> {
53    if out.len() > limit { Err(SmaliError::TooLarge(limit)) } else { Ok(()) }
54}
55
56/// The result of the disassembly functions.
57pub type Result<T> = std::result::Result<T, SmaliError>;
58
59/// Rendering options.
60#[derive(Debug, Clone, Copy)]
61#[non_exhaustive]
62pub struct Options {
63    /// Emit `.line` directives from debug info.
64    pub line_numbers: bool,
65    /// Emit `.param` names from debug info.
66    pub param_names: bool,
67}
68
69impl Default for Options {
70    fn default() -> Self {
71        Options { line_numbers: true, param_names: true }
72    }
73}
74
75impl Options {
76    /// Both kinds of debug info (`on`) or neither, as a front end's one
77    /// debug-info switch asks.
78    pub fn debug_info(on: bool) -> Options {
79        Options { line_numbers: on, param_names: on }
80    }
81
82    /// These options with `.line` directives on or off.
83    #[must_use]
84    pub fn line_numbers(self, line_numbers: bool) -> Options {
85        Options { line_numbers, ..self }
86    }
87
88    /// These options with `.param` names on or off.
89    #[must_use]
90    pub fn param_names(self, param_names: bool) -> Options {
91        Options { param_names, ..self }
92    }
93}
94
95/// Disassemble the class with the given descriptor.
96///
97/// # Errors
98///
99/// See [`disassemble_class_method`].
100pub fn disassemble_class(dex: &Dex<'_>, descriptor: &str, opts: Options) -> Result<String> {
101    disassemble_class_method(dex, descriptor, opts, None)
102}
103
104/// Disassemble the class with the given descriptor, keeping only the method
105/// whose `name(params)ret` is `method` when one is given (fields are left out
106/// then, like the Python `disassemble --method`). The signature is
107/// normalised with [`normalize_method_signature`]; a class that defines no
108/// such method is an error.
109///
110/// # Errors
111///
112/// [`SmaliError::BadMethodSignature`] for a `method` that does not
113/// normalise, [`SmaliError::ClassNotFound`] / [`SmaliError::MethodNotFound`]
114/// when the DEX does not define the class or method,
115/// [`SmaliError::TooLarge`] for a runaway listing, and
116/// [`SmaliError::Dex`] for a malformed DEX.
117pub fn disassemble_class_method(dex: &Dex<'_>, descriptor: &str, opts: Options, method: Option<&str>) -> Result<String> {
118    let filter = method.map(normalize_method_signature).transpose()?;
119    let (index, _) =
120        dex.find_class_def(&asc_dex::mutf8::encode(descriptor))?.ok_or_else(|| SmaliError::ClassNotFound(descriptor.to_string()))?;
121    let def = dex.class_def(index)?;
122    let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: filter.as_deref(), marks: None };
123    if let Some(sig) = w.method {
124        let cd = dex.class_data(def.class_data_off)?;
125        let mut found = false;
126        for m in cd.direct_methods.iter().chain(&cd.virtual_methods) {
127            if w.method_matches(m.method_idx)? {
128                found = true;
129                break;
130            }
131        }
132        if !found {
133            return Err(SmaliError::MethodNotFound(sig.to_string()));
134        }
135    }
136    w.class(&def)?;
137    Ok(w.out)
138}
139
140/// Disassemble class definition number `index`.
141///
142/// # Errors
143///
144/// [`SmaliError::Dex`] for an `index` out of range or a malformed DEX,
145/// [`SmaliError::TooLarge`] for a runaway listing.
146pub fn disassemble_class_def(dex: &Dex<'_>, index: u32, opts: Options) -> Result<String> {
147    let def = dex.class_def(index)?;
148    let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: None, marks: None };
149    w.class(&def)?;
150    Ok(w.out)
151}
152
153/// One method rendered on its own (`.method` .. `.end method`), with the line
154/// each instruction starts on.
155#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub struct MethodListing {
158    /// The smali, from `.method` to `.end method` and its newline.
159    pub text: String,
160    /// `(pc, line)` per instruction in pc order: `pc` in code units from the
161    /// start of the method's code, `line` a 0-based index into `text.lines()`.
162    pub insns: Vec<(u32, u32)>,
163    /// `(pc, source line)` from the debug info, in pc order (empty without it).
164    pub positions: Vec<(u32, u32)>,
165}
166
167impl MethodListing {
168    /// Line of `text` holding the instruction at `pc` (code units).
169    pub fn line_of(&self, pc: u32) -> Option<u32> {
170        self.insns.binary_search_by_key(&pc, |&(p, _)| p).ok().map(|i| self.insns[i].1)
171    }
172
173    /// Source line (debug info) covering the instruction at `pc`.
174    pub fn source_line(&self, pc: u32) -> Option<u32> {
175        match self.positions.partition_point(|&(p, _)| p <= pc) {
176            0 => None,
177            n => Some(self.positions[n - 1].1),
178        }
179    }
180}
181
182/// Render method `method_idx` of `dex` (it must be defined by a class of
183/// this DEX), annotations and parameters included, as in a class listing.
184///
185/// # Errors
186///
187/// [`SmaliError::MethodNotFound`] when no class of this DEX defines the
188/// method, [`SmaliError::TooLarge`] for a runaway listing, and
189/// [`SmaliError::Dex`] for an index out of range or a malformed DEX.
190pub fn disassemble_method(dex: &Dex<'_>, method_idx: u32, opts: Options) -> Result<MethodListing> {
191    let id = dex.method_id(method_idx)?;
192    let not_found = || SmaliError::MethodNotFound(method_signature(dex, method_idx).unwrap_or_else(|_| format!("method@{method_idx}")));
193    let (_, def) = dex.find_class_def_by_type(id.class_idx as u32)?.ok_or_else(not_found)?;
194    let cd = dex.class_data(def.class_data_off)?;
195    let m = cd.direct_methods.iter().chain(&cd.virtual_methods).find(|m| m.method_idx == method_idx).ok_or_else(not_found)?;
196    let mut w = Writer { names: Names::new(dex), out: String::with_capacity(4 * 1024), opts, method: None, marks: Some(Vec::new()) };
197    let ann = w.load_annotations(&def)?;
198    w.method(m, &ann)?;
199    let mut positions = Vec::new();
200    if m.code_off != 0 {
201        let code = dex.code_item(m.code_off)?;
202        if code.debug_info_off != 0
203            && let Ok((d, _)) = DebugInfo::parse(dex.buf(), code.debug_info_off as usize)
204        {
205            positions = d.positions();
206            positions.sort_by_key(|&(pc, _)| pc);
207        }
208    }
209    let marks = w.marks.take().unwrap_or_default();
210    let text = w.out;
211    // Byte offset in `text` -> line index, walking both in order.
212    let mut insns = Vec::with_capacity(marks.len());
213    let (mut line, mut at) = (0u32, 0usize);
214    for (pc, off) in marks {
215        line += text.as_bytes()[at..off].iter().filter(|&&b| b == b'\n').count() as u32;
216        at = off;
217        insns.push(((pc / 2) as u32, line));
218    }
219    Ok(MethodListing { text, insns, positions })
220}
221
222/// Normalise a `--method` signature such as `run(I)V` or `put(I, Ljava/lang/String;) V`:
223/// whitespace is dropped from the parameter and return descriptors. The name,
224/// both parentheses and a return type are required; a bare name is rejected.
225///
226/// # Errors
227///
228/// [`SmaliError::BadMethodSignature`] when one of those is missing.
229pub fn normalize_method_signature(signature: &str) -> Result<String> {
230    let bad = || SmaliError::BadMethodSignature(signature.to_string());
231    let (name, rest) = signature.split_once('(').ok_or_else(bad)?;
232    let (params, ret) = rest.split_once(')').ok_or_else(bad)?;
233    let compact = |s: &str| s.split_whitespace().collect::<String>();
234    let (params, ret) = (compact(params), compact(ret));
235    if name.is_empty() || ret.is_empty() {
236        return Err(bad());
237    }
238    Ok(format!("{name}({params}){ret}"))
239}
240
241/// Per-class memo of rendered type descriptors, prototypes and member
242/// signatures, keyed by index (the same method or field is typically
243/// referenced many times inside one class).
244pub(crate) struct Names<'d, 'a> {
245    pub dex: &'d Dex<'a>,
246    types: HashMap<u32, String>,
247    protos: HashMap<u32, String>,
248    fields: HashMap<u32, String>,
249    methods: HashMap<u32, String>,
250}
251
252impl<'d, 'a> Names<'d, 'a> {
253    fn new(dex: &'d Dex<'a>) -> Self {
254        Names { dex, types: HashMap::new(), protos: HashMap::new(), fields: HashMap::new(), methods: HashMap::new() }
255    }
256
257    pub(crate) fn type_(&mut self, idx: u32) -> Result<&str> {
258        if !self.types.contains_key(&idx) {
259            self.types.insert(idx, self.dex.type_descriptor(idx)?);
260        }
261        Ok(self.types[&idx].as_str())
262    }
263
264    pub(crate) fn proto(&mut self, idx: u32) -> Result<&str> {
265        if !self.protos.contains_key(&idx) {
266            self.protos.insert(idx, proto_descriptor(self.dex, idx)?);
267        }
268        Ok(self.protos[&idx].as_str())
269    }
270
271    pub(crate) fn field(&mut self, idx: u32) -> Result<&str> {
272        if !self.fields.contains_key(&idx) {
273            self.fields.insert(idx, format::field_signature(self.dex, idx)?);
274        }
275        Ok(self.fields[&idx].as_str())
276    }
277
278    pub(crate) fn method(&mut self, idx: u32) -> Result<&str> {
279        if !self.methods.contains_key(&idx) {
280            self.methods.insert(idx, method_signature(self.dex, idx)?);
281        }
282        Ok(self.methods[&idx].as_str())
283    }
284}
285
286struct Annotations {
287    class: Vec<AnnotationItem>,
288    fields: HashMap<u32, AnnotationSet>,
289    methods: HashMap<u32, AnnotationSet>,
290    params: HashMap<u32, Rc<Vec<AnnotationSet>>>,
291}
292
293/// One `annotation_set_item`, parsed once however many members share it.
294type AnnotationSet = Rc<Vec<AnnotationItem>>;
295
296struct Writer<'d, 'a, 'm> {
297    names: Names<'d, 'a>,
298    out: String,
299    opts: Options,
300    /// Only this normalised `name(params)ret` is rendered (and no fields).
301    method: Option<&'m str>,
302    /// When set, `(pc in bytes, byte offset in out)` of every rendered instruction.
303    marks: Option<Vec<(usize, usize)>>,
304}
305
306const CLASS_FLAGS: &[(u32, &str)] = &[
307    (access::PUBLIC, "public"),
308    (access::PRIVATE, "private"),
309    (access::PROTECTED, "protected"),
310    (access::STATIC, "static"),
311    (access::FINAL, "final"),
312    (access::INTERFACE, "interface"),
313    (access::ABSTRACT, "abstract"),
314    (access::SYNTHETIC, "synthetic"),
315    (access::ANNOTATION, "annotation"),
316    (access::ENUM, "enum"),
317];
318
319const FIELD_FLAGS: &[(u32, &str)] = &[
320    (access::PUBLIC, "public"),
321    (access::PRIVATE, "private"),
322    (access::PROTECTED, "protected"),
323    (access::STATIC, "static"),
324    (access::FINAL, "final"),
325    (access::VOLATILE, "volatile"),
326    (access::TRANSIENT, "transient"),
327    (access::SYNTHETIC, "synthetic"),
328    (access::ENUM, "enum"),
329];
330
331const METHOD_FLAGS: &[(u32, &str)] = &[
332    (access::PUBLIC, "public"),
333    (access::PRIVATE, "private"),
334    (access::PROTECTED, "protected"),
335    (access::STATIC, "static"),
336    (access::FINAL, "final"),
337    (access::SYNCHRONIZED, "synchronized"),
338    (access::BRIDGE, "bridge"),
339    (access::VARARGS, "varargs"),
340    (access::NATIVE, "native"),
341    (access::ABSTRACT, "abstract"),
342    (access::STRICT, "strictfp"),
343    (access::SYNTHETIC, "synthetic"),
344    (access::CONSTRUCTOR, "constructor"),
345    (access::DECLARED_SYNCHRONIZED, "declared-synchronized"),
346];
347
348fn flags(value: u32, table: &[(u32, &str)]) -> String {
349    let mut s = String::new();
350    for (bit, name) in table {
351        if value & bit != 0 {
352            s.push_str(name);
353            s.push(' ');
354        }
355    }
356    s
357}
358
359impl<'d, 'a, 'm> Writer<'d, 'a, 'm> {
360    /// Whether method `idx` passes the `method` filter (always without one).
361    fn method_matches(&mut self, idx: u32) -> Result<bool> {
362        let Some(sig) = self.method else { return Ok(true) };
363        let dex = self.names.dex;
364        let id = dex.method_id(idx)?;
365        let name = dex.string(id.name_idx)?;
366        let Some(rest) = sig.strip_prefix(name.as_str()) else { return Ok(false) };
367        Ok(rest == self.names.proto(id.proto_idx as u32)?)
368    }
369
370    /// The annotations of `def`. Sets and parameter lists may be shared
371    /// by any number of members: each is parsed once, so a directory naming
372    /// one large set many times costs one copy of it.
373    fn load_annotations(&self, def: &ClassDef) -> Result<Annotations> {
374        let mut ann = Annotations { class: Vec::new(), fields: HashMap::new(), methods: HashMap::new(), params: HashMap::new() };
375        if def.annotations_off == 0 {
376            return Ok(ann);
377        }
378        let buf = self.names.dex.buf();
379        let dir = AnnotationsDirectory::parse(buf, def.annotations_off as usize)?;
380        let mut sets: HashMap<u32, AnnotationSet> = HashMap::new();
381        let mut load_set = |off: u32| -> Result<AnnotationSet> {
382            if let Some(set) = sets.get(&off) {
383                return Ok(Rc::clone(set));
384            }
385            let mut items = Vec::new();
386            if off != 0 {
387                for item_off in parse_annotation_set(buf, off as usize)? {
388                    if item_off != 0 {
389                        items.push(parse_annotation_item(buf, item_off as usize)?.0);
390                    }
391                }
392            }
393            let set = Rc::new(items);
394            sets.insert(off, Rc::clone(&set));
395            Ok(set)
396        };
397        ann.class = load_set(dir.class_annotations_off)?.to_vec();
398        for (f, off) in dir.field_annotations {
399            ann.fields.insert(f, load_set(off)?);
400        }
401        for (m, off) in dir.method_annotations {
402            ann.methods.insert(m, load_set(off)?);
403        }
404        let mut lists: HashMap<u32, Rc<Vec<AnnotationSet>>> = HashMap::new();
405        for (m, off) in dir.parameter_annotations {
406            if off == 0 {
407                continue;
408            }
409            let params = match lists.get(&off) {
410                Some(params) => Rc::clone(params),
411                None => {
412                    let params =
413                        parse_annotation_set_ref_list(buf, off as usize)?.into_iter().map(&mut load_set).collect::<Result<Vec<_>>>()?;
414                    let params = Rc::new(params);
415                    lists.insert(off, Rc::clone(&params));
416                    params
417                }
418            };
419            ann.params.insert(m, params);
420        }
421        Ok(ann)
422    }
423
424    fn class(&mut self, def: &ClassDef) -> Result<()> {
425        let dex = self.names.dex;
426        let _ = writeln!(self.out, ".class {}{}", flags(def.access_flags, CLASS_FLAGS), self.names.type_(def.class_idx)?);
427        if def.superclass_idx != NO_INDEX {
428            let _ = writeln!(self.out, ".super {}", self.names.type_(def.superclass_idx)?);
429        }
430        if def.source_file_idx != NO_INDEX {
431            let _ = writeln!(self.out, ".source \"{}\"", escape_string(&dex.string(def.source_file_idx)?));
432        }
433        let interfaces = dex.type_list(def.interfaces_off)?;
434        if !interfaces.is_empty() {
435            self.out.push_str("\n\n# interfaces\n");
436            for t in interfaces {
437                let _ = writeln!(self.out, ".implements {}", self.names.type_(t as u32)?);
438            }
439        }
440        let ann = self.load_annotations(def)?;
441        if !ann.class.is_empty() {
442            self.out.push_str("\n\n# annotations\n");
443            for (i, item) in ann.class.iter().enumerate() {
444                if i > 0 {
445                    self.out.push('\n');
446                }
447                self.annotation(item, 0)?;
448            }
449        }
450
451        let mut cd = dex.class_data(def.class_data_off)?;
452        if self.method.is_some() {
453            cd.static_fields.clear();
454            cd.instance_fields.clear();
455            for methods in [&mut cd.direct_methods, &mut cd.virtual_methods] {
456                let mut kept = Vec::new();
457                for m in methods.drain(..) {
458                    if self.method_matches(m.method_idx)? {
459                        kept.push(m);
460                    }
461                }
462                *methods = kept;
463            }
464        }
465        let static_values = if def.static_values_off != 0 && self.method.is_none() {
466            parse_array(dex.buf(), def.static_values_off as usize)?.0
467        } else {
468            Vec::new()
469        };
470
471        if !cd.static_fields.is_empty() {
472            self.out.push_str("\n\n# static fields\n");
473            for (i, f) in cd.static_fields.iter().enumerate() {
474                if i > 0 {
475                    self.out.push('\n');
476                }
477                self.field(f, static_values.get(i), ann.fields.get(&f.field_idx).map(|set| set.as_slice()))?;
478            }
479        }
480        if !cd.instance_fields.is_empty() {
481            self.out.push_str("\n\n# instance fields\n");
482            for (i, f) in cd.instance_fields.iter().enumerate() {
483                if i > 0 {
484                    self.out.push('\n');
485                }
486                self.field(f, None, ann.fields.get(&f.field_idx).map(|set| set.as_slice()))?;
487            }
488        }
489        if !cd.direct_methods.is_empty() {
490            self.out.push_str("\n\n# direct methods\n");
491            for (i, m) in cd.direct_methods.iter().enumerate() {
492                if i > 0 {
493                    self.out.push('\n');
494                }
495                self.method(m, &ann)?;
496            }
497        }
498        if !cd.virtual_methods.is_empty() {
499            self.out.push_str("\n\n# virtual methods\n");
500            for (i, m) in cd.virtual_methods.iter().enumerate() {
501                if i > 0 {
502                    self.out.push('\n');
503                }
504                self.method(m, &ann)?;
505            }
506        }
507        Ok(())
508    }
509
510    fn field(&mut self, f: &EncodedField, value: Option<&EncodedValue>, annotations: Option<&[AnnotationItem]>) -> Result<()> {
511        let dex = self.names.dex;
512        let id = dex.field_id(f.field_idx)?;
513        let _ = write!(
514            self.out,
515            ".field {}{}:{}",
516            flags(f.access_flags, FIELD_FLAGS),
517            dex.string(id.name_idx)?,
518            self.names.type_(id.type_idx as u32)?
519        );
520        if let Some(v) = value {
521            let _ = write!(self.out, " = {}", format::value(dex, v)?);
522        }
523        self.out.push('\n');
524        if let Some(items) = annotations.filter(|a| !a.is_empty()) {
525            for item in items {
526                self.annotation(item, 1)?;
527            }
528            self.out.push_str(".end field\n");
529        }
530        Ok(())
531    }
532
533    fn method(&mut self, m: &EncodedMethod, ann: &Annotations) -> Result<()> {
534        let dex = self.names.dex;
535        let id = dex.method_id(m.method_idx)?;
536        let _ = writeln!(
537            self.out,
538            ".method {}{}{}",
539            flags(m.access_flags, METHOD_FLAGS),
540            dex.string(id.name_idx)?,
541            self.names.proto(id.proto_idx as u32)?
542        );
543        let code = if m.code_off != 0 { Some(dex.code_item(m.code_off)?) } else { None };
544        if let Some(code) = &code {
545            let _ = writeln!(self.out, "    .registers {}", code.registers_size);
546            if code.ins_size > code.registers_size {
547                let _ = writeln!(
548                    self.out,
549                    "    # malformed code_item: ins_size {} exceeds registers_size {}",
550                    code.ins_size, code.registers_size
551                );
552            }
553        }
554        let debug = match &code {
555            Some(c) if c.debug_info_off != 0 => DebugInfo::parse(dex.buf(), c.debug_info_off as usize).ok().map(|d| d.0),
556            _ => None,
557        };
558        // Parameters: names from debug info, annotations from the directory.
559        // Methods without code (abstract / native) still carry both, and their
560        // parameter registers start at 0 (or 1 after `this`), exactly as if
561        // `registers_size == ins_size` (baksmali's `writeEmptyMethodTo`).
562        let params = dex.proto_params(id.proto_idx as u32)?;
563        let param_annotations = ann.params.get(&m.method_idx);
564        let is_static = m.access_flags & access::STATIC != 0;
565        let mut reg = code.as_ref().map_or(0, |c| c.registers_size.saturating_sub(c.ins_size) as usize);
566        if !is_static {
567            reg += 1;
568        }
569        for (i, &pt) in params.iter().enumerate() {
570            let name = debug.as_ref().and_then(|d| d.parameter_names.get(i).copied().flatten());
571            let anns = param_annotations.and_then(|p| p.get(i)).filter(|a| !a.is_empty());
572            let desc = self.names.type_(pt as u32)?;
573            let wide = desc == "J" || desc == "D";
574            if (self.opts.param_names && name.is_some()) || anns.is_some() {
575                let _ = write!(self.out, "    .param v{reg}");
576                if let Some(n) = name.filter(|_| self.opts.param_names) {
577                    let _ = write!(self.out, ", \"{}\"", escape_string(&dex.string(n)?));
578                }
579                let _ = writeln!(self.out, "    # {desc}");
580                if let Some(items) = anns {
581                    for item in items.iter() {
582                        self.annotation(item, 2)?;
583                    }
584                    self.out.push_str("    .end param\n");
585                }
586            }
587            reg += if wide { 2 } else { 1 };
588        }
589        if let Some(items) = ann.methods.get(&m.method_idx) {
590            for item in items.iter() {
591                self.annotation(item, 1)?;
592            }
593        }
594        if let Some(code) = &code {
595            self.out.push('\n');
596            insn::method_body(&mut self.names, code, debug.as_ref(), self.opts, &mut self.out, self.marks.as_mut())?;
597        }
598        self.out.push_str(".end method\n");
599        Ok(())
600    }
601
602    fn annotation(&mut self, item: &AnnotationItem, depth: usize) -> Result<()> {
603        let vis = match item.visibility {
604            0 => "build",
605            1 => "runtime",
606            2 => "system",
607            _ => "runtime",
608        };
609        let indent = "    ".repeat(depth);
610        let _ = writeln!(self.out, "{indent}.annotation {vis} {}", self.names.type_(item.annotation.type_idx)?);
611        let dex = self.names.dex;
612        for (name, value) in &item.annotation.elements {
613            let _ = writeln!(self.out, "{indent}    {} = {}", dex.string(*name)?, format::value_indented(dex, value, depth + 1)?);
614        }
615        let _ = writeln!(self.out, "{indent}.end annotation");
616        check_size(&self.out, listing_limit(dex))
617    }
618}