jdc_core/ir/expr.rs
1//! Expression tree for decompiled code.
2
3use crate::types::{GenericType, JavaType, MethodDescriptor};
4
5/// A type reference that may carry generics from a Signature attribute.
6#[derive(Debug, Clone, PartialEq)]
7pub enum TypeRef {
8 /// Plain descriptor type.
9 J(JavaType),
10 /// Generic signature type.
11 G(GenericType),
12}
13
14impl TypeRef {
15 pub fn to_java(&self) -> String {
16 match self {
17 TypeRef::J(t) => t.to_java(true),
18 TypeRef::G(g) => g.to_java(),
19 }
20 }
21
22 /// Underlying erased Java type (for generics: erased class/primitive).
23 pub fn erased(&self) -> JavaType {
24 match self {
25 TypeRef::J(t) => t.clone(),
26 TypeRef::G(g) => generic_to_erased(g),
27 }
28 }
29
30 pub fn slot_size(&self) -> usize {
31 self.erased().slot_size()
32 }
33
34 pub fn is_wide(&self) -> bool {
35 self.erased().is_wide()
36 }
37
38 /// The internal class name if this is a plain object reference.
39 pub fn internal_name(&self) -> Option<&str> {
40 match self {
41 TypeRef::J(JavaType::Object(n)) => Some(n),
42 TypeRef::G(GenericType::Class(cs)) if cs.parts.iter().all(|p| p.args.is_empty()) => None, // owned string; caller uses erased
43 _ => None,
44 }
45 }
46}
47
48pub fn generic_to_erased(g: &GenericType) -> JavaType {
49 match g {
50 GenericType::Primitive(c) => match c {
51 'V' => JavaType::Void,
52 'Z' => JavaType::Boolean,
53 'B' => JavaType::Byte,
54 'C' => JavaType::Char,
55 'S' => JavaType::Short,
56 'I' => JavaType::Int,
57 'F' => JavaType::Float,
58 'J' => JavaType::Long,
59 'D' => JavaType::Double,
60 _ => JavaType::Void,
61 },
62 GenericType::Class(cs) => JavaType::Object(cs.internal_name()),
63 GenericType::Array(inner) => JavaType::Array(Box::new(generic_to_erased(inner))),
64 GenericType::TypeVar(_) => JavaType::Object("java/lang/Object".into()),
65 GenericType::Wildcard(_) => JavaType::Object("java/lang/Object".into()),
66 }
67}
68
69impl From<JavaType> for TypeRef {
70 fn from(t: JavaType) -> Self {
71 TypeRef::J(t)
72 }
73}
74
75/// A constant value with its source-level type.
76#[derive(Debug, Clone, PartialEq)]
77pub enum ConstVal {
78 Int(i32),
79 Long(i64),
80 Float(f32),
81 Double(f64),
82 Str(String),
83 /// `null` literal.
84 Null,
85 /// Unloaded class constant resolved to a class literal or `X.class`.
86 ClassLit(TypeRef),
87}
88
89impl ConstVal {
90 pub fn type_ref(&self) -> TypeRef {
91 match self {
92 ConstVal::Int(_) => JavaType::Int.into(),
93 ConstVal::Long(_) => JavaType::Long.into(),
94 ConstVal::Float(_) => JavaType::Float.into(),
95 ConstVal::Double(_) => JavaType::Double.into(),
96 ConstVal::Str(_) => JavaType::Object("java/lang/String".into()).into(),
97 ConstVal::Null => JavaType::Object("java/lang/Object".into()).into(),
98 ConstVal::ClassLit(_) => JavaType::Object("java/lang/Class".into()).into(),
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum UnOp {
105 Neg, // -x
106 Not, // !x
107 BitNot, // ~x
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum BinOp {
112 Add, Sub, Mul, Div, Rem,
113 Shl, Shr, Ushr,
114 And, Or, Xor,
115 Eq, Ne, Lt, Ge, Gt, Le,
116 /// Reference equality on objects (==).
117 RefEq, RefNe,
118 /// Logical && and || (from short-circuit structuring).
119 LogAnd, LogOr,
120 /// String concatenation `+`.
121 StrCat,
122}
123
124impl BinOp {
125 /// Java operator precedence (higher binds tighter).
126 pub fn precedence(&self) -> u8 {
127 match self {
128 BinOp::Mul | BinOp::Div | BinOp::Rem => 13,
129 BinOp::Add | BinOp::Sub | BinOp::StrCat => 12,
130 BinOp::Shl | BinOp::Shr | BinOp::Ushr => 11,
131 BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le => 10,
132 BinOp::Eq | BinOp::Ne | BinOp::RefEq | BinOp::RefNe => 9,
133 BinOp::And => 8,
134 BinOp::Xor => 7,
135 BinOp::Or => 6,
136 BinOp::LogAnd => 4,
137 BinOp::LogOr => 3,
138 }
139 }
140
141 pub fn symbol(&self) -> &'static str {
142 match self {
143 BinOp::Add | BinOp::StrCat => "+",
144 BinOp::Sub => "-",
145 BinOp::Mul => "*",
146 BinOp::Div => "/",
147 BinOp::Rem => "%",
148 BinOp::Shl => "<<",
149 BinOp::Shr => ">>",
150 BinOp::Ushr => ">>>",
151 BinOp::And => "&",
152 BinOp::Or => "|",
153 BinOp::Xor => "^",
154 BinOp::Eq | BinOp::RefEq => "==",
155 BinOp::Ne | BinOp::RefNe => "!=",
156 BinOp::Lt => "<",
157 BinOp::Ge => ">=",
158 BinOp::Gt => ">",
159 BinOp::Le => "<=",
160 BinOp::LogAnd => "&&",
161 BinOp::LogOr => "||",
162 }
163 }
164
165 /// The reversed comparison operator (for condition inversion).
166 pub fn invert(self) -> Option<BinOp> {
167 Some(match self {
168 BinOp::Eq | BinOp::RefEq => BinOp::Ne,
169 BinOp::Ne | BinOp::RefNe => BinOp::Eq,
170 BinOp::Lt => BinOp::Ge,
171 BinOp::Ge => BinOp::Lt,
172 BinOp::Gt => BinOp::Le,
173 BinOp::Le => BinOp::Gt,
174 // LogAnd/LogOr have NO operand-preserving inversion: negating
175 // them is De Morgan (negate both operands AND swap the op) —
176 // convert.rs negate() handles that recursively; an op-only swap
177 // here silently changed the condition's meaning (Arrays.equals'
178 // folded `a == null || a2 == null` inverted to `a == null &&
179 // a2 == null`, NPE-ing the a2==null path).
180 _ => return None,
181 })
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum AssignOp {
187 Plain,
188 Add, Sub, Mul, Div, Rem,
189 Shl, Shr, Ushr,
190 And, Or, Xor,
191}
192
193impl AssignOp {
194 pub fn symbol(&self) -> &'static str {
195 match self {
196 AssignOp::Plain => "=",
197 AssignOp::Add => "+=",
198 AssignOp::Sub => "-=",
199 AssignOp::Mul => "*=",
200 AssignOp::Div => "/=",
201 AssignOp::Rem => "%=",
202 AssignOp::Shl => "<<=",
203 AssignOp::Shr => ">>=",
204 AssignOp::Ushr => ">>>=",
205 AssignOp::And => "&=",
206 AssignOp::Or => "|=",
207 AssignOp::Xor => "^=",
208 }
209 }
210}
211
212/// Lambda kind inferred from invokedynamic + LambdaMetafactory.
213#[derive(Debug, Clone, PartialEq)]
214pub enum LambdaKind {
215 /// `args -> body` referencing an implementation method.
216 Lambda,
217 /// `Cls::name` static/instance reference.
218 MethodRef,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222pub struct LambdaExpr {
223 pub kind: LambdaKind,
224 /// Functional interface method name/desc for casting ambiguity if needed.
225 pub sam_name: String,
226 pub sam_desc: MethodDescriptor,
227 /// LambdaMetafactory instantiatedMethodType (bootstrap arg 2): the
228 /// SAM descriptor as javac INSTANTIATED it at this site — for
229 /// `.<ConstantDesc>map(Utf8Entry::stringValue)` it is
230 /// `(LUtf8Entry;)LConstantDesc;` against the erased samMethodType
231 /// `(LObject;)LObject;`. The only bytecode record of explicit type
232 /// arguments on a lambda/method-ref call.
233 pub inst_sam_desc: Option<MethodDescriptor>,
234 /// Implementation method (for Lambda kind: the body is emitted from it,
235 /// or referenced by name when it belongs to another class).
236 pub impl_owner: String,
237 pub impl_name: String,
238 pub impl_desc: MethodDescriptor,
239 pub impl_is_static: bool,
240 /// Captured arguments (evaluated at indy call site), in impl-arg order
241 /// after the SAM parameters.
242 pub captures: Vec<Expr>,
243 /// SAM parameter names (synthesized if absent).
244 pub param_names: Vec<String>,
245 /// The SAM interface's internal name (the indy descriptor's return):
246 /// a lambda used as a call RECEIVER must print with the source's SAM
247 /// cast (`((BooleanSupplier) () -> ..).getAsBoolean()` — a bare lambda
248 /// cannot be an owner: 此处不应为 lambda 表达式, jdk26 Proxy).
249 pub sam_cls: String,
250 /// Method-ref receiver class name, for `X::y` form.
251 pub ref_receiver: Option<String>,
252 /// Snapshot renames for captured outer locals that are NOT
253 /// effectively final (the decompiler hoists+reassigns them):
254 /// (outer var id, impl-method param var id, snapshot name). The
255 /// method pass declares `final T name = outer;` before the lambda's
256 /// statement and the printer renames the impl param accordingly.
257 pub capture_snaps: Vec<(u32, u32, String)>,
258}
259
260/// Resolved static bootstrap-method argument.
261#[derive(Debug, Clone, PartialEq)]
262pub enum BsmArg {
263 Str(String),
264 Cls(String),
265 /// Integer-valued constant label (Java 21+ typeSwitch constant
266 /// patterns: `case 1:`, char constants arrive as their code point).
267 Int(i32),
268 Other,
269}
270
271#[derive(Debug, Clone, PartialEq)]
272pub enum ConcatPart {
273 Str(Expr),
274 /// Recipe constant segment.
275 Const(String),
276}
277
278#[derive(Debug, Clone, PartialEq)]
279pub enum Expr {
280 Const(ConstVal),
281 /// Local variable by VarId, with its declared type (needed for
282 /// category-1 vs category-2 stack operations like dup2/pop2).
283 Local { var: u32, ty: TypeRef },
284 This,
285 /// `new T(args)` (constructor call folded with New when possible).
286 New {
287 cls: String,
288 ty: TypeRef,
289 args: Vec<Expr>,
290 /// true if this is a raw `new` whose `<init>` hasn't been folded yet.
291 raw: bool,
292 },
293 /// Array creation: `new int[n]`, `new int[]{...}`, `new int[a][b][]`.
294 NewArray {
295 elem: TypeRef,
296 /// dimension sizes for the created dimensions
297 dims: Vec<Expr>,
298 /// trailing empty dimensions count (e.g. `new int[3][]` -> dims=[3], trailing=1)
299 trailing_dims: u8,
300 /// initializer for `new T[]{...}` form
301 init: Option<Vec<Expr>>,
302 },
303 /// Multi-dimensional array creation `multianewarray`.
304 NewMultiArray {
305 ty: TypeRef,
306 dims: Vec<Expr>,
307 },
308 Field {
309 /// None = `this` (instance field of current class) or static import-style.
310 owner: Option<Box<Expr>>,
311 /// Declaring class internal name.
312 cls: String,
313 name: String,
314 ty: TypeRef,
315 is_static: bool,
316 },
317 Method {
318 owner: Option<Box<Expr>>,
319 cls: String,
320 name: String,
321 desc: MethodDescriptor,
322 args: Vec<Expr>,
323 is_static: bool,
324 is_interface: bool,
325 /// invokespecial (super call / constructor / private).
326 is_special: bool,
327 /// `super.m(...)` form.
328 is_super: bool,
329 /// invokedynamic without recognized pattern.
330 is_dynamic: bool,
331 /// Explicit type arguments (`Cls.<E>m(...)`) restored for generic
332 /// calls in throw position, where inference would pick the bound.
333 type_args: Vec<String>,
334 },
335 ArrayIndex {
336 array: Box<Expr>,
337 index: Box<Expr>,
338 },
339 Cast {
340 ty: TypeRef,
341 e: Box<Expr>,
342 },
343 InstanceOf {
344 e: Box<Expr>,
345 ty: TypeRef,
346 },
347 Un {
348 op: UnOp,
349 e: Box<Expr>,
350 },
351 Bin {
352 op: BinOp,
353 l: Box<Expr>,
354 r: Box<Expr>,
355 /// Type context for `+` disambiguation (string vs numeric).
356 ty: Option<TypeRef>,
357 },
358 Cond {
359 c: Box<Expr>,
360 t: Box<Expr>,
361 f: Box<Expr>,
362 },
363 Assign {
364 target: Box<Expr>,
365 op: AssignOp,
366 value: Box<Expr>,
367 },
368 /// `++x` / `--x` (delta = +1 / -1)
369 PreIncDec {
370 e: Box<Expr>,
371 delta: i64,
372 wide: bool,
373 },
374 /// `x++` / `x--`
375 PostIncDec {
376 e: Box<Expr>,
377 delta: i64,
378 wide: bool,
379 },
380 Lambda(Box<LambdaExpr>),
381 /// String concatenation via StringConcatFactory recipe.
382 StringConcat(Vec<ConcatPart>),
383 /// Pre-rendered source fragment (used for captured expressions that are
384 /// substituted across method contexts).
385 Raw(String),
386 /// Pre-rendered capture text WITH the captured local's declared type:
387 /// comparisons inside local-class bodies need the operand's generic
388 /// type to drive witnesses (erased val$ field types cannot).
389 RawT(String, TypeRef),
390 /// Unrecognized invokedynamic fallback.
391 Invokedynamic {
392 name: String,
393 desc: MethodDescriptor,
394 args: Vec<Expr>,
395 bsm_text: String,
396 /// Resolved static bootstrap arguments (for SwitchBootstraps
397 /// typeSwitch restoration).
398 bsm_static_args: Vec<BsmArg>,
399 },
400 /// `new Anon(args) { ...class body... }` — anonymous class instantiation.
401 AnonNew {
402 /// The anonymous class itself.
403 cls: String,
404 /// Interface being implemented, or superclass (Object suppressed).
405 base: TypeRef,
406 args: Vec<Expr>,
407 /// Decompiled class body lines (already indented relative).
408 body: String,
409 },
410}
411
412impl Expr {
413 /// Best-effort static type of this expression.
414 pub fn type_ref(&self) -> TypeRef {
415 match self {
416 Expr::Const(c) => c.type_ref(),
417 Expr::Local { ty, .. } => ty.clone(),
418 Expr::This => JavaType::Object("java/lang/Object".into()).into(),
419 Expr::New { ty, .. } => ty.clone(),
420 // dims carries the lengths; `trailing_dims` counts EXTRA []
421 // levels beyond the element type (`new byte[][]{a,b,c}` =
422 // elem Byte + the init dimension + 1 trailing). type_ref
423 // dropped the trailing levels (ML_DSA_Impls.implGenerate-
424 // KeyPair: the byte[][] literal evidenced byte[] for the
425 // return-temp slot — byte[][]无法转换为byte[] x2 classes).
426 Expr::NewArray { elem, trailing_dims, .. } => {
427 let mut t = elem.erased();
428 for _ in 0..=(*trailing_dims as usize) {
429 t = JavaType::Array(Box::new(t));
430 }
431 t.into()
432 }
433 Expr::NewMultiArray { ty, .. } => ty.clone(),
434 Expr::Field { ty, .. } => ty.clone(),
435 Expr::Method { desc, .. } => desc.ret.clone().into(),
436 Expr::ArrayIndex { array, .. } => match &**array {
437 other => match other.type_ref().erased() {
438 JavaType::Array(inner) => (*inner).clone().into(),
439 t => t.into(),
440 },
441 },
442 Expr::Cast { ty, .. } => ty.clone(),
443 Expr::InstanceOf { .. } => JavaType::Boolean.into(),
444 Expr::Un { op: UnOp::Not, .. } => JavaType::Boolean.into(),
445 Expr::Un { e, .. } => e.type_ref(),
446 Expr::Bin { op, ty, l, .. } => match op {
447 // Comparisons and short-circuit logicals are boolean
448 // regardless of operand types — the stored `ty` (when
449 // present it mirrors the operands) misled the bool->int
450 // assignment rewrite (`int mode = e != null;` stayed
451 // unrewrapped: jdk11 SynchronousQueue).
452 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt
453 | BinOp::Le | BinOp::RefEq | BinOp::RefNe | BinOp::LogAnd
454 | BinOp::LogOr => JavaType::Boolean.into(),
455 _ => ty.clone().unwrap_or_else(|| l.type_ref()),
456 },
457 Expr::Cond { t, f, .. } => {
458 // Divergent reference branches join to their LUB; without
459 // hierarchy access that is plain Object (jdk17
460 // CodePointTrie: `c ? new Small32(..) : new Fast32(..)`
461 // typed the merge var Small32 and the sibling branch
462 // became "条件表达式中的类型错误").
463 let tt = t.type_ref();
464 let ft = f.type_ref();
465 match (&tt, &ft) {
466 (TypeRef::J(JavaType::Object(a)), TypeRef::J(JavaType::Object(b)))
467 if a != b =>
468 {
469 JavaType::Object("java/lang/Object".into()).into()
470 }
471 _ => tt,
472 }
473 }
474 Expr::Assign { value, .. } => value.type_ref(),
475 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => e.type_ref(),
476 Expr::Lambda(_) => JavaType::Object("java/lang/Object".into()).into(),
477 Expr::Raw(_) => JavaType::Object("java/lang/Object".into()).into(),
478 Expr::RawT(_, ty) => ty.clone(),
479 Expr::AnonNew { cls, .. } => JavaType::Object(cls.clone()).into(),
480 Expr::StringConcat(_) => JavaType::Object("java/lang/String".into()).into(),
481 Expr::Invokedynamic { desc, .. } => desc.ret.clone().into(),
482 }
483 }
484
485 /// True if emitting this expression as a statement component needs parens
486 /// at the given outer precedence context.
487 pub fn needs_parens(&self, outer_prec: u8, is_right: bool) -> bool {
488 match self {
489 Expr::Bin { op, .. } => {
490 let p = op.precedence();
491 p < outer_prec || (is_right && p == outer_prec)
492 }
493 // String concat is an additive-level expression: as a method
494 // receiver or cast operand it MUST parenthesize, or a call
495 // textually binds to the last operand (jdk11
496 // SSLSessionContextImpl.getKey: `(hostname + ":" + port)
497 // .toLowerCase(..)` rendered as .. + port.toLowerCase(..) —
498 // 无法取消引用int; SSLTrafficKeyDerivation's ("tls13 "+label)
499 // .getBytes lost to byte[] — String无法转换为byte[]).
500 Expr::StringConcat(_) => {
501 let p = BinOp::Add.precedence();
502 p < outer_prec || (is_right && p == outer_prec)
503 }
504 Expr::InstanceOf { .. } => outer_prec > 10,
505 Expr::Cond { .. } => outer_prec > 2,
506 Expr::Assign { .. } | Expr::PreIncDec { .. } | Expr::PostIncDec { .. } => outer_prec > 1,
507 Expr::Un { .. } => outer_prec > 14,
508 Expr::Cast { .. } => outer_prec > 13,
509 // `new int[1][0]` as an indexing base or receiver must
510 // parenthesize: the emitted `new int[1][0][0]` re-parses as a
511 // THREE-dim creation, not an index into a two-dim one — and as
512 // an assignment target it is not a variable at all
513 // (CFR prints `(new int[1][0])[0] = new int[]{a};`).
514 Expr::NewArray { .. } | Expr::NewMultiArray { .. } => outer_prec >= 15,
515 _ => false,
516 }
517 }
518}