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