jdc_core/convert.rs
1//! Region tree → Stmt tree conversion.
2//!
3//! Responsibilities:
4//! * weave in per-block statements and terminals (return/throw),
5//! * resolve `Goto{target}` nodes against the enclosing loop/switch scope
6//! stack into `break` / `continue` / labeled goto,
7//! * classify loops into while / do-while / infinite forms,
8//! * assemble try/catch with exception parameter variables.
9
10use crate::fx::{FxHashMap as HashMap, FxHashSet as HashSet};
11
12use crate::cfg::Cfg;
13use crate::ir::build::{BlockResult, Term};
14use crate::ir::expr::{BinOp, ConstVal, Expr, UnOp};
15use crate::ir::stmt::{Catch, Stmt};
16use crate::structure::Region;
17
18/// A named Java statement for `break`/`continue` targets.
19#[derive(Debug)]
20pub enum Jump {
21 Break(Option<String>),
22 Continue(Option<String>),
23 /// Continue of the loop at the given depth-from-innermost, prefixed by
24 /// an emission of the target block's own statements (the jump re-runs
25 /// a shared statement-bearing tail that flows back into the header;
26 /// a bare Continue would skip those statements).
27 ContinueVia(usize, Option<String>),
28 /// Unstructured: emit `label:` + goto as comments (best effort).
29 RawGoto(usize),
30}
31
32#[derive(Debug)]
33struct LoopCtx {
34 header: usize,
35 exits: HashSet<usize>,
36 label: String,
37}
38
39#[derive(Debug)]
40struct SwitchCtx {
41 follow: Option<usize>,
42 label: String,
43 /// Loop-stack depth when the switch was entered: a `break` from a case
44 /// body only needs a label when the jump sits inside a loop that was
45 /// opened WITHIN the switch (an enclosing loop does not intercept it).
46 loops_depth: usize,
47}
48
49pub struct Converter<'a> {
50 pub cfg: &'a Cfg,
51 pub results: &'a Vec<BlockResult>,
52 pub groups: std::borrow::Cow<'a, [crate::structure::TryGroup]>,
53 /// Dominators over the full method graph (for goto fallback decisions).
54 pub dom: std::borrow::Cow<'a, crate::structure::DomInfo>,
55 /// Block whose statements are currently being converted (for postdom
56 /// checks in goto resolution).
57 cur_block: usize,
58 loops: Vec<LoopCtx>,
59 switches: Vec<SwitchCtx>,
60 /// Labels actually referenced by break/continue statements.
61 used_labels: HashSet<String>,
62 /// Follow block of each enclosing If region; a goto to the innermost
63 /// follow is natural structured flow and disappears.
64 if_follows: Vec<usize>,
65 label_counter: usize,
66 /// True while converting the last element of a Seq (a trailing Goto
67 /// there may be inlined as a copy instead of an unemittable jump).
68 goto_is_last: bool,
69 /// The block whose emission IMMEDIATELY follows the current position:
70 /// the innermost enclosing If's follow while converting its arms, or
71 /// the next Seq sibling's head. A RawGoto may elide to fallthrough
72 /// ONLY when its target is this block — the old stack-wide
73 /// `if_follows.contains` check elided jumps to OUTER follows whose
74 /// emission was separated by intervening follow/copy emissions
75 /// (jdk17 DatagramChannelImpl.receive: the b15 fall arm's Goto{SET}
76 /// elided into cur=14's follow emission, skipping
77 /// `sender = sourceSocketAddress()` on the n==0&&isOpen path;
78 /// sun.net.www.protocol.http HttpURLConnection.getInputStream0: the
79 /// clone arms' Goto{addToCache} fell into the post-loop tail — the
80 /// documented addToCache-skip residual).
81 expect_next: std::cell::Cell<Option<usize>>,
82 /// Set while converting a Seq element that DIRECTLY follows a
83 /// CopyStmts of that same block: the back-edge-stub pattern
84 /// [CopyStmts{b}, Goto{b}] already emitted b's statements, so the
85 /// Goto must not inline them again (jdk11 HttpURLConnection
86 /// getInputStream0: the clone tail rendered the clone/removeFromCache
87 /// block twice — the second removeFromCache removed the just-cloned
88 /// entry from the auth cache; a header-adjacent b would also make
89 /// ContinueVia re-emit b's statements after the copy).
90 prev_copy: std::cell::Cell<Option<usize>>,
91 /// Heads of copy-walked shared tails (from the structurer).
92 copied_tails: HashSet<usize>,
93 /// Collected label emissions: block target -> label name (for `Label` stmts).
94 pub pending_labels: HashMap<usize, String>,
95 /// Names of FINAL fields of the class being decompiled. A shared
96 /// terminator block whose statements write one of them must never be
97 /// inlined/copied at extra arrival sites: javac rejects a second
98 /// assignment to a final field (sun.security.util.Debug `hexDigits`).
99 final_fields: HashSet<String>,
100}
101
102/// Remove a trailing `Goto` whose target is the natural continuation.
103fn strip_trailing_goto(s: &mut Stmt, follow: Option<usize>) {
104 let Some(f) = follow else { return };
105 let items = match s {
106 Stmt::Block(v) => v,
107 Stmt::Goto(t) if *t as usize == f => {
108 *s = Stmt::Block(vec![]);
109 return;
110 }
111 _ => return,
112 };
113 // Single descent, no iteration: every arm either strips or stops
114 // (clippy::never_loop was right — this was a while-let in name
115 // only; `if let` states the actual control flow).
116 if let Some(last) = items.last_mut() {
117 match last {
118 Stmt::Goto(t) if *t as usize == f => {
119 items.pop();
120 }
121 Stmt::Block(inner) if !inner.is_empty() => {
122 // descend into the trailing block one level
123 let n = inner.len();
124 if matches!(inner[n - 1], Stmt::Goto(t) if t as usize == f) {
125 inner.pop();
126 }
127 }
128 _ => {}
129 }
130 }
131}
132
133impl<'a> Converter<'a> {
134 pub fn with_copied_tails(mut self, tails: HashSet<usize>) -> Self {
135 self.copied_tails = tails;
136 self
137 }
138
139 pub fn with_final_fields(mut self, finals: HashSet<String>) -> Self {
140 self.final_fields = finals;
141 self
142 }
143
144 /// True when any statement assigns a final field of this class.
145 fn stmts_write_final(&self, v: &[Stmt]) -> bool {
146 v.iter().any(|s| match s {
147 Stmt::ExprStmt(Expr::Assign { target, .. }) => {
148 matches!(&**target, Expr::Field { name, .. } if self.final_fields.contains(name.as_ref()))
149 }
150 _ => false,
151 })
152 }
153
154 /// Chain-aware twin of stmts_write_final for a tail HEAD: walks the
155 /// linear single-succ Fall/Goto chain (bounded) so a blank-final write
156 /// PAST the head still rejects the inline term-copy (URICertStore
157 /// clinit's two-field tail; mirrors structure's
158 /// terminator_writes_final chain probe).
159 fn tail_chain_writes_final(&self, t: usize) -> bool {
160 if self.final_fields.is_empty() {
161 return false;
162 }
163 let mut x = t;
164 let mut seen: HashSet<usize> = HashSet::default();
165 for _ in 0..8 {
166 if !seen.insert(x) {
167 return false;
168 }
169 if self.stmts_write_final(&self.results[x].stmts) {
170 return true;
171 }
172 if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
173 return false;
174 }
175 let succs = &self.cfg.blocks[x].succ;
176 if succs.len() != 1 {
177 return false;
178 }
179 x = succs[0];
180 }
181 false
182 }
183
184 pub fn new(cfg: &'a Cfg, results: &'a Vec<BlockResult>) -> Self {
185 let universe: HashSet<usize> = (0..cfg.blocks.len()).collect();
186 let dom = crate::structure::compute_dominators(cfg, &universe, cfg.entry);
187 let groups = crate::structure::group_exceptions_with(cfg, Some(results));
188 Self::from_parts(
189 cfg,
190 results,
191 std::borrow::Cow::Owned(groups),
192 std::borrow::Cow::Owned(dom),
193 )
194 }
195
196 /// `new` with the group set and dominator tree precomputed and shared
197 /// with the Structurer (see `Structurer::with_precomputed_groups`).
198 pub fn with_precomputed(
199 cfg: &'a Cfg,
200 results: &'a Vec<BlockResult>,
201 groups: Vec<crate::structure::TryGroup>,
202 dom: crate::structure::DomInfo,
203 ) -> Self {
204 Self::from_parts(
205 cfg,
206 results,
207 std::borrow::Cow::Owned(groups),
208 std::borrow::Cow::Owned(dom),
209 )
210 }
211
212 /// `with_precomputed` borrowing the shared groups/dominators instead
213 /// of taking clones: ddc computes both once per method and feeds the
214 /// SAME data to the Structurer and (per retry) the Converter — three
215 /// deep clones (groups twice, idom vec once) per method vanished.
216 pub fn with_precomputed_ref(
217 cfg: &'a Cfg,
218 results: &'a Vec<BlockResult>,
219 groups: &'a [crate::structure::TryGroup],
220 dom: &'a crate::structure::DomInfo,
221 ) -> Self {
222 Self::from_parts(
223 cfg,
224 results,
225 std::borrow::Cow::Borrowed(groups),
226 std::borrow::Cow::Borrowed(dom),
227 )
228 }
229
230 fn from_parts(
231 cfg: &'a Cfg,
232 results: &'a Vec<BlockResult>,
233 groups: std::borrow::Cow<'a, [crate::structure::TryGroup]>,
234 dom: std::borrow::Cow<'a, crate::structure::DomInfo>,
235 ) -> Self {
236 Converter {
237 cfg,
238 results,
239 groups,
240 dom,
241 cur_block: usize::MAX,
242 loops: Vec::new(),
243 switches: Vec::new(),
244 if_follows: Vec::new(),
245 used_labels: HashSet::default(),
246 label_counter: 0,
247 goto_is_last: false,
248 expect_next: std::cell::Cell::new(None),
249 prev_copy: std::cell::Cell::new(None),
250 copied_tails: HashSet::default(),
251 pending_labels: HashMap::default(),
252 final_fields: HashSet::default(),
253 }
254 }
255
256 fn next_label(&mut self) -> String {
257 self.label_counter += 1;
258 format!("L{}", self.label_counter)
259 }
260
261 fn groups_end(&self, gi: usize) -> Option<u32> {
262 self.groups.get(gi).map(|g| g.end)
263 }
264
265 pub fn convert(&mut self, r: Region) -> Stmt {
266 let mut st = self.conv(r);
267 prune_unused_labels(&mut st, &self.used_labels);
268 st
269 }
270
271 fn conv(&mut self, r: Region) -> Stmt {
272 match r {
273 Region::Empty => Stmt::Block(vec![]),
274 Region::Basic { block } => {
275 self.cur_block = block;
276 if crate::dbg_flag!("JCDC_DBG_BLOCKS") {
277 eprintln!(
278 "conv Basic {} ({} stmts)",
279 block,
280 self.results[block].stmts.len()
281 );
282 }
283 self.block_stmts(block)
284 }
285 Region::CopyStmts { block } => {
286 // Copies include the block's terminator when it is a
287 // return/throw (shared terminator blocks are inlined at
288 // every arrival site).
289 let mut v = self.results[block].stmts.clone();
290 match &self.results[block].term {
291 Term::Return(e) => v.push(Stmt::Return(e.clone())),
292 Term::Throw(e) => v.push(Stmt::Throw(e.clone())),
293 _ => {}
294 }
295 if v.len() == 1 {
296 v.into_iter().next().unwrap()
297 } else {
298 Stmt::Block(v)
299 }
300 }
301 Region::Seq(v) => {
302 let mut out = Vec::new();
303 let n = v.len();
304 let heads: Vec<usize> = v.iter().map(crate::structure::region_head_block).collect();
305 let prev_copies: Vec<Option<usize>> = (0..n)
306 .map(|k| {
307 if k > 0 {
308 match &v[k - 1] {
309 Region::CopyStmts { block } => Some(*block),
310 _ => None,
311 }
312 } else {
313 None
314 }
315 })
316 .collect();
317 for (k, x) in v.into_iter().enumerate() {
318 let save = self.goto_is_last;
319 self.goto_is_last = k + 1 == n;
320 let save_pc = self.prev_copy.get();
321 self.prev_copy.set(prev_copies[k]);
322 let save_exp = self.expect_next.get();
323 // Only override when the next emission is KNOWN: a
324 // headless tail (Goto/Empty — a jump out) must keep
325 // the inherited expectation, or a nested arm's Goto
326 // would elide against a phantom (jdk17 dci: Basic17's
327 // MAX head erased the follow expectation and Goto{16}
328 // elided past the SET).
329 if k + 1 < n && heads[k + 1] != usize::MAX {
330 self.expect_next.set(Some(heads[k + 1]));
331 }
332 out.push(self.conv(x));
333 self.expect_next.set(save_exp);
334 self.prev_copy.set(save_pc);
335 self.goto_is_last = save;
336 }
337 Stmt::Block(out)
338 }
339 Region::If {
340 block,
341 cond,
342 then_r,
343 else_r,
344 follow,
345 ternary,
346 } => {
347 self.cur_block = block;
348 let mut head = self.block_stmts_no_term(block);
349 if let Some((_tv, _fv)) = ternary {
350 // Pure value diamond: the folded conditional flows into
351 // the merge block's rebuilt input stack; nothing to emit
352 // here (branch blocks are statement-free by definition).
353 let _ = cond;
354 } else {
355 if let Some(f) = follow {
356 self.if_follows.push(f);
357 }
358 let save_exp = self.expect_next.get();
359 // follow=None keeps the inherited expectation: the
360 // flow leaving this If's arms continues to whatever
361 // follows the If itself.
362 self.expect_next.set(follow.or(save_exp));
363 let then_stmt = self.conv(*then_r);
364 let else_stmt = self.conv(*else_r);
365 self.expect_next.set(save_exp);
366 if follow.is_some() {
367 self.if_follows.pop();
368 }
369 let if_stmt = Stmt::If {
370 cond,
371 then_stmt: Box::new(then_stmt),
372 else_stmt: if else_stmt.is_empty_block() {
373 None
374 } else {
375 Some(Box::new(else_stmt))
376 },
377 };
378 head.push(if_stmt);
379 }
380 if head.len() == 1 {
381 head.pop().unwrap()
382 } else {
383 Stmt::Block(head)
384 }
385 }
386 Region::Loop {
387 header,
388 body,
389 members,
390 exits,
391 } => {
392 let label = self.next_label();
393 self.loops.push(LoopCtx {
394 header,
395 exits: exits.iter().copied().collect(),
396 label: label.clone(),
397 });
398 let body_stmt = self.conv(*body);
399 let ctx = self.loops.pop().unwrap();
400 let _ = members;
401 let mut st = self.classify_loop(header, body_stmt, ctx);
402 if crate::dbg_flag!("JCDC_DBG_LOOP") {
403 let kind = match &st {
404 Stmt::While { .. } => "While".to_string(),
405 Stmt::DoWhile { .. } => "DoWhile".to_string(),
406 Stmt::For { .. } => "For".to_string(),
407 Stmt::Block(b) => format!(
408 "Block{}[{}]",
409 b.len(),
410 b.iter()
411 .map(|x| match x {
412 Stmt::DoWhile { .. } => "DoWhile",
413 Stmt::While { .. } => "While",
414 Stmt::Break(_) => "Break",
415 Stmt::Return(_) => "Return",
416 _ => "_",
417 })
418 .collect::<Vec<_>>()
419 .join(",")
420 ),
421 Stmt::Labeled { .. } => "Labeled".to_string(),
422 _ => "Other".to_string(),
423 };
424 eprintln!("CLASSIFY-OUT header={} -> {}", header, kind);
425 }
426 // ALWAYS wrap; prune_unused_labels (post-convert) strips
427 // the wrapper when no break/continue ended up using it.
428 // Deciding at region-close missed breaks born in copied
429 // regions AFTER the loop popped (results[..].stmts
430 // clones / tail copies) — orphan `break L5` with no `L5:`
431 // (weixin 未定义的标签 ×687).
432 st = Stmt::Labeled {
433 label,
434 body: Box::new(st),
435 };
436 st
437 }
438 Region::Switch {
439 block,
440 selector,
441 cases,
442 default,
443 follow,
444 } => {
445 self.cur_block = block;
446 let sw_label = self.next_label();
447 self.switches.push(SwitchCtx {
448 follow,
449 label: sw_label.clone(),
450 loops_depth: self.loops.len(),
451 });
452 let mut case_groups = Vec::new();
453 for (vals, r) in cases {
454 let s = self.conv(r);
455 case_groups.push(crate::ir::stmt::CaseGroup {
456 labels: vals,
457 string_labels: vec![],
458 enum_labels: vec![],
459 raw_labels: vec![],
460 guard: None,
461 body: stmt_to_vec(s),
462 });
463 }
464 let default_stmt = default.map(|d| Box::new(self.conv(*d)));
465 self.switches.pop();
466 let mut head = self.block_stmts_no_term(block);
467 let mut sw = Stmt::Switch {
468 selector,
469 cases: case_groups,
470 default: default_stmt,
471 on_string: false,
472 };
473 {
474 sw = Stmt::Labeled {
475 label: sw_label,
476 body: Box::new(sw),
477 };
478 }
479 head.push(sw);
480 if head.len() == 1 {
481 head.pop().unwrap()
482 } else {
483 Stmt::Block(head)
484 }
485 }
486 Region::Try {
487 group_idx,
488 body,
489 catches,
490 } => {
491 let mut body_stmt = self.conv(*body);
492 // Natural exits of the try body / handlers jump to the block
493 // right after the try span; that is structured fallthrough.
494 let gend = self.groups_end(group_idx);
495 let try_follow = gend.and_then(|e| self.cfg.block_at(e));
496 strip_trailing_goto(&mut body_stmt, try_follow);
497 let mut catch_stmts = Vec::new();
498 for (tys, _hblock, r) in catches {
499 let mut s = self.conv(*r);
500 strip_trailing_goto(&mut s, try_follow);
501 catch_stmts.push(Catch {
502 exc: tys,
503 var: u32::MAX, // assigned by the method pipeline (handler store)
504 var_name: None,
505 body: Box::new(s),
506 });
507 }
508 Stmt::Try {
509 body: Box::new(body_stmt),
510 catches: catch_stmts,
511 finally: None,
512 }
513 }
514 Region::Goto { target } => {
515 if crate::dbg_flag!("JCDC_DBG_GOTO") {
516 eprintln!(
517 "conv Goto target={} cur_block={} loops={:?} switches={} if_follows={:?}",
518 target,
519 self.cur_block,
520 self.loops
521 .iter()
522 .map(|l| (l.header, l.exits.len()))
523 .collect::<Vec<_>>(),
524 self.switches.len(),
525 self.if_follows
526 );
527 }
528 // A jump to an enclosing if's follow that TERMINATES
529 // (return/throw) can be inlined: the copy ends this path
530 // exactly like the jump would, without needing a label.
531 // Same for a jump to a LOOP EXIT that heads a pure
532 // terminator chain (stmt-bearing fall-through links
533 // ending in return/throw): resolve_goto's loop-exit arm
534 // would turn it into a bare `break`, silently dropping
535 // the chain's statements between the loop end and the
536 // shared return (jdk26 Bits.reserveMemory phase-1:
537 // `if (interrupted) <goto interrupt-block>; ` rendered
538 // as else-break and the Thread.currentThread()
539 // .interrupt() vanished — interruption lost). The
540 // inlined copy keeps the abrupt ending (no fall-out
541 // duplication); a genuine fall-out exit still breaks.
542 //
543 let inline_terminator = self.if_follows.contains(&target)
544 && matches!(self.results[target].term, Term::Return(_) | Term::Throw(_))
545 && !self.tail_chain_writes_final(target);
546 if inline_terminator {
547 // Copy the WHOLE terminator chain: the target's own
548 // statements plus every fall-through link down to
549 // the final return/throw (a single-block chain is
550 // the original if-follow case).
551 let mut v: Vec<Stmt> = Vec::new();
552 let mut t = target;
553 let mut guard = 0;
554 loop {
555 v.extend(self.results[t].stmts.clone());
556 match &self.results[t].term {
557 Term::Return(e) => {
558 v.push(Stmt::Return(e.clone()));
559 break;
560 }
561 Term::Throw(e) => {
562 v.push(Stmt::Throw(e.clone()));
563 break;
564 }
565 Term::Fallthrough | Term::Goto
566 if self.cfg.blocks[t].succ.len() == 1 && guard < 8 =>
567 {
568 t = self.cfg.blocks[t].succ[0];
569 guard += 1;
570 }
571 _ => break,
572 }
573 }
574 return if v.len() == 1 {
575 v.into_iter().next().unwrap()
576 } else {
577 Stmt::Block(v)
578 };
579 }
580 if let Some(j) = self.resolve_goto(target) {
581 match j {
582 Jump::Break(lbl) => {
583 if let Some(l) = &lbl {
584 self.used_labels.insert(l.clone());
585 }
586 Stmt::Break(lbl)
587 }
588 Jump::Continue(lbl) => {
589 if let Some(l) = &lbl {
590 self.used_labels.insert(l.clone());
591 }
592 Stmt::Continue(lbl)
593 }
594 Jump::ContinueVia(t, lbl) => {
595 if let Some(l) = &lbl {
596 self.used_labels.insert(l.clone());
597 }
598 let mut cv = if self.prev_copy.get() == Some(t) {
599 // Statements already emitted by the
600 // immediately preceding CopyStmts.
601 Vec::new()
602 } else {
603 self.results[t].stmts.clone()
604 };
605 cv.push(Stmt::Continue(lbl));
606 if cv.len() == 1 {
607 cv.into_iter().next().unwrap()
608 } else {
609 Stmt::Block(cv)
610 }
611 }
612 Jump::RawGoto(t) => {
613 if crate::dbg_flag!("JCDC_DBG_GOTO") {
614 eprintln!(
615 "RAWGOTO t={} copied_tails={:?} if_follows={:?} last={}",
616 t, self.copied_tails, self.if_follows, self.goto_is_last
617 );
618 }
619 // A Goto region is always the last part of its
620 // walk, so the jump either falls through to the
621 // enclosing merge or must be inlined.
622 let term_copy =
623 matches!(self.results[t].term, Term::Return(_) | Term::Throw(_))
624 && !self.tail_chain_writes_final(t)
625 && !self.is_retry_loop_header(t);
626 if term_copy {
627 let mut cv = self.results[t].stmts.clone();
628 match &self.results[t].term {
629 Term::Return(e) => cv.push(Stmt::Return(e.clone())),
630 Term::Throw(e) => cv.push(Stmt::Throw(e.clone())),
631 _ => {}
632 }
633 if cv.len() == 1 {
634 cv.into_iter().next().unwrap()
635 } else {
636 Stmt::Block(cv)
637 }
638 } else if self
639 .loops
640 .iter()
641 .any(|l| l.exits.contains(&t) || l.header == t)
642 && (self.goto_is_last || self.if_follows.contains(&t))
643 {
644 // The target is an ENCLOSING LOOP's exit:
645 // inside a loop body, falling out of an if
646 // continues the LOOP — the if-follow
647 // elision would silently drop the jump
648 // (jdk11 ObjectInputStream.readSerialData's
649 // finally-copy retry loop lost its `break`,
650 // the finally's while(true) never completed
651 // normally, and the tail went unreachable —
652 // 无法访问的语句 x2 trees). Materialize the
653 // break (innermost enclosing loop).
654 Stmt::Break(None)
655 } else if !matches!(
656 self.results[t].term,
657 Term::Return(_) | Term::Throw(_)
658 ) && !self.results[t].stmts.is_empty()
659 && !self.copied_tails.contains(&t)
660 && self.prev_copy.get() != Some(t)
661 && self
662 .expect_next
663 .get()
664 .map(|nx| {
665 // The next emission is a copy that
666 // STARTS PAST t (at a successor of t
667 // already in copied_tails): eliding
668 // would silently skip t's own
669 // statements, and no emitted copy
670 // covers them. (sj17 receive:
671 // Goto{16=SET}, expect=Some(17),
672 // 17 copied, 16 not.) If t itself is
673 // in copied_tails, an emitted full
674 // copy already includes t's stmts —
675 // inlining would double-emit (w26
676 // receive: Goto{14}, 14 copied).
677 let mut x = t;
678 for _ in 0..8 {
679 if x == nx {
680 return x != t;
681 }
682 if self.copied_tails.contains(&x) {
683 return false;
684 }
685 match self.cfg.blocks[x].succ.first() {
686 Some(&n) => x = n,
687 None => return false,
688 }
689 }
690 false
691 })
692 .unwrap_or(false)
693 && (self
694 .if_follows
695 .iter()
696 .any(|f| self.cfg.blocks[t].succ.contains(f)))
697 {
698 // Statement-bearing target FIRST: eliding
699 // (goto_is_last / expect / copy-tail) would
700 // silently skip t's own statements when the
701 // next emission is a copy of t's SUCCESSOR
702 // rather than t itself (jdk17
703 // DatagramChannelImpl.receive: Goto{16=
704 // `sender = sourceSocketAddress()`} elided
705 // via reaches_copy_tail(16->17) into the
706 // arm's Basic(17) copy — the n==0&&isOpen
707 // path returned a null sender). The inline
708 // below emits t's statements and then falls
709 // through to the same continuation.
710 let cv = self.results[t].stmts.clone();
711 if cv.len() == 1 {
712 cv.into_iter().next().unwrap()
713 } else {
714 Stmt::Block(cv)
715 }
716 } else if self.goto_is_last
717 || self.expect_next.get() == Some(t)
718 || self.reaches_copy_tail(t)
719 {
720 // Natural fallthrough reaches the same
721 // continuation the jump targeted.
722 Stmt::Block(vec![])
723 } else if !matches!(
724 self.results[t].term,
725 Term::Return(_) | Term::Throw(_)
726 ) && self.prev_copy.get() != Some(t)
727 && (self
728 .if_follows
729 .iter()
730 .any(|f| self.cfg.blocks[t].succ.contains(f)))
731 {
732 // The jump re-enters an already-structured
733 // block whose flow ends at an enclosing
734 // if-follow: inline its statements; the
735 // natural continuation matches the jump's.
736 let cv = self.results[t].stmts.clone();
737 if cv.len() == 1 {
738 cv.into_iter().next().unwrap()
739 } else {
740 Stmt::Block(cv)
741 }
742 } else {
743 Stmt::Goto(t as u32)
744 }
745 }
746 }
747 } else {
748 Stmt::Block(vec![])
749 }
750 }
751 }
752 }
753
754 /// Statements of a block including its terminal (return/throw), but NOT
755 /// the structural terminals (cond/switch/goto — handled by regions).
756 fn block_stmts(&mut self, block: usize) -> Stmt {
757 let mut v = self.block_stmts_no_term(block);
758 match &self.results[block].term {
759 Term::Return(e) => v.push(Stmt::Return(e.clone())),
760 Term::Throw(e) => v.push(Stmt::Throw(e.clone())),
761 Term::Jsr | Term::Ret => v.push(Stmt::Comment("// jsr/ret not supported".into())),
762 _ => {}
763 }
764 if v.len() == 1 {
765 v.pop().unwrap()
766 } else {
767 Stmt::Block(v)
768 }
769 }
770
771 fn block_stmts_no_term(&self, block: usize) -> Vec<Stmt> {
772 // Monitor markers are kept; synchronized reconstruction happens later.
773 self.results[block].stmts.clone()
774 }
775
776 /// Resolve a goto target against the scope stack.
777 fn resolve_goto(&mut self, target: usize) -> Option<Jump> {
778 // continue: innermost loop whose header == target
779 if let Some(i) = (0..self.loops.len())
780 .rev()
781 .find(|&i| self.loops[i].header == target)
782 {
783 let depth = self.loops.len() - 1 - i;
784 return Some(if depth == 0 {
785 Jump::Continue(None)
786 } else {
787 Jump::Continue(Some(self.loops[i].label.clone()))
788 });
789 }
790 // break out of a loop whose exit set contains the target; pick the
791 // OUTERMOST such loop — an exit of nested loops (e.g. `break outer`)
792 // belongs to every enclosing loop's exit set, and breaking only the
793 // innermost would wrongly re-enter the outer loop.
794 if let Some(li) = (0..self.loops.len()).find(|&i| self.loops[i].exits.contains(&target)) {
795 // RESTART-STUB exit: the target chains through statement-free
796 // stubs back into an enclosing-or-self loop HEADER — the jump
797 // re-iterates THAT loop, it does not break out of anything
798 // (jdk11/17/26 ConcurrentLinkedQueue.poll's p==q arm `goto
799 // restartFromHead`: exit 12 is the `goto 0` stub; rendering
800 // break+break fell to the post-loop tail and returned the
801 // STALE item instead of restarting from head). The broad
802 // walk-side continue-stub precedence was reverted once
803 // (Pattern.clazz: switch-case goto stubs must stay
804 // RawGoto-elided so the emitter re-appends case breaks) —
805 // this form is scoped to genuine loop exits with no open
806 // switch on the stack.
807 if self.switches.is_empty() {
808 let mut x = target;
809 let mut guard = 0;
810 let header_hit = loop {
811 if let Some(hi) = self.loops.iter().position(|l| l.header == x) {
812 if hi <= li {
813 break Some(hi);
814 }
815 }
816 if guard >= 8 {
817 break None;
818 }
819 let r = &self.results[x];
820 let stub = r.stmts.is_empty()
821 && r.out_stack.is_empty()
822 && matches!(r.term, Term::Fallthrough | Term::Goto)
823 && self.cfg.blocks[x].succ.len() == 1;
824 if !stub {
825 break None;
826 }
827 x = self.cfg.blocks[x].succ[0];
828 guard += 1;
829 };
830 if let Some(hi) = header_hit {
831 let depth = self.loops.len() - 1 - hi;
832 return Some(if depth == 0 {
833 Jump::Continue(None)
834 } else {
835 Jump::Continue(Some(self.loops[hi].label.clone()))
836 });
837 }
838 }
839 // switches opened after that loop intercept a plain `break`
840 let crosses_switch = !self.switches.is_empty();
841 return Some(if li + 1 == self.loops.len() && !crosses_switch {
842 Jump::Break(None)
843 } else {
844 Jump::Break(Some(self.loops[li].label.clone()))
845 });
846 }
847 if let Some(si) = (0..self.switches.len())
848 .rev()
849 .find(|&i| self.switches[i].follow == Some(target))
850 {
851 return Some(
852 if si + 1 == self.switches.len()
853 && self.loops.len() == self.switches[si].loops_depth
854 {
855 Jump::Break(None)
856 } else {
857 Jump::Break(Some(self.switches[si].label.clone()))
858 },
859 );
860 }
861 // Natural flow to the enclosing if's merge point: no statement.
862 // Only the INNERMOST open follow qualifies — eliding a jump to an
863 // outer follow would silently skip the statements between (e.g. a
864 // shared throw at the end of an else-branch).
865 if self.if_follows.last() == Some(&target) {
866 return None;
867 }
868 // Inside a loop, a jump to an already-structured block that flows
869 // back into the loop is a CONTINUE of the innermost such loop: the
870 // target's statements were emitted earlier in this body, and the
871 // natural continuation after this region is the loop bottom (an
872 // if-follow or a fall-out exit), never the target. Eliding it as
873 // fallthrough (the RawGoto `goto_is_last` arm) silently dropped the
874 // iteration: jdk11/17 InstantPrinterParser.format's second loop
875 // copy emitted `if (i >= fractionalDigits) {}` with no way out --
876 // the digit append + back edge vanished (semantically an infinite
877 // loop; javac: 缺少返回语句 on the method tail).
878 // A target that is a loop EXIT keeps its RawGoto resolution (the
879 // conv arm materializes the break): Pattern.clazz's switch-case
880 // `break` targets sit on the loop-bottom path AND name exits —
881 // continuing there made every switch exit abrupt and the post-
882 // switch tail unreachable (无法访问的语句 x3 trees).
883 let is_any_exit = self.loops.iter().any(|l| l.exits.contains(&target));
884 if !is_any_exit && self.switches.is_empty() {
885 // STUB-TARGET continue: a statement-free confluence stub that
886 // can reach an enclosing header re-iterates that loop (its own
887 // flow was emitted earlier in this body copy; IPP's can_reach
888 // targets t=43/18/96/89/114/21/7 are all stubs).
889 // HEADER-ADJACENT statement-bearing target: the jump re-runs a
890 // shared tail whose statements were NOT emitted on this path —
891 // inline them and continue (jdk11 BigInteger.nextProbablePrime
892 // sieve arms: Goto{result=result.add(TWO)} flowing straight to
893 // the header; a bare Continue skipped the advance and left
894 // while(true) as the method's only completion: 缺少返回语句).
895 // Statement-bearing targets with a BRANCHING or statement
896 // bearing chain to the header keep RawGoto resolution (huc
897 // getInputStream0's clone/getValue tails flow through shared
898 // forward code — continuing there would skip it).
899 let mut via_label: Option<Option<String>> = None;
900 if self.results[target].stmts.is_empty() {
901 if let Some(i) = (0..self.loops.len()).rev().find(|&i| {
902 crate::structure::can_reach_cfg(self.cfg, target, self.loops[i].header, 4096)
903 }) {
904 let depth = self.loops.len() - 1 - i;
905 via_label = Some(if depth == 0 {
906 None
907 } else {
908 Some(self.loops[i].label.clone())
909 });
910 }
911 } else {
912 let mut x = target;
913 for _ in 0..8 {
914 if let Some(hi) = self.loops.iter().position(|l| l.header == x) {
915 let depth = self.loops.len() - 1 - hi;
916 via_label = Some(if depth == 0 {
917 None
918 } else {
919 Some(self.loops[hi].label.clone())
920 });
921 break;
922 }
923 if !self.results[x].stmts.is_empty() && x != target {
924 break;
925 }
926 if self.cfg.blocks[x].succ.len() != 1 {
927 break;
928 }
929 x = self.cfg.blocks[x].succ[0];
930 }
931 }
932 if let Some(lbl) = via_label {
933 return Some(if self.results[target].stmts.is_empty() {
934 Jump::Continue(lbl)
935 } else {
936 Jump::ContinueVia(target, lbl)
937 });
938 }
939 }
940 // Inside a loop, a forward escape that cannot reach back into the
941 // loop is a break of the innermost loop.
942 if !self.loops.is_empty()
943 && !crate::structure::can_reach_cfg(
944 self.cfg,
945 target,
946 self.loops[self.loops.len() - 1].header,
947 4096,
948 )
949 {
950 return Some(Jump::Break(None));
951 }
952 // Unstructured jump.
953 Some(Jump::RawGoto(target))
954 }
955
956 /// True if `a` post-dominates `b` (every path from b exits through a).
957 /// True when a jump to `t` is equivalent to natural fallthrough: `t`
958 /// (or the end of a chain of statement-free blocks starting at `t`) is
959 /// a copied shared tail or an open if-follow that the enclosing flow
960 /// will emit next anyway.
961 /// True when `t` is the head of a javac retry loop: some exception
962 /// handler protecting `t` has no normal preds and flows straight back
963 /// to `t` (`catch (InterruptedException e) { ..; goto head }`). A Goto
964 /// to such a header re-executes the protected body — inlining the
965 /// body's terminator as a "shared tail copy" drops it out of its try
966 /// (jdk26 Future.exceptionNow: the IE catch got `get(); throw ISE`
967 /// unprotected — 未报告的异常错误 InterruptedException).
968 fn is_retry_loop_header(&self, t: usize) -> bool {
969 self.cfg.exc_edges.iter().any(|e| {
970 e.from == t
971 && e.to != t
972 && self.cfg.blocks[e.to].pred.is_empty()
973 && self.cfg.blocks[e.to].succ.len() == 1
974 && self.cfg.blocks[e.to].succ[0] == t
975 })
976 }
977
978 fn reaches_copy_tail(&self, t: usize) -> bool {
979 let mut x = t;
980 for _ in 0..8 {
981 if self.copied_tails.contains(&x) || self.if_follows.contains(&x) {
982 return true;
983 }
984 if !self.results[x].stmts.is_empty() {
985 return false;
986 }
987 match &self.results[x].term {
988 Term::Fallthrough | Term::Goto => match self.cfg.blocks[x].succ.first() {
989 Some(&n) => x = n,
990 None => return false,
991 },
992 _ => return false,
993 }
994 }
995 false
996 }
997
998 #[allow(dead_code)]
999 fn postdominates(&self, a: usize, b: usize, universe: &HashSet<usize>) -> bool {
1000 // BFS from b avoiding a; if no exit block is reachable, a post-dominates.
1001 let mut seen = HashSet::default();
1002 let mut q = std::collections::VecDeque::new();
1003 q.push_back(b);
1004 seen.insert(b);
1005 while let Some(x) = q.pop_front() {
1006 for &s in &self.cfg.blocks[x].succ {
1007 if s == a {
1008 continue;
1009 }
1010 if !universe.contains(&s) {
1011 continue;
1012 }
1013 if seen.insert(s) {
1014 q.push_back(s);
1015 }
1016 }
1017 if self.cfg.blocks[x].succ.is_empty() && x != a && x != b {
1018 // reached an exit without passing through a
1019 return false;
1020 }
1021 }
1022 // also treat blocks leaving the universe as exits
1023 true
1024 }
1025
1026 /// Classify a loop region into while / do-while / infinite and assemble
1027 /// the final statement.
1028 fn classify_loop(&mut self, header: usize, body_stmt: Stmt, ctx: LoopCtx) -> Stmt {
1029 let out = self.classify_loop_impl(header, body_stmt, ctx);
1030 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1031 fn shape(s: &Stmt) -> String {
1032 fn short(e: &Expr) -> String {
1033 format!("{:?}", e).chars().take(90).collect()
1034 }
1035 match s {
1036 Stmt::While { cond, body } => {
1037 format!("While({}) body={}", short(cond), shape(body))
1038 }
1039 Stmt::DoWhile { cond, .. } => format!("DoWhile({})", short(cond)),
1040 Stmt::For { cond, .. } => {
1041 format!("For({})", cond.as_ref().map(short).unwrap_or_default())
1042 }
1043 Stmt::Block(v) => format!(
1044 "Block[{}]",
1045 v.iter()
1046 .take(4)
1047 .map(|x| match x {
1048 Stmt::ExprStmt(_) => "expr".to_string(),
1049 Stmt::LocalDef { .. } => "def".to_string(),
1050 Stmt::If { cond, .. } => format!("if({})", short(cond)),
1051 Stmt::While { cond, .. } => format!("while({})", short(cond)),
1052 Stmt::DoWhile { .. } => "do".to_string(),
1053 Stmt::Break(_) => "break".to_string(),
1054 Stmt::Continue(l) => format!("cont{:?}", l),
1055 Stmt::Return(_) => "ret".to_string(),
1056 Stmt::Throw(_) => "throw".to_string(),
1057 Stmt::Try { .. } => "try".to_string(),
1058 _ => "…".to_string(),
1059 })
1060 .collect::<Vec<_>>()
1061 .join(",")
1062 ),
1063 _ => "…".to_string(),
1064 }
1065 }
1066 eprintln!("CLASSIFY-OUT header={} {}", header, shape(&out));
1067 }
1068 out
1069 }
1070
1071 fn classify_loop_impl(&mut self, header: usize, body_stmt: Stmt, ctx: LoopCtx) -> Stmt {
1072 let term = self.results[header].term.clone();
1073 let succs = self.cfg.blocks[header].succ.clone();
1074
1075 match term {
1076 Term::Cond { cond } if succs.len() == 2 => {
1077 let taken = succs[1];
1078 let fall = succs[0];
1079 if taken == header && ctx.exits.contains(&fall) {
1080 // Self-loop: the header block holds both the body and
1081 // the trailing conditional back edge → do-while. The
1082 // fallthrough side is the loop exit. (When the fall
1083 // side is INTERIOR the self-edge is an in-body retry
1084 // jump of a loop with no normal completion at all —
1085 // rotating it into a do-while bottom invents an exit
1086 // path: jdk11/17/26 DSAParameterGenerator.generatePandQ's
1087 // `if (!resultQ.isProbablePrime(..)) goto <Q-gen>`
1088 // self-retry falls through into the P-search interior;
1089 // the fabricated do-while fell out to the method end —
1090 // 缺少返回语句. Those shapes take the interior-diamond
1091 // while(true) arm below.)
1092 // A compound (multi-test) trailing condition folds first:
1093 // `if(c1)continue; .. if(cN)continue; else exit;` becomes
1094 // `do{body}while(c1||..||cN); exit`.
1095 if let Some((stmts, c, exit)) = extract_compound_do_while(&body_stmt) {
1096 // An extracted exit that is the loop's OWN unlabeled
1097 // break must be dropped: the do-while condition false
1098 // already exits, and re-emitting the break OUTSIDE the
1099 // loop is a stray `break;` (compile error). SESE's
1100 // explicit `Goto{header}` -> continue makes self-loop
1101 // bodies hit this shape (`if (c) continue; else break;`).
1102 // return/throw/labeled-break exits stay (real fall-out).
1103 if matches!(exit, Stmt::Break(None)) {
1104 return Stmt::DoWhile {
1105 body: Box::new(Stmt::Block(stmts)),
1106 cond: c,
1107 };
1108 }
1109 return Stmt::Block(vec![
1110 Stmt::DoWhile {
1111 body: Box::new(Stmt::Block(stmts)),
1112 cond: c,
1113 },
1114 exit,
1115 ]);
1116 }
1117 let (stmts, c) = match extract_trailing_do_while(&body_stmt) {
1118 Some(x) => x,
1119 None => (stmt_to_vec(body_stmt), cond.clone()),
1120 };
1121 return Stmt::DoWhile {
1122 body: Box::new(Stmt::Block(stmts)),
1123 cond: c,
1124 };
1125 }
1126 if fall == header && ctx.exits.contains(&taken) {
1127 // Inverted self-loop: `while (!(c)) body` — the taken
1128 // side exits, the fallthrough loops back.
1129 let inner2 = strip_trailing_continue(body_stmt);
1130 // Stale-phi guard: the body's FIRST statement
1131 // recomputes the header's condition local (`X =
1132 // call(); if (X) {body}` — weixin DatabaseUtils'
1133 // allocRow cursor loop, hu4/k iterator). `while (X)`
1134 // would read X's PRE-loop value on the first test
1135 // (可能尚未初始化变量 / stale register), and a plain
1136 // do-while bottom test could read a register the
1137 // body interior reused. Inject the else-break and
1138 // rotate to do-while(true): the exit test then runs
1139 // exactly where the bytecode has it — after the
1140 // compute, before the body interior.
1141 if cond_var_reassigned_first(&inner2, &cond) {
1142 match try_protected_dowhile(inner2, &cond) {
1143 Ok(dw) => return dw,
1144 Err(inner) => {
1145 return Stmt::While {
1146 cond: negate(cond.clone()),
1147 body: Box::new(inner),
1148 };
1149 }
1150 }
1151 }
1152 return Stmt::While {
1153 cond: negate(cond.clone()),
1154 body: Box::new(inner2),
1155 };
1156 }
1157 let taken_is_exit = ctx.exits.contains(&taken);
1158 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1159 eprintln!(
1160 "CLASSIFY2 header={} taken={} fall={} exits={:?}",
1161 header, taken, fall, ctx.exits
1162 );
1163 }
1164 if !taken_is_exit && !ctx.exits.contains(&fall) {
1165 // Multi-block loop condition. The header's fallthrough
1166 // may be a pure condition-continuation test block whose
1167 // taken side is the loop exit and whose fall side rejoins
1168 // the body (`taken`). javac compiles `while (cH || !cB)`
1169 // exactly this way:
1170 // H: if cH goto body; B: if cB goto exit; body: ..; goto H
1171 // Fold B into the while condition. Without this the body
1172 // walk re-emits H and B as empty `if`s inside a
1173 // `while(true)`, dropping the exit and leaving an infinite
1174 // loop plus an unreachable trailing statement.
1175 if self.results[fall].stmts.is_empty() {
1176 if let Term::Cond { cond: cb } = &self.results[fall].term {
1177 let bs = self.cfg.blocks[fall].succ.clone();
1178 if bs.len() == 2 && ctx.exits.contains(&bs[1]) && bs[0] == taken {
1179 let combined = Expr::Bin {
1180 op: BinOp::LogOr,
1181 l: Box::new(cond.clone()),
1182 r: Box::new(negate(cb.clone())),
1183 ty: None,
1184 };
1185 // The body is the converted region
1186 // remainder, NOT the taken block's raw
1187 // statements: when `taken` is wrapped in
1188 // regions (a try/catch group around the
1189 // body call), the raw-stmt body silently
1190 // drops them (jdk11 SocketChannelImpl
1191 // .implCloseSelectableChannel: the
1192 // wait()-loop's try/catch(Interrupted
1193 // Exception) region vanished — bare
1194 // wait() — 未报告的异常错误). The
1195 // leading guard If is the condition being
1196 // folded here; strip it and keep the rest.
1197 let mut parts = stmt_to_vec(body_stmt);
1198 if matches!(parts.first(), Some(Stmt::If { .. })) {
1199 parts.remove(0);
1200 }
1201 let body = if parts.is_empty() {
1202 Stmt::Block(self.results[taken].stmts.clone())
1203 } else {
1204 let rest = if parts.len() == 1 {
1205 parts.pop().unwrap()
1206 } else {
1207 Stmt::Block(parts)
1208 };
1209 strip_trailing_continue(rest)
1210 };
1211 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1212 eprintln!(
1213 "CLASSIFY-CONDCHAIN header={} B={} body={} exits={:?}",
1214 header, fall, taken, ctx.exits
1215 );
1216 }
1217 return Stmt::While {
1218 cond: combined,
1219 body: Box::new(body),
1220 };
1221 }
1222 }
1223 }
1224 // Neither successor leaves the loop: the header's Cond
1225 // is an interior diamond (e.g. a ternary inside an
1226 // infinite `for(;;)` whose back edge targets the header
1227 // top), not an exit test. Keep it in the body.
1228 return Stmt::While {
1229 cond: Expr::Const(ConstVal::Int(1)),
1230 body: Box::new(body_stmt),
1231 };
1232 }
1233 // The body walk re-emitted the header's own If region; unwrap
1234 // it so the loop reads `while (C) { else-part }`. `stripped`
1235 // is false when the test was NOT the body's first statement
1236 // (the header COMPUTES its condition: `v = it.hasNext();
1237 // if-eqz v`) — the re-emission then stays in the body.
1238 let (inner, exit_stmts, stripped) = split_leading_if(&body_stmt, &cond);
1239 // A top test the structurer wrapped in a TRY (the header
1240 // block sits inside its handler's protected range — jdk26
1241 // Bits.reserveMemory's backoff retry loop tests
1242 // tryReserveOrClean(..) throws InterruptedException under
1243 // its own catch(IE) retry handler): rotating the test into
1244 // `while (C)` evaluates the call OUTSIDE every handler —
1245 // 未报告的异常错误InterruptedException. split_leading_if
1246 // deliberately cannot reach through the Try (the rotated
1247 // cond would still be unprotected), so keep the test where
1248 // the bytecode has it and stay on while(true). The earlier
1249 // implCloseSelectableChannel guard (raw-stmt bodies losing
1250 // the leading If's statements) does not apply: the If lives
1251 // intact inside the Try.
1252
1253 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1254 eprintln!(
1255 "CLASSIFY header={} taken_is_exit={} exit_stmts={}",
1256 header,
1257 taken_is_exit,
1258 exit_stmts.len()
1259 );
1260 }
1261 let inner = strip_trailing_continue(inner);
1262 let while_cond = if taken_is_exit {
1263 negate(cond.clone())
1264 } else {
1265 cond.clone()
1266 };
1267 let plain_exit = exit_stmts.is_empty()
1268 || matches!(exit_stmts.as_slice(), [Stmt::Break(_)])
1269 || matches!(exit_stmts.as_slice(), [Stmt::Block(b)] if b.is_empty());
1270 if taken_is_exit && plain_exit {
1271 match try_protected_dowhile(inner, &cond) {
1272 Ok(dw) => dw,
1273 Err(inner) => {
1274 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1275 eprintln!(
1276 "STALEDBG arm=taken_plain header={} stripped={} leading={} cond={:?}",
1277 header,
1278 stripped,
1279 leading_test_present(&inner, &cond, true),
1280 cond
1281 );
1282 }
1283 // Strip failed and the header's own test-and-
1284 // break is still inside the body: the rotated
1285 // cond would re-read the condition local's
1286 // STALE pre-loop value — `while (v) {
1287 // v = it.hasNext(); ... }` is a definite-
1288 // assignment error (可能尚未初始化变量, weixin
1289 // ×14k files), and even a pre-initialized v
1290 // tests the stale register on entry. The
1291 // in-body break is the real exit; while(true)
1292 // is the faithful header (same intent as the
1293 // TRY-wrapped guard above).
1294 let hc = if !stripped && leading_test_present(&inner, &cond, true) {
1295 Expr::Const(ConstVal::Int(1))
1296 } else {
1297 while_cond
1298 };
1299 Stmt::While {
1300 cond: hc,
1301 body: Box::new(inner),
1302 }
1303 }
1304 }
1305 } else if taken_is_exit {
1306 // Exit branch runs statements before leaving:
1307 // while (true) { if (C_exit) { stmts; break; } body }
1308 // `cond` IS the exit test here (the taken side exits —
1309 // negate(cond) inverted the guard: jdk17 CHM
1310 // comparableClassFor emitted `if (i < len) return null`
1311 // inside while(true), inverting the loop). The break is
1312 // only appended when the exit statements do not already
1313 // terminate (return null + break = unreachable stmt).
1314 let mut ex = exit_stmts;
1315 let already_term = matches!(
1316 ex.last(),
1317 Some(Stmt::Return(_)) | Some(Stmt::Throw(_)) | Some(Stmt::Break(_))
1318 );
1319 if !already_term {
1320 ex.push(Stmt::Break(None));
1321 }
1322 let guard = Stmt::If {
1323 cond: cond.clone(),
1324 then_stmt: Box::new(Stmt::Block(ex)),
1325 else_stmt: None,
1326 };
1327 let mut v = vec![guard];
1328 v.extend(stmt_to_vec(inner));
1329 Stmt::While {
1330 cond: Expr::Const(ConstVal::Int(1)),
1331 body: Box::new(Stmt::Block(v)),
1332 }
1333 } else {
1334 // Unusual orientation: the fallthrough side exits.
1335 if exit_stmts.is_empty()
1336 || matches!(exit_stmts.as_slice(), [Stmt::Break(_)])
1337 || matches!(exit_stmts.as_slice(), [Stmt::Block(b)] if b.is_empty())
1338 {
1339 // then-side was the exit scaffolding; body is `inner`
1340 match try_protected_dowhile(inner, &cond) {
1341 Ok(dw) => dw,
1342 Err(inner) => {
1343 // Same stale-phi guard as the taken_is_exit
1344 // arm above (this orientation keeps `cond`
1345 // un-negated).
1346 let hc = if !stripped && leading_test_present(&inner, &cond, false) {
1347 Expr::Const(ConstVal::Int(1))
1348 } else {
1349 while_cond
1350 };
1351 Stmt::While {
1352 cond: hc,
1353 body: Box::new(inner),
1354 }
1355 }
1356 }
1357 } else {
1358 // then-side carries the body ending with the back
1359 // edge; fallthrough exits.
1360 let body2 = strip_trailing_continue(stmts_to_stmt(exit_stmts));
1361 Stmt::While {
1362 cond: while_cond,
1363 body: Box::new(body2),
1364 }
1365 }
1366 }
1367 }
1368 Term::Goto | Term::Fallthrough => {
1369 // Possibly do-while: the body ends with a conditional jump
1370 // back to the header (`if (c) continue;` or inverted), or a
1371 // COMPOUND run of such tests ending in the loop exit.
1372 if let Some((stmts, c, exit)) = extract_compound_do_while(&body_stmt) {
1373 // Same guard as the self-loop compound path above: an
1374 // extracted exit that is the loop's OWN unlabeled break
1375 // must be dropped — the do-while condition false already
1376 // exits, and a `break;` re-emitted OUTSIDE the loop is a
1377 // stray (it also makes stmt_terminates treat the sequence
1378 // as terminated, so prune_unreachable drops the real
1379 // tail: jdk26 Resolver.bind `return this` — 缺少返回语句).
1380 // SESE's rotated do-whiles hit this shape: the tail test
1381 // block becomes `if (c) continue; else break;`.
1382 // return/throw/labeled-break exits stay (real fall-out).
1383 if matches!(exit, Stmt::Break(None)) {
1384 return Stmt::DoWhile {
1385 body: Box::new(Stmt::Block(stmts)),
1386 cond: c,
1387 };
1388 }
1389 return Stmt::Block(vec![
1390 Stmt::DoWhile {
1391 body: Box::new(Stmt::Block(stmts)),
1392 cond: c,
1393 },
1394 exit,
1395 ]);
1396 }
1397 if let Some((stmts, c)) = extract_trailing_do_while(&body_stmt) {
1398 return Stmt::DoWhile {
1399 body: Box::new(Stmt::Block(stmts)),
1400 cond: c,
1401 };
1402 }
1403 Stmt::While {
1404 cond: Expr::Const(ConstVal::Int(1)),
1405 body: Box::new(body_stmt),
1406 }
1407 }
1408 _ => Stmt::While {
1409 cond: Expr::Const(ConstVal::Int(1)),
1410 body: Box::new(body_stmt),
1411 },
1412 }
1413 }
1414
1415 /// True if `target` is inside the loop currently being classified
1416 /// (approximated: target can reach the header again).
1417 #[allow(dead_code)]
1418 fn loop_contains(&self, header: usize, target: usize) -> bool {
1419 can_reach(self.cfg, target, header, 8192)
1420 }
1421
1422 #[allow(dead_code)]
1423 fn loop_exit_contains(&self, header: usize, target: usize) -> bool {
1424 // The loop currently being classified is the innermost on the stack
1425 // BEFORE it was popped; ctx.exits was consumed. Recompute cheaply:
1426 // target is an exit if it is not reachable-back-to-header within the
1427 // loop members — approximated by checking the parent scope recorded
1428 // in loops stack is empty here; use cfg: header's own successors that
1429 // leave... For the header itself, an exit is any successor that is
1430 // not dominated by header OR is not in the natural loop. Simple
1431 // approximation: a successor s is "in loop" if s can reach header
1432 // again via normal edges.
1433 can_reach(self.cfg, target, header, 4096).not()
1434 }
1435}
1436
1437#[allow(dead_code)]
1438trait NotExt {
1439 fn not(self) -> bool;
1440}
1441impl NotExt for bool {
1442 fn not(self) -> bool {
1443 !self
1444 }
1445}
1446
1447#[allow(dead_code)]
1448fn can_reach(cfg: &Cfg, from: usize, to: usize, budget: usize) -> bool {
1449 if from == to {
1450 return true;
1451 }
1452 let mut seen = HashSet::default();
1453 let mut q = std::collections::VecDeque::new();
1454 q.push_back(from);
1455 seen.insert(from);
1456 let mut n = 0;
1457 while let Some(b) = q.pop_front() {
1458 n += 1;
1459 if n > budget {
1460 return false;
1461 }
1462 for &s in &cfg.blocks[b].succ {
1463 if s == to {
1464 return true;
1465 }
1466 if seen.insert(s) {
1467 q.push_back(s);
1468 }
1469 }
1470 }
1471 false
1472}
1473
1474/// If the body starts with the loop header's own `If` (same condition
1475/// expression), return (else-branch content, then-branch content).
1476/// Otherwise return (body, []).
1477fn stmts_to_stmt(v: Vec<Stmt>) -> Stmt {
1478 match v.len() {
1479 0 => Stmt::Block(vec![]),
1480 1 => v.into_iter().next().unwrap(),
1481 _ => Stmt::Block(v),
1482 }
1483}
1484
1485/// When the loop's top test lives inside a Try in `inner` (the header
1486/// block sits in its handler's protected range — jdk26
1487/// Bits.reserveMemory's backoff retry loop tests
1488/// `tryReserveOrClean(..) throws InterruptedException` under its own
1489/// catch(IE) handler), the `while (C)` rotation would evaluate C
1490/// OUTSIDE every handler — 未报告的异常错误InterruptedException.
1491/// Rewrite as a do-while instead: inject `break` into the embedded
1492/// If's EMPTY arm (the exit side — the exit blocks live outside the
1493/// loop region as its continuation siblings, so the empty arm is the
1494/// fall-out) and loop unconditionally. The sibling tail then stays
1495/// reachable as the do-while's natural completion. Returns None when
1496/// the test is not try-wrapped (plain rotation applies) or when the
1497/// shape is ambiguous (both If arms materialized).
1498// The Err payload IS the statement being rejected — boxing it would
1499// churn every call site for a cold path.
1500#[allow(clippy::result_large_err)]
1501fn try_protected_dowhile(inner: Stmt, cond: &Expr) -> Result<Stmt, Stmt> {
1502 // The exit side of the embedded test: an Empty region conversion
1503 // (empty block / missing else) or an ALREADY-materialized plain
1504 // `break` (the merged-span shape walks the sleep region as the
1505 // then-arm and breaks on the exit side directly).
1506 fn is_empty(s: &Stmt) -> bool {
1507 match s {
1508 Stmt::Block(v) => v.is_empty() || (v.len() == 1 && is_empty(&v[0])),
1509 Stmt::Break(None) => false,
1510 _ => false,
1511 }
1512 }
1513 fn inject(s: &mut Stmt, cond: &Expr, ncond: &Expr) -> bool {
1514 match s {
1515 Stmt::If {
1516 cond: c,
1517 then_stmt,
1518 else_stmt,
1519 } if same_test(c, cond) => {
1520 let then_empty = is_empty(then_stmt);
1521 let else_empty = else_stmt.as_ref().map(|e| is_empty(e)).unwrap_or(true);
1522 if then_empty && !else_empty {
1523 **then_stmt = Stmt::Break(None);
1524 true
1525 } else if else_empty && !then_empty {
1526 *else_stmt = Some(Box::new(Stmt::Break(None)));
1527 true
1528 } else {
1529 false
1530 }
1531 }
1532 // Flipped polarity: the emission normalizer dropped an EMPTY
1533 // exit arm and re-oriented the test (`if (X) {body}` where
1534 // X = ¬cond — weixin hu4/k `while (hasNext) { hasNext =
1535 // it.hasNext(); if (hasNext) {…} }`). THEN is the interior
1536 // and ELSE the exit side; only the missing/empty-else shape
1537 // is safely fillable (`if (X) {body} else break;`). An empty
1538 // THEN with a materialized ELSE would need the break AFTER
1539 // the else statements — this filler cannot express that, so
1540 // refuse rather than skip the exit path.
1541 Stmt::If {
1542 cond: c,
1543 then_stmt,
1544 else_stmt,
1545 } if same_test(c, ncond) => {
1546 let else_empty = else_stmt.as_ref().map(|e| is_empty(e)).unwrap_or(true);
1547 if else_empty && !is_empty(then_stmt) {
1548 *else_stmt = Some(Box::new(Stmt::Break(None)));
1549 true
1550 } else {
1551 false
1552 }
1553 }
1554 Stmt::Try { body, .. } | Stmt::TryWithResources { body, .. } => {
1555 inject(body, cond, ncond)
1556 }
1557 Stmt::Block(v) => {
1558 for st in v.iter_mut() {
1559 match st {
1560 // The header's condition computation precedes the
1561 // re-emitted test — scan on.
1562 Stmt::ExprStmt(_)
1563 | Stmt::LocalDef { .. }
1564 | Stmt::Comment(_)
1565 | Stmt::MonitorEnter(_)
1566 | Stmt::MonitorExit(_) => continue,
1567 other => return inject(other, cond, ncond),
1568 }
1569 }
1570 false
1571 }
1572 Stmt::Labeled { body, .. } | Stmt::Synchronized { body, .. } => {
1573 inject(body, cond, ncond)
1574 }
1575 _ => false,
1576 }
1577 }
1578 let mut b = inner;
1579 let ncond = negate(cond.clone());
1580 let r = inject(&mut b, cond, &ncond);
1581 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1582 eprintln!(
1583 "TPD inject={} cond={} body0={}",
1584 r,
1585 format!("{:?}", cond).chars().take(70).collect::<String>(),
1586 match &b {
1587 Stmt::Block(v) => v
1588 .iter()
1589 .take(3)
1590 .map(|x| match x {
1591 Stmt::If { cond: c, then_stmt, else_stmt } => format!(
1592 "if({} )then_empty={} else={}",
1593 format!("{:?}", c).chars().take(50).collect::<String>(),
1594 matches!(&**then_stmt, Stmt::Block(bb) if bb.is_empty()),
1595 else_stmt.is_some()
1596 ),
1597 Stmt::LocalDef { .. } => "def".to_string(),
1598 Stmt::ExprStmt(_) => "expr".to_string(),
1599 _ => "other".to_string(),
1600 })
1601 .collect::<Vec<_>>()
1602 .join("|"),
1603 Stmt::If { cond: c, .. } => format!("topif({:?})", c).chars().take(60).collect(),
1604 _ => "nonblock".to_string(),
1605 }
1606 );
1607 }
1608 if r {
1609 Ok(Stmt::DoWhile {
1610 body: Box::new(b),
1611 cond: Expr::Const(ConstVal::Int(1)),
1612 })
1613 } else {
1614 Err(b)
1615 }
1616}
1617
1618/// Unwrap the header's re-emitted `if (cond) {exit} else {body}` from the
1619/// top of a loop body. The bool reports whether the strip happened: false
1620/// means the test was NOT the body's first statement (a condition-computing
1621/// header leads with its Assign) and the re-emission is still inside.
1622fn split_leading_if(body: &Stmt, cond: &Expr) -> (Stmt, Vec<Stmt>, bool) {
1623 let items = match body {
1624 Stmt::Block(v) => v,
1625 other => {
1626 return match other {
1627 Stmt::If {
1628 cond: c,
1629 then_stmt,
1630 else_stmt,
1631 } if c == cond => (
1632 else_stmt
1633 .as_ref()
1634 .map(|e| (**e).clone())
1635 .unwrap_or(Stmt::Block(vec![])),
1636 stmt_to_vec((**then_stmt).clone()),
1637 true,
1638 ),
1639 _ => (other.clone(), vec![], false),
1640 };
1641 }
1642 };
1643 if let Some(Stmt::If {
1644 cond: c,
1645 then_stmt,
1646 else_stmt,
1647 }) = items.first()
1648 {
1649 {
1650 if c == cond {
1651 let inner = if items.len() == 1 {
1652 else_stmt
1653 .as_ref()
1654 .map(|e| (**e).clone())
1655 .unwrap_or(Stmt::Block(vec![]))
1656 } else {
1657 let mut rest = vec![else_stmt
1658 .as_ref()
1659 .map(|e| (**e).clone())
1660 .unwrap_or(Stmt::Block(vec![]))];
1661 rest.extend(items[1..].iter().cloned());
1662 Stmt::Block(rest)
1663 };
1664 return (inner, stmt_to_vec((**then_stmt).clone()), true);
1665 }
1666 }
1667 }
1668 (body.clone(), vec![], false)
1669}
1670
1671/// Canonical form of a boolean test for STRUCTURAL comparison. The header
1672/// Term keeps the raw dex idiom (`X == 0` for if-eqz on a boolean
1673/// register) while the re-emitted in-body If often carries the simplified
1674/// `X` / `!X` — raw `==` misses the pair (weixin allocRow cursor-loop
1675/// family stayed stale-rotated without this). Folds `X == 0` → `!X`,
1676/// `X != 0` → `X`, and double negations.
1677fn canon_test(e: &Expr) -> Expr {
1678 fn is_zero(c: &Expr) -> bool {
1679 matches!(c, Expr::Const(ConstVal::Int(0)))
1680 }
1681 match e {
1682 Expr::Bin {
1683 op: BinOp::Eq,
1684 l,
1685 r,
1686 ..
1687 } => {
1688 if is_zero(r) {
1689 Expr::Un {
1690 op: UnOp::Not,
1691 e: Box::new(canon_test(l)),
1692 }
1693 } else if is_zero(l) {
1694 Expr::Un {
1695 op: UnOp::Not,
1696 e: Box::new(canon_test(r)),
1697 }
1698 } else {
1699 e.clone()
1700 }
1701 }
1702 Expr::Bin {
1703 op: BinOp::Ne,
1704 l,
1705 r,
1706 ..
1707 } => {
1708 if is_zero(r) {
1709 canon_test(l)
1710 } else if is_zero(l) {
1711 canon_test(r)
1712 } else {
1713 e.clone()
1714 }
1715 }
1716 Expr::Un {
1717 op: UnOp::Not,
1718 e: inner,
1719 } => match canon_test(inner) {
1720 Expr::Un {
1721 op: UnOp::Not,
1722 e: x,
1723 } => *x,
1724 other => Expr::Un {
1725 op: UnOp::Not,
1726 e: Box::new(other),
1727 },
1728 },
1729 _ => e.clone(),
1730 }
1731}
1732
1733/// Structural equality of two boolean tests modulo the zero-comparison
1734/// idiom (`X == 0` ≡ `!X`).
1735fn same_test(a: &Expr, b: &Expr) -> bool {
1736 if a == b {
1737 return true;
1738 }
1739 canon_test(a) == canon_test(b)
1740}
1741
1742/// True when the loop body still carries the header block's re-emitted exit
1743/// test at its leading position: zero or more condition-computing simple
1744/// statements followed by `if (cond) …` (possibly under region wrappers).
1745/// The first If/control statement encountered is decisive — a later
1746/// same-condition diamond deeper in the body does not count.
1747///
1748/// `exit_on_taken` orients the test: the taken_is_exit arm of classify_loop
1749/// has the EXIT on the term's taken side (`if (c) {exit}`), the fall-exit
1750/// arm has it on the else (`if (c) {interior} else {exit}`). The exit side
1751/// must TERMINATE (break/return/throw/labeled-continue — recursively
1752/// through blocks and both-arm-terminating ifs); the interior side may end
1753/// anywhere (falling to the loop bottom IS the back edge). Without a
1754/// terminating exit side, `while (true)` would spin forever — the
1755/// empty-exit-arm shapes belong to try_protected_dowhile's break
1756/// injection, which runs first.
1757fn leading_test_present(s: &Stmt, cond: &Expr, exit_on_taken: bool) -> bool {
1758 fn terminates(t: &Stmt) -> bool {
1759 match t {
1760 Stmt::Break(_) | Stmt::Return(_) | Stmt::Throw(_) => true,
1761 // A LABELED continue targets an enclosing loop — it leaves
1762 // this one, so the in-body exit is real (weixin aa2/b:
1763 // `if (!hasNext) continue L1;`). A BARE continue is this
1764 // loop's own back edge — NOT an exit (while(true) would
1765 // re-spin the test forever); it must stay on the old path.
1766 Stmt::Continue(Some(_)) => true,
1767 Stmt::Block(v) => v.last().map(terminates).unwrap_or(false),
1768 // An if whose BOTH arms leave the loop terminates too.
1769 Stmt::If {
1770 then_stmt,
1771 else_stmt,
1772 ..
1773 } => {
1774 terminates(then_stmt)
1775 && else_stmt
1776 .as_ref()
1777 .map(|e| terminates(e))
1778 .unwrap_or(false)
1779 }
1780 Stmt::Labeled { body, .. } => terminates(body),
1781 _ => false,
1782 }
1783 }
1784 fn term_opt(o: &Option<Box<Stmt>>) -> bool {
1785 o.as_ref().map(|e| terminates(e)).unwrap_or(false)
1786 }
1787 fn go(s: &Stmt, cond: &Expr, ncond: &Expr, exit_on_taken: bool) -> bool {
1788 match s {
1789 Stmt::If {
1790 cond: c,
1791 then_stmt,
1792 else_stmt,
1793 } => {
1794 let direct = same_test(c, cond);
1795 let flipped = !direct && same_test(c, ncond);
1796 if !direct && !flipped {
1797 return false;
1798 }
1799 // Exit side: a direct-oriented test carries the term's
1800 // taken side in THEN; a flipped test swaps the arms.
1801 // `exit_on_taken` says which term side exits in the
1802 // calling classify arm.
1803 let exit_is_then = direct == exit_on_taken;
1804 if exit_is_then {
1805 terminates(then_stmt)
1806 } else {
1807 term_opt(else_stmt)
1808 }
1809 }
1810 Stmt::Try { body, .. }
1811 | Stmt::TryWithResources { body, .. }
1812 | Stmt::Labeled { body, .. }
1813 | Stmt::Synchronized { body, .. } => go(body, cond, ncond, exit_on_taken),
1814 Stmt::Block(v) => {
1815 for st in v {
1816 match st {
1817 Stmt::ExprStmt(_)
1818 | Stmt::LocalDef { .. }
1819 | Stmt::Comment(_)
1820 | Stmt::MonitorEnter(_)
1821 | Stmt::MonitorExit(_) => continue,
1822 other => return go(other, cond, ncond, exit_on_taken),
1823 }
1824 }
1825 false
1826 }
1827 _ => false,
1828 }
1829 }
1830 go(s, cond, &negate(cond.clone()), exit_on_taken)
1831}
1832
1833/// True when the body's first meaningful statement assigns the SAME local
1834/// the header condition tests (`X = call(); …` where cond = ±X) — the
1835/// compute-then-test shape whose rotated `while (X)` header would read X's
1836/// stale pre-loop value.
1837fn cond_var_reassigned_first(s: &Stmt, cond: &Expr) -> bool {
1838 fn cond_local(c: &Expr) -> Option<u32> {
1839 match c {
1840 Expr::Local { var, .. } => Some(*var),
1841 Expr::Un {
1842 op: UnOp::Not,
1843 e,
1844 } => cond_local(e),
1845 // boolean-zero idiom: `X == 0` / `0 != X` (dex if-eqz on a
1846 // boolean register keeps this raw form in the Term).
1847 Expr::Bin {
1848 op: BinOp::Eq | BinOp::Ne,
1849 l,
1850 r,
1851 ..
1852 } => {
1853 if matches!(&**r, Expr::Const(ConstVal::Int(0))) {
1854 cond_local(l)
1855 } else if matches!(&**l, Expr::Const(ConstVal::Int(0))) {
1856 cond_local(r)
1857 } else {
1858 None
1859 }
1860 }
1861 _ => None,
1862 }
1863 }
1864 let Some(v) = cond_local(cond) else {
1865 return false;
1866 };
1867 fn go(s: &Stmt, v: u32) -> bool {
1868 match s {
1869 Stmt::ExprStmt(Expr::Assign { target, .. }) => {
1870 matches!(&**target, Expr::Local { var, .. } if *var == v)
1871 }
1872 Stmt::LocalDef {
1873 var, init: Some(_), ..
1874 } => *var == v,
1875 Stmt::Block(b) => b.first().map(|f| go(f, v)).unwrap_or(false),
1876 Stmt::Try { body, .. }
1877 | Stmt::TryWithResources { body, .. }
1878 | Stmt::Labeled { body, .. }
1879 | Stmt::Synchronized { body, .. } => go(body, v),
1880 _ => false,
1881 }
1882 }
1883 go(s, v)
1884}
1885
1886/// Remove a trailing `continue;` (loop back-edge) from a statement list.
1887fn strip_trailing_continue(s: Stmt) -> Stmt {
1888 match s {
1889 Stmt::Block(mut v) => {
1890 while matches!(v.last(), Some(Stmt::Continue(None))) {
1891 v.pop();
1892 }
1893 if v.len() == 1 && matches!(v[0], Stmt::Block(_)) {
1894 let inner = match v.pop().unwrap() {
1895 Stmt::Block(inner) => inner,
1896 _ => unreachable!(),
1897 };
1898 return strip_trailing_continue(Stmt::Block(inner));
1899 }
1900 Stmt::Block(v)
1901 }
1902 Stmt::Continue(None) => Stmt::Block(vec![]),
1903 other => other,
1904 }
1905}
1906
1907pub fn negate(e: Expr) -> Expr {
1908 match e {
1909 Expr::Un { op: UnOp::Not, e } => *e,
1910 // De Morgan for the short-circuit operators: !(A && B) =
1911 // !A || !B, !(A || B) = !A && !B — recursing through negate
1912 // keeps comparison operands cleanly inverted. An op-only swap
1913 // (the historical invert() LogAnd<->LogOr mapping) silently
1914 // changed the condition's meaning: Arrays.equals' folded
1915 // `a == null || a2 == null` inverted to `a == null && a2 ==
1916 // null`, NPE-ing the a2==null path.
1917 Expr::Bin {
1918 op: op @ (crate::ir::expr::BinOp::LogAnd | crate::ir::expr::BinOp::LogOr),
1919 l,
1920 r,
1921 ty,
1922 } => {
1923 let swapped = if matches!(op, crate::ir::expr::BinOp::LogAnd) {
1924 crate::ir::expr::BinOp::LogOr
1925 } else {
1926 crate::ir::expr::BinOp::LogAnd
1927 };
1928 Expr::Bin {
1929 op: swapped,
1930 l: Box::new(negate(*l)),
1931 r: Box::new(negate(*r)),
1932 ty,
1933 }
1934 }
1935 Expr::Bin { op, l, r, ty } => {
1936 if let Some(inv) = op.invert() {
1937 Expr::Bin { op: inv, l, r, ty }
1938 } else {
1939 Expr::Un {
1940 op: UnOp::Not,
1941 e: Box::new(Expr::Bin { op, l, r, ty }),
1942 }
1943 }
1944 }
1945 other => Expr::Un {
1946 op: UnOp::Not,
1947 e: Box::new(other),
1948 },
1949 }
1950}
1951
1952/// True for a bare `continue;` (or a block wrapping just one).
1953fn is_continue_stmt(s: &Stmt) -> bool {
1954 matches!(s, Stmt::Continue(None))
1955 || matches!(s, Stmt::Block(v) if v.len() == 1 && matches!(v[0], Stmt::Continue(None)))
1956}
1957
1958/// Detect a COMPOUND trailing do-while condition: the body ends with a run of
1959/// `if (c1) continue;` ... `if (cN) continue; else <exit>;`. javac emits this
1960/// for `do { body } while (c1 || ... || cN);` followed by the loop exit when
1961/// the condition is a short-circuit `||` whose tests each jump back to the
1962/// body head (e.g. `Random.internalNextInt`: `while (r < origin || r >=
1963/// bound)`). Fold the run into a single do-while whose condition is the `||`
1964/// of the tests, returning the body statements, that condition, and the exit
1965/// action (`<exit>`) to emit right after the loop.
1966fn extract_compound_do_while(body: &Stmt) -> Option<(Vec<Stmt>, Expr, Stmt)> {
1967 // Flatten nested straight-line blocks: the loop body may group the first
1968 // condition test(s) inside a nested Block (the self-loop header's region)
1969 // while later tests are siblings. Splicing pure sequence blocks is safe.
1970 fn flatten_seq(s: &Stmt) -> Vec<Stmt> {
1971 match s {
1972 Stmt::Block(v) => {
1973 let mut out = Vec::new();
1974 for x in v {
1975 out.extend(flatten_seq(x));
1976 }
1977 out
1978 }
1979 other => vec![other.clone()],
1980 }
1981 }
1982 let mut stmts = flatten_seq(body);
1983 // Unnest the rendered if/else-if CHAIN spine: javac's bottom-tested
1984 // `if (c1) continue; if (c2) continue; ... else exit;` disjunct run
1985 // converts to ONE nested If whose else arms hold the remaining
1986 // tests — opaque to the backwards scan below, which would fold only
1987 // c1 and push the whole c2..cN chain out of the loop as the "exit"
1988 // (jdk26 ML_DSA.signInternal: `do { expandMask.. } while
1989 // (vectorNormBound(z,..));` with the c2/hint chain — and its
1990 // continue/break — stranded after the loop, continue 在 loop 外部
1991 // ×2). Splitting each continue-then If back into siblings restores
1992 // the flat run; semantics are identical (the then arm is a bare
1993 // continue with no fallthrough, and the LAST else is preserved as
1994 // the final statement's else = the loop's exit route).
1995 {
1996 let mut flat: Vec<Stmt> = Vec::new();
1997 for st in stmts.into_iter() {
1998 let mut cur = st;
1999 loop {
2000 match cur {
2001 Stmt::If {
2002 cond,
2003 then_stmt,
2004 else_stmt: Some(e),
2005 } if is_continue_stmt(&then_stmt) => {
2006 flat.push(Stmt::If {
2007 cond,
2008 then_stmt,
2009 else_stmt: None,
2010 });
2011 cur = *e;
2012 }
2013 other => {
2014 flat.push(other);
2015 break;
2016 }
2017 }
2018 }
2019 }
2020 stmts = flat;
2021 }
2022 let n = stmts.len();
2023 if n < 2 {
2024 return None;
2025 }
2026 // Last statement: `if (c_last) continue; else <exit>` — the exit is the
2027 // loop's fall-out (a return/throw/break), NOT another continue.
2028 let (c_last, exit) = match &stmts[n - 1] {
2029 Stmt::If {
2030 cond,
2031 then_stmt,
2032 else_stmt: Some(e),
2033 } if is_continue_stmt(then_stmt) && !is_continue_stmt(e) => (cond.clone(), (**e).clone()),
2034 _ => return None,
2035 };
2036 // Walk backwards over preceding `if (ci) continue;` (no else) tests.
2037 let mut conds = vec![c_last];
2038 let mut i = n - 1;
2039 while i >= 1 {
2040 match &stmts[i - 1] {
2041 Stmt::If {
2042 cond,
2043 then_stmt,
2044 else_stmt: None,
2045 } if is_continue_stmt(then_stmt) => {
2046 conds.push(cond.clone());
2047 i -= 1;
2048 }
2049 _ => break,
2050 }
2051 }
2052 let body_stmts: Vec<Stmt> = stmts[..i].to_vec();
2053 // cond = c1 || c2 || ... || c_last (conds currently reversed).
2054 let mut it = conds.into_iter().rev();
2055 let mut cond = it.next().unwrap();
2056 for c in it {
2057 cond = Expr::Bin {
2058 op: BinOp::LogOr,
2059 l: Box::new(c),
2060 r: Box::new(cond),
2061 ty: None,
2062 };
2063 }
2064 Some((body_stmts, cond, exit))
2065}
2066
2067/// Detect a trailing do-while condition at the end of a loop body:
2068/// `if (c) continue;` → cond c, or `if (c) {} else continue;` → cond !c.
2069fn extract_trailing_do_while(body: &Stmt) -> Option<(Vec<Stmt>, Expr)> {
2070 let mut stmts = match body {
2071 Stmt::Block(v) => v.clone(),
2072 other => vec![other.clone()],
2073 };
2074 if stmts.is_empty() {
2075 return None;
2076 }
2077 let n = stmts.len() - 1;
2078 if let Stmt::If {
2079 cond,
2080 then_stmt,
2081 else_stmt,
2082 } = &stmts[n]
2083 {
2084 let then_is_cont = |s: &Stmt| {
2085 matches!(s, Stmt::Continue(None))
2086 || matches!(s, Stmt::Block(v) if v.len() == 1 && matches!(v[0], Stmt::Continue(None)))
2087 };
2088 if then_is_cont(then_stmt) && else_stmt.is_none() {
2089 let c = cond.clone();
2090 stmts.pop();
2091 return Some((stmts, c));
2092 }
2093 if else_stmt.as_ref().map(|e| then_is_cont(e)).unwrap_or(false)
2094 && (matches!(&**then_stmt, Stmt::Block(v) if v.is_empty()))
2095 {
2096 let c = negate(cond.clone());
2097 stmts.pop();
2098 return Some((stmts, c));
2099 }
2100 }
2101 None
2102}
2103
2104/// If the body ends with `if (c) continue;` (Continue(None), no else),
2105/// strip it and return (body without trailing if, c).
2106#[allow(dead_code)]
2107fn extract_trailing_continue_if(body: &Stmt) -> Option<(Vec<Stmt>, Expr)> {
2108 let mut stmts = match body {
2109 Stmt::Block(v) => v.clone(),
2110 other => vec![other.clone()],
2111 };
2112 if stmts.is_empty() {
2113 return None;
2114 }
2115 let n = stmts.len() - 1;
2116 // Direct trailing `if (c) continue;`.
2117 let is_direct = match &stmts[n] {
2118 Stmt::If {
2119 then_stmt,
2120 else_stmt: None,
2121 ..
2122 } => {
2123 matches!(&**then_stmt, Stmt::Continue(None))
2124 || matches!(&**then_stmt, Stmt::Block(tb) if tb.len() == 1 && matches!(tb[0], Stmt::Continue(None)))
2125 }
2126 _ => false,
2127 };
2128 if is_direct {
2129 if let Stmt::If { cond, .. } = stmts.pop().unwrap() {
2130 return Some((stmts, cond));
2131 }
2132 }
2133 // Descend into a trailing block.
2134 if matches!(stmts.last(), Some(Stmt::Block(_))) {
2135 let last = stmts.pop().unwrap();
2136 if let Some((inner, c)) = extract_trailing_continue_if(&last) {
2137 if !inner.is_empty() {
2138 stmts.push(Stmt::Block(inner));
2139 }
2140 return Some((stmts, c));
2141 } else {
2142 stmts.push(last);
2143 }
2144 }
2145 None
2146}
2147
2148/// Separate the first Basic block's statements from the rest of a body.
2149/// (Bodies built by `walk` start with the header's statements.)
2150#[allow(dead_code)]
2151fn split_first_block(body: Stmt) -> (Stmt, Option<usize>) {
2152 // The body is whatever the walk produced; we keep it intact — header
2153 // statements were already woven in by conv(Basic). Return as-is.
2154 (body, None)
2155}
2156
2157fn stmt_to_vec(s: Stmt) -> Vec<Stmt> {
2158 match s {
2159 Stmt::Block(v) => v,
2160 other => vec![other],
2161 }
2162}
2163
2164/// Strip `Labeled` wrappers no Break/Continue ended up referencing.
2165/// Wrappers are attached unconditionally during conv (a break born in a
2166/// copied/follow region lands after its loop's region-close, so the
2167/// at-close used_labels check orphaned it); after the full conversion
2168/// used_labels is complete and the unused wrappers come off. An unused
2169/// wrapper's body replaces it in-place (a Block body stays a nested
2170/// block — downstream cleanup flattens).
2171fn prune_unused_labels(st: &mut Stmt, used: &HashSet<String>) {
2172 loop {
2173 let take = matches!(st, Stmt::Labeled { label, .. } if !used.contains(label));
2174 if !take {
2175 break;
2176 }
2177 let inner = match std::mem::replace(st, Stmt::Block(Vec::new())) {
2178 Stmt::Labeled { body, .. } => *body,
2179 _ => unreachable!(),
2180 };
2181 *st = inner;
2182 }
2183 match st {
2184 Stmt::Block(v) => v.iter_mut().for_each(|x| prune_unused_labels(x, used)),
2185 Stmt::Labeled { body, .. } => prune_unused_labels(body, used),
2186 Stmt::If { then_stmt, else_stmt, .. } => {
2187 prune_unused_labels(then_stmt, used);
2188 if let Some(e) = else_stmt {
2189 prune_unused_labels(e, used);
2190 }
2191 }
2192 Stmt::While { body, .. }
2193 | Stmt::DoWhile { body, .. }
2194 | Stmt::ForEach { body, .. }
2195 | Stmt::Synchronized { body, .. } => prune_unused_labels(body, used),
2196 Stmt::For { init, body, .. } => {
2197 init.iter_mut().for_each(|x| prune_unused_labels(x, used));
2198 prune_unused_labels(body, used);
2199 }
2200 Stmt::Switch { cases, default, .. } => {
2201 for c in cases.iter_mut() {
2202 c.body.iter_mut().for_each(|x| prune_unused_labels(x, used));
2203 }
2204 if let Some(d) = default {
2205 prune_unused_labels(d, used);
2206 }
2207 }
2208 Stmt::Try { body, catches, finally }
2209 | Stmt::TryWithResources { body, catches, finally, .. } => {
2210 prune_unused_labels(body, used);
2211 for c in catches.iter_mut() {
2212 prune_unused_labels(&mut c.body, used);
2213 }
2214 if let Some(f) = finally {
2215 prune_unused_labels(f, used);
2216 }
2217 }
2218 _ => {}
2219 }
2220}