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 { e: Expr },
84 /// Raw monitorenter (before synchronized reconstruction).
85 MonitorEnter(Expr),
86 /// Raw monitorexit.
87 MonitorExit(Expr),
88 /// Fallback: label (for unstructured control flow / jsr-ret leftovers).
89 Label(u32),
90 /// Fallback: goto a label.
91 Goto(u32),
92 /// Local class declaration rendered inline in a method body.
93 /// `header` is e.g. `Local implements Runnable`.
94 ClassDecl { name: String, header: String, body: String },
95 /// `label: statement`
96 Labeled { label: String, body: Box<Stmt> },
97 /// `break;` / `break label;`
98 Break(Option<String>),
99 /// `continue;` / `continue label;`
100 Continue(Option<String>),
101 /// A raw line we could not decompile (error resilience).
102 Comment(String),
103}
104
105#[derive(Debug, Clone, PartialEq)]
106pub struct CaseGroup {
107 /// Case labels (empty = fallthrough continuation of previous group).
108 pub labels: Vec<i64>,
109 /// String case values (when on_string).
110 pub string_labels: Vec<String>,
111 /// Enum constant names (restored $SwitchMap switch).
112 pub enum_labels: Vec<String>,
113 /// Verbatim case labels (`null`, type patterns) from typeSwitch
114 /// restoration.
115 pub raw_labels: Vec<String>,
116 /// Pattern guard (`case T v when GUARD:`) folded from the desugared
117 /// restart shape (guarded typeSwitch: `if (guard) body else
118 /// {state=N; continue;}`). Printed after the raw label.
119 pub guard: Option<crate::ir::expr::Expr>,
120 pub body: Vec<Stmt>,
121}
122
123#[derive(Debug, Clone, PartialEq)]
124pub struct Catch {
125 /// Exception class internal names (multi-catch when >1);
126 /// empty = catch-all (`finally` style / Throwable).
127 pub exc: Vec<String>,
128 /// Variable holding the exception parameter.
129 pub var: u32,
130 /// Per-occurrence printed name (avoids nested-catch collisions).
131 pub var_name: Option<String>,
132 pub body: Box<Stmt>,
133}
134
135impl Stmt {
136 pub fn is_empty_block(&self) -> bool {
137 matches!(self, Stmt::Block(b) if b.is_empty())
138 }
139
140 /// Wrap in a block if not already one.
141 pub fn as_block(self) -> Stmt {
142 match self {
143 Stmt::Block(_) => self,
144 other => Stmt::Block(vec![other]),
145 }
146 }
147}
148
149/// Flatten nested single-statement blocks for cleaner output.
150pub fn flatten(s: &mut Stmt) {
151 match s {
152 Stmt::Block(v) => {
153 for x in v.iter_mut() {
154 flatten(x);
155 }
156 }
157 Stmt::If { then_stmt, else_stmt, .. } => {
158 flatten(then_stmt);
159 if let Some(e) = else_stmt {
160 flatten(e);
161 }
162 }
163 Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => flatten(body),
164 Stmt::For { body, .. } | Stmt::ForEach { body, .. } => flatten(body),
165 Stmt::Try { body, catches, finally } => {
166 flatten(body);
167 for c in catches {
168 flatten(&mut c.body);
169 }
170 if let Some(f) = finally {
171 flatten(f);
172 }
173 }
174 Stmt::TryWithResources { body, catches, finally, .. } => {
175 flatten(body);
176 for c in catches {
177 flatten(&mut c.body);
178 }
179 if let Some(f) = finally {
180 flatten(f);
181 }
182 }
183 Stmt::Synchronized { body, .. } => flatten(body),
184 Stmt::Switch { cases, default, .. } => {
185 for c in cases {
186 for s in c.body.iter_mut() {
187 flatten(s);
188 }
189 }
190 if let Some(d) = default {
191 flatten(d);
192 }
193 }
194 _ => {}
195 }
196}