Skip to main content

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}