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.type_ref().erased() {
476 JavaType::Array(inner) => (*inner).clone().into(),
477 t => t.into(),
478 },
479 Expr::Cast { ty, .. } => ty.clone(),
480 Expr::InstanceOf { .. } => JavaType::Boolean.into(),
481 Expr::Un { op: UnOp::Not, .. } => JavaType::Boolean.into(),
482 Expr::Un { e, .. } => e.type_ref(),
483 Expr::Bin { op, ty, l, .. } => match op {
484 // Comparisons and short-circuit logicals are boolean
485 // regardless of operand types — the stored `ty` (when
486 // present it mirrors the operands) misled the bool->int
487 // assignment rewrite (`int mode = e != null;` stayed
488 // unrewrapped: jdk11 SynchronousQueue).
489 BinOp::Eq
490 | BinOp::Ne
491 | BinOp::Lt
492 | BinOp::Ge
493 | BinOp::Gt
494 | BinOp::Le
495 | BinOp::RefEq
496 | BinOp::RefNe
497 | BinOp::LogAnd
498 | BinOp::LogOr => JavaType::Boolean.into(),
499 _ => ty.clone().unwrap_or_else(|| l.type_ref()),
500 },
501 Expr::Cond { t, f, .. } => {
502 // Divergent reference branches join to their LUB; without
503 // hierarchy access that is plain Object (jdk17
504 // CodePointTrie: `c ? new Small32(..) : new Fast32(..)`
505 // typed the merge var Small32 and the sibling branch
506 // became "条件表达式中的类型错误").
507 let tt = t.type_ref();
508 let ft = f.type_ref();
509 match (&tt, &ft) {
510 (TypeRef::J(JavaType::Object(a)), TypeRef::J(JavaType::Object(b)))
511 if a != b =>
512 {
513 JavaType::Object("java/lang/Object".into()).into()
514 }
515 _ => tt,
516 }
517 }
518 Expr::Assign { value, .. } => value.type_ref(),
519 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => e.type_ref(),
520 Expr::Lambda(_) => JavaType::Object("java/lang/Object".into()).into(),
521 Expr::Raw(_) => JavaType::Object("java/lang/Object".into()).into(),
522 Expr::RawT(_, ty) => ty.clone(),
523 Expr::AnonNew { cls, .. } => JavaType::Object(cls.as_str().into()).into(),
524 Expr::StringConcat(_) => JavaType::Object("java/lang/String".into()).into(),
525 Expr::Invokedynamic { desc, .. } => desc.ret.clone().into(),
526 }
527 }
528
529 /// True if emitting this expression as a statement component needs parens
530 /// at the given outer precedence context.
531 pub fn needs_parens(&self, outer_prec: u8, is_right: bool) -> bool {
532 match self {
533 Expr::Bin { op, .. } => {
534 let p = op.precedence();
535 p < outer_prec || (is_right && p == outer_prec)
536 }
537 // String concat is an additive-level expression: as a method
538 // receiver or cast operand it MUST parenthesize, or a call
539 // textually binds to the last operand (jdk11
540 // SSLSessionContextImpl.getKey: `(hostname + ":" + port)
541 // .toLowerCase(..)` rendered as .. + port.toLowerCase(..) —
542 // 无法取消引用int; SSLTrafficKeyDerivation's ("tls13 "+label)
543 // .getBytes lost to byte[] — String无法转换为byte[]).
544 Expr::StringConcat(_) => {
545 let p = BinOp::Add.precedence();
546 p < outer_prec || (is_right && p == outer_prec)
547 }
548 Expr::InstanceOf { .. } => outer_prec > 10,
549 Expr::Cond { .. } => outer_prec > 2,
550 Expr::Assign { .. } | Expr::PreIncDec { .. } | Expr::PostIncDec { .. } => {
551 outer_prec > 1
552 }
553 Expr::Un { .. } => outer_prec > 14,
554 Expr::Cast { .. } => outer_prec > 13,
555 // `new int[1][0]` as an indexing base or receiver must
556 // parenthesize: the emitted `new int[1][0][0]` re-parses as a
557 // THREE-dim creation, not an index into a two-dim one — and as
558 // an assignment target it is not a variable at all
559 // (CFR prints `(new int[1][0])[0] = new int[]{a};`).
560 Expr::NewArray { .. } | Expr::NewMultiArray { .. } => outer_prec >= 15,
561 _ => false,
562 }
563 }
564}