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().into()),
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(std::sync::Arc<str>),
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(std::sync::Arc<str>),
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: std::sync::Arc<str>,
316 ty: TypeRef,
317 args: Vec<Expr>,
318 /// Formal parameter types from the folded `<init>` descriptor —
319 /// the ctor's ground truth for argument-position repairs when
320 /// the class is NOT in the pool (framework ctors like
321 /// `new Byte(b)`). Empty for raw/unfolded news.
322 arg_tys: Vec<JavaType>,
323 /// true if this is a raw `new` whose `<init>` hasn't been folded yet.
324 raw: bool,
325 },
326 /// Array creation: `new int[n]`, `new int[]{...}`, `new int[a][b][]`.
327 NewArray {
328 elem: TypeRef,
329 /// dimension sizes for the created dimensions
330 dims: Vec<Expr>,
331 /// trailing empty dimensions count (e.g. `new int[3][]` -> dims=[3], trailing=1)
332 trailing_dims: u8,
333 /// initializer for `new T[]{...}` form
334 init: Option<Vec<Expr>>,
335 },
336 /// Multi-dimensional array creation `multianewarray`.
337 NewMultiArray {
338 ty: TypeRef,
339 dims: Vec<Expr>,
340 },
341 Field {
342 /// None = `this` (instance field of current class) or static import-style.
343 owner: Option<Box<Expr>>,
344 /// Declaring class internal name.
345 ///
346 /// String payloads are `Arc<str>`: Expr is the most-cloned node
347 /// in the whole pipeline (block materialization, copy-walks,
348 /// forwarding) and every clone used to allocate fresh copies of
349 /// the class/method names. Front-ends back these with the dex/
350 /// constant-pool string tables, so construction is a refcount
351 /// bump and clones never allocate.
352 cls: std::sync::Arc<str>,
353 name: std::sync::Arc<str>,
354 ty: TypeRef,
355 is_static: bool,
356 },
357 Method {
358 owner: Option<Box<Expr>>,
359 cls: std::sync::Arc<str>,
360 name: std::sync::Arc<str>,
361 desc: std::sync::Arc<MethodDescriptor>,
362 args: Vec<Expr>,
363 is_static: bool,
364 is_interface: bool,
365 /// invokespecial (super call / constructor / private).
366 is_special: bool,
367 /// `super.m(...)` form.
368 is_super: bool,
369 /// invokedynamic without recognized pattern.
370 is_dynamic: bool,
371 /// Explicit type arguments (`Cls.<E>m(...)`) restored for generic
372 /// calls in throw position, where inference would pick the bound.
373 type_args: Vec<String>,
374 },
375 ArrayIndex {
376 array: Box<Expr>,
377 index: Box<Expr>,
378 },
379 Cast {
380 ty: TypeRef,
381 e: Box<Expr>,
382 },
383 InstanceOf {
384 e: Box<Expr>,
385 ty: TypeRef,
386 },
387 Un {
388 op: UnOp,
389 e: Box<Expr>,
390 },
391 Bin {
392 op: BinOp,
393 l: Box<Expr>,
394 r: Box<Expr>,
395 /// Type context for `+` disambiguation (string vs numeric).
396 ty: Option<TypeRef>,
397 },
398 Cond {
399 c: Box<Expr>,
400 t: Box<Expr>,
401 f: Box<Expr>,
402 },
403 Assign {
404 target: Box<Expr>,
405 op: AssignOp,
406 value: Box<Expr>,
407 },
408 /// `++x` / `--x` (delta = +1 / -1)
409 PreIncDec {
410 e: Box<Expr>,
411 delta: i64,
412 wide: bool,
413 },
414 /// `x++` / `x--`
415 PostIncDec {
416 e: Box<Expr>,
417 delta: i64,
418 wide: bool,
419 },
420 Lambda(Box<LambdaExpr>),
421 /// String concatenation via StringConcatFactory recipe.
422 StringConcat(Vec<ConcatPart>),
423 /// Pre-rendered source fragment (used for captured expressions that are
424 /// substituted across method contexts).
425 Raw(String),
426 /// Pre-rendered capture text WITH the captured local's declared type:
427 /// comparisons inside local-class bodies need the operand's generic
428 /// type to drive witnesses (erased val$ field types cannot).
429 RawT(String, TypeRef),
430 /// Unrecognized invokedynamic fallback.
431 Invokedynamic {
432 name: String,
433 desc: MethodDescriptor,
434 args: Vec<Expr>,
435 bsm_text: String,
436 /// Resolved static bootstrap arguments (for SwitchBootstraps
437 /// typeSwitch restoration).
438 bsm_static_args: Vec<BsmArg>,
439 },
440 /// `new Anon(args) { ...class body... }` — anonymous class instantiation.
441 AnonNew {
442 /// The anonymous class itself.
443 cls: String,
444 /// Interface being implemented, or superclass (Object suppressed).
445 base: TypeRef,
446 args: Vec<Expr>,
447 /// Decompiled class body lines (already indented relative).
448 body: String,
449 },
450}
451
452impl Expr {
453 /// Best-effort static type of this expression.
454 pub fn type_ref(&self) -> TypeRef {
455 match self {
456 Expr::Const(c) => c.type_ref(),
457 Expr::Local { ty, .. } => ty.clone(),
458 Expr::This => JavaType::Object("java/lang/Object".into()).into(),
459 Expr::New { ty, .. } => ty.clone(),
460 // dims carries the lengths; `trailing_dims` counts EXTRA []
461 // levels beyond the element type (`new byte[][]{a,b,c}` =
462 // elem Byte + the init dimension + 1 trailing). type_ref
463 // dropped the trailing levels (ML_DSA_Impls.implGenerate-
464 // KeyPair: the byte[][] literal evidenced byte[] for the
465 // return-temp slot — byte[][]无法转换为byte[] x2 classes).
466 Expr::NewArray {
467 elem,
468 trailing_dims,
469 ..
470 } => {
471 let mut t = elem.erased();
472 for _ in 0..=(*trailing_dims as usize) {
473 t = JavaType::Array(Box::new(t));
474 }
475 t.into()
476 }
477 Expr::NewMultiArray { ty, .. } => ty.clone(),
478 Expr::Field { ty, .. } => ty.clone(),
479 Expr::Method { desc, .. } => desc.ret.clone().into(),
480 Expr::ArrayIndex { array, .. } => match array.type_ref().erased() {
481 JavaType::Array(inner) => (*inner).clone().into(),
482 t => t.into(),
483 },
484 Expr::Cast { ty, .. } => ty.clone(),
485 Expr::InstanceOf { .. } => JavaType::Boolean.into(),
486 Expr::Un { op: UnOp::Not, .. } => JavaType::Boolean.into(),
487 Expr::Un { e, .. } => e.type_ref(),
488 Expr::Bin { op, ty, l, .. } => match op {
489 // Comparisons and short-circuit logicals are boolean
490 // regardless of operand types — the stored `ty` (when
491 // present it mirrors the operands) misled the bool->int
492 // assignment rewrite (`int mode = e != null;` stayed
493 // unrewrapped: jdk11 SynchronousQueue).
494 BinOp::Eq
495 | BinOp::Ne
496 | BinOp::Lt
497 | BinOp::Ge
498 | BinOp::Gt
499 | BinOp::Le
500 | BinOp::RefEq
501 | BinOp::RefNe
502 | BinOp::LogAnd
503 | BinOp::LogOr => JavaType::Boolean.into(),
504 _ => ty.clone().unwrap_or_else(|| l.type_ref()),
505 },
506 Expr::Cond { t, f, .. } => {
507 // Divergent reference branches join to their LUB; without
508 // hierarchy access that is plain Object (jdk17
509 // CodePointTrie: `c ? new Small32(..) : new Fast32(..)`
510 // typed the merge var Small32 and the sibling branch
511 // became "条件表达式中的类型错误").
512 let tt = t.type_ref();
513 let ft = f.type_ref();
514 match (&tt, &ft) {
515 (TypeRef::J(JavaType::Object(a)), TypeRef::J(JavaType::Object(b)))
516 if a != b =>
517 {
518 JavaType::Object("java/lang/Object".into()).into()
519 }
520 _ => tt,
521 }
522 }
523 Expr::Assign { value, .. } => value.type_ref(),
524 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => e.type_ref(),
525 Expr::Lambda(_) => JavaType::Object("java/lang/Object".into()).into(),
526 Expr::Raw(_) => JavaType::Object("java/lang/Object".into()).into(),
527 Expr::RawT(_, ty) => ty.clone(),
528 Expr::AnonNew { cls, .. } => JavaType::Object(cls.as_str().into()).into(),
529 Expr::StringConcat(_) => JavaType::Object("java/lang/String".into()).into(),
530 Expr::Invokedynamic { desc, .. } => desc.ret.clone().into(),
531 }
532 }
533
534 /// True if emitting this expression as a statement component needs parens
535 /// at the given outer precedence context.
536 pub fn needs_parens(&self, outer_prec: u8, is_right: bool) -> bool {
537 match self {
538 Expr::Bin { op, .. } => {
539 let p = op.precedence();
540 p < outer_prec || (is_right && p == outer_prec)
541 }
542 // String concat is an additive-level expression: as a method
543 // receiver or cast operand it MUST parenthesize, or a call
544 // textually binds to the last operand (jdk11
545 // SSLSessionContextImpl.getKey: `(hostname + ":" + port)
546 // .toLowerCase(..)` rendered as .. + port.toLowerCase(..) —
547 // 无法取消引用int; SSLTrafficKeyDerivation's ("tls13 "+label)
548 // .getBytes lost to byte[] — String无法转换为byte[]).
549 Expr::StringConcat(_) => {
550 let p = BinOp::Add.precedence();
551 p < outer_prec || (is_right && p == outer_prec)
552 }
553 Expr::InstanceOf { .. } => outer_prec > 10,
554 Expr::Cond { .. } => outer_prec > 2,
555 Expr::Assign { .. } | Expr::PreIncDec { .. } | Expr::PostIncDec { .. } => {
556 outer_prec > 1
557 }
558 Expr::Un { .. } => outer_prec > 14,
559 Expr::Cast { .. } => outer_prec > 13,
560 // `new int[1][0]` as an indexing base or receiver must
561 // parenthesize: the emitted `new int[1][0][0]` re-parses as a
562 // THREE-dim creation, not an index into a two-dim one — and as
563 // an assignment target it is not a variable at all
564 // (CFR prints `(new int[1][0])[0] = new int[]{a};`).
565 Expr::NewArray { .. } | Expr::NewMultiArray { .. } => outer_prec >= 15,
566 _ => false,
567 }
568 }
569}