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()) => {
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}