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