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}