Skip to main content

jdc_core/ir/
stmt.rs

1//! Statement tree for decompiled code.
2
3use crate::ir::expr::Expr;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Stmt {
7    /// A plain `{ ... }` block (or statement sequence during construction).
8    Block(Vec<Stmt>),
9    /// Expression statement (the expression must have side effects).
10    ExprStmt(Expr),
11    /// Local variable declaration: `[final] T name [= init];`
12    LocalDef {
13        var: u32,
14        init: Option<Expr>,
15        is_final: bool,
16        /// Force declaration with explicit type even when inferrable.
17        force_type: bool,
18    },
19    Return(Option<Expr>),
20    Throw(Expr),
21    If {
22        cond: Expr,
23        then_stmt: Box<Stmt>,
24        else_stmt: Option<Box<Stmt>>,
25    },
26    /// `while (cond) body`; cond may be `true` for infinite loops.
27    While {
28        cond: Expr,
29        body: Box<Stmt>,
30    },
31    DoWhile {
32        body: Box<Stmt>,
33        cond: Expr,
34    },
35    /// Classic `for (init; cond; update) body`.
36    For {
37        init: Vec<Stmt>,
38        cond: Option<Expr>,
39        update: Vec<Expr>,
40        body: Box<Stmt>,
41    },
42    /// Enhanced for: `for (T v : iterable) body`.
43    ForEach {
44        var: u32,
45        iterable: Expr,
46        /// true if iterating an array (index-based lowering).
47        is_array: bool,
48        body: Box<Stmt>,
49    },
50    Switch {
51        selector: Expr,
52        cases: Vec<CaseGroup>,
53        default: Option<Box<Stmt>>,
54        /// true if this was a string switch (selector hashed).
55        on_string: bool,
56    },
57    Try {
58        body: Box<Stmt>,
59        catches: Vec<Catch>,
60        finally: Option<Box<Stmt>>,
61    },
62    /// `try (resources) body` — restored try-with-resources.
63    TryWithResources {
64        /// Resource declarations (`LocalDef` statements).
65        resources: Vec<Stmt>,
66        body: Box<Stmt>,
67        catches: Vec<Catch>,
68        finally: Option<Box<Stmt>>,
69    },
70    /// `assert cond [: msg];` restored from the $assertionsDisabled idiom.
71    Assert {
72        cond: Expr,
73        msg: Option<Expr>,
74    },
75    /// `synchronized (lock) body`
76    Synchronized {
77        lock: Expr,
78        body: Box<Stmt>,
79    },
80    /// A folded value-diamond: this "statement" represents a value left on
81    /// the operand stack (consumed by the merge block). Removed after the
82    /// merge statements are woven.
83    TernaryValue {
84        e: Expr,
85    },
86    /// Raw monitorenter (before synchronized reconstruction).
87    MonitorEnter(Expr),
88    /// Raw monitorexit.
89    MonitorExit(Expr),
90    /// Fallback: label (for unstructured control flow / jsr-ret leftovers).
91    Label(u32),
92    /// Fallback: goto a label.
93    Goto(u32),
94    /// Local class declaration rendered inline in a method body.
95    /// `header` is e.g. `Local implements Runnable`.
96    ClassDecl {
97        name: String,
98        header: String,
99        body: String,
100    },
101    /// `label: statement`
102    Labeled {
103        label: String,
104        body: Box<Stmt>,
105    },
106    /// `break;` / `break label;`
107    Break(Option<String>),
108    /// `continue;` / `continue label;`
109    Continue(Option<String>),
110    /// A raw line we could not decompile (error resilience).
111    Comment(String),
112}
113
114#[derive(Debug, Clone, PartialEq)]
115pub struct CaseGroup {
116    /// Case labels (empty = fallthrough continuation of previous group).
117    pub labels: Vec<i64>,
118    /// String case values (when on_string).
119    pub string_labels: Vec<String>,
120    /// Enum constant names (restored $SwitchMap switch).
121    pub enum_labels: Vec<String>,
122    /// Verbatim case labels (`null`, type patterns) from typeSwitch
123    /// restoration.
124    pub raw_labels: Vec<String>,
125    /// Pattern guard (`case T v when GUARD:`) folded from the desugared
126    /// restart shape (guarded typeSwitch: `if (guard) body else
127    /// {state=N; continue;}`). Printed after the raw label.
128    pub guard: Option<crate::ir::expr::Expr>,
129    pub body: Vec<Stmt>,
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub struct Catch {
134    /// Exception class internal names (multi-catch when >1);
135    /// empty = catch-all (`finally` style / Throwable). `Arc<str>`:
136    /// shared with the dex/classfile string tables through the whole
137    /// catch pipeline (Cfg → TryGroup → Region → Catch) — these used to
138    /// be re-allocated at every hop for every handler of every method.
139    pub exc: Vec<std::sync::Arc<str>>,
140    /// Variable holding the exception parameter.
141    pub var: u32,
142    /// Per-occurrence printed name (avoids nested-catch collisions).
143    pub var_name: Option<String>,
144    pub body: Box<Stmt>,
145}
146
147impl Stmt {
148    pub fn is_empty_block(&self) -> bool {
149        matches!(self, Stmt::Block(b) if b.is_empty())
150    }
151
152    /// Wrap in a block if not already one.
153    pub fn as_block(self) -> Stmt {
154        match self {
155            Stmt::Block(_) => self,
156            other => Stmt::Block(vec![other]),
157        }
158    }
159}
160
161/// Flatten nested single-statement blocks for cleaner output.
162pub fn flatten(s: &mut Stmt) {
163    match s {
164        Stmt::Block(v) => {
165            for x in v.iter_mut() {
166                flatten(x);
167            }
168        }
169        Stmt::If {
170            then_stmt,
171            else_stmt,
172            ..
173        } => {
174            flatten(then_stmt);
175            if let Some(e) = else_stmt {
176                flatten(e);
177            }
178        }
179        Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => flatten(body),
180        Stmt::For { body, .. } | Stmt::ForEach { body, .. } => flatten(body),
181        Stmt::Try {
182            body,
183            catches,
184            finally,
185        } => {
186            flatten(body);
187            for c in catches {
188                flatten(&mut c.body);
189            }
190            if let Some(f) = finally {
191                flatten(f);
192            }
193        }
194        Stmt::TryWithResources {
195            body,
196            catches,
197            finally,
198            ..
199        } => {
200            flatten(body);
201            for c in catches {
202                flatten(&mut c.body);
203            }
204            if let Some(f) = finally {
205                flatten(f);
206            }
207        }
208        Stmt::Synchronized { body, .. } => flatten(body),
209        Stmt::Switch { cases, default, .. } => {
210            for c in cases {
211                for s in c.body.iter_mut() {
212                    flatten(s);
213                }
214            }
215            if let Some(d) = default {
216                flatten(d);
217            }
218        }
219        _ => {}
220    }
221}