jdc_core/emit.rs
1//! Java source emission from the statement/expression tree.
2
3use crate::types::JavaType;
4
5use crate::ctx::MethodBody;
6use crate::ir::expr::{BinOp, ConcatPart, ConstVal, Expr, LambdaKind, TypeRef, UnOp};
7use crate::ir::stmt::{CaseGroup, Catch, Stmt};
8use crate::var::VarTable;
9
10pub struct Printer<'a> {
11 /// Front-end services: naming, nesting, type queries, nested bodies.
12 pub ctx: &'a dyn crate::ctx::Ctx,
13 pub vt: &'a VarTable,
14 out: String,
15 indent: usize,
16 /// Depth guard for recursive lambda printing.
17 lambda_depth: usize,
18 /// Names visible in enclosing lambda/method scopes BEYOND self.vt,
19 /// accumulated down the lambda print chain: a nested lambda's locals
20 /// must not redeclare ANY of them (jdk26 PackageSnippets
21 /// .fooToBarUnrolled: the depth-2 codeBuilder lambda's foreach `i$`
22 /// collided with the METHOD-level `i$` — the depth-1 rename only
23 /// consulted depth-1's vt, 已在方法中定义了变量 i$).
24 outer_names: Vec<String>,
25 /// Sequence for unbound catch parameters (`ignored`, `ignored2`, …):
26 /// two catch scopes can NEST (a try inside a catch body) and javac
27 /// rejects duplicate names across overlapping scopes ("已在方法
28 /// invokeSuspend(Object)中定义了变量 ignored", weixin hl5/m ×56).
29 ignored_seq: u32,
30 /// True when the enclosing method returns boolean.
31 /// Reused per-statement line buffer (millions of statements: the
32 /// fresh-String-per-line pattern re-allocated and re-grew each time).
33 line_buf: String,
34 pub ret_bool: bool,
35 /// True while rendering the ROOT of an expression statement: a
36 /// signature-polymorphic call there must stay bare (`NEXT.compareAndSet
37 /// (b, n, p);`) — `(boolean) call();` is not a legal statement.
38 suppress_poly_cast: bool,
39 /// True while rendering the direct child of a cast expression: a cast
40 /// gives the diamond NO target type, so ` (T) new X<>(args)` infers
41 /// Object bounds and then fails the cast — while the bare/raw form
42 /// `(T) new X(args)` compiles (unchecked).
43 suppress_diamond: bool,
44 /// True while rendering a conditional expression's branches. Pre-Java-8
45 /// (major < 52) conditionals are NOT poly expressions: a diamond branch
46 /// (`c ? new HashMap<>(..) : new LinkedHashMap<>(..)`) infers each side
47 /// standalone to <Object,Object> and fails the assignment target (jdk7
48 /// HashSet.readObject 不兼容的类型 — the golden source carried explicit
49 /// <E,Object> args). Diamond branches print RAW there instead:
50 /// unchecked but always compilable.
51 in_cond: bool,
52 /// Instantiated SAM return type while rendering the Lambda directly
53 /// inside a cast to a generic functional interface: the lambda body's
54 /// returns may need `(R) value` witnesses (jdk17 Collectors
55 /// `(Function<I,R>) i -> i`).
56 lambda_sam_ret: Option<TypeRef>,
57 /// The method's generic signature return (functional-interface or
58 /// array form): a lambda in RETURN position has no cast node to hang
59 /// the SAM witness on, so the Return arm derives it from this.
60 ret_sam: Option<TypeRef>,
61 /// True when the enclosing method returns char: int constants in the
62 /// returned expression render as char literals (bytecode chars are
63 /// ints; `return cond ? 63 : 105;` in a char method is a lossy
64 /// conversion — source was `? '?' : 'i'`, jdk XML Parser x23).
65 pub ret_char: bool,
66 /// Narrowing-return markers: int-valued expressions in the return
67 /// need `(byte)`/`(short)` casts (jdk11 MemberName:
68 /// `byte normalVirtual = cond ? 5 : 9;`).
69 pub ret_byte: bool,
70 pub ret_short: bool,
71 /// Memoized twin-exists probe for the same-package member-shadow
72 /// check, keyed by first segment (class_name is constant per ctx;
73 /// shared with sub-printers). The chain-walk does up to 4 registry
74 /// lookups per level — per-call that cost ~30% on weixin; per
75 /// distinct segment it is amortized to nothing.
76 twin_cache: std::rc::Rc<
77 std::cell::RefCell<std::collections::HashMap<String, bool>>,
78 >,
79}
80
81impl<'a> Printer<'a> {
82 pub fn new(ctx: &'a dyn crate::ctx::Ctx, vt: &'a VarTable) -> Self {
83 Printer {
84 ctx,
85 vt,
86 out: String::new(),
87 indent: 0,
88 lambda_depth: 0,
89 outer_names: Vec::new(),
90 ignored_seq: 0,
91 line_buf: String::new(),
92 ret_bool: false,
93 suppress_poly_cast: false,
94 suppress_diamond: false,
95 in_cond: false,
96 lambda_sam_ret: None,
97 ret_sam: None,
98 ret_char: false,
99 ret_byte: false,
100 ret_short: false,
101 twin_cache: std::rc::Rc::new(std::cell::RefCell::new(
102 std::collections::HashMap::new(),
103 )),
104 }
105 }
106
107 pub fn with_ret_bool(mut self, b: bool) -> Self {
108 self.ret_bool = b;
109 self
110 }
111
112 pub fn with_ret_sam(mut self, t: Option<TypeRef>) -> Self {
113 self.ret_sam = t;
114 self
115 }
116 pub fn with_ret_char(mut self, b: bool) -> Self {
117 self.ret_char = b;
118 self
119 }
120
121 pub fn with_ret_narrow(mut self, byte: bool, short: bool) -> Self {
122 self.ret_byte = byte;
123 self.ret_short = short;
124 self
125 }
126
127 /// Printer rendering into a caller-provided buffer: the method text
128 /// (signature, braces) and the printed body land in ONE string —
129 /// ddc used to render the body separately and copy it line-by-line
130 /// into the class buffer (a full extra pass over every method).
131 pub fn with_output(mut self, out: String) -> Self {
132 self.out = out;
133 self
134 }
135
136 /// Printer starting at a fixed indentation level (method bodies at 1).
137 pub fn with_indent(mut self, indent: usize) -> Self {
138 self.indent = indent;
139 self
140 }
141
142 pub fn into_string(mut self, body: &Stmt) -> String {
143 // The old unconditional truncate_dead_ends rebuilt (deep-cloned)
144 // the whole statement tree per method — a top profile cost. The
145 // truncation case is rare: probe read-only first.
146 if tree_has_dead_end(body) {
147 let truncated = truncate_dead_ends(body);
148 self.stmt(&truncated);
149 } else {
150 self.stmt(body);
151 }
152 self.out
153 }
154
155 /// Print an expression that appears in a boolean context; int 0/1
156 /// constants become true/false.
157 fn bool_ish_expr(x: &Expr) -> bool {
158 match x {
159 Expr::Const(ConstVal::Int(n)) => *n == 0 || *n == 1,
160 Expr::Cond { t, f, .. } => {
161 matches!(&**t, Expr::Const(ConstVal::Int(0 | 1)))
162 && matches!(&**f, Expr::Const(ConstVal::Int(0 | 1)))
163 }
164 Expr::Un {
165 op: crate::ir::expr::UnOp::Not,
166 ..
167 }
168 | Expr::InstanceOf { .. } => true,
169 Expr::Bin { op, .. } => matches!(
170 op,
171 crate::ir::expr::BinOp::Eq
172 | crate::ir::expr::BinOp::Ne
173 | crate::ir::expr::BinOp::Lt
174 | crate::ir::expr::BinOp::Ge
175 | crate::ir::expr::BinOp::Gt
176 | crate::ir::expr::BinOp::Le
177 | crate::ir::expr::BinOp::RefEq
178 | crate::ir::expr::BinOp::RefNe
179 | crate::ir::expr::BinOp::LogAnd
180 | crate::ir::expr::BinOp::LogOr
181 ),
182 other => other.type_ref().erased() == crate::types::JavaType::Boolean,
183 }
184 }
185
186 /// bool_ish_expr with VarTable resolution: booleanize retypes the vt
187 /// while embedded Local tys stay frozen, and a bitwise bin over
188 /// bool-ish sides is itself boolean (jdk26 LinkedTransferQueue
189 /// `(spin & !upc) == 0`).
190 fn bool_ish_vt(&self, x: &Expr) -> bool {
191 match x {
192 Expr::Local { var, .. } => {
193 matches!(
194 self.vt.var(*var).ty.erased(),
195 crate::types::JavaType::Boolean
196 )
197 }
198 Expr::Bin {
199 op: crate::ir::expr::BinOp::And | crate::ir::expr::BinOp::Or | crate::ir::expr::BinOp::Xor,
200 l,
201 r,
202 ..
203 } => self.bool_ish_vt(l) && self.bool_ish_vt(r),
204 _ => Self::bool_ish_expr(x),
205 }
206 }
207
208 pub fn expr_bool(&mut self, e: &Expr, out: &mut String) {
209 match e {
210 Expr::Const(ConstVal::Int(n)) => {
211 out.push_str(if *n != 0 { "true" } else { "false" });
212 }
213 // Bitwise ops over boolean-ish operands print as their
214 // logical form: javac computes `a != b` over booleans as
215 // IXOR of 0/1 ints (jdk17 DecimalFormat isNegative:
216 // `(c1 ? 1 : 0) ^ (c2 ? 1 : 0)` against a boolean local —
217 // "int无法转换为boolean").
218 Expr::Bin { op: bop, l, r, .. }
219 if matches!(
220 bop,
221 crate::ir::expr::BinOp::Xor
222 | crate::ir::expr::BinOp::And
223 | crate::ir::expr::BinOp::Or
224 ) && self.bool_ish_vt(l)
225 && self.bool_ish_vt(r) =>
226 {
227 let sym = match bop {
228 crate::ir::expr::BinOp::Xor => "^",
229 crate::ir::expr::BinOp::And => "&",
230 _ => "|",
231 };
232 // Parenthesize composite sides: expr_bool renders `||`/
233 // `&&` conditions bare and ^ binds LOOSER than && but
234 // TIGHTER than || (a || b && c ^ d would regroup).
235 for (i, side) in [l.as_ref(), r.as_ref()].iter().enumerate() {
236 if i > 0 {
237 out.push(' ');
238 out.push_str(sym);
239 out.push(' ');
240 }
241 let needs = matches!(
242 side,
243 Expr::Bin { .. } | Expr::Cond { .. } | Expr::Assign { .. }
244 );
245 if needs {
246 out.push('(');
247 }
248 self.expr_bool(side, out);
249 if needs {
250 out.push(')');
251 }
252 }
253 }
254 Expr::Cond { c, t, f } => {
255 // `x ? true : false` → x ; `x ? false : true` → !x
256 let tb = const_bool(t);
257 let fb = const_bool(f);
258 match (tb, fb) {
259 (Some(true), Some(false)) => self.expr_bool(c, out),
260 (Some(false), Some(true)) => {
261 // !(!x) collapses to x
262 if let Expr::Un {
263 op: crate::ir::expr::UnOp::Not,
264 e: inner,
265 } = &**c
266 {
267 self.expr_bool(inner, out);
268 } else {
269 out.push('!');
270 // expr_bool renders composite conditions bare:
271 // `!` + `ssign != 45` would regroup as
272 // `(!ssign) != 45` (jdk26 FloatingDecimal
273 // "一元运算符 '!' 的操作数类型int错误").
274 let needs = matches!(
275 &**c,
276 Expr::Bin { .. } | Expr::Cond { .. } | Expr::Assign { .. }
277 );
278 if needs {
279 out.push('(');
280 }
281 self.expr_bool(c, out);
282 if needs {
283 out.push(')');
284 }
285 }
286 }
287 _ => {
288 self.expr(c, 3, out);
289 out.push_str(" ? ");
290 self.expr_bool(t, out);
291 out.push_str(" : ");
292 self.expr_bool(f, out);
293 }
294 }
295 }
296 _ => self.expr(e, 1, out),
297 }
298 }
299
300 // ---------------- statements ----------------
301
302
303 /// Unique name for an unbound catch parameter; bound ones take
304 /// their recorded/var-table name. Uniqueness covers the printer's
305 /// own sequence, the var table and every enclosing-scope name the
306 /// lambda chain accumulated (a lambda body may not redeclare a
307 /// name visible outside it).
308 fn catch_var_name(&mut self, c: &crate::ir::stmt::Catch) -> String {
309 if c.var != u32::MAX {
310 return c
311 .var_name
312 .clone()
313 .unwrap_or_else(|| self.vt.var(c.var).name.clone());
314 }
315 loop {
316 self.ignored_seq += 1;
317 let name = if self.ignored_seq == 1 {
318 "ignored".to_string()
319 } else {
320 format!("ignored{}", self.ignored_seq)
321 };
322 let clash = self.vt.vars.iter().any(|v| v.name == name)
323 || self.outer_names.contains(&name);
324 if !clash {
325 return name;
326 }
327 }
328 }
329 fn line(&mut self, s: &str) {
330 self.push_indent();
331 self.out.push_str(s);
332 self.out.push('\n');
333 }
334
335 /// Indentation as one contiguous push (the per-level push_str loop
336 /// ran per printed line).
337 fn push_indent(&mut self) {
338 const UNIT: &str = " "; // 32 spaces
339 let mut n = self.indent * 4;
340 while n >= 32 {
341 self.out.push_str(UNIT);
342 n -= 32;
343 }
344 if n > 0 {
345 self.out.push_str(&UNIT[..n]);
346 }
347 }
348
349 /// Render an int-typed expression in a byte/short target: constants
350 /// and ternary branches get the narrowing cast (javac's conditional
351 /// expression only narrows when the target type is known — a return
352 /// or local-decl of byte/short with int constants needs explicit
353 /// casts: `byte normalVirtual = !if() ? (byte) 5 : (byte) 9;`).
354 pub fn expr_narrow(&mut self, e: &Expr, ty: &crate::types::JavaType, out: &mut String) {
355 self.expr_narrow_f(e, ty, false, out)
356 }
357
358 /// `force_consts`: the enclosing conditional's condition is not a
359 /// compile-time constant, so the whole expression is not a constant
360 /// expression and javac's assignment-context narrowing does NOT apply
361 /// to in-range int constants either (byte getDirectionality: `return
362 /// (ch & 0xFFFE) == 0xFFFE ? -1 : 0` — 从int转换到byte可能会有损失;
363 /// the source branches are byte static finals the bytecode inlined).
364 fn expr_narrow_f(
365 &mut self,
366 e: &Expr,
367 ty: &crate::types::JavaType,
368 force_consts: bool,
369 out: &mut String,
370 ) {
371 match e {
372 Expr::Const(ConstVal::Int(n)) => {
373 let (lo, hi) = match ty {
374 crate::types::JavaType::Byte => (-128i64, 127i64),
375 crate::types::JavaType::Short => (-32768i64, 32767i64),
376 _ => (i32::MIN as i64, i32::MAX as i64),
377 };
378 if force_consts || (*n as i64) < lo || (*n as i64) > hi {
379 out.push('(');
380 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
381 out.push_str(") ");
382 }
383 out.push_str(&n.to_string());
384 }
385 Expr::Cond { c, t, f } => {
386 let force = force_consts || !matches!(&**c, Expr::Const(_));
387 self.expr(c, 3, out);
388 out.push_str(" ? ");
389 let prev = std::mem::replace(&mut self.in_cond, true);
390 self.expr_narrow_f(t, ty, force, out);
391 out.push_str(" : ");
392 self.expr_narrow_f(f, ty, force, out);
393 self.in_cond = prev;
394 }
395 // An int-typed local (typically a branch-merge stack temp)
396 // under a byte/short target needs the explicit narrowing cast
397 // (jdk17 InvokerBytecodeGenerator `return stack61;` —
398 // 从int转换到byte可能会有损失; the merged branches carried
399 // byte values the evidence widened to int).
400 Expr::Local { var, .. } => {
401 if matches!(
402 (self.vt.var(*var).ty.erased(), ty),
403 (crate::types::JavaType::Int, crate::types::JavaType::Byte)
404 | (crate::types::JavaType::Int, crate::types::JavaType::Short)
405 ) {
406 out.push('(');
407 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
408 out.push_str(") ");
409 }
410 self.expr(e, 1, out);
411 }
412 _ => self.expr(e, 1, out),
413 }
414 }
415
416 /// Call-argument variant of expr_narrow: invocation conversion never
417 /// narrows, so in-range int constants keep the explicit cast too, and
418 /// int conditionals get it per branch.
419 fn expr_narrow_arg(&mut self, e: &Expr, ty: &crate::types::JavaType, out: &mut String) {
420 match e {
421 Expr::Const(ConstVal::Int(n)) => {
422 out.push('(');
423 out.push_str(&self.type_name(&TypeRef::J(ty.clone())));
424 out.push_str(") ");
425 out.push_str(&n.to_string());
426 }
427 Expr::Cond { c, t, f } => {
428 self.expr(c, 3, out);
429 out.push_str(" ? ");
430 let prev = std::mem::replace(&mut self.in_cond, true);
431 self.expr_narrow_arg(t, ty, out);
432 out.push_str(" : ");
433 self.expr_narrow_arg(f, ty, out);
434 self.in_cond = prev;
435 }
436 _ => self.expr(e, 1, out),
437 }
438 }
439
440 /// Render an expression in a char-typed target: int constants become
441 /// char literals, ternary branches recurse (the lossy-conversion fix
442 /// for `char name2type(..) { return c ? 63 : 105; }`).
443 pub fn expr_char(&mut self, e: &Expr, out: &mut String) {
444 match e {
445 Expr::Const(ConstVal::Int(n)) => push_char_lit(out, *n),
446 Expr::Cond { c, t, f } => {
447 self.expr(c, 3, out);
448 out.push_str(" ? ");
449 let prev = std::mem::replace(&mut self.in_cond, true);
450 self.expr_char(t, out);
451 out.push_str(" : ");
452 self.expr_char(f, out);
453 self.in_cond = prev;
454 }
455 _ => self.expr(e, 1, out),
456 }
457 }
458
459 fn stmt(&mut self, s: &Stmt) {
460 match s {
461 Stmt::Block(v) => {
462 for x in v {
463 self.stmt(x);
464 }
465 }
466 Stmt::ExprStmt(e) => {
467 if !is_statement_expr(e) {
468 // Only assignments/calls/inc-dec/new are legal
469 // expression statements; a leftover pure concat
470 // (`"" + 48;`) renders as a comment instead.
471 let mut text = String::new();
472 self.expr(e, 0, &mut text);
473 self.line(&format!("/* {text}; */"));
474 return;
475 }
476 let mut line = std::mem::take(&mut self.line_buf);
477 line.clear();
478 if matches!(e, Expr::Method { .. }) {
479 self.suppress_poly_cast = true;
480 }
481 self.expr(e, 0, &mut line);
482 self.suppress_poly_cast = false;
483 line.push(';');
484 self.line(&line);
485 self.line_buf = line;
486 }
487 Stmt::LocalDef {
488 var,
489 init,
490 is_final,
491 force_type,
492 } => {
493 let _ = force_type;
494 let info = self.vt.var(*var);
495 let mut line = std::mem::take(&mut self.line_buf);
496 line.clear();
497 if *is_final {
498 line.push_str("final ");
499 }
500 line.push_str(&self.local_decl_ty(*var, &info.ty));
501 line.push(' ');
502 line.push_str(&java_ident(&info.name));
503 if let Some(e) = init {
504 line.push_str(" = ");
505 // A lambda initializing a generically-typed SAM local:
506 // the declared type is the only record of the
507 // instantiated SAM return (the indy's
508 // instantiatedMethodType is erased — IntFunction<T[]>
509 // records (I)[Object), so prime the lambda-body cast
510 // from it (jdk11 ForEachOps: `IntFunction<T[]>
511 // generator = size -> (T[]) new Object[size]` —
512 // without the cast "lambda 表达式中的返回类型错误").
513 if let (TypeRef::G(g @ crate::types::GenericType::Class(_)), Expr::Lambda(l)) =
514 (&info.ty, e)
515 {
516 if self.lambda_sam_ret.is_none() {
517 self.lambda_sam_ret = self.sam_ret_cast(g, &l.sam_name);
518 }
519 }
520 // Assigning a concrete type to a type-variable local
521 // needs the (erased-away) cast back in source form.
522 let need_cast =
523 matches!(&info.ty, TypeRef::G(crate::types::GenericType::TypeVar(_)))
524 && e.type_ref() != info.ty;
525 if need_cast {
526 line.push('(');
527 line.push_str(&self.type_name(&info.ty));
528 line.push_str(") ");
529 self.expr(e, 14, &mut line);
530 } else if info.ty.erased() == crate::types::JavaType::Boolean {
531 self.expr_bool(e, &mut line);
532 } else if info.ty.erased() == crate::types::JavaType::Char {
533 self.expr_char(e, &mut line);
534 } else if info.ty.erased() == crate::types::JavaType::Byte
535 || info.ty.erased() == crate::types::JavaType::Short
536 {
537 let t = info.ty.erased();
538 self.expr_narrow(e, &t, &mut line);
539 } else {
540 self.expr(e, 1, &mut line);
541 }
542 }
543 line.push(';');
544 self.line(&line);
545 self.line_buf = line;
546 }
547 Stmt::Return(Some(e)) => {
548 let mut line = String::from("return ");
549 if self.ret_bool {
550 self.expr_bool(e, &mut line);
551 } else if self.ret_char {
552 self.expr_char(e, &mut line);
553 } else if self.ret_byte {
554 self.expr_narrow(e, &crate::types::JavaType::Byte, &mut line);
555 } else if self.ret_short {
556 self.expr_narrow(e, &crate::types::JavaType::Short, &mut line);
557 } else {
558 // Lambda in return position of a generic method: the
559 // SAM return witnesses the erased body value
560 // (castingIdentity `return i -> (R) i`, toArray
561 // `() -> (T[]) new Object[]{..}` — jdk17 Collectors).
562 if let (Some(TypeRef::G(g)), Expr::Lambda(l)) = (&self.ret_sam, e) {
563 self.lambda_sam_ret = match g {
564 crate::types::GenericType::Class(_) => {
565 self.sam_ret_cast(g, &l.sam_name)
566 }
567 crate::types::GenericType::Array(_) => Some(TypeRef::G(g.clone())),
568 _ => None,
569 };
570 }
571 self.expr(e, 1, &mut line);
572 self.lambda_sam_ret = None;
573 }
574 line.push(';');
575 self.line(&line);
576 }
577 Stmt::Return(None) => self.line("return;"),
578 Stmt::Throw(e) => {
579 let mut line = String::from("throw ");
580 self.expr(e, 1, &mut line);
581 line.push(';');
582 self.line(&line);
583 }
584 Stmt::If {
585 cond,
586 then_stmt,
587 else_stmt,
588 } => {
589 self.print_if(cond, then_stmt, else_stmt.as_deref(), "");
590 }
591 Stmt::While { cond, body } => {
592 let mut head = String::from("while (");
593 if matches!(cond, Expr::Const(ConstVal::Int(1))) {
594 head.push_str("true");
595 } else {
596 head.push_str(&self.cond_str(cond));
597 }
598 head.push(')');
599 self.block_stmt(&head, body);
600 }
601 Stmt::DoWhile { body, cond } => {
602 self.line("do {");
603 self.indent += 1;
604 self.stmt(body);
605 self.indent -= 1;
606 let mut tail = String::from("} while (");
607 tail.push_str(&self.cond_str(cond));
608 tail.push_str(");");
609 self.line(&tail);
610 }
611 Stmt::For {
612 init,
613 cond,
614 update,
615 body,
616 } => {
617 let mut head = String::from("for (");
618 for (i, s) in init.iter().enumerate() {
619 if i > 0 {
620 head.push_str(", ");
621 }
622 self.stmt_inline(s, &mut head);
623 }
624 head.push_str("; ");
625 if let Some(c) = cond {
626 let cs = self.cond_str(c);
627 head.push_str(&cs);
628 }
629 head.push_str("; ");
630 for (i, u) in update.iter().enumerate() {
631 if i > 0 {
632 head.push_str(", ");
633 }
634 self.expr(u, 1, &mut head);
635 }
636 head.push(')');
637 self.block_stmt(&head, body);
638 }
639 Stmt::ForEach {
640 var,
641 iterable,
642 is_array,
643 body,
644 } => {
645 let _ = is_array;
646 let info = self.vt.var(*var);
647 let mut head = String::from("for (");
648 head.push_str(&self.type_name(&info.ty));
649 head.push(' ');
650 head.push_str(&java_ident(&info.name));
651 head.push_str(" : ");
652 self.expr(iterable, 1, &mut head);
653 head.push(')');
654 self.block_stmt(&head, body);
655 }
656 Stmt::Switch {
657 selector,
658 cases,
659 default,
660 on_string,
661 } => {
662 let _ = on_string;
663 let mut head = String::from("switch (");
664 self.expr(selector, 1, &mut head);
665 head.push(')');
666 self.line(&format!("{} {{", head));
667 self.indent += 1;
668 for c in cases {
669 // MULTIPLE pattern labels in one arm cannot share a
670 // colon body: neither stacked (`case P1 x:` `case P2
671 // y:` — 贯穿到模式非法) nor comma-joined (`case P1 x,
672 // P2 y:` — javac still demands a break between pattern
673 // labels) compile. Duplicate the body per label — the
674 // source's arrow-form multi-pattern arm
675 // (`case A _, B _ -> r;`, jdk26 ParserVerifier
676 // valueSize) in colon form.
677 if c.raw_labels.len() > 1
678 && c.labels.is_empty()
679 && c.string_labels.is_empty()
680 && c.enum_labels.is_empty()
681 {
682 for l in &c.raw_labels {
683 match &c.guard {
684 Some(g) => {
685 let mut gtxt = String::new();
686 self.expr(g, 1, &mut gtxt);
687 self.line(&format!("case {} when {}:", l, gtxt));
688 }
689 None => self.line(&format!("case {}:", l)),
690 }
691 self.line("{");
692 self.indent += 1;
693 for st in &c.body {
694 self.stmt(st);
695 }
696 self.indent -= 1;
697 self.line("}");
698 }
699 continue;
700 }
701 for l in &c.enum_labels {
702 self.line(&format!("case {}:", l));
703 }
704 if let Some(g) = &c.guard {
705 // Folded pattern guard: the desugared restart shape
706 // (`if (guard) body else { state=N; continue; }`)
707 // restored to `case T v when guard:` (jdk26
708 // DecimalFormat — duplicate type-pattern labels are
709 // "此 case 标签由前一个 case 标签支配" without it).
710 let mut gtxt = String::new();
711 self.expr(g, 1, &mut gtxt);
712 let joined = c
713 .raw_labels
714 .iter()
715 .map(|l| format!("{} when {}", l, gtxt))
716 .collect::<Vec<_>>()
717 .join(", ");
718 self.line(&format!("case {}:", joined));
719 } else if !c.raw_labels.is_empty() {
720 // Pattern labels of one arm MUST be comma-joined:
721 // stacked `case P1 x:` / `case P2 y:` lines are a
722 // fall-through between patterns ("从模式贯穿非法",
723 // jdk26 ParserVerifier `case OfConstant _,
724 // OfClass _ -> 2`).
725 self.line(&format!("case {}:", c.raw_labels.join(", ")));
726 }
727 for l in &c.labels {
728 self.line(&format!("case {}:", l));
729 }
730 for l in &c.string_labels {
731 self.line(&format!("case \"{}\":", escape_string(l)));
732 }
733 // Braces give each case its own scope: pattern-switch
734 // restorations reuse binding names across cases.
735 self.line("{");
736 self.indent += 1;
737 for st in &c.body {
738 self.stmt(st);
739 }
740 self.indent -= 1;
741 self.line("}");
742 }
743 if let Some(d) = default {
744 self.line("default:");
745 self.indent += 1;
746 self.stmt(d);
747 self.indent -= 1;
748 }
749 self.indent -= 1;
750 self.line("}");
751 }
752 Stmt::Try {
753 body,
754 catches,
755 finally,
756 } => {
757 self.line("try {");
758 self.indent += 1;
759 self.stmt(body);
760 self.indent -= 1;
761 for c in catches {
762 let exc_name = if c.exc.is_empty() {
763 "Throwable".to_string()
764 } else {
765 c.exc
766 .iter()
767 .map(|e| self.shorten(e))
768 .collect::<Vec<_>>()
769 .join(" | ")
770 };
771 let var_name = self.catch_var_name(c);
772 self.line(&format!("}} catch ({} {}) {{", exc_name, var_name));
773 self.indent += 1;
774 self.stmt(&c.body);
775 self.indent -= 1;
776 }
777 if let Some(f) = finally {
778 self.line("} finally {");
779 self.indent += 1;
780 self.stmt(f);
781 self.indent -= 1;
782 }
783 // A try with no catches and no finally is not valid Java
784 // (the structurer can emit a bare degenerate try); an
785 // empty finally keeps the statement compilable.
786 if catches.is_empty() && finally.is_none() {
787 self.line("} finally {");
788 }
789 self.line("}");
790 }
791 Stmt::TryWithResources {
792 resources,
793 body,
794 catches,
795 finally,
796 } => {
797 let mut res: Vec<String> = Vec::new();
798 for r in resources {
799 if let Stmt::LocalDef { var, init, .. } = r {
800 let info = self.vt.var(*var);
801 let mut t = String::new();
802 t.push_str(&self.type_name(&info.ty));
803 t.push(' ');
804 t.push_str(&java_ident(&info.name));
805 if let Some(e) = init {
806 t.push_str(" = ");
807 self.expr(e, 1, &mut t);
808 }
809 res.push(t);
810 } else {
811 // The resource is not a declaration: a reused
812 // variable was rewritten to a plain assignment by
813 // dedupe_declarations, and `try (x = expr)` is not
814 // Java. Print it as the statement it is, BEFORE the
815 // try (resource init runs before the body anyway) —
816 // emitting nothing turned the header into the
817 // illegal `try () {`.
818 self.stmt(r);
819 }
820 }
821 if res.is_empty() && catches.is_empty() && finally.is_none() {
822 // Vestigial try-with-resources: the resource was
823 // hoisted into a plain assignment above and its close
824 // is a sibling statement, so nothing is left to head
825 // the statement — a bare `try {` does not compile
826 // ("'try' without 'catch', 'finally' or resource
827 // declarations"). A handler-less try IS a block.
828 self.line("{");
829 } else if res.is_empty() {
830 self.line("try {");
831 } else {
832 self.line(&format!("try ({}) {{", res.join("; ")));
833 }
834 self.indent += 1;
835 self.stmt(body);
836 self.indent -= 1;
837 for c in catches {
838 let exc_name = if c.exc.is_empty() {
839 "Throwable".to_string()
840 } else {
841 c.exc
842 .iter()
843 .map(|e| self.shorten(e))
844 .collect::<Vec<_>>()
845 .join(" | ")
846 };
847 let var_name = self.catch_var_name(c);
848 self.line(&format!("}} catch ({} {}) {{", exc_name, var_name));
849 self.indent += 1;
850 self.stmt(&c.body);
851 self.indent -= 1;
852 }
853 if let Some(f) = finally {
854 self.line("} finally {");
855 self.indent += 1;
856 self.stmt(f);
857 self.indent -= 1;
858 }
859 self.line("}");
860 }
861 Stmt::Assert { cond, msg } => {
862 let mut line = String::from("assert ");
863 // The assert-condition is boolean-typed: the synthesized
864 // `assert false` carries Const Int(0) — plain expr would
865 // print `assert 0` (int无法转换为boolean, jdk17 URI).
866 self.expr_bool(cond, &mut line);
867 if let Some(m) = msg {
868 line.push_str(" : ");
869 self.expr(m, 1, &mut line);
870 }
871 line.push(';');
872 self.line(&line);
873 }
874 Stmt::Synchronized { lock, body } => {
875 let mut head = String::from("synchronized (");
876 self.expr(lock, 1, &mut head);
877 head.push(')');
878 self.block_stmt(&head, body);
879 }
880 Stmt::TernaryValue { e } => {
881 let mut line = String::from("/* ternary */ ");
882 self.expr(e, 1, &mut line);
883 line.push(';');
884 self.line(&line);
885 }
886 Stmt::MonitorEnter(e) => {
887 // The lock expression goes INSIDE the comment: a bare
888 // local/field is not a valid expression statement
889 // (`/* monitorenter */ a;` failed javac).
890 let mut line = String::from("/* monitorenter ");
891 self.expr(e, 1, &mut line);
892 line.push_str(" */");
893 self.line(&line);
894 }
895 Stmt::MonitorExit(e) => {
896 let mut line = String::from("/* monitorexit ");
897 self.expr(e, 1, &mut line);
898 line.push_str(" */");
899 self.line(&line);
900 }
901 Stmt::ClassDecl { header, body, .. } => {
902 let kw = if header.starts_with("record ")
903 || header.starts_with("interface ")
904 || header.starts_with("enum ")
905 {
906 String::new()
907 } else {
908 "class ".to_string()
909 };
910 self.line(&format!("{}{} {{", kw, header));
911 for line in body.lines() {
912 if line.trim().is_empty() {
913 self.out.push('\n');
914 } else {
915 for _ in 0..self.indent + 1 {
916 self.out.push_str(" ");
917 }
918 self.out.push_str(line.trim_start());
919 self.out.push('\n');
920 }
921 }
922 self.line("}");
923 }
924 Stmt::Labeled { label, body } => {
925 // label prefix on the following statement's line
926 let save = self.out.len();
927 self.stmt(body);
928 // prepend "label: " before the first line just emitted
929 let indent_str = " ".repeat(self.indent);
930 let emitted = self.out[save..].to_string();
931 self.out.truncate(save);
932 if let Some(rest) = emitted.strip_prefix(&indent_str) {
933 self.out.push_str(&indent_str);
934 self.out.push_str(label);
935 self.out.push_str(": ");
936 self.out.push_str(rest);
937 } else {
938 self.out.push_str(&emitted);
939 }
940 }
941 Stmt::Break(lbl) => match lbl {
942 Some(l) => self.line(&format!("break {};", l)),
943 None => self.line("break;"),
944 },
945 Stmt::Continue(lbl) => match lbl {
946 Some(l) => self.line(&format!("continue {};", l)),
947 None => self.line("continue;"),
948 },
949 Stmt::Label(id) => {
950 // Emitted by breaking the following statement; standalone
951 // labels are invalid before non-statements, so use a marker.
952 self.line(&format!("L{}: ;", id));
953 }
954 Stmt::Goto(id) => self.line(&format!("break L{};", id)),
955 Stmt::Comment(c) => self.line(&format!("// {}", c)),
956 }
957 }
958
959 /// `head {` + body + `}`.
960 fn block_stmt(&mut self, head: &str, body: &Stmt) {
961 self.line(&format!("{} {{", head));
962 self.indent += 1;
963 self.stmt(body);
964 self.indent -= 1;
965 self.line("}");
966 }
967
968 /// Print an if statement; `opener` is "" for the first if and
969 /// "} else " when continuing an else-if chain on the closing line.
970 /// The statement-condition position already supplies its own
971 /// parentheses; an expression that renders fully wrapped in ONE
972 /// balanced layer (`((a & b))`, `((v))` — inner parens from a
973 /// composite-boolean fold or a cast-precedence wrap) carries a
974 /// redundant outer layer. Strip only when the FIRST `(` matches
975 /// the LAST `)` (a complete wrap), so `(a) & (b)` and `(cast) x
976 /// <= 0` keep their structural parens.
977 fn strip_outer_parens(s: &str) -> &str {
978 let b = s.as_bytes();
979 if b.len() < 2 || b[0] != b'(' || b[b.len() - 1] != b')' {
980 return s;
981 }
982 let mut depth = 0i32;
983 for (i, &c) in b.iter().enumerate() {
984 if c == b'(' {
985 depth += 1;
986 } else if c == b')' {
987 depth -= 1;
988 if depth == 0 {
989 if i + 1 == b.len() {
990 let inner = &s[1..s.len() - 1];
991 // Recurse: `((x))` peels to `x` — the balanced
992 // check stops at any structural paren.
993 return Self::strip_outer_parens(inner);
994 }
995 return s;
996 }
997 }
998 }
999 s
1000 }
1001
1002 fn cond_str(&mut self, cond: &Expr) -> String {
1003 let mut c = String::new();
1004 self.expr_bool(cond, &mut c);
1005 let stripped = Self::strip_outer_parens(&c);
1006 stripped.to_string()
1007 }
1008
1009 fn print_if(&mut self, cond: &Expr, then_stmt: &Stmt, else_stmt: Option<&Stmt>, opener: &str) {
1010 let mut head = String::from(opener);
1011 head.push_str("if (");
1012 head.push_str(&self.cond_str(cond));
1013 head.push_str(") {");
1014 self.line(&head);
1015 self.indent += 1;
1016 self.stmt(then_stmt);
1017 self.indent -= 1;
1018 match else_stmt {
1019 Some(Stmt::If {
1020 cond: c2,
1021 then_stmt: t2,
1022 else_stmt: e2,
1023 }) => {
1024 self.print_if(c2, t2, e2.as_deref(), "} else ");
1025 }
1026 Some(other) => {
1027 self.line("} else {");
1028 self.indent += 1;
1029 self.stmt(other);
1030 self.indent -= 1;
1031 self.line("}");
1032 }
1033 None => self.line("}"),
1034 }
1035 }
1036
1037 fn stmt_inline(&self, s: &Stmt, out: &mut String) {
1038 match s {
1039 Stmt::ExprStmt(e) => {
1040 let mut p = self.sub();
1041 if matches!(e, Expr::Method { .. }) {
1042 p.suppress_poly_cast = true;
1043 }
1044 p.expr(e, 1, out);
1045 }
1046 Stmt::LocalDef { var, init, .. } => {
1047 let info = self.vt.var(*var);
1048 out.push_str(&self.local_decl_ty(*var, &info.ty));
1049 out.push(' ');
1050 out.push_str(&java_ident(&info.name));
1051 if let Some(e) = init {
1052 out.push_str(" = ");
1053 let mut p = self.sub();
1054 p.expr(e, 1, out);
1055 }
1056 }
1057 other => {
1058 let mut p = self.sub();
1059 p.stmt(other);
1060 out.push_str(p.out.trim_end());
1061 }
1062 }
1063 }
1064
1065 fn sub(&self) -> Printer<'a> {
1066 Printer {
1067 ctx: self.ctx,
1068 vt: self.vt,
1069 out: String::new(),
1070 line_buf: String::new(),
1071 indent: 0,
1072 lambda_depth: self.lambda_depth,
1073 ignored_seq: self.ignored_seq,
1074 outer_names: self.outer_names.clone(),
1075 ret_bool: self.ret_bool,
1076 ret_char: self.ret_char,
1077 ret_byte: self.ret_byte,
1078 ret_short: self.ret_short,
1079 suppress_poly_cast: self.suppress_poly_cast,
1080 suppress_diamond: self.suppress_diamond,
1081 in_cond: self.in_cond,
1082 lambda_sam_ret: self.lambda_sam_ret.clone(),
1083 ret_sam: self.ret_sam.clone(),
1084 twin_cache: self.twin_cache.clone(),
1085 }
1086 }
1087
1088 // ---------------- expressions ----------------
1089
1090 pub fn expr(&mut self, e: &Expr, outer_prec: u8, out: &mut String) {
1091 let parens = e.needs_parens(outer_prec, false);
1092 if parens {
1093 out.push('(');
1094 }
1095 match e {
1096 Expr::Const(c) => self.const_val(c, out),
1097 Expr::Raw(t) if t == "\u{3}" => out.push_str("this"),
1098 Expr::Raw(t) => out.push_str(t),
1099 Expr::RawT(t, _) => out.push_str(t),
1100 Expr::Local { var, .. } => {
1101 let info = self.vt.var(*var);
1102 // The receiver var carries the dex debug name "this" —
1103 // reads render as the keyword (legal in expression
1104 // position, and it is never declared).
1105 if info.name == "this" {
1106 out.push_str("this");
1107 } else {
1108 out.push_str(&java_ident(&info.name));
1109 }
1110 }
1111 Expr::This => out.push_str("this"),
1112 Expr::New {
1113 cls,
1114 args,
1115 ty,
1116 arg_tys,
1117 ..
1118 } => {
1119 // The dex invoke-direct descriptor's formals, when the
1120 // node carries them at full arity: GROUND TRUTH for the
1121 // typed-arg rendering. The arity-based ctx lookup picks
1122 // `ctors_by_arity(..).first()` — a class with two
1123 // same-arity ctors (netease EnterLive: `(long,boolean)`
1124 // beside `(List,int)`) got the WRONG overload's formals
1125 // and the boolean formal rendered the int const arg as
1126 // `false` ("找不到合适的构造器" ×1,053-family).
1127 let pinned: Option<&[crate::types::JavaType]> =
1128 if !arg_tys.is_empty() && arg_tys.len() == args.len() {
1129 Some(arg_tys.as_slice())
1130 } else {
1131 None
1132 };
1133 let no_diamond = self.suppress_diamond;
1134 self.suppress_diamond = false;
1135 // Explicit type arguments pinned by the AST passes
1136 // (diamond_explicit_args_from_call_args): print them
1137 // instead of the diamond.
1138 let explicit = match ty {
1139 TypeRef::G(crate::types::GenericType::Class(cs))
1140 if cs.parts.last().map(|p| !p.args.is_empty()).unwrap_or(false)
1141 && crate::typeutil::classsig_internal(cs).as_str() == cls.as_ref() =>
1142 {
1143 let rendered: Vec<String> = cs
1144 .parts
1145 .last()
1146 .unwrap()
1147 .args
1148 .iter()
1149 .map(|a| self.generic_name(a))
1150 .collect();
1151 Some(format!("<{}>", rendered.join(", ")))
1152 }
1153 _ => None,
1154 };
1155 // A wildcard-typed ctor argument dooms diamond inference
1156 // (the inference variable gets an equality constraint from
1157 // the capture and a different bound from the context —
1158 // "cannot infer type arguments for Entry<>", jdk11
1159 // Hashtable `tab[i] = new Entry<>(hash, key, value, e)`
1160 // with e: Entry<?,?>; the source declared e as Entry<K,V>
1161 // via an unchecked cast that leaves no bytecode trace).
1162 // The raw form is always applicable (unchecked).
1163 let args_wildcard = args.iter().any(|a| {
1164 matches!(a.type_ref(), TypeRef::G(ref g)
1165 if crate::typeutil::g_has_wildcard(g))
1166 });
1167 let diamond: std::borrow::Cow<str> = match &explicit {
1168 Some(x) => x.as_str().into(),
1169 // Pre-Java-8 conditional branches are not poly
1170 // expressions: the diamond cannot see the assignment
1171 // target through the `?:` and infers <Object,Object>
1172 // (jdk7 HashSet.readObject map-ternary 不兼容的类型).
1173 // Raw is unchecked but always compilable.
1174 None if no_diamond
1175 || args_wildcard
1176 || (self.in_cond && self.ctx.source_level() < 52) =>
1177 {
1178 "".into()
1179 }
1180 None => self.diamond_for(cls).into(),
1181 };
1182 let diamond = diamond.as_ref();
1183 if let Some(local) = cls.strip_prefix('\u{2}') {
1184 out.push_str("new ");
1185 out.push_str(local);
1186 out.push_str(diamond);
1187 out.push('(');
1188 // Local records/classes keep bytecode-arity ctors:
1189 // typed rendering turns an int constant into the
1190 // char/boolean literal the ctor expects (jdk17
1191 // MessageFormat `new Qchar(39, quoted)`).
1192 // Nested local-class method printing runs under a
1193 // TAKEN LOCAL_CLASS_INTERNALS registry — resolve a
1194 // self-construction (`new BitSetSpliterator(..)` inside
1195 // BitSetSpliterator.trySplit) through the printer's
1196 // own class when the registry misses. Local-class
1197 // ctors capture the enclosing instance as a leading
1198 // param that the printed new-site omits; the this$0
1199 // fallback in ctor_param_types lines the formals up
1200 // (jdk11 BitSet$1BitSetSpliterator(BitSet,int,int,int,
1201 // boolean) — the boolean formal rendered its arg `0`).
1202 let internal = self.ctx.local_class_internal(local).or_else(|| {
1203 let tail = self.ctx.class_name().rsplit('$').next().unwrap_or("");
1204 let simple = tail.trim_start_matches(|c: char| c.is_ascii_digit());
1205 if !simple.is_empty() && simple == local {
1206 return Some(self.ctx.class_name().to_string());
1207 }
1208 // Probe the nested-name space of the printing
1209 // context (javac numbers method-local classes
1210 // `$1Name`): the new-site needs the real class to
1211 // resolve its ctor formals.
1212 let base = format!("{}${}", self.ctx.class_name(), local);
1213 if self.ctx.has_class(&base) {
1214 return Some(base);
1215 }
1216 for d in 0..10 {
1217 let cand = format!("{}${}{}", self.ctx.class_name(), d, local);
1218 if self.ctx.has_class(&cand) {
1219 return Some(cand);
1220 }
1221 }
1222 None
1223 });
1224 match internal
1225 .as_deref()
1226 .and_then(|i| self.ctor_param_types(i, 0, args.len(), args))
1227 {
1228 Some(pt) => self.args_typed(args, &pt, out),
1229 None => self.args(args, out),
1230 }
1231 out.push(')');
1232 } else if self.is_member_inner(cls)
1233 && !args.is_empty()
1234 && !matches!(args[0], Expr::This)
1235 && !is_this_expr(&args[0])
1236 // A constant (null/0) as the first argument is a real
1237 // parameter value, not the synthetic outer instance —
1238 // `0.new a()` / `null.new a()` are not valid Java.
1239 && !matches!(args[0], Expr::Const(_))
1240 // Anonymous classes (digit tails) are emitted as
1241 // separate top-level-ish classes — a qualified
1242 // `this.new 1(...)` is not valid Java; the flat
1243 // `new Outer$1(...)` form is. Test the RAW tail:
1244 // inner_simple already sanitizes a leading digit to
1245 // `_`, which silently bypassed this guard and emitted
1246 // `this.new _1(..)` against a flat top-level
1247 // declaration (weibo HorseRaceDetector$4 family).
1248 && !cls
1249 .rsplit('$')
1250 .next()
1251 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1252 .unwrap_or(false)
1253 {
1254 // `outerExpr.new Inner(rest...)` — the first ctor arg
1255 // is the synthetic outer instance when the class has a
1256 // this$0 field, or when the ctor forwards it straight
1257 // to super (no field of its own; is_member_inner
1258 // detected it). Either way the qualified-new form
1259 // supplies the outer implicitly, so skip args[0].
1260 self.expr(&args[0], 15, out);
1261 out.push_str(".new ");
1262 out.push_str(&inner_simple(cls));
1263 out.push_str(diamond);
1264 out.push('(');
1265 if let Some(pt) = pinned {
1266 self.args_typed(&args[1..], &pt[1..], out);
1267 } else {
1268 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1269 Some(pt) => self.args_typed(&args[1..], &pt, out),
1270 None => self.args(&args[1..], out),
1271 }
1272 }
1273 out.push(')');
1274 } else {
1275 out.push_str("new ");
1276 let digit_tail = cls
1277 .rsplit('$')
1278 .next()
1279 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1280 .unwrap_or(false);
1281 // A digit-tail class (anonymous `Outer$1`, d8 lambda)
1282 // is rendered as a SEPARATE top-level file declaring
1283 // `class Outer$1` with the outer instance as an
1284 // ordinary ctor parameter — the ctor signature keeps
1285 // that parameter (the inner-ctor strip excludes
1286 // digit tails for exactly this shape). The member
1287 // form `this.new _1(..)` would look for a member of
1288 // the enclosing class that does not exist there —
1289 // the flat `new Outer$1(outer, ..)` with ALL args is
1290 // the shape that matches the declaration.
1291 let member_this = self.is_member_inner(cls)
1292 && !digit_tail
1293 && !args.is_empty()
1294 && matches!(args[0], Expr::This);
1295 let shown = if member_this {
1296 // `new Inner()` from inside the outer class
1297 inner_simple(cls)
1298 } else if digit_tail {
1299 // Digit-suffixed class at a `new` site: a d8/R8-
1300 // desugared lambda (`...$lambda$N$$inlined$log$1`,
1301 // `Outer$$ExternalSyntheticLambda0`) or an anonymous
1302 // class (`Outer$1`). Both are concrete, pool-rendered
1303 // classes, so print the real binary name — NOT the
1304 // abstract SAM interface `shorten` falls back to
1305 // (which yields an invalid `new Iface(..)`: "a是抽象
1306 // 的; 无法实例化", the biggest abstract-instantiation
1307 // family on Kotlin corpora, v6.a/l/p = Function0/1/2).
1308 // Type positions keep `shorten` (interface), so the
1309 // declaration stays phi-compatible across branches
1310 // that mint different lambdas: `Iface v = new C(..)`.
1311 self.shorten_concrete(cls)
1312 } else {
1313 self.shorten(cls)
1314 };
1315 out.push_str(&shown);
1316 out.push_str(diamond);
1317 out.push('(');
1318 if member_this {
1319 if let Some(pt) = pinned {
1320 self.args_typed(&args[1..], &pt[1..], out);
1321 } else {
1322 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1323 Some(pt) => self.args_typed(&args[1..], &pt, out),
1324 None => self.args(&args[1..], out),
1325 }
1326 }
1327 } else {
1328 // Pinned generic instantiation: prime lambda args
1329 // from the ctor's instantiated Signature formals.
1330 let sam_rets: Vec<Option<TypeRef>> = match ty {
1331 TypeRef::G(crate::types::GenericType::Class(cs))
1332 if explicit.is_some()
1333 && crate::typeutil::classsig_internal(cs).as_str() == cls.as_ref() =>
1334 {
1335 let pinned = cs.parts.last().map(|p| p.args.clone());
1336 let formals = self.ctx.ctor_formals_by_arity(cls, args.len());
1337 // The raw ctor formals carry the class's
1338 // own typevars — substitute the pinned
1339 // instantiation before reading the SAM
1340 // return (an unsubstituted R printed `(R)`
1341 // — 找不到符号).
1342 let class_params = self.ctx.class_type_params(cls);
1343 args.iter()
1344 .enumerate()
1345 .map(|(i, a)| {
1346 if !matches!(a, Expr::Lambda(_)) {
1347 return None;
1348 }
1349 let f = formals.as_ref()?.get(i)?;
1350 let pinned = pinned.as_ref()?;
1351 if pinned.len() != class_params.len() {
1352 return None;
1353 }
1354 let inst = crate::typeutil::subst_typevars(
1355 f,
1356 &class_params,
1357 pinned,
1358 );
1359 let Expr::Lambda(l) = a else { return None };
1360 self.sam_ret_cast(&inst, &l.sam_name)
1361 })
1362 .collect()
1363 }
1364 _ => Vec::new(),
1365 };
1366 if let Some(pt) = pinned {
1367 self.args_typed_sam(args, pt, &sam_rets, out);
1368 } else {
1369 match self.ctor_param_types(cls, 0, args.len(), args) {
1370 Some(pt) => self.args_typed_sam(args, &pt, &sam_rets, out),
1371 None => self.args(args, out),
1372 }
1373 }
1374 }
1375 out.push(')');
1376 }
1377 }
1378 Expr::NewArray {
1379 elem,
1380 dims,
1381 trailing_dims,
1382 init,
1383 } => {
1384 out.push_str("new ");
1385 out.push_str(&self.type_name(elem));
1386 match init {
1387 Some(vals) => {
1388 for _ in 0..=*trailing_dims {
1389 out.push_str("[]");
1390 }
1391 out.push_str(" {");
1392 // boolean[]/char[] initializers hold int constants
1393 // in bytecode (iconst + bastore/castore): render
1394 // them as true/false and char literals (jdk11
1395 // PKCS9Attribute `new boolean[] {0, 0, 1, ..}` x18).
1396 let elem_bool = elem.erased() == crate::types::JavaType::Boolean;
1397 let elem_char = elem.erased() == crate::types::JavaType::Char;
1398 for (i, v) in vals.iter().enumerate() {
1399 if i > 0 {
1400 out.push_str(", ");
1401 }
1402 if elem_bool {
1403 self.expr_bool(v, out);
1404 } else if elem_char {
1405 self.expr_char(v, out);
1406 } else {
1407 self.expr(v, 1, out);
1408 }
1409 }
1410 out.push('}');
1411 }
1412 None => {
1413 for d in dims {
1414 out.push('[');
1415 self.expr(d, 1, out);
1416 out.push(']');
1417 }
1418 for _ in 0..*trailing_dims {
1419 out.push_str("[]");
1420 }
1421 }
1422 }
1423 }
1424 Expr::NewMultiArray { ty, dims } => {
1425 // Strip the dims.len() leading array levels from the type.
1426 let mut base = ty.erased();
1427 for _ in 0..dims.len() {
1428 match base {
1429 crate::types::JavaType::Array(inner) => base = *inner,
1430 _ => break,
1431 }
1432 }
1433 out.push_str("new ");
1434 out.push_str(&self.type_name(&TypeRef::J(base)));
1435 for d in dims {
1436 out.push('[');
1437 self.expr(d, 1, out);
1438 out.push(']');
1439 }
1440 }
1441 Expr::Field {
1442 owner,
1443 cls,
1444 name,
1445 is_static,
1446 ..
1447 } => {
1448 // javac reserves `$assertionsDisabled`; the declaration and
1449 // all references are emitted under a private alias.
1450 let name: &str = if name.as_ref() == "$assertionsDisabled" {
1451 crate::analysis::ASSERT_FIELD
1452 } else {
1453 name
1454 };
1455 if name == crate::analysis::ASSERT_FIELD {
1456 // Assert guards print bare: the field is declared on
1457 // the enclosing emitted class or inlined body; the
1458 // bytecode owner is often a synthetic holder class
1459 // (`ConstantGroup$1`) whose shorten() fallback is
1460 // `Object` — a qualified read can never resolve.
1461 out.push_str(&java_ident(name));
1462 } else if name == "length" && cls.is_empty() {
1463 if let Some(o) = owner {
1464 if let Expr::Const(_) = o.as_ref() {
1465 // Receiver tracking lost: `0.length` is not
1466 // parseable — cast-null array form.
1467 out.push_str("((java.lang.Object[]) null)");
1468 } else {
1469 self.expr(o, 15, out);
1470 }
1471 }
1472 out.push_str(".length");
1473 } else if let Some(o) = owner {
1474 if let Expr::Const(cv) = o.as_ref() {
1475 if matches!(
1476 cv,
1477 crate::ir::expr::ConstVal::Str(_)
1478 | crate::ir::expr::ConstVal::ClassLit(_)
1479 ) {
1480 // Literal receivers keep their value (see the
1481 // Method arm): `Foo.class.name`, not
1482 // `((Class) null).name`.
1483 self.expr(o, 15, out);
1484 out.push('.');
1485 } else {
1486 // Receiver tracking lost (a null placeholder leaked
1487 // through): `0.b` is not parseable — the same
1488 // cast-null form the Method arm uses.
1489 out.push_str("((");
1490 out.push_str(&self.shorten(cls));
1491 out.push_str(") null).");
1492 }
1493 out.push_str(&java_ident(name));
1494 } else if matches!(o.as_ref(), Expr::Raw(t) if t == "\u{3}") {
1495 // Outer anonymous class member: unqualified lexical
1496 // resolution (an anon outer has no nameable this).
1497 out.push_str(&java_ident(name));
1498 } else if self.private_super_field(cls, name, o.as_ref()) {
1499 // A private field of a SUPERCLASS is not inherited
1500 // into the subclass's member scope: `this.algorithm`
1501 // inside Delegate (extends MessageDigest, algorithm
1502 // private) is "private access" even though the
1503 // source comment says it all — jdk11
1504 // MessageDigest.Delegate.clone casts
1505 // `((MessageDigest)this).algorithm`.
1506 out.push_str("((");
1507 out.push_str(&self.shorten(cls));
1508 out.push_str(") ");
1509 if matches!(o.as_ref(), Expr::This) {
1510 out.push_str("this");
1511 } else {
1512 self.expr(o, 14, out);
1513 }
1514 out.push_str(").");
1515 out.push_str(&java_ident(name));
1516 } else if match o.type_ref().erased() {
1517 crate::types::JavaType::Object(ref n) => {
1518 n.as_ref() != cls.as_ref()
1519 && !matches!(o.as_ref(), Expr::Raw(_) | Expr::RawT(..) | Expr::This)
1520 && (n.as_ref() == "java/lang/Object"
1521 || self.ctx.is_subtype_of(
1522 &crate::types::JavaType::Object(
1523 std::sync::Arc::from(cls.as_ref()),
1524 ),
1525 n,
1526 ))
1527 }
1528 _ => false,
1529 } {
1530 // The receiver's static type is a strict SUPERTYPE
1531 // of the declaring class: either widened to Object
1532 // (when-materialization residue) or a register
1533 // reuse the vt kept at the supertype (`v9.label`
1534 // with v9: ContinuationImpl, label on the suspend
1535 // frame subclass — weibo coroutine cluster ×1.3k).
1536 // The downcast is what the bytecode's field
1537 // resolution did; same honest cast as the Method
1538 // arm. Concrete name: a `$<digits>` declaring
1539 // class loses its pushed-down members under the
1540 // anon fallback.
1541 out.push_str("((");
1542 out.push_str(&self.shorten_concrete(cls));
1543 out.push_str(") ");
1544 self.expr(o, 14, out);
1545 out.push_str(").");
1546 out.push_str(&java_ident(name));
1547 } else {
1548 self.expr(o, 15, out);
1549 out.push('.');
1550 out.push_str(&java_ident(name));
1551 }
1552 } else if *is_static {
1553 // A same-class static read prints bare — unless a local
1554 // of this method shadows the field name: javac resolves
1555 // the bare name as the LOCAL (jdk17 Socket.setImpl's
1556 // `SocketImplFactory factory = factory;` self-reference
1557 // — 可能尚未初始化变量factory x2 sites). Qualify with the
1558 // class name to reach the field.
1559 let shadowed_by_local = cls.as_ref() == self.ctx.class_name()
1560 && self.vt.vars.iter().any(|v| v.name == name);
1561 // ENCLOSING-class static fields read from a nested
1562 // class print bare too (lexical scope) — and MUST when
1563 // the class name itself is shadowed by a member of the
1564 // same name: jdk26 HPKE$Impl reads the outer
1565 // `byte[] HPKE`/PSK_ID_HASH/SECRET/EXP/KEY constants;
1566 // `HPKE.PSK_ID_HASH` resolves the qualifier as the
1567 // byte[] FIELD (variables obscure type names in
1568 // expression names) — 找不到符号 变量 PSK_ID_HASH x6.
1569 let enclosing_static = cls.as_ref() != self.ctx.class_name()
1570 && self.renders_nested_in(cls)
1571 && !self.vt.vars.iter().any(|v| v.name == name)
1572 && {
1573 let mut cur = self.ctx.class_name().to_string();
1574 let mut shadow = false;
1575 while cur.as_str() != cls.as_ref() {
1576 // Inherited fields shadow too (superclass
1577 // chain — see the method-side twin).
1578 let mut sup = Some(cur.clone());
1579 while let Some(c) = sup.take() {
1580 {
1581 // Display-space: the reference's
1582 // name is the registry DISPLAY
1583 // (the lift applied it), so the
1584 // shadow comparison must be too —
1585 // the nested class's raw `a`
1586 // displaying `a17` shadows the
1587 // outer field that displays a17
1588 // (lark ch6/b$a: `a17.length` bound
1589 // to the List instead of the
1590 // outer array).
1591 if self.ctx.declares_field_display(&c, name) {
1592 shadow = true;
1593 break;
1594 }
1595 sup = self.ctx.super_name(&c);
1596 }
1597 }
1598 if shadow {
1599 break;
1600 }
1601 match cur.rfind('$') {
1602 Some(i) => cur.truncate(i),
1603 None => break,
1604 }
1605 }
1606 !shadow
1607 };
1608 if !enclosing_static && (cls.as_ref() != self.ctx.class_name() || shadowed_by_local) {
1609 // JLS 6.4.2 obscured-owner escape: the dotted
1610 // render's first segment binds to an in-scope
1611 // variable (inherited field), the member chain
1612 // dangles. The import-simple route dies the same
1613 // way when a field owns the simple name — fall
1614 // to the single-static-import (bare member).
1615 // A local named like the member captures the
1616 // bare form (same rule as shadowed_by_local).
1617 if !shadowed_by_local
1618 && !self.vt.vars.iter().any(|v| v.name == name)
1619 && self.ctx.obscured_member_escape(cls, name)
1620 {
1621 // bare: the trailing name print below covers
1622 // the member (Method-arm twin structure — the
1623 // escape only suppresses the qualifier)
1624 } else {
1625 // The owner must name a DECLARABLE class: the
1626 // anonymous→interface/Object fallback (right for
1627 // phi-compatible type positions) leaves the field
1628 // unresolvable — `Object.$SwitchMap$java$math$
1629 // RoundingMode` (485 weibo / 308 lark / 217
1630 // weixin cannot-finds; the synthetic holder
1631 // `LongMath$1` IS rendered as a flat file with
1632 // the field, 0 own errors). Fall back to the
1633 // concrete `$N` name — `$` and digits are legal
1634 // Java identifier characters, and the holder
1635 // always sits in the referring class's package.
1636 // The qualifier of a STATIC member is always the
1637 // CONCRETE name: anon/lambda classes emit as flat
1638 // `$`-named top-level files, while shorten()'s
1639 // anon fallback renders the SAM interface — which
1640 // does not declare the member. `Function0.INSTANCE`
1641 // (1.7k weibo lines) is the sget of
1642 // ActivityResultRegistry$generateRandomNumber$1
1643 // .INSTANCE; the same trap took $SwitchMap holders
1644 // to `Object.` before (the narrow rendered=="Object"
1645 // check this replaces).
1646 out.push_str(&self.shorten_concrete(cls));
1647 out.push('.');
1648 }
1649 }
1650 out.push_str(&java_ident(name));
1651 } else if self.private_super_field(cls, name, &Expr::This) {
1652 out.push_str("((");
1653 out.push_str(&self.shorten(cls));
1654 out.push_str(") this).");
1655 out.push_str(&java_ident(name));
1656 } else {
1657 out.push_str("this.");
1658 out.push_str(&java_ident(name));
1659 }
1660 }
1661 Expr::Method {
1662 owner,
1663 cls,
1664 name,
1665 desc,
1666 args,
1667 is_static,
1668 is_special,
1669 is_super,
1670 type_args,
1671 ..
1672 } => {
1673 // Signature-polymorphic calls need the descriptor return cast
1674 // in source form (see classdec::polymorphic_ret_cast) — except
1675 // at the root of an expression statement, where the bare call
1676 // is the only legal form. Consume the flag so a poly call
1677 // NESTED in the args still gets its cast.
1678 let bare_stmt_root = self.suppress_poly_cast;
1679 self.suppress_poly_cast = false;
1680 if !bare_stmt_root {
1681 if let Some(t) = self.ctx.polymorphic_ret_cast(cls, name, desc) {
1682 out.push('(');
1683 out.push_str(&self.type_name(&crate::ir::expr::TypeRef::J(t)));
1684 out.push_str(") ");
1685 }
1686 }
1687 if name.as_ref() == "<init>" && *is_special {
1688 // The ctor fold normally turns `new X; <init>` into a
1689 // New expr at the LIFT. When the new-instance view was
1690 // lost (crossed a merge / got materialized), the call
1691 // arrives here with a NON-this receiver: rendering a
1692 // this/super delegation produced `this.super(...)`,
1693 // which is not valid Java. The allocation did happen —
1694 // print the construction call.
1695 let lost_alloc = owner
1696 .as_deref()
1697 .is_some_and(|o| !is_this_expr(o) && !matches!(o, Expr::This));
1698 if lost_alloc {
1699 // A member-inner construction call carries the
1700 // synthetic outer as args[0] and the emitted
1701 // ctor signature drops it (the inner-ctor
1702 // normalization) — supply it implicitly with the
1703 // qualified-new form, mirroring the New-expr
1704 // branches. Digit-tail classes keep the flat
1705 // full-args form (their ctor declares the
1706 // outer parameter).
1707 let digit_tail = cls
1708 .rsplit('$')
1709 .next()
1710 .map(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
1711 .unwrap_or(false);
1712 if self.is_member_inner(cls)
1713 && !digit_tail
1714 && !args.is_empty()
1715 && !matches!(args[0], Expr::Const(_))
1716 {
1717 if matches!(args[0], Expr::This) || is_this_expr(&args[0]) {
1718 out.push_str("this.new ");
1719 } else {
1720 self.expr(&args[0], 15, out);
1721 out.push_str(".new ");
1722 }
1723 out.push_str(&inner_simple(cls));
1724 out.push('(');
1725 match self.ctor_param_types(cls, 1, args.len() - 1, &args[1..]) {
1726 Some(pt) => self.args_typed(&args[1..], &pt, out),
1727 None => self.args(&args[1..], out),
1728 }
1729 out.push(')');
1730 return;
1731 }
1732 out.push_str("new ");
1733 out.push_str(&self.shorten(cls));
1734 out.push('(');
1735 self.args(args, out);
1736 out.push(')');
1737 return;
1738 }
1739 // super(...) / this(...)
1740 let is_super_form = *is_super || cls.as_ref() != self.ctx.class_name();
1741 // Qualified super for a STATIC class extending an
1742 // INNER superclass: javac synthesizes the enclosing
1743 // instance as the ctor's first param, and the source
1744 // form is `outer.super(rest)` (jdk11/17/26
1745 // BoundMethodHandle.SpeciesData extends
1746 // ClassSpecializer<..>.SpeciesData — plain
1747 // super(outer, key) is "需要包含..的封闭实例" x3 trees).
1748 let qualified_outer = if is_super_form
1749 && !args.is_empty()
1750 && cls.contains('$')
1751 && !self.ctx.class_has_this0(self.ctx.class_name())
1752 {
1753 let enclosing = cls.rsplit_once('$').map(|(o, _)| o.to_string());
1754 match enclosing {
1755 Some(enc)
1756 if self.ctx.class_has_this0(cls)
1757 && self
1758 .ctx
1759 .is_subtype_of(&args[0].type_ref().erased(), &enc) =>
1760 {
1761 Some(enc)
1762 }
1763 _ => None,
1764 }
1765 } else {
1766 None
1767 };
1768 // The enclosing instance passed explicitly is `this`
1769 // itself (qualified_outer found the enclosing, but the
1770 // argument IS this class's own outer): the source form
1771 // is plain `super(rest...)` — the implicit outer —
1772 // because `this.super(...)` is not valid Java (weibo
1773 // HorseRaceDetector, reqable androidx.fragment.app.d).
1774 let this_outer =
1775 is_super_form && !args.is_empty() && is_this_expr(&args[0]);
1776 let skip_outer_arg = qualified_outer.is_some() || this_outer;
1777 if qualified_outer.is_some() && !this_outer {
1778 self.expr(&args[0], 15, out);
1779 out.push_str(".super(");
1780 } else if is_super_form {
1781 out.push_str("super(");
1782 } else {
1783 out.push_str("this(");
1784 }
1785 // Typed rendering: boolean/char params must not receive
1786 // raw 0/1 int constants (`this(refKind, false)`). The
1787 // methodref's descriptor `desc.args` is the EXACT invoked
1788 // ctor signature, so prefer it over `ctor_param_types`
1789 // (which matches by arg count and picks the wrong
1790 // overload when a class has several ctors of equal arity,
1791 // e.g. jdk26 Thread(ThreadGroup,Runnable,String,long,
1792 // boolean) vs (...,long,Thread[])).
1793 let _ = is_super_form;
1794 // Upcast restoration only for SELF delegation: this(..)
1795 // overload resolution binds the arg's static type (a
1796 // JarEntry arg would recurse into JarEntry(JarEntry)
1797 // instead of the descriptor's JarEntry(ZipEntry)).
1798 // super(..) resolution is unambiguous through the
1799 // generic formals — casting there broke FindOps
1800 // `super(parent, spliterator)` (K := FindTask<..>
1801 // accepts the arg fine; the raw erasure cast does not).
1802 if skip_outer_arg {
1803 if desc.args.len() == args.len() && !desc.args.is_empty() {
1804 self.args_typed(&args[1..], &desc.args[1..], out);
1805 } else {
1806 self.args(&args[1..], out);
1807 }
1808 } else if desc.args.len() == args.len() {
1809 if is_super_form {
1810 self.args_typed(args, &desc.args, out);
1811 } else {
1812 self.args_delegation(args, &desc.args, out);
1813 }
1814 } else if !is_super_form {
1815 // desc is the EXACT invoked ctor signature and the
1816 // args are outer-stripped: args_delegation aligns
1817 // by tail (jdk11 ZipFileInflaterInputStream —
1818 // ctor_param_types' arity match would pick the
1819 // wrong same-arity overload and cast against
1820 // shifted formals, (ZipFile) zfin x3).
1821 self.args_delegation(args, &desc.args, out);
1822 } else {
1823 match self.ctor_param_types(cls, 0, args.len(), args) {
1824 Some(pt) if pt.len() == args.len() => self.args_typed(args, &pt, out),
1825 _ => self.args(args, out),
1826 }
1827 }
1828 out.push(')');
1829 } else {
1830 if *is_super {
1831 // invokespecial on `this` targeting another class:
1832 // a superclass method call. Interface targets are
1833 // qualified `Iface.super.m()` (JDK8 default-method
1834 // invocations from implementors) — but only a
1835 // DIRECT superinterface may carry the qualifier
1836 // (JLS 15.11.2). A default reached through a
1837 // SUBINTERFACE (weixin jsapi/f0 implements e0
1838 // extends l; `l.super.m()` = "不是封闭类: l" ×67)
1839 // qualifies with the direct interface inheriting
1840 // it; one reached only through the superclass
1841 // chain falls back to plain `super.` (the super
1842 // inherits the default as its own member).
1843 let itf = self.ctx.is_interface(cls);
1844 if itf {
1845 let mut qual: Option<String> = None;
1846 if let Some((ifs, _)) =
1847 self.ctx.class_bases(self.ctx.class_name())
1848 {
1849 if ifs.iter().any(|i| i.as_str() == cls.as_ref()) {
1850 qual = Some(cls.to_string());
1851 } else {
1852 qual = ifs.into_iter().find(|i| {
1853 self.ctx.is_subtype_of(
1854 &crate::types::JavaType::Object(
1855 i.as_str().into(),
1856 ),
1857 cls,
1858 )
1859 });
1860 }
1861 }
1862 if let Some(q) = qual {
1863 out.push_str(&self.shorten(&q));
1864 out.push('.');
1865 }
1866 }
1867 out.push_str("super.");
1868 } else if let Some(o) = owner {
1869 if let Expr::Const(cv) = o.as_ref() {
1870 if matches!(
1871 cv,
1872 crate::ir::expr::ConstVal::Str(_)
1873 | crate::ir::expr::ConstVal::ClassLit(_)
1874 ) {
1875 // A literal receiver is valid, faithful
1876 // Java (`"http".equalsIgnoreCase(x)`,
1877 // `Foo.class.getName()`). Rendering it as
1878 // cast-null silently DROPPED the constant
1879 // — 73.5k sites across the 4 corpora
1880 // (`((String) null).m()`), invisible to
1881 // javac (it compiles) and an NPE at run
1882 // time. Only valueless placeholders take
1883 // the cast-null path below.
1884 self.expr(o, 15, out);
1885 out.push('.');
1886 } else {
1887 // Receiver tracking lost: a null placeholder
1888 // (const/4 0) leaked through as the receiver —
1889 // `0.close()` is not parseable. Cast-null keeps
1890 // the statement compilable and honestly marks
1891 // the value as unknown on this path.
1892 out.push_str("((");
1893 out.push_str(&self.shorten(cls));
1894 out.push_str(") null).");
1895 }
1896 } else if let Expr::Lambda(l) = o.as_ref() {
1897 // A lambda RECEIVER needs the source's SAM cast
1898 // (`((BooleanSupplier) () -> ..).getAsBoolean()`
1899 // — a bare lambda cannot own a call: 此处不应为
1900 // lambda 表达式, jdk26 Proxy assert).
1901 if !l.sam_cls.is_empty() {
1902 out.push_str("((");
1903 out.push_str(&self.shorten(&l.sam_cls));
1904 out.push_str(") ");
1905 self.expr(o, 14, out);
1906 out.push_str(").");
1907 } else {
1908 self.expr(o, 15, out);
1909 out.push('.');
1910 }
1911 } else if matches!(o.as_ref(), Expr::Raw(t) if t == "\u{3}") {
1912 // Outer anonymous member call: unqualified
1913 // lexical resolution.
1914 } else if self.needs_owner_cast(cls, name, desc, o) {
1915 // Private / cross-package members are NOT
1916 // inherited by the receiver's static type:
1917 // the source cast to the declaring class
1918 // (`((ZipFile) jar).getManifestNum()`) leaves
1919 // no bytecode trace when the receiver is
1920 // already a subtype.
1921 out.push_str("((");
1922 out.push_str(&self.shorten_concrete(cls));
1923 out.push_str(") ");
1924 self.expr(o, 14, out);
1925 out.push_str(").");
1926 } else {
1927 self.expr(o, 15, out);
1928 out.push('.');
1929 }
1930 } else if *is_static && (cls.as_ref() != self.ctx.class_name() || !type_args.is_empty())
1931 {
1932 // A static member of an ENCLOSING class resolves
1933 // through lexical scope unqualified — and the
1934 // qualified form is a TRAP when a field/local is
1935 // named after the class: expression-name
1936 // resolution prefers variables, so jdk26
1937 // HPKE$Impl's `private static final byte[] HPKE`
1938 // hijacked `HPKE.usePSK(psk)` into a member
1939 // lookup on the array (找不到符号 方法
1940 // usePSK(SecretKey) 位置: 类型为byte[]的变量
1941 // HPKE). Keep the qualifier when an own or
1942 // intermediate class declares a same-named
1943 // method (real member shadowing) or when type
1944 // witnesses need an explicit receiver.
1945 // renders_nested_in: flat-rendered (digit-tail)
1946 // nested classes have NO lexical scope.
1947 let enclosing_static = type_args.is_empty()
1948 && self.renders_nested_in(cls)
1949 && {
1950 let mut cur = self.ctx.class_name().to_string();
1951 let mut shadow = false;
1952 while cur.as_str() != cls.as_ref() {
1953 // Inherited members shadow too: the
1954 // lexical member scope of a class
1955 // includes its whole superclass chain
1956 // (jdk26 GaloisCounterMode$GCMDecrypt
1957 // extends GCMEngine, whose own
1958 // implGCMCrypt(ByteBuffer,..) hijacks
1959 // the bare 9-arg call to the OUTER
1960 // static — 无法将方法应用到给定类型).
1961 let mut sup = Some(cur.clone());
1962 while let Some(c) = sup.take() {
1963 {
1964 if self.ctx.declares_method_named(&c, name) {
1965 shadow = true;
1966 break;
1967 }
1968 sup = self.ctx.super_name(&c);
1969 }
1970 }
1971 if shadow {
1972 break;
1973 }
1974 match cur.rfind('$') {
1975 Some(i) => cur.truncate(i),
1976 None => break,
1977 }
1978 }
1979 !shadow
1980 };
1981 if self.ctx.obscured_member_escape(cls, name)
1982 && !self.vt.vars.iter().any(|v| *v.name == **name)
1983 {
1984 // JLS 6.4.2 obscured-owner escape (method twin
1985 // of the static-field arm): single-static-import,
1986 // bare member call.
1987 } else if !enclosing_static {
1988 // Concrete name: same anon-class trap as the
1989 // static FIELD qualifier (the SAM interface
1990 // does not declare the synthetic static).
1991 out.push_str(&self.shorten_concrete(cls));
1992 out.push('.');
1993 }
1994 } else if !type_args.is_empty() {
1995 // Type witnesses require an explicit receiver.
1996 out.push_str("this.");
1997 }
1998 if !type_args.is_empty() {
1999 out.push('<');
2000 out.push_str(&type_args.join(", "));
2001 out.push('>');
2002 }
2003 // A BARE `yield(...)` invocation trips Java 13's
2004 // restricted identifier (Kotlin suspend funs name
2005 // their continuation entry `yield`; news_article
2006 // ships CoroutineExtKt$yield). Qualified calls are
2007 // fine — when no receiver was printed, add one.
2008 if name.as_ref() == "yield" && !out.ends_with('.') && !out.ends_with('>') {
2009 out.push_str(&self.shorten(cls));
2010 out.push('.');
2011 }
2012 out.push_str(&java_ident(name));
2013 out.push('(');
2014 // Lambda args of a GENERIC call: prime each one's
2015 // body-return cast from the instantiated formal's SAM
2016 // return (jdk17 AbstractPipeline.opEvaluateParallelLazy:
2017 // `i -> (E_OUT[]) new Object[i]` — the erasure-equal
2018 // cast leaves no bytecode trace and without it the
2019 // IntFunction<E_OUT[]> formal starves: Object[]无法
2020 // 转换为E_OUT[]).
2021 let sam_rets: Vec<Option<TypeRef>> = {
2022 let mut v: Vec<Option<TypeRef>> =
2023 std::iter::repeat_with(|| None).take(args.len()).collect();
2024 if self.ctx.is_generic_call(e) {
2025 if let Some((formals, mtvars)) = self.ctx.generic_call_formals(e) {
2026 if formals.len() == args.len() {
2027 for (i, (a, f)) in args.iter().zip(formals.iter()).enumerate() {
2028 // A raw-SAM-cast wrapper still
2029 // exposes the lambda (the overload
2030 // disambiguation cast; the primed
2031 // body return types the lambda
2032 // precisely).
2033 let a_inner = match a {
2034 Expr::Cast { e: ce, .. } => ce.as_ref(),
2035 other => other,
2036 };
2037 if let Expr::Lambda(l) = a_inner {
2038 // A formal still carrying the
2039 // callee's own method typevars
2040 // is not denotable at this
2041 // call site.
2042 if crate::typeutil::g_mentions_any(f, &mtvars) {
2043 continue;
2044 }
2045 v[i] = self.sam_ret_cast(f, &l.sam_name);
2046 }
2047 }
2048 }
2049 }
2050 }
2051 v
2052 };
2053 if sam_rets.iter().any(|x| x.is_some()) {
2054 self.args_typed_sam(args, &desc.args, &sam_rets, out);
2055 } else {
2056 self.args_typed(args, &desc.args, out);
2057 }
2058 out.push(')');
2059 }
2060 }
2061 Expr::ArrayIndex { array, index } => {
2062 self.expr(array, 15, out);
2063 out.push('[');
2064 // Array indexes are int by definition: a long-typed index
2065 // local (slot-sharing/merge typed the foreach lowering's
2066 // counter long — jdk26 Files.copy `options[i]`,
2067 // 从long转换到int可能会有损失) needs the narrowing cast.
2068 let idx_ty = match &**index {
2069 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
2070 other => other.type_ref().erased(),
2071 };
2072 if matches!(idx_ty, crate::types::JavaType::Long) {
2073 out.push_str("(int) ");
2074 }
2075 self.expr(index, 1, out);
2076 out.push(']');
2077 }
2078 Expr::Cast { ty, e } => {
2079 // `(T) (Serializable) lambda` is invalid Java: the source
2080 // form of a serializable-lambda cast is the intersection
2081 // `(T & Serializable) lambda`.
2082 if let Expr::Cast {
2083 ty: inner_ty,
2084 e: inner_e,
2085 } = &**e
2086 {
2087 let is_serializable = |t: &crate::ir::expr::TypeRef| {
2088 t.erased() == crate::types::JavaType::Object("java/io/Serializable".into())
2089 };
2090 if matches!(&**inner_e, Expr::Lambda(_)) && is_serializable(inner_ty) {
2091 out.push('(');
2092 out.push_str(&self.type_name(ty));
2093 out.push_str(" & ");
2094 out.push_str(&self.type_name(inner_ty));
2095 out.push_str(") ");
2096 self.expr(inner_e, 14, out);
2097 return;
2098 }
2099 }
2100 // CLASS-argument generic casts only: a TYPEVAR cast is
2101 // load-bearing (`(T) new SimpleEntry<>(..)` — jdk11
2102 // IdentityHashMap.toArray: the diamond infers from the
2103 // ctor args and the unchecked (T) makes the T[] store
2104 // legal; dropping it left the diamond to infer from the
2105 // array target T — 无法推断SimpleEntry<>的类型参数).
2106 if let (TypeRef::G(crate::types::GenericType::Class(_)), Expr::New { cls, .. }) =
2107 (ty, &**e)
2108 {
2109 if !self.diamond_for(cls).is_empty() {
2110 // Synthetic generics-only cast around a diamond new:
2111 // DROP the cast and keep the diamond. A generic-typed
2112 // cast is always jcdc's own witness (a real checkcast
2113 // carries only erasure), and every cast form loses
2114 // here: with the diamond the cast starves inference
2115 // (`(PendingFuture<Void,A>) new PendingFuture<>(..)`
2116 // infers Object bounds and fails the cast), and the
2117 // bare/raw form makes the whole creation raw so
2118 // method-ref/lambda arguments stop type-checking
2119 // (jdk17 Collectors `(Collector<T,?,C>) new
2120 // CollectorImpl(.., Collection::add, ..)`). Cast-free
2121 // `return new CollectorImpl<>(..)` / `x = new
2122 // PendingFuture<>(..)` target-types the diamond —
2123 // the source shape.
2124 self.expr(e, outer_prec, out);
2125 return;
2126 }
2127 }
2128 out.push('(');
2129 // A cast to a pool `$<digits>` class (desugared lambda /
2130 // coroutine frame) must keep the CONCRETE name: the
2131 // anon-fallback would print the SAM interface/super and
2132 // drop exactly the members the cast exists to reach
2133 // (weibo `(($reportWhenComplete$1) continuation).label`
2134 // rendered `(ContinuationImpl) …` — label lives on the
2135 // frame class only, R8 pushed it down; 找不到符号 ×420).
2136 match ty {
2137 TypeRef::J(crate::types::JavaType::Object(n))
2138 if self.ctx.has_class(n)
2139 && n.rsplit('$')
2140 .next()
2141 .is_some_and(|l| !l.is_empty() && l.chars().all(|c| c.is_ascii_digit())) =>
2142 {
2143 out.push_str(&self.shorten_concrete(n));
2144 }
2145 _ => out.push_str(&self.type_name(ty)),
2146 }
2147 out.push_str(") ");
2148 // Inconvertible direct casts from mis-joined stack-merge
2149 // vars (jdk26 AnnotationReader: `(SupertypeTarget)
2150 // var3_341` with var3 typed Iterator — sealed targets
2151 // reject the cross-cast): relay through Object, which is
2152 // always legal and semantically identical (the runtime
2153 // value IS the target type).
2154 if self.needs_object_relay(ty, e) {
2155 out.push_str("(Object) ");
2156 }
2157 if matches!(&**e, Expr::New { .. }) {
2158 self.suppress_diamond = true;
2159 }
2160 if let (TypeRef::G(g), Expr::Lambda(l)) = (ty, &**e) {
2161 self.lambda_sam_ret = self.sam_ret_cast(g, &l.sam_name);
2162 }
2163 self.expr(e, 14, out);
2164 self.suppress_diamond = false;
2165 self.lambda_sam_ret = None;
2166 }
2167 Expr::InstanceOf { e, ty } => {
2168 // `error2 instanceof CodecException` with error2: Error
2169 // is "不兼容的类型" — instanceof demands castability
2170 // exactly like a cast, so it takes the same Object
2171 // relay (semantics unchanged: instanceof is a runtime
2172 // type test).
2173 if self.needs_object_relay(ty, e) {
2174 out.push_str("((Object) ");
2175 self.expr(e, 14, out);
2176 out.push(')');
2177 } else {
2178 self.expr(e, 10, out);
2179 }
2180 out.push_str(" instanceof ");
2181 out.push_str(&self.type_name(ty));
2182 }
2183 Expr::Un { op, e } => {
2184 out.push_str(match op {
2185 UnOp::Neg => "-",
2186 UnOp::Not => "!",
2187 UnOp::BitNot => "~",
2188 });
2189 self.expr(e, 14, out);
2190 }
2191 Expr::Bin { op, l, r, .. } => {
2192 // Boolean context special cases: `b == 0` → `!b`. A
2193 // Local's embedded type can be stale (booleanize retypes
2194 // the VarTable), and a boolified bitwise bin over
2195 // bool-ish operands is boolean too (jdk26
2196 // LinkedTransferQueue `(spin & !upc) == 0` — the frozen-Int
2197 // And with an int-form ternary side printed
2198 // `spin & (!upc ? 1 : 0)`: boolean & int).
2199 let lt = match &**l {
2200 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
2201 other => other.type_ref().erased(),
2202 };
2203 let l_bool = lt == JavaType::Boolean;
2204 let l_boolish = l_bool || self.bool_ish_vt(l);
2205 if matches!(op, BinOp::Eq | BinOp::Ne | BinOp::RefEq | BinOp::RefNe) && l_boolish {
2206 if let Expr::Const(ConstVal::Int(n)) = &**r {
2207 let polarity = matches!(op, BinOp::Eq | BinOp::RefEq);
2208 let want_true = (*n != 0) == polarity;
2209 if !want_true {
2210 out.push('!');
2211 }
2212 if l_bool {
2213 self.expr(l, 14, out);
2214 } else if matches!(
2215 &**l,
2216 Expr::Bin { .. } | Expr::Cond { .. } | Expr::Assign { .. }
2217 ) {
2218 // Composite boolean (a bitwise bin): parenthesize
2219 // so a leading `!` binds the whole value. A BARE
2220 // operand — the 0/1 boolean encoding of a const,
2221 // a local whose vt type is stale-Int, a Not —
2222 // cannot regroup and renders bare: the
2223 // unconditional parens wrapped every folded
2224 // comparison into `x = (false)` / `return (!v)`
2225 // (kimi: ~85k noise sites).
2226 out.push('(');
2227 self.expr_bool(l, out);
2228 out.push(')');
2229 } else {
2230 self.expr_bool(l, out);
2231 }
2232 if parens {
2233 out.push(')');
2234 }
2235 return;
2236 }
2237 }
2238 // Mixed boolean/int equality on non-constants (stack-merge
2239 // vars of an assert desugaring: jdk26 VirtualThread
2240 // `stack38 == stack39` with stack38 boolean, stack39 int
2241 // 0/1 — "boolean和int不可比较"): normalize the int side
2242 // through `!= 0`.
2243 {
2244 let rt = r.type_ref().erased();
2245 if matches!(op, BinOp::Eq | BinOp::Ne)
2246 && ((lt == crate::types::JavaType::Boolean
2247 && rt == crate::types::JavaType::Int)
2248 || (lt == crate::types::JavaType::Int
2249 && rt == crate::types::JavaType::Boolean))
2250 {
2251 for (i, side) in [l, r].iter().enumerate() {
2252 if i > 0 {
2253 out.push(' ');
2254 out.push_str(op.symbol());
2255 out.push(' ');
2256 }
2257 if side.type_ref().erased() == crate::types::JavaType::Boolean {
2258 self.expr_bool(side, out);
2259 } else {
2260 out.push('(');
2261 self.expr(side, 1, out);
2262 out.push_str(" != 0)");
2263 }
2264 }
2265 if parens {
2266 out.push(')');
2267 }
2268 return;
2269 }
2270 }
2271 let p = op.precedence();
2272 self.expr(l, p, out);
2273 out.push(' ');
2274 out.push_str(op.symbol());
2275 out.push(' ');
2276 self.expr(r, p + 1, out);
2277 }
2278 Expr::Cond { c, t, f } => {
2279 self.expr(c, 3, out);
2280 out.push_str(" ? ");
2281 let prev = std::mem::replace(&mut self.in_cond, true);
2282 self.expr(t, 2, out);
2283 out.push_str(" : ");
2284 self.expr(f, 2, out);
2285 self.in_cond = prev;
2286 }
2287 Expr::Assign { target, op, value } => {
2288 // The local's VarTable type is authoritative: booleanize
2289 // flips vt types after the embedded Local tys were frozen
2290 // (Security `boolean var4_122 = 1` printed the int form —
2291 // "int无法转换为boolean").
2292 let tgt_er = match &**target {
2293 Expr::Local { var, .. } => self.vt.var(*var).ty.erased(),
2294 other => other.type_ref().erased(),
2295 };
2296 self.expr(target, 1, out);
2297 out.push(' ');
2298 out.push_str(op.symbol());
2299 out.push(' ');
2300 if matches!(op, crate::ir::expr::AssignOp::Plain) {
2301 // Narrow-target assignments render int constants in the
2302 // target's form (jdk11/17/26 xml Parser: `mESt = ch !=
2303 // 116 ? 512 : 60` against `private char mESt` —
2304 // "从int转换到char可能会有损失").
2305 match tgt_er {
2306 crate::types::JavaType::Boolean => self.expr_bool(value, out),
2307 crate::types::JavaType::Char => self.expr_char(value, out),
2308 crate::types::JavaType::Byte => {
2309 self.expr_narrow(value, &crate::types::JavaType::Byte, out)
2310 }
2311 crate::types::JavaType::Short => {
2312 self.expr_narrow(value, &crate::types::JavaType::Short, out)
2313 }
2314 _ => self.expr(value, 1, out),
2315 }
2316 } else {
2317 self.expr(value, 1, out);
2318 }
2319 }
2320 Expr::PreIncDec { e, delta, .. } => {
2321 out.push_str(if *delta > 0 { "++" } else { "--" });
2322 self.expr(e, 14, out);
2323 }
2324 Expr::PostIncDec { e, delta, .. } => {
2325 self.expr(e, 14, out);
2326 out.push_str(if *delta > 0 { "++" } else { "--" });
2327 }
2328 Expr::Lambda(l) => self.lambda(l, out),
2329 Expr::AnonNew {
2330 base, args, body, ..
2331 } => {
2332 out.push_str("new ");
2333 let base_s = self.type_name(base);
2334 out.push_str(&base_s);
2335 out.push('(');
2336 // Render the ctor args through the base class's matching
2337 // <init> descriptor: an int 0/1 at a boolean parameter
2338 // must print as false/true (jdk11 Console's anonymous
2339 // PrintWriter subclass — `new PrintWriter(out, 1)` finds
2340 // no ctor). Pick the arity-matching ctor whose param
2341 // erasures best fit the actual arg types.
2342 let base_internal = match base {
2343 TypeRef::J(crate::types::JavaType::Object(n)) => Some(n.clone()),
2344 TypeRef::G(crate::types::GenericType::Class(cs)) => {
2345 Some(crate::typeutil::classsig_internal(cs).into())
2346 }
2347 _ => None,
2348 };
2349 // Overload choice for the ctor args (int 0/1 at a boolean
2350 // formal must print false/true) reads the base class's own
2351 // `<init>` set: front-end metadata.
2352 let ctor_params = base_internal
2353 .and_then(|bi| self.ctx.ctor_param_types(&bi, 0, args.len(), args));
2354 match ctor_params {
2355 Some(pt) => self.args_typed(args, &pt, out),
2356 None => self.args(args, out),
2357 }
2358 out.push_str(") {\n");
2359 for line in body.lines() {
2360 for _ in 0..self.indent + 1 {
2361 out.push_str(" ");
2362 }
2363 out.push_str(line.trim_start_matches(' '));
2364 out.push('\n');
2365 }
2366 for _ in 0..self.indent {
2367 out.push_str(" ");
2368 }
2369 out.push('}');
2370 }
2371 Expr::StringConcat(parts) => {
2372 let mut first = true;
2373 // Java semantics: the concatenation must start with a
2374 // String. NOT for an empty parts list: the trailing
2375 // is_empty guard below already prints `""` — both firing
2376 // printed four quotes.
2377 let needs_prefix =
2378 !parts.is_empty() && !matches!(parts.first(), Some(ConcatPart::Const(_)));
2379 if needs_prefix {
2380 out.push_str("\"\"");
2381 first = false;
2382 }
2383 for p in parts {
2384 // Right-hand operands of `+` need parens at equal
2385 // precedence to keep arithmetic grouping (`"t" + (x+1)`).
2386 let is_first = first;
2387 if !first {
2388 out.push_str(" + ");
2389 }
2390 first = false;
2391 match p {
2392 ConcatPart::Const(s) => {
2393 out.push('"');
2394 out.push_str(&escape_string(s));
2395 out.push('"');
2396 }
2397 ConcatPart::Str(e) => self.expr(e, if is_first { 12 } else { 13 }, out),
2398 }
2399 }
2400 if parts.is_empty() {
2401 out.push_str("\"\"");
2402 }
2403 }
2404 Expr::Invokedynamic {
2405 name,
2406 args,
2407 bsm_text,
2408 ..
2409 } => {
2410 if name.starts_with('\u{0}') {
2411 out.push_str("/*bad-cmp*/0");
2412 } else {
2413 out.push_str("/* invokedynamic ");
2414 out.push_str(name);
2415 out.push(' ');
2416 out.push_str(bsm_text);
2417 out.push_str(" */ (");
2418 self.args(args, out);
2419 out.push(')');
2420 }
2421 }
2422 }
2423 if parens {
2424 out.push(')');
2425 }
2426 }
2427
2428 fn args(&mut self, args: &[Expr], out: &mut String) {
2429 for (i, a) in args.iter().enumerate() {
2430 if i > 0 {
2431 out.push_str(", ");
2432 }
2433 self.expr(a, 1, out);
2434 }
2435 }
2436
2437 /// Print call args applying boolean-parameter constant adjustment.
2438 /// Delegation (this(..)/super(..)) args: typed rendering PLUS the
2439 /// elided-upcast restoration when the arg's static type differs from
2440 /// the descriptor formal — overload resolution binds the arg's type,
2441 /// not the descriptor (jdk17 JarEntry copy ctor: `this(je)` resolves
2442 /// recursively to JarEntry(JarEntry); the source `this((ZipEntry) je)`
2443 /// upcast is elided from bytecode because je <: ZipEntry is provable).
2444 fn args_delegation(
2445 &mut self,
2446 args: &[Expr],
2447 param_types: &[crate::types::JavaType],
2448 out: &mut String,
2449 ) {
2450 // Inner-class ctor descriptors carry the synthetic outer param
2451 // while the expression args are outer-stripped: align the formals
2452 // with the TRAIL of the args, like apply_ctor_param_casts (jdk11
2453 // ZipFileInflaterInputStream this(zfin, res, res.getInflater(),
2454 // size) was cast against the shifted descriptor formals —
2455 // (ZipFile) zfin, (ZipFileInputStream) res... x3 inconvertible).
2456 let off = param_types.len().saturating_sub(args.len());
2457 for (i, a) in args.iter().enumerate() {
2458 if i > 0 {
2459 out.push_str(", ");
2460 }
2461 match param_types.get(off + i) {
2462 Some(pt @ crate::types::JavaType::Object(_)) => {
2463 let have = a.type_ref().erased();
2464 // Generically-typed args (typevars/parameterized) are
2465 // already source-typed: casting them to the descriptor
2466 // erasure breaks convertibility to typevar formals
2467 // (jdk17 StreamSpliterators SliceSpliterator.OfPrimitive
2468 // this((Spliterator.OfPrimitive) s, ..) against a
2469 // T_SPLITR formal — OfPrimitive无法转换为T_SPLITR).
2470 if &have != pt
2471 && !matches!(a.type_ref(), TypeRef::G(_))
2472 && !matches!(
2473 a,
2474 Expr::Cast { .. }
2475 | Expr::Const(_)
2476 | Expr::Lambda(_)
2477 | Expr::New { .. }
2478 | Expr::AnonNew { .. }
2479 )
2480 {
2481 out.push('(');
2482 out.push_str(&self.type_name(&TypeRef::J(pt.clone())));
2483 out.push_str(") ");
2484 self.expr(a, 14, out);
2485 continue;
2486 }
2487 self.expr(a, 1, out);
2488 }
2489 Some(crate::types::JavaType::Boolean) => self.expr_bool(a, out),
2490 Some(crate::types::JavaType::Char) => self.expr_char(a, out),
2491 Some(pt @ (crate::types::JavaType::Byte | crate::types::JavaType::Short)) => {
2492 self.expr_narrow_arg(a, pt, out)
2493 }
2494 _ => self.expr(a, 1, out),
2495 }
2496 }
2497 }
2498
2499 fn args_typed(
2500 &mut self,
2501 args: &[Expr],
2502 param_types: &[crate::types::JavaType],
2503 out: &mut String,
2504 ) {
2505 self.args_typed_sam(args, param_types, &[], out)
2506 }
2507
2508 /// args_typed with per-argument SAM-return priming: a lambda actual at
2509 /// a parameterized-SAM formal of a pinned generic ctor gets its body
2510 /// return cast from the formal's instantiated SAM return (jdk11/17/26
2511 /// Collectors.toUnmodifiable*'s finisher `list -> (List<T>)
2512 /// List.of(list.toArray())` — the erased instantiatedMethodType says
2513 /// only List, so the generic cast is recoverable solely from the
2514 /// diamond's resolved formal).
2515 fn args_typed_sam(
2516 &mut self,
2517 args: &[Expr],
2518 param_types: &[crate::types::JavaType],
2519 sam_rets: &[Option<TypeRef>],
2520 out: &mut String,
2521 ) {
2522 for (i, a) in args.iter().enumerate() {
2523 if i > 0 {
2524 out.push_str(", ");
2525 }
2526 match param_types.get(i) {
2527 Some(crate::types::JavaType::Boolean) => self.expr_bool(a, out),
2528 // Char/narrow params: expr_char/expr_narrow render both
2529 // constants AND conditional branches in the target form
2530 // (jdk17 BasicAuthentication `super(!isProxy ? 115 : 112,
2531 // ..)` — the char param needs `!isProxy ? 's' : 'p'`;
2532 // a plain int ternary is "条件表达式中的类型错误").
2533 Some(crate::types::JavaType::Char) => self.expr_char(a, out),
2534 // Byte/short parameters: an int literal argument needs an
2535 // explicit cast (invocation conversion never narrows, even
2536 // for in-range constants) — and so do the branches of an
2537 // int conditional (jdk CompressedResourceHeader
2538 // `buffer.put(isTerminal ? (byte) 1 : (byte) 0)` — the
2539 // byte casts fold away in bytecode; a bare int ternary is
2540 // "对于put(int), 找不到合适的方法" x3 trees).
2541 Some(crate::types::JavaType::Byte) | Some(crate::types::JavaType::Short) => {
2542 self.expr_narrow_arg(a, ¶m_types[i].clone(), out);
2543 }
2544 _ => {
2545 let lam_under_cast = matches!(a, Expr::Lambda(_))
2546 || matches!(a, Expr::Cast { e: ce, .. } if matches!(&**ce, Expr::Lambda(_)));
2547 match sam_rets.get(i).and_then(|x| x.as_ref()) {
2548 Some(t) if lam_under_cast => {
2549 let prev = self.lambda_sam_ret.replace(t.clone());
2550 self.expr(a, 1, out);
2551 self.lambda_sam_ret = prev;
2552 }
2553 _ => self.expr(a, 1, out),
2554 }
2555 }
2556 }
2557 }
2558 }
2559
2560 fn const_val(&self, c: &ConstVal, out: &mut String) {
2561 match c {
2562 ConstVal::Int(i) => out.push_str(&i.to_string()),
2563 ConstVal::Long(l) => {
2564 if *l == i64::MIN {
2565 out.push_str("-9223372036854775808L");
2566 } else {
2567 out.push_str(&format!("{}L", l));
2568 }
2569 }
2570 ConstVal::Float(f) => out.push_str(&format_float(*f as f64, true)),
2571 ConstVal::Double(d) => out.push_str(&format_float(*d, false)),
2572 ConstVal::Str(s) => {
2573 out.push('"');
2574 out.push_str(&escape_string(s));
2575 out.push('"');
2576 }
2577 ConstVal::Null => out.push_str("null"),
2578 ConstVal::ClassLit(t) => {
2579 out.push_str(&self.type_name(t));
2580 out.push_str(".class");
2581 }
2582 }
2583 }
2584
2585 fn lambda(&mut self, l: &crate::ir::expr::LambdaExpr, out: &mut String) {
2586 // A `lambda$...` implementation method in the current class is a
2587 // compiler-generated lambda body, never a real method reference —
2588 // emit the body inline even when classified as a reference (the
2589 // method itself is hidden from the output).
2590 let synthetic_self_lambda =
2591 l.impl_name.starts_with("lambda$") && l.impl_owner == self.ctx.class_name();
2592 match l.kind {
2593 LambdaKind::MethodRef if !synthetic_self_lambda => {
2594 // Determine receiver form.
2595 if l.impl_name == "<init>" {
2596 out.push_str(&self.shorten(&l.impl_owner));
2597 out.push_str("::new");
2598 return;
2599 }
2600 if let Some(recv) = &l.ref_receiver {
2601 out.push_str(&self.shorten(recv));
2602 out.push_str("::");
2603 out.push_str(&l.impl_name);
2604 } else if !l.captures.is_empty() {
2605 // bound receiver: first capture is the instance
2606 // A method-ref/lambda receiver needs its functional
2607 // type spelled out: `A::new::get` parses as a
2608 // QUALIFIED reference and is rejected — the source form
2609 // is `((Supplier<A>) A::new)::get`.
2610 let cap = &l.captures[0];
2611 let func_ty = match cap {
2612 Expr::Lambda(l2) => Some(l2.sam_cls.clone()),
2613 Expr::Cast { ty, e } if matches!(**e, Expr::Lambda(_)) => {
2614 Some(ty.erased().to_descriptor())
2615 }
2616 _ => None,
2617 };
2618 if let Some(f) = func_ty {
2619 out.push_str("((");
2620 out.push_str(&self.shorten(&f));
2621 out.push_str(") ");
2622 self.expr(cap, 1, out);
2623 out.push(')');
2624 } else {
2625 self.expr(cap, 15, out);
2626 }
2627 out.push_str("::");
2628 out.push_str(&l.impl_name);
2629 } else {
2630 out.push_str(&self.shorten(&l.impl_owner));
2631 out.push_str("::");
2632 out.push_str(&l.impl_name);
2633 }
2634 }
2635 _ => {
2636 if self.lambda_depth >= 8 {
2637 out.push_str("/*nested-lambda*/null");
2638 return;
2639 }
2640 // The nested-body preparation — decompiling the impl
2641 // method and running the front-end's idiom recovery
2642 // (anonymous inlining, capture snapshots, enum switches,
2643 // witness casts, outer-this substitution, label pruning,
2644 // ...) — belongs to the front-end: that is where
2645 // machine-specific desugaring lives. Ask it for a finished
2646 // statement tree and print that.
2647 // The nested-body preparation — decompiling the impl method
2648 // and running the front-end's idiom recovery (anonymous
2649 // inlining, capture snapshots, enum switches, witness casts,
2650 // outer-this substitution, label pruning, ...) — belongs to
2651 // the front-end: that is where machine-specific desugaring
2652 // lives. Ask it for a finished statement tree and print that.
2653 let nested = self.ctx.nested_method(l, self.vt);
2654 // params: prefer the impl method's own parameter names so the
2655 // printed parameter list matches the body's references
2656 // (instance lambda impls carry only the SAM params).
2657 let mut pnames = l.param_names.clone();
2658 if let Some(MethodBody { vt, .. }) = &nested {
2659 let mut vt_params: Vec<String> = vt
2660 .vars
2661 .iter()
2662 .filter(|v| v.is_param && v.name != "this")
2663 .map(|v| v.name.clone())
2664 .collect();
2665 if vt_params.len() == pnames.len() {
2666 pnames = vt_params;
2667 } else if vt_params.len() > pnames.len() {
2668 // The impl method's signature is (captures..., SAM
2669 // params...): take the TRAILING SAM slice so the
2670 // printed parameter list matches the body's variable
2671 // references (ConcurrentMap.replaceAll printed
2672 // `(x0, x1) -> { .. replace(k, v, ..) }` — undefined
2673 // symbols; the captured `function` occupied the first
2674 // impl slot, breaking the exact-length alignment).
2675 let n = pnames.len();
2676 pnames = vt_params.split_off(vt_params.len() - n);
2677 }
2678 }
2679 let single = pnames.len() == 1;
2680 if !single {
2681 out.push('(');
2682 }
2683 for (i, n) in pnames.iter().enumerate() {
2684 if i > 0 {
2685 out.push_str(", ");
2686 }
2687 out.push_str(n);
2688 }
2689 if !single {
2690 out.push(')');
2691 }
2692 out.push_str(" -> ");
2693 let sam_wrap = self.lambda_sam_ret.take();
2694 match nested {
2695 Some(MethodBody {
2696 mut body, mut vt, ..
2697 }) => {
2698 // Single-return body → expression lambda.
2699 let mut single_expr = match &body {
2700 Stmt::Return(Some(e)) => Some(e.clone()),
2701 Stmt::Block(v) if v.len() == 1 => match &v[0] {
2702 Stmt::Return(Some(e)) => Some(e.clone()),
2703 _ => None,
2704 },
2705 _ => None,
2706 };
2707 // Witness the instantiated SAM return on the body's
2708 // returns: the impl method is erased, so `i -> i`
2709 // under `(Function<I,R>)` fails inference without
2710 // `(R) i`.
2711 fn sam_ok(t: &TypeRef, e: &Expr) -> bool {
2712 !matches!(e, Expr::Cast { .. } | Expr::Const(_)) && e.type_ref() != *t
2713 }
2714 fn wrap_returns(s: &mut Stmt, t: &TypeRef) {
2715 match s {
2716 Stmt::Return(Some(e)) => {
2717 if sam_ok(t, e) {
2718 let v = std::mem::replace(e, Expr::This);
2719 *e = Expr::Cast {
2720 ty: t.clone(),
2721 e: Box::new(v),
2722 };
2723 }
2724 }
2725 Stmt::Block(v) => v.iter_mut().for_each(|x| wrap_returns(x, t)),
2726 Stmt::If {
2727 then_stmt,
2728 else_stmt,
2729 ..
2730 } => {
2731 wrap_returns(then_stmt, t);
2732 if let Some(x) = else_stmt {
2733 wrap_returns(x, t);
2734 }
2735 }
2736 Stmt::While { body, .. }
2737 | Stmt::DoWhile { body, .. }
2738 | Stmt::ForEach { body, .. }
2739 | Stmt::Labeled { body, .. }
2740 | Stmt::Synchronized { body, .. } => wrap_returns(body, t),
2741 Stmt::For { init, body, .. } => {
2742 init.iter_mut().for_each(|i| wrap_returns(i, t));
2743 wrap_returns(body, t);
2744 }
2745 Stmt::Switch { cases, default, .. } => {
2746 for c in cases.iter_mut() {
2747 for st in c.body.iter_mut() {
2748 wrap_returns(st, t);
2749 }
2750 }
2751 if let Some(d) = default {
2752 wrap_returns(d, t);
2753 }
2754 }
2755 Stmt::Try {
2756 body,
2757 catches,
2758 finally,
2759 } => {
2760 wrap_returns(body, t);
2761 for c in catches.iter_mut() {
2762 wrap_returns(&mut c.body, t);
2763 }
2764 if let Some(f) = finally {
2765 wrap_returns(f, t);
2766 }
2767 }
2768 _ => {}
2769 }
2770 }
2771 if let Some(t) = &sam_wrap {
2772 if let Some(e) = &mut single_expr {
2773 if sam_ok(t, e) {
2774 let v = std::mem::replace(e, Expr::This);
2775 *e = Expr::Cast {
2776 ty: t.clone(),
2777 e: Box::new(v),
2778 };
2779 } else if let TypeRef::G(_) = t {
2780 // The body already carries the erasure
2781 // checkcast: retype it to the generic
2782 // SAM return (jdk26
2783 // CopyOnWriteArrayList.toArray `i ->
2784 // (T[]) new Object[i]` printed
2785 // `(Object[])` — Object[]无法转换为T[]
2786 // against IntFunction<T[]>).
2787 if let Expr::Cast { ty: ct, e: ce } = e {
2788 if ct.erased() == t.erased()
2789 && !matches!(&**ce, Expr::Cast { .. })
2790 {
2791 *ct = t.clone();
2792 }
2793 }
2794 }
2795 } else {
2796 wrap_returns(&mut body, t);
2797 }
2798 } else if let Some(inst) = &l.inst_sam_desc {
2799 if crate::dbg_flag!("JCDC_DBG_LAMRET") {
2800 eprintln!(
2801 "LAMRET impl={}.{} inst_ret={:?} single={}",
2802 l.impl_owner,
2803 l.impl_name,
2804 inst.ret,
2805 single_expr.is_some()
2806 );
2807 }
2808 // The invokedynamic's instantiatedMethodType
2809 // records the lambda's inferred return at the
2810 // call site; a source upcast can be elided from
2811 // the bytecode when provable (jdk17
2812 // JrtFileSystem.iteratorOf: `(Path)` around
2813 // path.resolve(..) inside map(child -> ...) —
2814 // JrtPath <: Path needs no checkcast, but
2815 // without the cast map infers Stream<JrtPath>
2816 // and .iterator() fails the Iterator<Path>
2817 // return). Restore it when the expression body
2818 // is typed a strict subtype of the recorded
2819 // return.
2820 fn upcast_ok(
2821 e: &Expr,
2822 target: &crate::types::JavaType,
2823 ctx: &dyn crate::ctx::Ctx,
2824 ) -> bool {
2825 let crate::types::JavaType::Object(t) = target else {
2826 return false;
2827 };
2828 // A parameterized (generic) value whose
2829 // erasure IS the target reaches it unchecked
2830 // (jdk17 CompletionStage exceptionallyCompose:
2831 // the cond branch fn.apply(ex) types as the
2832 // capture ? extends CompletionStage<T>; the
2833 // erased impl return CompletionStage needs no
2834 // cast, and printing one breaks the outer
2835 // chain's inference).
2836 if let TypeRef::G(crate::types::GenericType::Class(cs)) =
2837 e.type_ref()
2838 {
2839 if crate::typeutil::classsig_internal(&cs).as_str() == t.as_ref() {
2840 return true;
2841 }
2842 }
2843 let crate::types::JavaType::Object(e0) = e.type_ref().erased()
2844 else {
2845 return false;
2846 };
2847 if &e0 == t {
2848 return false;
2849 }
2850 let mut queue = vec![e0];
2851 let mut seen = std::collections::HashSet::new();
2852 while let Some(cur) = queue.pop() {
2853 if !seen.insert(cur.clone()) {
2854 continue;
2855 }
2856 if !ctx.has_class(&cur) {
2857 continue;
2858 }
2859 for (sup, _) in ctx.class_supers_args(&cur, &[]) {
2860 if sup.as_str() == t.as_ref() {
2861 return true;
2862 }
2863 queue.push(sup.into());
2864 }
2865 }
2866 false
2867 }
2868 let want = TypeRef::J(inst.ret.clone());
2869 // The sam_wrap prime (declared local / pinned
2870 // ctor formal / generic-call formal) carries the
2871 // GENERIC SAM return the erased
2872 // instantiatedMethodType lost: upgrade an
2873 // existing erasure-equal cast on the body (jdk26
2874 // CopyOnWriteArrayList.toArray's `i -> (T[]) new
2875 // Object[i]` printed `(Object[])` — the real
2876 // checkcast erases T[] and Object[] alike;
2877 // Object[]无法转换为T[] against
2878 // IntFunction<T[]>).
2879 let g_want: Option<TypeRef> = sam_wrap.as_ref().and_then(|t| {
2880 if let TypeRef::G(g) = t {
2881 if TypeRef::G(g.clone()).erased() == inst.ret {
2882 return Some(t.clone());
2883 }
2884 }
2885 None
2886 });
2887 if let Some(e) = &mut single_expr {
2888 if crate::dbg_flag!("JCDC_DBG_LAMRET") {
2889 eprintln!(
2890 "LAMRET2 inst_ret={:?} expr_ty={:?} sam_ok={} upcast_ok={} g_want={:?}",
2891 inst.ret,
2892 e.type_ref(),
2893 sam_ok(&want, e),
2894 upcast_ok(e, &inst.ret, self.ctx),
2895 g_want
2896 );
2897 }
2898 let mut upgraded = false;
2899 if let Some(gw) = &g_want {
2900 if let Expr::Cast { ty, e: ce } = e {
2901 if ty.erased() == inst.ret
2902 && !matches!(&**ce, Expr::Cast { .. })
2903 {
2904 *ty = gw.clone();
2905 upgraded = true;
2906 }
2907 }
2908 }
2909 if !upgraded
2910 && sam_ok(&want, e)
2911 && upcast_ok(e, &inst.ret, self.ctx)
2912 {
2913 let v = std::mem::replace(e, Expr::This);
2914 *e = Expr::Cast {
2915 ty: want,
2916 e: Box::new(v),
2917 };
2918 }
2919 }
2920 }
2921 // A nested lambda body shares the ENCLOSING
2922 // lambda's scope rules: its own locals must not
2923 // redeclare names visible there (jdk26
2924 // MethodHandleProxies.createTemplate — the clb
2925 // lambda's foreach-desugared `i$`/`mi` collided
2926 // with the nested cob lambda's own `i$`/`mi`,
2927 // 已在方法中定义了变量 x2). Rename the inner
2928 // impl's non-param locals on collision; the
2929 // method-level twin is handled by
2930 // disambiguate_lambda_locals.
2931 {
2932 let mut outer_names: std::collections::HashSet<&str> =
2933 self.vt.vars.iter().map(|v| v.name.as_str()).collect();
2934 outer_names.extend(self.outer_names.iter().map(|n| n.as_str()));
2935 let mut used: std::collections::HashSet<String> =
2936 vt.vars.iter().map(|v| v.name.clone()).collect();
2937 for v in vt.vars.iter_mut() {
2938 if v.is_param || !outer_names.contains(v.name.as_str()) {
2939 continue;
2940 }
2941 let base = v.name.clone();
2942 let mut k = 1;
2943 loop {
2944 let cand = format!("{}${}", base, k);
2945 if !used.contains(&cand) && !outer_names.contains(cand.as_str())
2946 {
2947 used.insert(cand.clone());
2948 v.name = cand;
2949 break;
2950 }
2951 k += 1;
2952 }
2953 }
2954 }
2955 let mut sub = Printer {
2956 ctx: self.ctx,
2957 vt: &vt,
2958 out: String::new(),
2959 line_buf: String::new(),
2960 indent: self.indent,
2961 lambda_depth: self.lambda_depth + 1,
2962 ignored_seq: self.ignored_seq,
2963 outer_names: {
2964 let mut n = self.outer_names.clone();
2965 n.extend(self.vt.vars.iter().map(|v| v.name.clone()));
2966 n
2967 },
2968 // The SAM's return type governs the impl
2969 // body's renders: boolean SAMs must print
2970 // `return true;` not the JVM-int `return 1;`
2971 // (jdk26 Gatherers.fold's ofGreedy lambda).
2972 ret_bool: l.sam_desc.ret == crate::types::JavaType::Boolean,
2973 ret_char: l.sam_desc.ret == crate::types::JavaType::Char,
2974 ret_byte: l.sam_desc.ret == crate::types::JavaType::Byte,
2975 ret_short: l.sam_desc.ret == crate::types::JavaType::Short,
2976 suppress_poly_cast: false,
2977 suppress_diamond: false,
2978 // A lambda body is a fresh expression context:
2979 // the enclosing conditional's pre-8 non-poly
2980 // rule does not propagate into it (the lambda
2981 // is a standalone method body).
2982 in_cond: false,
2983 lambda_sam_ret: None,
2984 ret_sam: None,
2985 twin_cache: self.twin_cache.clone(),
2986 };
2987 if let Some(e) = single_expr {
2988 if l.sam_desc.ret == crate::types::JavaType::Boolean {
2989 sub.expr_bool(&e, out);
2990 } else {
2991 sub.expr(&e, 1, out);
2992 }
2993 } else {
2994 out.push_str("{\n");
2995 sub.indent += 1;
2996 sub.stmt(&body);
2997 let text = sub.out;
2998 out.push_str(&text);
2999 for _ in 0..self.indent {
3000 out.push_str(" ");
3001 }
3002 out.push('}');
3003 }
3004 }
3005 None => {
3006 out.push_str("/* lambda body in ");
3007 out.push_str(&l.impl_owner);
3008 out.push('.');
3009 out.push_str(&l.impl_name);
3010 out.push_str(" */ {}");
3011 }
3012 }
3013 }
3014 }
3015 }
3016
3017 // ---------------- names & types ----------------
3018
3019 /// True if `cls` is a member inner class (declares this$0) per the pool.
3020 /// Parameter types of the `<init>` matching `skip + n` descriptor args,
3021 /// returning the tail after `skip` leading synthetic params. Used to
3022 /// print `new` arguments with boolean/char constant adjustment.
3023 fn ctor_param_types(
3024 &self,
3025 cls: &str,
3026 skip: usize,
3027 n: usize,
3028 args: &[Expr],
3029 ) -> Option<Vec<crate::types::JavaType>> {
3030 // Overload/parameter recovery from the class's own `<init>` set is
3031 // container metadata: the front-end answers it.
3032 self.ctx.ctor_param_types(cls, skip, n, args)
3033 }
3034
3035 fn is_member_inner(&self, cls: &str) -> bool {
3036 // Front-end metadata question: synthetic outer field, or an enclosing
3037 // instance forwarded to super?
3038 if self.ctx.class_has_this0(cls) {
3039 return true;
3040 }
3041 if !cls.contains('$') || self.ctx.nested_is_static(cls) {
3042 return false;
3043 }
3044 self.ctx.outer_param_via_super(cls)
3045 }
3046
3047 /// Is `cls` lexically enclosing the CURRENT class in the RENDERED
3048 /// layout? Digit-tail classes (anonymous/d8-lambda shapes) and
3049 /// `-`-tail classes (Kotlin `X$-CC` facades — `-` is not a Java
3050 /// identifier start, so the class renders as a flat mangled
3051 /// top-level file, mirroring ddc-dec's clean_member_tail) are
3052 /// emitted as separate flat top-level files — the nested lexical
3053 /// scope they would provide does not exist there, so members of an
3054 /// outer class must be QUALIFIED from them (weibo
3055 /// CronetUrlRequest$3: bare `_$$Nest$misDoneLocked(..)` calls of
3056 /// the outer's statics were unresolvable).
3057 fn renders_nested_in(&self, cls: &str) -> bool {
3058 let cur = self.ctx.class_name();
3059 if !cur.starts_with(&format!("{}$", cls)) {
3060 return false;
3061 }
3062 let mut c = cur.to_string();
3063 while c.as_str() != cls {
3064 let tail = c.rsplit('$').next().unwrap_or(&c);
3065 if tail.is_empty()
3066 || tail.chars().next().is_some_and(|ch| ch.is_ascii_digit())
3067 || tail.starts_with('-')
3068 {
3069 return false; // flat-rendered boundary: no lexical scope
3070 }
3071 match c.rfind('$') {
3072 Some(i) => c.truncate(i),
3073 None => return false,
3074 }
3075 }
3076 true
3077 }
3078
3079 /// Instantiated SAM return for a cast to a generic functional
3080 /// interface (`(Function<I,R>) lambda`): resolves the interface's SAM
3081 /// method Signature return against the cast's type arguments. Only
3082 /// meaningful when the result still contains a type variable — the
3083 /// erased impl body then needs `(R) value` witnesses on its returns
3084 /// (javac: "return type I cannot be converted to R" for `i -> i`).
3085 fn sam_ret_cast(&self, g: &crate::types::GenericType, sam_name: &str) -> Option<TypeRef> {
3086 // Resolving a functional interface's SAM return against a cast's type
3087 // arguments reads the interface's method signatures: front-end
3088 // metadata.
3089 self.ctx.sam_ret_cast(g, sam_name)
3090 }
3091
3092 /// `<>` when `new cls(...)` should carry a diamond: the class is
3093 /// generic (class-level Signature with type params) and THIS file is
3094 /// Java 7+. A bare generic new compiles under old-style inference
3095 /// (standalone, not target-typed), which collapses to Object when
3096 /// implicitly-typed lambda args are involved — jdk11 Collectors.
3097 /// summingInt's `new CollectorImpl<>(() -> new int[1], (a, t) -> ...)`
3098 /// fails as bare `new CollectorImpl(...)` ("array required, but found
3099 /// Object", 72 errors in the concurrent-family closure). Bytecode
3100 /// cannot distinguish diamond from bare, so always prefer the diamond
3101 /// for generic classes.
3102 fn diamond_for(&self, cls: &str) -> &'static str {
3103 if self.ctx.source_level() < 51 {
3104 return "";
3105 }
3106 if self.ctx.class_declares_generics(cls) {
3107 "<>"
3108 } else {
3109 ""
3110 }
3111 }
3112
3113 /// Shorten an internal class name for emission: java.lang.* and same
3114 /// package use simple names; others fully qualified dotted.
3115 /// True when `cls.name` is a PRIVATE instance field and the owner
3116 /// expression's compile-time type is a strict subclass of cls: the
3117 /// field is not in the subclass's member scope, so the source must
3118 /// cast the owner to the declaring class
3119 /// (`((MessageDigest)this).algorithm` — jdk11 Delegate.clone x3,
3120 /// Signature.clone).
3121 fn private_super_field(&self, cls: &str, name: &str, o: &Expr) -> bool {
3122 if matches!(o, Expr::Raw(_) | Expr::RawT(..)) {
3123 return false;
3124 }
3125 let owner_ct = if matches!(o, Expr::This) {
3126 self.ctx.class_name().into()
3127 } else {
3128 match o.type_ref().erased() {
3129 crate::types::JavaType::Object(n) => n,
3130 _ => return false,
3131 }
3132 };
3133 if owner_ct.as_ref() == cls {
3134 return false;
3135 }
3136 let Some(flags) = self.ctx.field_flags(cls, name) else {
3137 return false;
3138 };
3139 if flags.contains(crate::types::FieldAccessFlags::STATIC)
3140 || !flags.contains(crate::types::FieldAccessFlags::PRIVATE)
3141 {
3142 return false;
3143 }
3144 // cls must be a strict superclass of the owner's compile-time type.
3145 let mut cur = owner_ct;
3146 for _ in 0..64 {
3147 let Some(sup) = self.ctx.super_name(&cur) else {
3148 return false;
3149 };
3150 if sup == cls {
3151 return true;
3152 }
3153 cur = sup.into();
3154 }
3155 false
3156 }
3157
3158 fn needs_owner_cast(
3159 &self,
3160 cls: &str,
3161 name: &str,
3162 desc: &crate::types::MethodDescriptor,
3163 o: &Expr,
3164 ) -> bool {
3165 // Raw-rendered owners (qualified `HashMap.this`, substituted
3166 // capture text) carry no trustworthy static type — and casting
3167 // them to a RAW class erases the whole generic member chain
3168 // (`((HashMap) HashMap.this).<T>keysToArray(..)` made the call
3169 // raw: "Object[]无法转换为T[]").
3170 if matches!(o, Expr::Raw(_) | Expr::RawT(..) | Expr::This) {
3171 return false;
3172 }
3173 let crate::types::JavaType::Object(on) = o.type_ref().erased() else {
3174 return false;
3175 };
3176 if on.as_ref() == cls {
3177 return false;
3178 }
3179 // A receiver widened to Object (Kotlin `when` materialization
3180 // residue — every branch's value lowered into one slot) cannot
3181 // resolve ANY member: cast to the invoke's resolved class. The
3182 // bytecode guarantees the runtime type (invokevirtual dispatch
3183 // on exactly this object), so the cast is always honest.
3184 if on.as_ref() == "java/lang/Object" && cls != "java/lang/Object" {
3185 return true;
3186 }
3187 let want = format!(
3188 "({}){}",
3189 desc.args
3190 .iter()
3191 .map(|t| t.to_descriptor())
3192 .collect::<String>(),
3193 desc.ret.to_descriptor()
3194 );
3195 // The receiver's static type is a strict SUPERTYPE of the
3196 // resolved class (register reuse left the vt at the supertype):
3197 // the member lives on `cls`, and the downcast is exactly what
3198 // the bytecode's resolution performed. ONLY when the owner type
3199 // cannot resolve the member itself: an interface-typed owner
3200 // whose IMPL the dex named resolves fine through the
3201 // interface, and casting to the impl sprinkles obscured/flat
3202 // impl names into expression position (weixin +3.3k
3203 // cannot-find when this fired unguarded).
3204 if self.ctx.is_subtype_of(
3205 &crate::types::JavaType::Object(std::sync::Arc::from(cls)),
3206 on.as_ref(),
3207 ) && self.ctx.method_flags(on.as_ref(), name, &want).is_none()
3208 {
3209 return true;
3210 }
3211 let Some(acc) = self.ctx.method_flags(cls, name, &want) else {
3212 return false;
3213 };
3214 use crate::types::MethodAccessFlags as M;
3215 if acc.contains(M::PRIVATE) {
3216 return true;
3217 }
3218 if !acc.contains(M::PUBLIC) && !acc.contains(M::PROTECTED) {
3219 fn pkg(n: &str) -> &str {
3220 n.rsplit_once('/').map(|(p, _)| p).unwrap_or("")
3221 }
3222 return pkg(&on) != pkg(cls);
3223 }
3224 false
3225 }
3226
3227 fn needs_object_relay(&self, ty: &TypeRef, e: &Expr) -> bool {
3228 if matches!(e, Expr::Cast { .. } | Expr::Const(_)) {
3229 return false;
3230 }
3231 let (crate::types::JavaType::Object(from), crate::types::JavaType::Object(to)) =
3232 (e.type_ref().erased(), ty.erased())
3233 else {
3234 return false;
3235 };
3236 if from == to || from.as_ref() == "java/lang/Object" || to.as_ref() == "java/lang/Object" {
3237 return false;
3238 }
3239 // A KNOWN interface on either side keeps the direct form legal
3240 // (casts/instanceof against an interface compile regardless of
3241 // the operand's class).
3242 let known_itf = |n: &str| self.ctx.has_class(n) && self.ctx.is_interface(n);
3243 if known_itf(&from) || known_itf(&to) {
3244 if self.ctx.has_class(&from) && self.ctx.has_class(&to) {
3245 if self.ctx.is_subtype_of(&e.type_ref().erased(), &to)
3246 || self.ctx.is_subtype_of(&ty.erased(), &from)
3247 {
3248 return false;
3249 }
3250 return self.ctx.is_sealed(&from) || self.ctx.is_sealed(&to);
3251 }
3252 return false;
3253 }
3254 if self.ctx.has_class(&from) && self.ctx.has_class(&to) {
3255 if self.ctx.is_subtype_of(&e.type_ref().erased(), &to)
3256 || self.ctx.is_subtype_of(&ty.erased(), &from)
3257 {
3258 return false;
3259 }
3260 let both_classes = !self.ctx.is_interface(&from) && !self.ctx.is_interface(&to);
3261 return both_classes || self.ctx.is_sealed(&from) || self.ctx.is_sealed(&to);
3262 }
3263 // Off-pool (framework) side: the hierarchy is unknowable here.
3264 // A direct cast fails exactly when both sides are classes and
3265 // unrelated — treat unknowns as classes (java/lang/Throwable,
3266 // Error, android.media.MediaCodec$CodecException all are) and
3267 // relay through Object; the relay never changes cast semantics
3268 // (`(T) e` vs `(T)(Object) e` — same runtime check), it only
3269 // widens compile legality (weixin EncoderWriter
3270 // `(CodecException) error2` with error2: Error, ry0/d4
3271 // `(k) th3x` with th3x: Throwable — 不兼容的类型 ×250+).
3272 true
3273 }
3274
3275 pub fn shorten(&self, internal: &str) -> String {
3276 // NOTE: deliberately NOT memoized across Printers — shorten_inner
3277 // consults per-Printer state (the emitting class's package via
3278 // ctx.class_name, its fields, and this method's vt.vars for the
3279 // shadowed-simple-name check), so the result is not a pure
3280 // function of (pool, internal).
3281 // Class-file names may contain characters Java cannot parse
3282 // (R8 desugared `Collection$-EL`) — EVERY return path runs the
3283 // deterministic sanitizer (early returns bypassed it).
3284 sanitize_source_name(&self.shorten_inner(internal, true))
3285 }
3286
3287 /// Like `shorten` but WITHOUT the anonymous-class → interface/super
3288 /// fallback: used at `new <cls>` sites so a d8/R8-desugared lambda
3289 /// class (which ends in `$<digits>` like an anonymous class) prints as
3290 /// its concrete, instantiable binary name instead of its abstract SAM
3291 /// interface. Type positions keep using `shorten` (interface), so the
3292 /// declaration and the constructor disagree in the correct direction:
3293 /// `Function0 v = new Outer$..$log$1(..)`.
3294 pub fn shorten_concrete(&self, internal: &str) -> String {
3295 // Field-obscured package first segments (JLS 6.4.2): the
3296 // qualified render is captured by the field, but the simple
3297 // resolves through the file's import (JLS 7.5.1 legally
3298 // shadows same-package siblings — the blocked set proved this
3299 // file uses none of them). Static-member qualifiers printed
3300 // qualified through the capture (lark pq/e: field `e2` ate
3301 // `e2.c.b(..)` — "变量 c" ×47); digit-tail names never reach
3302 // here (obscured_render_pub rejects un-importable simples).
3303 if let Some(simple) = self.ctx.obscured_simple(internal) {
3304 return sanitize_source_name(&simple);
3305 }
3306 sanitize_source_name(&self.shorten_inner(internal, false))
3307 }
3308
3309 /// Nesting-aware display chain: a `$` boundary dots ONLY when the
3310 /// prefix has a resolvable outer (a true member); when the prefix
3311 /// is itself an emission-unit top-level (R8 deleted the outer —
3312 /// weixin matrix `JiffiesMonitorFeature` gone, `$JiffiesSnapshot`
3313 /// survives as a flat file), its `$` stays and the member tail dots
3314 /// onto it: `...JiffiesMonitorFeature$JiffiesSnapshot.ThreadJiffiesEntry`.
3315 /// Blind `$`→`.` rendered a chain through the missing type
3316 /// ("是在不可访问的类或接口中定义的").
3317 fn nested_display(&self, internal: &str) -> String {
3318 if let Some(o) = self.ctx.find_outer(internal) {
3319 // The nesting boundary must be a literal `$` — a pool prefix
3320 // followed by another char (`c$1` inside `c$1-IA`) is not an
3321 // outer of this name.
3322 if internal.len() > o.len() + 1
3323 && internal.starts_with(&o)
3324 && internal.as_bytes()[o.len()] == b'$'
3325 {
3326 let tail = &internal[o.len() + 1..];
3327 if !tail.is_empty() && !tail.starts_with('$') && !tail.contains('/') {
3328 // The tail may span several member levels
3329 // (`a17$a18`): below a RESOLVED outer they are all
3330 // inline members of the family file — dot them.
3331 return format!(
3332 "{}.{}",
3333 self.nested_display(&o),
3334 tail.replace('$', ".")
3335 );
3336 }
3337 }
3338 }
3339 // Base: a POOL class with no resolvable outer is a flat `$`-named
3340 // emission unit — keep the `$` (its file name carries it). An
3341 // off-pool name is a framework member class (android.jar's
3342 // `LinearLayout$LayoutParams`) — dot it like the old blind
3343 // replace; the jar declares it as a member.
3344 if self.ctx.has_class(internal) {
3345 internal.replace('/', ".")
3346 } else {
3347 internal.replace(['/', '$'], ".")
3348 }
3349 }
3350
3351 /// Walk the MEMBER-TYPE scope of the emitting class: every lexical
3352 /// `$`-outer level, and at each level its superclass chain and direct
3353 /// interfaces — JLS 8.1.6: member types are INHERITED into scope, so
3354 /// a superclass's nested `a` captures the bare simple name `a` exactly
3355 /// like an own nestee (weixin pk4.d extends k0: same-package class ref
3356 /// `a.d` bound to inherited k0$a — "找不到符号 变量 d, 位置: 类 a").
3357 /// `f` is called with each in-scope class's internal name; the walk
3358 /// stops early when `f` returns true.
3359 fn scope_member_walk<F: FnMut(&str) -> bool>(&self, f: &mut F) -> bool {
3360 let mut cur: &str = self.ctx.class_name();
3361 loop {
3362 let mut stack: Vec<String> = vec![cur.to_string()];
3363 let mut seen: crate::FxHashSet<String> = crate::FxHashSet::default();
3364 while let Some(c) = stack.pop() {
3365 if !seen.insert(c.clone()) {
3366 continue;
3367 }
3368 if f(&c) {
3369 return true;
3370 }
3371 if let Some((ifaces, sup)) = self.ctx.class_bases(&c) {
3372 for i in ifaces {
3373 stack.push(i);
3374 }
3375 if let Some(s) = sup {
3376 if s != "java/lang/Object" {
3377 stack.push(s);
3378 }
3379 }
3380 }
3381 }
3382 match cur.rfind('$') {
3383 Some(i) => cur = &cur[..i],
3384 None => break,
3385 }
3386 }
3387 false
3388 }
3389
3390 fn shorten_inner(&self, internal: &str, anon_fallback: bool) -> String {
3391 if internal.is_empty() {
3392 return String::new();
3393 }
3394
3395 // An import-resolved obscured reference renders its simple name
3396 // (the qualified form binds to an in-scope class — JLS 6.4.2).
3397 if let Some(simple) = self.ctx.obscured_simple(internal) {
3398 return simple;
3399 }
3400 // Case-collision renames (`X/Cua` → `X/Cua_2`): the registry is
3401 // empty for ordinary corpora — one relaxed atomic load.
3402 let orig_internal = internal;
3403 // Framework-shadow stub: the reference means the PLATFORM class —
3404 // the raw FQN (only the stub's own file identity relocated).
3405 let cow = if self.ctx.is_fw_shadow(internal) {
3406 std::borrow::Cow::Borrowed(internal)
3407 } else {
3408 crate::rename::apply_class_rename(internal)
3409 };
3410 let internal: &str = &cow;
3411 // Literal-$ top-level class (in pool, no InnerClasses nesting
3412 // evidence): the $ is part of the SOURCE name (jextract-generated
3413 // errno_h$shared) — never dot it into a nested qualifier. Same rule
3414 // whenever dotting could not parse: `DolTest2$$dollah$$` has empty
3415 // segments, and `$` is a legal identifier character while `A..b` is
3416 // not.
3417 let dot_safe = |simple: &str| -> bool {
3418 // A LEADING `$` (`$Gson$Types`) is part of the source name —
3419 // dotting it yields `.Gson.Types` with a leading dot.
3420 simple.split('$').all(|seg| {
3421 !seg.is_empty()
3422 && seg
3423 .chars()
3424 .next()
3425 .map(|c| c.is_ascii_alphabetic() || c == '_')
3426 .unwrap_or(false)
3427 })
3428 };
3429 let simple_here = internal.rsplit('/').next().unwrap_or(internal).to_string();
3430 // Identifier-unsafe characters ANYWHERE in the name (Kotlin's
3431 // `-IA`/`-EL` hyphen markers: `org/a/c/c$1-IA`; R8 unicode
3432 // obfuscation: the Thai/Yi/Syriac classes of bin.mt.plus,
3433 // ejfkdev/ddc#4) are not source identifiers and can never be
3434 // nested members or local classes — they emit as FLAT sanitized
3435 // files, so references must render the same sanitized full name
3436 // (`_u<hex>` escapes on BOTH sides). The local-class digit-strip
3437 // path mangled them (`1-IA` → `-IA` → `_IA`, weibo ×157), and a
3438 // raw-unicode ref never resolves against the escaped decl.
3439 if !internal
3440 .chars()
3441 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$' || c == '/')
3442 {
3443 let dotted = sanitize_source_name(&internal.replace('/', "."));
3444 // NESTED member whose tail is structurally clean (starts an
3445 // identifier) but carries unsafe characters (weibo's Kotlin
3446 // `header/a$a-IA` → member `class a_u2dIA` INSIDE a.java):
3447 // the reference must dot the `$` boundary like the decl side
3448 // does — the flat `header.a$a_u2dIA` resolves to nothing
3449 // (找不到符号 类 ×~500: b2/a$a_u2dIA/b$a_u2dIA families).
3450 // Digit-start tails (`j$1-IA`) are flat emission-unit files
3451 // KEEPING the `$`, and non-pool names (framework
3452 // `Collection$-EL`) have no decl at all — both stay flat.
3453 if !simple_here.contains("$$ExternalSynthetic") {
3454 if let Some(i) = internal.rfind('$') {
3455 let tail = &internal[i + 1..];
3456 let outer = &internal[..i];
3457 let tail_clean = tail.chars().next().is_some_and(|c| {
3458 (c.is_ascii_alphabetic() || c == '_') && !c.is_ascii_digit()
3459 });
3460 let outer_safe = outer
3461 .chars()
3462 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$' || c == '/');
3463 // has_class on BOTH forms: when an outer rename rule
3464 // moved the family (obscuring a → a2), `internal` is
3465 // the DISPLAY chain and the pool key is the original
3466 // (weibo page/discover a$a-IA displayed under a2).
3467 if tail_clean
3468 && outer_safe
3469 && (self.ctx.has_class(internal)
3470 || self.ctx.has_class(orig_internal))
3471 {
3472 return format!(
3473 "{}.{}",
3474 sanitize_source_name(&outer.replace('/', ".")),
3475 sanitize_source_name(tail)
3476 );
3477 }
3478 }
3479 }
3480 return dotted;
3481 }
3482 // d8's synthetic outline/lambda/backport classes
3483 // (`X$$ExternalSyntheticLambda0`, `…ApiModelOutline0`,
3484 // `…BackportWithForwarding0`) are TOP-LEVEL synthetics emitted as
3485 // flat files with their full `$` name. The local-class digit-strip
3486 // path below mangles them to the bare innermost segment
3487 // (`ExternalSyntheticBackportWithForwarding0`) when the outer chain
3488 // has a digit segment (`PreviewView$1$$…`), which resolves to
3489 // nothing cross-package ("找不到符号 变量 ExternalSynthetic…", weibo
3490 // ×467). Render the full dotted name, like the hyphen case.
3491 if simple_here.contains("$$ExternalSynthetic") {
3492 return sanitize_source_name(&internal.replace('/', "."));
3493 }
3494 let keep_dollar_pool = internal.contains('$')
3495 && self.ctx.has_class(internal)
3496 && self.ctx.find_outer(internal).is_none();
3497 // R8 desugared-library names carry `$` inside the PACKAGE path
3498 // (`j$/util/Collection$-EL`): a `$` followed by `/` is never a
3499 // nesting boundary — dotting it produced `j..util`.
3500 let dollar_in_pkg = internal.contains("$/");
3501 let keep_dollar = keep_dollar_pool
3502 || dollar_in_pkg
3503 || (internal.contains('$') && !dot_safe(&simple_here));
3504 // Anonymous class types (all-digit last segment) have no source
3505 // name: print the base interface/superclass instead. Skipped for
3506 // `new <cls>` sites (anon_fallback=false): a d8/R8-desugared
3507 // LAMBDA class also ends in `$<digits>` (`...$lambda$N$$inlined
3508 // $log$1`, `Outer$$ExternalSyntheticLambda0`) but IS a concrete,
3509 // pool-rendered class — printing its SAM interface there yields an
3510 // invalid `new Iface(..)` ("a是抽象的; 无法实例化", the biggest
3511 // abstract-instantiation family on Kotlin corpora: v6.a/l/p =
3512 // Function0/1/2). The var/phi TYPE positions keep the interface
3513 // (shorten with fallback), so `Iface v = new ConcreteLambda(..)`
3514 // stays phi-compatible across branches that mint different lambdas.
3515 if anon_fallback {
3516 if let Some(last) = internal.rsplit('$').next() {
3517 if !last.is_empty() && last.chars().all(|c| c.is_ascii_digit()) {
3518 if let Some((ifaces, sup)) = self.ctx.class_bases(internal) {
3519 if let Some(n) = ifaces.first() {
3520 return self.shorten(n);
3521 }
3522 if let Some(sup) = sup {
3523 if sup != "java/lang/Object" {
3524 return self.shorten(&sup);
3525 }
3526 }
3527 return "Object".to_string();
3528 }
3529 }
3530 }
3531 }
3532 // Local classes are emitted with their simple source name.
3533 if let Some(last) = internal.rsplit('$').next() {
3534 if !keep_dollar_pool
3535 && !last.is_empty()
3536 && !last.chars().all(|c| c.is_ascii_digit())
3537 && internal.contains('$')
3538 {
3539 // Nested-class names print as Outer.Inner, except local
3540 // classes whose outer chain includes digits (e.g. Foo$1Bar).
3541 let mut segs = internal.split('$');
3542 if let Some(first) = segs.next() {
3543 let rest: Vec<&str> = segs.collect();
3544 if rest
3545 .iter()
3546 .any(|r| r.starts_with(|c: char| c.is_ascii_digit()))
3547 {
3548 // Local/synthetic class (digit-led segment): javac
3549 // encodes a method-local class as `Outer$1Name`; its
3550 // source name is the digit prefix stripped, declared
3551 // `class Name` at its use site and referenced only
3552 // within that scope. Printing the binary name leaves
3553 // an unresolvable symbol (ClassSpecializer$Factory$1Var).
3554 if let Some(last) = internal.rsplit('$').next() {
3555 let stripped = last.trim_start_matches(|c: char| c.is_ascii_digit());
3556 if !stripped.is_empty()
3557 && !stripped.chars().next().unwrap().is_ascii_digit()
3558 {
3559 return stripped.to_string();
3560 }
3561 }
3562 return internal.replace('/', ".");
3563 }
3564 let _ = first;
3565 let _ = last;
3566 }
3567 }
3568 }
3569 let dotted = internal.replace('/', ".");
3570 // A simple type name shadowed by an in-scope VARIABLE (a field of
3571 // this class or a method local) cannot qualify a static access:
3572 // javac resolves the leading name as the variable first
3573 // (无法取消引用int — jdk26 VerificationType declares
3574 // `private static final int Integer` and the source spells
3575 // java.lang.Integer.toHexString in full). Keep the fully-qualified
3576 // form whenever the leading segment collides.
3577 let shadowed = |first: &str| {
3578 self.ctx.declares_field(self.ctx.class_name(), first)
3579 || self.vt.vars.iter().any(|v| v.name == first)
3580 };
3581 if internal.starts_with("java/lang/") && !internal[10..].contains('/') {
3582 let simple = if keep_dollar {
3583 internal[10..].to_string()
3584 } else {
3585 internal[10..].replace('$', ".")
3586 };
3587 let first = simple.split('.').next().unwrap_or(simple.as_str());
3588 if !shadowed(first) {
3589 return simple;
3590 }
3591 return dotted.replace('$', ".");
3592 }
3593 if pkg_of(internal) == pkg_of(self.ctx.class_name()) {
3594 let simple = if keep_dollar {
3595 internal.rsplit('/').next().unwrap_or(internal).to_string()
3596 } else {
3597 let d = self.nested_display(internal);
3598 let pkg_dotted = pkg_of(internal).replace('/', ".");
3599 d.strip_prefix(&pkg_dotted)
3600 .and_then(|r| r.strip_prefix('.'))
3601 .map(str::to_string)
3602 .unwrap_or(d)
3603 };
3604 let first = simple.split('.').next().unwrap_or(simple.as_str());
3605 // Member-type shadow: the emitting class's OWN nested type
3606 // with the same simple name captures the bare render
3607 // (member types outrank same-package types in class scope) —
3608 // weibo PhoneInfoUtils: `b2.b(ctx,..)` for sibling class
3609 // com/weibo/ssosdk/b2 bound to nested PhoneInfoUtils$b2
3610 // (找不到符号 方法 b). The reference to the twin itself (or
3611 // its own nestees) keeps the simple form. FQN fallback is
3612 // unambiguous — no field/variable can shadow a dotted
3613 // package chain past its first segment.
3614 // Member-type twin shadow: a nested type of ANY enclosing
3615 // level whose display simple name equals `first` captures
3616 // the bare render (member types outrank same-package types
3617 // in class scope — weixin d2's `((w1) h0)` bound to nested
3618 // d2$$w1, not sibling viewitems/w1: 1,045 h0→w1 lines).
3619 // Walk the `$`-chain of the emitting class (the ctx may be a
3620 // nested `d2$$c`, whose lexical scope still sees d2's
3621 // members); R8 orphans use `$$`, normalization displays use
3622 // `$`, and rename displays live only in the registry's
3623 // reverse index — check all four shapes per level.
3624 let twin_exists = {
3625 // The borrow temp must die before the else arm's
3626 // borrow_mut (if-let scrutinee temps live across the
3627 // whole statement — RefCell double-borrow panic).
3628 let cached = self.twin_cache.borrow().get(first).copied();
3629 if let Some(v) = cached {
3630 v
3631 } else {
3632 // Own nestees AND inherited member types (the scope
3633 // walk) capture the bare simple name alike.
3634 let v = self.scope_member_walk(&mut |c| {
3635 let t1 = format!("{c}${first}");
3636 let t2 = format!("{c}$${first}");
3637 self.ctx.has_class(&t1)
3638 || self.ctx.has_class(&t2)
3639 || crate::rename::is_renamed_display(&t1)
3640 || crate::rename::is_renamed_display(&t2)
3641 });
3642 self.twin_cache
3643 .borrow_mut()
3644 .insert(first.to_string(), v);
3645 v
3646 }
3647 };
3648 // is_twin_ref only matters when a twin exists (otherwise
3649 // `!twin_exists` already grants the simple form) — keep it
3650 // off the hot path.
3651 let is_twin_ref = twin_exists
3652 && self.scope_member_walk(&mut |c| {
3653 let t1 = format!("{c}${first}");
3654 let t2 = format!("{c}$${first}");
3655 internal == t1
3656 || internal == t2
3657 || internal.starts_with(&format!("{t1}$"))
3658 || internal.starts_with(&format!("{t2}$"))
3659 });
3660 if !shadowed(first) && (is_twin_ref || !twin_exists) {
3661 return simple;
3662 }
3663 }
3664 if keep_dollar {
3665 dotted
3666 } else {
3667 self.nested_display(internal)
3668 }
3669 }
3670
3671 pub fn type_name(&self, ty: &TypeRef) -> String {
3672 match ty {
3673 TypeRef::J(t) => self.java_type_name(t),
3674 TypeRef::G(g) => {
3675 // Render generic signature with shortened class names.
3676 self.generic_name(g)
3677 }
3678 }
3679 }
3680
3681 /// Declared-type name for a local: normally `type_name` (which lets a
3682 /// `$<digits>` class fall back to its SAM interface for phi-friendly
3683 /// readability), but a var in `force_concrete_vars` keeps its concrete
3684 /// binary name so concrete-member accesses (`v.L$0`, the Kotlin
3685 /// coroutine capture fields) resolve — interfaces have no instance
3686 /// fields.
3687 fn local_decl_ty(&self, var: u32, ty: &TypeRef) -> String {
3688 if self.vt.force_concrete_vars.contains(&var) {
3689 if let TypeRef::J(JavaType::Object(cls)) = ty {
3690 return sanitize_source_name(&self.shorten_concrete(cls));
3691 }
3692 }
3693 self.type_name(ty)
3694 }
3695
3696 fn java_type_name(&self, t: &JavaType) -> String {
3697 // An import-resolved obscured class renders its simple name in
3698 // every type position (signatures, casts, type refs).
3699 if let JavaType::Object(c) = t {
3700 if let Some(simple) = self.ctx.obscured_simple(c) {
3701 // The simple is a RAW `$`/path tail — digit-leading and
3702 // other illegal source forms must pass the sanitizer
3703 // like every other name path (WhatsApp `11o_2 v21;`
3704 // local-decl types, 需要';' ×16,926).
3705 return sanitize_source_name(&simple);
3706 }
3707 }
3708 match t {
3709 JavaType::Object(n) => self.shorten(n),
3710 JavaType::Array(inner) => format!("{}[]", self.java_type_name(inner)),
3711 other => other.to_java(false),
3712 }
3713 }
3714
3715 fn generic_name(&self, g: &crate::types::GenericType) -> String {
3716 use crate::types::{GenericType, WildcardBound};
3717 match g {
3718 GenericType::Primitive(c) => prim_name(*c).to_string(),
3719 GenericType::Class(cs) => {
3720 // The signature parser splits a literal-$ top-level name
3721 // (jextract-generated errno_h$shared) into nested-looking
3722 // parts [errno_h, shared]; rejoin and, when the pool
3723 // proves the class is NOT nested, render the binary name
3724 // through shorten (same-package simple form keeps the $).
3725 if cs.parts.len() > 1 && cs.parts.iter().skip(1).all(|p| p.args.is_empty()) {
3726 let joined = cs
3727 .parts
3728 .iter()
3729 .map(|p| p.name.as_str())
3730 .collect::<Vec<_>>()
3731 .join("$");
3732 let full = if cs.package.is_empty() {
3733 joined
3734 } else {
3735 format!("{}/{}", cs.package, joined)
3736 };
3737 let literal = self.ctx.has_class(&full) && self.ctx.find_outer(&full).is_none();
3738 if literal {
3739 return self.shorten(&full);
3740 }
3741 }
3742 let mut s = String::new();
3743 if !cs.package.is_empty() {
3744 let full = format!(
3745 "{}/{}",
3746 cs.package,
3747 cs.parts.first().map(|p| p.name.as_str()).unwrap_or("")
3748 );
3749 s.push_str(&self.shorten(&full));
3750 } else {
3751 s.push_str(&cs.parts.first().map(|p| p.name.clone()).unwrap_or_default());
3752 }
3753 if let Some(first) = cs.parts.first() {
3754 if !first.args.is_empty() {
3755 s.push('<');
3756 for (i, a) in first.args.iter().enumerate() {
3757 if i > 0 {
3758 s.push_str(", ");
3759 }
3760 s.push_str(&self.generic_name(a));
3761 }
3762 s.push('>');
3763 }
3764 }
3765 for p in cs.parts.iter().skip(1) {
3766 let digit_led = p
3767 .name
3768 .chars()
3769 .next()
3770 .map(|c| c.is_ascii_digit())
3771 .unwrap_or(false);
3772 let stripped = p.name.trim_start_matches(|c: char| c.is_ascii_digit());
3773 if digit_led && !stripped.is_empty() && p.args.is_empty() {
3774 // javac's local-class encoding `Outer$1Name`: the
3775 // source name is the prefix-stripped simple name,
3776 // referenced WITHOUT qualification (there is no
3777 // legal `DocLint.1Pair` — `> or ',' expected`).
3778 s = stripped.to_string();
3779 continue;
3780 }
3781 s.push('.');
3782 s.push_str(&p.name);
3783 if !p.args.is_empty() {
3784 s.push('<');
3785 for (i, a) in p.args.iter().enumerate() {
3786 if i > 0 {
3787 s.push_str(", ");
3788 }
3789 s.push_str(&self.generic_name(a));
3790 }
3791 s.push('>');
3792 }
3793 }
3794 s
3795 }
3796 GenericType::Array(inner) => format!("{}[]", self.generic_name(inner)),
3797 GenericType::TypeVar(n) => n.clone(),
3798 GenericType::Wildcard(w) => match w {
3799 WildcardBound::Any => "?".to_string(),
3800 WildcardBound::Extends(t) => format!("? extends {}", self.generic_name(t)),
3801 WildcardBound::Super(t) => format!("? super {}", self.generic_name(t)),
3802 },
3803 }
3804 }
3805}
3806
3807fn inner_simple(cls: &str) -> String {
3808 // Apply the class-rename registry FIRST: a renamed nested class must
3809 // print its display tail at `outer.new Inner(..)` sites too — this
3810 // free function was the one emit-side path bypassing the registry
3811 // (round-55: declarations carried the rename while `this.new b()`
3812 // printed the raw tail → cannot-find).
3813 // Framework-shadow stub: keep the raw framework name (refs mean the
3814 // platform class; only the file identity relocated).
3815 let shadowed = crate::rename::fw_shadow_contains(cls);
3816 let cow = if shadowed {
3817 std::borrow::Cow::Borrowed(cls)
3818 } else {
3819 crate::rename::apply_class_rename(cls)
3820 };
3821 let cls: &str = &cow;
3822 let last = cls.rsplit('/').next().unwrap_or(cls);
3823 // R8 leaves class names ending in `$` (rimet's `ThreadMsg$$$`):
3824 // the last `$`-segment is EMPTY — fall back to the whole simple
3825 // name instead of printing `receiver.new (args)`.
3826 let seg = last.rsplit('$').next().unwrap_or(last);
3827 let last = if seg.is_empty() { last } else { seg };
3828 // Obfuscators name classes after keywords (`v10.new do("")`): the
3829 // qualified-new form prints this simple name raw.
3830 let name = last.to_string();
3831 if is_java_keyword(name.as_str())
3832 || is_restricted_type_name(name.as_str())
3833 || name.chars().next().is_some_and(|c| c.is_ascii_digit())
3834 {
3835 format!("_{name}")
3836 } else if name
3837 .chars()
3838 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
3839 {
3840 name
3841 } else {
3842 // Non-ASCII single chars (Alipay names a field `支`) map to a
3843 // lone `_` — itself reserved since Java 9. Escape it.
3844 let mapped: String = name
3845 .chars()
3846 .map(|c| {
3847 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
3848 c
3849 } else {
3850 '_'
3851 }
3852 })
3853 .collect();
3854 if mapped == "_" {
3855 "__".to_string()
3856 } else {
3857 mapped
3858 }
3859 }
3860}
3861
3862fn push_char_lit(out: &mut String, n: i32) {
3863 let c = (n as u16) as u32;
3864 match char::from_u32(c) {
3865 Some('\'') => out.push_str("'\\''"),
3866 Some('\\') => out.push_str("'\\\\'"),
3867 Some('\n') => out.push_str("'\\n'"),
3868 Some('\r') => out.push_str("'\\r'"),
3869 Some('\t') => out.push_str("'\\t'"),
3870 Some(ch) if ch.is_control() => out.push_str(&format!("'\\u{:04x}'", c)),
3871 Some(ch) => {
3872 out.push('\'');
3873 out.push(ch);
3874 out.push('\'');
3875 }
3876 None => out.push_str(&format!("'\\u{:04x}'", c)),
3877 }
3878}
3879
3880fn const_bool(e: &Expr) -> Option<bool> {
3881 match e {
3882 Expr::Const(ConstVal::Int(n)) => Some(*n != 0),
3883 _ => None,
3884 }
3885}
3886
3887fn pkg_of(n: &str) -> &str {
3888 n.rfind('/').map(|i| &n[..i]).unwrap_or("")
3889}
3890
3891fn prim_name(c: char) -> &'static str {
3892 match c {
3893 'V' => "void",
3894 'Z' => "boolean",
3895 'B' => "byte",
3896 'C' => "char",
3897 'S' => "short",
3898 'I' => "int",
3899 'F' => "float",
3900 'J' => "long",
3901 'D' => "double",
3902 _ => "?",
3903 }
3904}
3905
3906/// Kotlin emits method names like `invokeSuspend$lambda-0` — `-` and any
3907/// other non-identifier character is not legal Java. Deterministic
3908/// mapping; ddc's classdec applies the identical rule at declarations.
3909pub fn java_ident(name: &str) -> std::borrow::Cow<'_, str> {
3910 let clean = name
3911 .chars()
3912 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
3913 let keyword = is_java_keyword(name);
3914 // A simple name may not START with a digit either (nova names fields
3915 // `1x`/`2x`/`3x`): ddc's declaration side has the same branch.
3916 let digit_start = name.chars().next().is_some_and(|c| c.is_ascii_digit());
3917 if clean && !keyword && !digit_start {
3918 // The overwhelmingly common case: borrow, zero allocation (this
3919 // runs per rendered IDENTIFIER).
3920 std::borrow::Cow::Borrowed(name)
3921 } else if keyword || digit_start {
3922 std::borrow::Cow::Owned(format!("_{name}"))
3923 } else {
3924 // Non-ASCII single chars (Alipay names a field `支`) map to a
3925 // lone `_` — itself reserved since Java 9. Escape it.
3926 let mapped: String = name
3927 .chars()
3928 .map(|c| {
3929 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
3930 c
3931 } else {
3932 '_'
3933 }
3934 })
3935 .collect();
3936 if mapped == "_" {
3937 std::borrow::Cow::Owned("__".to_string())
3938 } else {
3939 std::borrow::Cow::Owned(mapped)
3940 }
3941 }
3942}
3943
3944/// Java reserved words (plus the null/true/false literals, and `_` — a
3945/// reserved IDENTIFIER since Java 9: Alipay's instant-run patch system
3946/// names fields `_`, and `javac` rejects it with "underscores are not
3947/// allowed here"): Kotlin emits methods and fields named `default`
3948/// (Companion.default()) — no Java program can name them, so both ddc's
3949/// declarations and these call sites map them to `_<name>` identically
3950/// (which turns `_` into `__`). NOTE: these are MEMBER-level escapes —
3951/// locals/fields/params MAY legally carry the restricted contextual
3952/// type names below (rt.jar has `var` locals), so those live in their
3953/// own predicate.
3954pub fn is_java_keyword(name: &str) -> bool {
3955 matches!(
3956 name,
3957 "abstract" | "assert" | "boolean" | "break" | "byte" | "case" | "catch" | "char"
3958 | "class" | "const" | "continue" | "default" | "do" | "double" | "else" | "enum"
3959 | "extends" | "final" | "finally" | "float" | "for" | "goto" | "if" | "implements"
3960 | "import" | "instanceof" | "int" | "interface" | "long" | "native" | "new"
3961 | "package" | "private" | "protected" | "public" | "return" | "short" | "static"
3962 | "strictfp" | "super" | "switch" | "synchronized" | "this" | "throw" | "throws"
3963 | "transient" | "try" | "void" | "volatile" | "while" | "true" | "false" | "null"
3964 | "_"
3965 )
3966}
3967
3968/// Restricted CONTEXTUAL type names — javac rejects them in TYPE
3969/// declarations and type references (`'var' 是受限类型名称`), but they
3970/// stay legal as member/local names (rt.jar compiles `var` locals).
3971/// Consulted ONLY where a CLASS name renders: type declarations,
3972/// segment sanitizers, the qualified-new simple name.
3973pub fn is_restricted_type_name(name: &str) -> bool {
3974 matches!(
3975 name,
3976 "var" | "yield" | "record" | "sealed" | "permits"
3977 )
3978}
3979
3980/// `this` in its Raw encoding (prints as `this`; see the Raw arm).
3981fn is_this_expr(e: &Expr) -> bool {
3982 matches!(e, Expr::This) || matches!(e, Expr::Raw(t) if t == "\u{3}" || t == "this")
3983}
3984
3985/// Class-file names may contain characters Java source identifiers
3986/// cannot (R8 desugared `Collection$-EL`); deterministic -→_ mapping,
3987/// matching ddc's declaration-site java_ident.
3988/// Legal Java expression-statement forms (JLS 14.8): assignment, method
3989/// invocation, inc/dec, class instance creation.
3990fn is_statement_expr(e: &Expr) -> bool {
3991 matches!(
3992 e,
3993 Expr::Assign { .. }
3994 | Expr::Method { .. }
3995 | Expr::New { .. }
3996 | Expr::PreIncDec { .. }
3997 | Expr::PostIncDec { .. }
3998 )
3999}
4000
4001pub fn sanitize_source_name(name: &str) -> String {
4002 // Obfuscators emit classes named `if`/`do`, PACKAGES named `do`
4003 // (weixin `package do;`) and `..badge.new..` paths (qq), WhatsApp
4004 // nests digit-start simple names (`X/0Xx`), Kotlin mints hyphenated
4005 // synthetic markers (`c$1-IA`, `Collection$-EL`) and R8 hardening
4006 // even names classes in Thai/Yi/Syriac code points (bin.mt.plus —
4007 // ejfkdev/ddc#4). EVERY identifier-unsafe character escapes to
4008 // `_u<hex>` per code point, then identifier-position repairs apply.
4009 // Byte-for-byte the mapping the DECLARATION side uses (ddc
4010 // `classdec::sanitize_fq_seg`): declaration, file name and every
4011 // reference must agree, and the escape must be INJECTIVE — the old
4012 // fold-everything-to-`_` mapping collapsed 22,636 distinct classes
4013 // onto 3 file names.
4014 let segs: Vec<&str> = name.split('.').collect();
4015 let clean = name
4016 .chars()
4017 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$' || c == '.')
4018 && segs.iter().all(|seg| {
4019 let first = seg.chars().next().unwrap_or('a');
4020 !is_java_keyword(seg)
4021 && !is_restricted_type_name(seg)
4022 && !first.is_ascii_digit()
4023 && *seg != "_"
4024 });
4025 if clean {
4026 return name.to_string();
4027 }
4028 segs.iter()
4029 .copied()
4030 .map(sanitize_ident_seg)
4031 .collect::<Vec<_>>()
4032 .join(".")
4033}
4034
4035/// One identifier segment: escape every ident-unsafe character as
4036/// `_u<hex>`, then repair identifier positions (keyword / restricted
4037/// type name / digit start gain a leading `_`; the lone `_` reserved
4038/// since Java 9 becomes `__`). Mirrors ddc `sanitize_fq_seg` exactly.
4039fn sanitize_ident_seg(seg: &str) -> String {
4040 let plain = seg
4041 .chars()
4042 .all(|c| c.is_ascii() && (c.is_ascii_alphanumeric() || c == '_' || c == '$'));
4043 let mut out = if plain {
4044 seg.to_string()
4045 } else {
4046 use std::fmt::Write as _;
4047 let mut s = String::with_capacity(seg.len() + 8);
4048 for c in seg.chars() {
4049 if c.is_ascii() && (c.is_ascii_alphanumeric() || c == '_' || c == '$') {
4050 s.push(c);
4051 } else {
4052 s.push_str("_u");
4053 let _ = write!(s, "{:x}", c as u32);
4054 }
4055 }
4056 s
4057 };
4058 if is_java_keyword(&out)
4059 || is_restricted_type_name(&out)
4060 || out.chars().next().is_some_and(|c| c.is_ascii_digit())
4061 {
4062 out.insert(0, '_');
4063 }
4064 if out == "_" {
4065 out = "__".to_string();
4066 }
4067 out
4068}
4069
4070pub fn escape_string(s: &str) -> String {
4071 let mut out = String::with_capacity(s.len());
4072 for c in s.chars() {
4073 match c {
4074 '"' => out.push_str("\\\""),
4075 '\\' => out.push_str("\\\\"),
4076 '\n' => out.push_str("\\n"),
4077 '\r' => out.push_str("\\r"),
4078 '\t' => out.push_str("\\t"),
4079 '\u{0}' => out.push_str("\\0"),
4080 '\u{8}' => out.push_str("\\b"),
4081 '\u{C}' => out.push_str("\\f"),
4082 c if (c as u32) < 0x20 || (c as u32) == 0x7F => {
4083 out.push_str(&format!("\\u{:04x}", c as u32));
4084 }
4085 c => out.push(c),
4086 }
4087 }
4088 out
4089}
4090
4091pub fn escape_char(c: char) -> String {
4092 match c {
4093 '\'' => "\\'".to_string(),
4094 '\\' => "\\\\".to_string(),
4095 '\n' => "\\n".to_string(),
4096 '\r' => "\\r".to_string(),
4097 '\t' => "\\t".to_string(),
4098 '\0' => "\\0".to_string(),
4099 c if (c as u32) < 0x20 || (c as u32) == 0x7F => format!("\\u{:04x}", c as u32),
4100 c => c.to_string(),
4101 }
4102}
4103
4104pub fn format_float(v: f64, is_float: bool) -> String {
4105 let suffix = if is_float { "f" } else { "" };
4106 // Arithmetic literals, not `Double.NaN`/`POSITIVE_INFINITY` field refs:
4107 // inside java.lang.Double/Float themselves a field reference would be
4108 // a self-reference initializing the very field being printed.
4109 if v.is_nan() {
4110 return if is_float {
4111 "(0.0f / 0.0f)".into()
4112 } else {
4113 "(0.0 / 0.0)".into()
4114 };
4115 }
4116 if v.is_infinite() {
4117 let one = if is_float { "1.0f" } else { "1.0" };
4118 let zero = if is_float { "0.0f" } else { "0.0" };
4119 return if v < 0.0 {
4120 format!("(-{} / {})", one, zero)
4121 } else {
4122 format!("({} / {})", one, zero)
4123 };
4124 }
4125 let mut s = format!("{}", v);
4126 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
4127 s.push_str(".0");
4128 }
4129 // Java doesn't accept exponent forms like "1e-7"? It does: 1e-7 is a valid
4130 // double literal. But "inf"/"nan" handled above.
4131 format!("{}{}", s, suffix)
4132}
4133
4134// ---------------------------------------------------------------------------
4135// Unreachable-code truncation (final statement tree)
4136// ---------------------------------------------------------------------------
4137
4138/// Drop statements that follow a `while (true)` which cannot complete
4139/// normally (no `break` in its body binds to it): javac rejects them as
4140/// 无法访问的语句, and the structurers only emitted them as shared-tail
4141/// copies whose real arrivals all render INSIDE the loop arms (jdk17/26
4142/// Pattern.clazz's for(;;){switch}: case-93's `return prev/negate` tail
4143/// landed after the never-completing loop). Runs on the FINAL tree (after
4144/// switch restoration and goto resolution) so the break inventory is
4145/// exactly what gets emitted; every unknown shape stays conservative
4146/// (assumes the loop can complete).
4147fn tree_has_dead_end(s: &Stmt) -> bool {
4148 match s {
4149 Stmt::Block(v) => {
4150 for (i, x) in v.iter().enumerate() {
4151 if i > 0 && dead_end_infinite_while(&v[i - 1]) {
4152 return true;
4153 }
4154 if tree_has_dead_end(x) {
4155 return true;
4156 }
4157 }
4158 false
4159 }
4160 Stmt::If {
4161 then_stmt,
4162 else_stmt,
4163 ..
4164 } => tree_has_dead_end(then_stmt) || else_stmt.as_deref().is_some_and(tree_has_dead_end),
4165 Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => tree_has_dead_end(body),
4166 Stmt::For { init, body, .. } => {
4167 init.iter().any(tree_has_dead_end) || tree_has_dead_end(body)
4168 }
4169 Stmt::ForEach { body, .. } => tree_has_dead_end(body),
4170 Stmt::Switch { cases, default, .. } => {
4171 cases.iter().any(|c| c.body.iter().any(tree_has_dead_end))
4172 || default.as_deref().is_some_and(tree_has_dead_end)
4173 }
4174 Stmt::Try {
4175 body,
4176 catches,
4177 finally,
4178 } => {
4179 tree_has_dead_end(body)
4180 || catches.iter().any(|c| tree_has_dead_end(&c.body))
4181 || finally.as_deref().is_some_and(tree_has_dead_end)
4182 }
4183 Stmt::TryWithResources {
4184 resources,
4185 body,
4186 catches,
4187 finally,
4188 ..
4189 } => {
4190 resources.iter().any(tree_has_dead_end)
4191 || tree_has_dead_end(body)
4192 || catches.iter().any(|c| tree_has_dead_end(&c.body))
4193 || finally.as_deref().is_some_and(tree_has_dead_end)
4194 }
4195 Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => tree_has_dead_end(body),
4196 _ => false,
4197 }
4198}
4199
4200fn truncate_dead_ends(s: &Stmt) -> Stmt {
4201 match s {
4202 Stmt::Block(v) => {
4203 let mut out: Vec<Stmt> = Vec::with_capacity(v.len());
4204 for x in v {
4205 if !out.is_empty() && dead_end_infinite_while(out.last().unwrap()) {
4206 if crate::dbg_flag!("JCDC_DBG_GOTO") {
4207 eprintln!(
4208 "DEADEND truncate {} unreachable stmt(s)",
4209 v.len() - out.len()
4210 );
4211 }
4212 break;
4213 }
4214 out.push(truncate_dead_ends(x));
4215 }
4216 Stmt::Block(out)
4217 }
4218 Stmt::If {
4219 cond,
4220 then_stmt,
4221 else_stmt,
4222 } => Stmt::If {
4223 cond: cond.clone(),
4224 then_stmt: Box::new(truncate_dead_ends(then_stmt)),
4225 else_stmt: else_stmt.as_deref().map(truncate_dead_ends).map(Box::new),
4226 },
4227 Stmt::While { cond, body } => Stmt::While {
4228 cond: cond.clone(),
4229 body: Box::new(truncate_dead_ends(body)),
4230 },
4231 Stmt::DoWhile { body, cond } => Stmt::DoWhile {
4232 body: Box::new(truncate_dead_ends(body)),
4233 cond: cond.clone(),
4234 },
4235 Stmt::For {
4236 init,
4237 cond,
4238 update,
4239 body,
4240 } => Stmt::For {
4241 init: init.iter().map(truncate_dead_ends).collect(),
4242 cond: cond.clone(),
4243 update: update.clone(),
4244 body: Box::new(truncate_dead_ends(body)),
4245 },
4246 Stmt::ForEach {
4247 var,
4248 iterable,
4249 is_array,
4250 body,
4251 } => Stmt::ForEach {
4252 var: *var,
4253 iterable: iterable.clone(),
4254 is_array: *is_array,
4255 body: Box::new(truncate_dead_ends(body)),
4256 },
4257 Stmt::Switch {
4258 selector,
4259 cases,
4260 default,
4261 on_string,
4262 } => Stmt::Switch {
4263 selector: selector.clone(),
4264 cases: cases
4265 .iter()
4266 .map(|c| CaseGroup {
4267 labels: c.labels.clone(),
4268 string_labels: c.string_labels.clone(),
4269 enum_labels: c.enum_labels.clone(),
4270 raw_labels: c.raw_labels.clone(),
4271 guard: c.guard.clone(),
4272 body: c.body.iter().map(truncate_dead_ends).collect(),
4273 })
4274 .collect(),
4275 default: default.as_deref().map(truncate_dead_ends).map(Box::new),
4276 on_string: *on_string,
4277 },
4278 Stmt::Try {
4279 body,
4280 catches,
4281 finally,
4282 } => Stmt::Try {
4283 body: Box::new(truncate_dead_ends(body)),
4284 catches: catches
4285 .iter()
4286 .map(|c| Catch {
4287 exc: c.exc.clone(),
4288 var: c.var,
4289 var_name: c.var_name.clone(),
4290 body: Box::new(truncate_dead_ends(&c.body)),
4291 })
4292 .collect(),
4293 finally: finally.as_deref().map(truncate_dead_ends).map(Box::new),
4294 },
4295 Stmt::TryWithResources {
4296 resources,
4297 body,
4298 catches,
4299 finally,
4300 } => Stmt::TryWithResources {
4301 resources: resources.iter().map(truncate_dead_ends).collect(),
4302 body: Box::new(truncate_dead_ends(body)),
4303 catches: catches
4304 .iter()
4305 .map(|c| Catch {
4306 exc: c.exc.clone(),
4307 var: c.var,
4308 var_name: c.var_name.clone(),
4309 body: Box::new(truncate_dead_ends(&c.body)),
4310 })
4311 .collect(),
4312 finally: finally.as_deref().map(truncate_dead_ends).map(Box::new),
4313 },
4314 Stmt::Synchronized { lock, body } => Stmt::Synchronized {
4315 lock: lock.clone(),
4316 body: Box::new(truncate_dead_ends(body)),
4317 },
4318 Stmt::Labeled { label, body } => Stmt::Labeled {
4319 label: label.clone(),
4320 body: Box::new(truncate_dead_ends(body)),
4321 },
4322 other => other.clone(),
4323 }
4324}
4325
4326/// True when `s` is a `while (true)` (labeled or not) whose body holds
4327/// no `break` binding to it — it can never complete normally.
4328pub(crate) fn dead_end_infinite_while(s: &Stmt) -> bool {
4329 let (label, body, cond) = match s {
4330 Stmt::While { cond, body } => (None, body.as_ref(), cond),
4331 Stmt::Labeled { label, body } => match &**body {
4332 Stmt::While { cond, body } => (Some(label.as_str()), body.as_ref(), cond),
4333 _ => return false,
4334 },
4335 _ => return false,
4336 };
4337 if !matches!(cond, Expr::Const(ConstVal::Int(1))) {
4338 return false;
4339 }
4340 // JLS 14.14: `while (true)` completes normally iff its body holds a
4341 // reachable break that exits IT: an unlabeled break not captured by
4342 // an inner breakable construct, or `break L` where L is THIS loop's
4343 // own label. A labeled break to any OTHER label — inner (Pattern
4344 // .clazz's `break L4`) or OUTER (jdk11/26 ThreadPoolExecutor
4345 // .addWorker's `break retry` inside the inner for(;;)) — completes
4346 // the while ABRUPTLY: javac's flow model marks the statements after
4347 // such a loop unreachable (the w26 `continue;` after the inner
4348 // while(true) was a latent 无法访问的语句 masked by alphabetically
4349 // earlier census errors).
4350 !has_break_exiting(body, label, 0)
4351}
4352
4353fn has_break_exiting(s: &Stmt, lbl: Option<&str>, depth: usize) -> bool {
4354 match s {
4355 Stmt::Break(None) => depth == 0,
4356 // Only this loop's OWN label completes it; any other labeled
4357 // break exits abruptly (JLS 14.14/14.17).
4358 Stmt::Break(Some(l)) => Some(l.as_str()) == lbl,
4359 Stmt::Continue(_) | Stmt::Return(_) | Stmt::Throw(_) => false,
4360 Stmt::Goto(_) => true,
4361 Stmt::Block(v) => v.iter().any(|x| has_break_exiting(x, lbl, depth)),
4362 Stmt::If {
4363 then_stmt,
4364 else_stmt,
4365 ..
4366 } => {
4367 has_break_exiting(then_stmt, lbl, depth)
4368 || else_stmt
4369 .as_deref()
4370 .map(|e| has_break_exiting(e, lbl, depth))
4371 .unwrap_or(false)
4372 }
4373 Stmt::While { body, .. }
4374 | Stmt::DoWhile { body, .. }
4375 | Stmt::For { body, .. }
4376 | Stmt::ForEach { body, .. } => has_break_exiting(body, lbl, depth + 1),
4377 Stmt::Switch { cases, default, .. } => {
4378 cases
4379 .iter()
4380 .any(|c| c.body.iter().any(|x| has_break_exiting(x, lbl, depth + 1)))
4381 || default
4382 .as_deref()
4383 .map(|d| has_break_exiting(d, lbl, depth + 1))
4384 .unwrap_or(false)
4385 }
4386 Stmt::Try {
4387 body,
4388 catches,
4389 finally,
4390 }
4391 | Stmt::TryWithResources {
4392 body,
4393 catches,
4394 finally,
4395 ..
4396 } => {
4397 has_break_exiting(body, lbl, depth)
4398 || catches
4399 .iter()
4400 .any(|c| has_break_exiting(&c.body, lbl, depth))
4401 || finally
4402 .as_deref()
4403 .map(|f| has_break_exiting(f, lbl, depth))
4404 .unwrap_or(false)
4405 }
4406 Stmt::Synchronized { body, .. } => has_break_exiting(body, lbl, depth),
4407 Stmt::Labeled { body, .. } => has_break_exiting(body, lbl, depth),
4408 // Plain leaf statements never contain a break.
4409 Stmt::ExprStmt(_)
4410 | Stmt::LocalDef { .. }
4411 | Stmt::Assert { .. }
4412 | Stmt::TernaryValue { .. }
4413 | Stmt::MonitorEnter(_)
4414 | Stmt::MonitorExit(_)
4415 | Stmt::Comment(_) => false,
4416 // Opaque shapes (raw labels, inline class decls, anything new):
4417 // keep truncation off when an escape cannot be ruled out.
4418 _ => true,
4419 }
4420}