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