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).
136 pub exc: Vec<String>,
137 /// Variable holding the exception parameter.
138 pub var: u32,
139 /// Per-occurrence printed name (avoids nested-catch collisions).
140 pub var_name: Option<String>,
141 pub body: Box<Stmt>,
142}
143
144impl Stmt {
145 pub fn is_empty_block(&self) -> bool {
146 matches!(self, Stmt::Block(b) if b.is_empty())
147 }
148
149 /// Wrap in a block if not already one.
150 pub fn as_block(self) -> Stmt {
151 match self {
152 Stmt::Block(_) => self,
153 other => Stmt::Block(vec![other]),
154 }
155 }
156}
157
158/// Flatten nested single-statement blocks for cleaner output.
159pub fn flatten(s: &mut Stmt) {
160 match s {
161 Stmt::Block(v) => {
162 for x in v.iter_mut() {
163 flatten(x);
164 }
165 }
166 Stmt::If {
167 then_stmt,
168 else_stmt,
169 ..
170 } => {
171 flatten(then_stmt);
172 if let Some(e) = else_stmt {
173 flatten(e);
174 }
175 }
176 Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => flatten(body),
177 Stmt::For { body, .. } | Stmt::ForEach { body, .. } => flatten(body),
178 Stmt::Try {
179 body,
180 catches,
181 finally,
182 } => {
183 flatten(body);
184 for c in catches {
185 flatten(&mut c.body);
186 }
187 if let Some(f) = finally {
188 flatten(f);
189 }
190 }
191 Stmt::TryWithResources {
192 body,
193 catches,
194 finally,
195 ..
196 } => {
197 flatten(body);
198 for c in catches {
199 flatten(&mut c.body);
200 }
201 if let Some(f) = finally {
202 flatten(f);
203 }
204 }
205 Stmt::Synchronized { body, .. } => flatten(body),
206 Stmt::Switch { cases, default, .. } => {
207 for c in cases {
208 for s in c.body.iter_mut() {
209 flatten(s);
210 }
211 }
212 if let Some(d) = default {
213 flatten(d);
214 }
215 }
216 _ => {}
217 }
218}