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 while let Some(last) = items.last_mut() {
114 match last {
115 Stmt::Goto(t) if *t as usize == f => {
116 items.pop();
117 return;
118 }
119 Stmt::Block(inner) if !inner.is_empty() => {
120 // descend into trailing block
121 let n = inner.len();
122 if matches!(inner[n - 1], Stmt::Goto(t) if t as usize == f) {
123 inner.pop();
124 return;
125 }
126 break;
127 }
128 _ => break,
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 self.conv(r)
267 }
268
269 fn conv(&mut self, r: Region) -> Stmt {
270 match r {
271 Region::Empty => Stmt::Block(vec![]),
272 Region::Basic { block } => {
273 self.cur_block = block;
274 if crate::dbg_flag!("JCDC_DBG_BLOCKS") {
275 eprintln!(
276 "conv Basic {} ({} stmts)",
277 block,
278 self.results[block].stmts.len()
279 );
280 }
281 self.block_stmts(block)
282 }
283 Region::CopyStmts { block } => {
284 // Copies include the block's terminator when it is a
285 // return/throw (shared terminator blocks are inlined at
286 // every arrival site).
287 let mut v = self.results[block].stmts.clone();
288 match &self.results[block].term {
289 Term::Return(e) => v.push(Stmt::Return(e.clone())),
290 Term::Throw(e) => v.push(Stmt::Throw(e.clone())),
291 _ => {}
292 }
293 if v.len() == 1 {
294 v.into_iter().next().unwrap()
295 } else {
296 Stmt::Block(v)
297 }
298 }
299 Region::Seq(v) => {
300 let mut out = Vec::new();
301 let n = v.len();
302 let heads: Vec<usize> = v.iter().map(crate::structure::region_head_block).collect();
303 let prev_copies: Vec<Option<usize>> = (0..n)
304 .map(|k| {
305 if k > 0 {
306 match &v[k - 1] {
307 Region::CopyStmts { block } => Some(*block),
308 _ => None,
309 }
310 } else {
311 None
312 }
313 })
314 .collect();
315 for (k, x) in v.into_iter().enumerate() {
316 let save = self.goto_is_last;
317 self.goto_is_last = k + 1 == n;
318 let save_pc = self.prev_copy.get();
319 self.prev_copy.set(prev_copies[k]);
320 let save_exp = self.expect_next.get();
321 // Only override when the next emission is KNOWN: a
322 // headless tail (Goto/Empty — a jump out) must keep
323 // the inherited expectation, or a nested arm's Goto
324 // would elide against a phantom (jdk17 dci: Basic17's
325 // MAX head erased the follow expectation and Goto{16}
326 // elided past the SET).
327 if k + 1 < n && heads[k + 1] != usize::MAX {
328 self.expect_next.set(Some(heads[k + 1]));
329 }
330 out.push(self.conv(x));
331 self.expect_next.set(save_exp);
332 self.prev_copy.set(save_pc);
333 self.goto_is_last = save;
334 }
335 Stmt::Block(out)
336 }
337 Region::If {
338 block,
339 cond,
340 then_r,
341 else_r,
342 follow,
343 ternary,
344 } => {
345 self.cur_block = block;
346 let mut head = self.block_stmts_no_term(block);
347 if let Some((_tv, _fv)) = ternary {
348 // Pure value diamond: the folded conditional flows into
349 // the merge block's rebuilt input stack; nothing to emit
350 // here (branch blocks are statement-free by definition).
351 let _ = cond;
352 } else {
353 if let Some(f) = follow {
354 self.if_follows.push(f);
355 }
356 let save_exp = self.expect_next.get();
357 // follow=None keeps the inherited expectation: the
358 // flow leaving this If's arms continues to whatever
359 // follows the If itself.
360 self.expect_next.set(follow.or(save_exp));
361 let then_stmt = self.conv(*then_r);
362 let else_stmt = self.conv(*else_r);
363 self.expect_next.set(save_exp);
364 if follow.is_some() {
365 self.if_follows.pop();
366 }
367 let if_stmt = Stmt::If {
368 cond,
369 then_stmt: Box::new(then_stmt),
370 else_stmt: if else_stmt.is_empty_block() {
371 None
372 } else {
373 Some(Box::new(else_stmt))
374 },
375 };
376 head.push(if_stmt);
377 }
378 if head.len() == 1 {
379 head.pop().unwrap()
380 } else {
381 Stmt::Block(head)
382 }
383 }
384 Region::Loop {
385 header,
386 body,
387 members,
388 exits,
389 } => {
390 let label = self.next_label();
391 self.loops.push(LoopCtx {
392 header,
393 exits: exits.iter().copied().collect(),
394 label: label.clone(),
395 });
396 let body_stmt = self.conv(*body);
397 let ctx = self.loops.pop().unwrap();
398 let _ = members;
399 let mut st = self.classify_loop(header, body_stmt, ctx);
400 if crate::dbg_flag!("JCDC_DBG_LOOP") {
401 let kind = match &st {
402 Stmt::While { .. } => "While".to_string(),
403 Stmt::DoWhile { .. } => "DoWhile".to_string(),
404 Stmt::For { .. } => "For".to_string(),
405 Stmt::Block(b) => format!(
406 "Block{}[{}]",
407 b.len(),
408 b.iter()
409 .map(|x| match x {
410 Stmt::DoWhile { .. } => "DoWhile",
411 Stmt::While { .. } => "While",
412 Stmt::Break(_) => "Break",
413 Stmt::Return(_) => "Return",
414 _ => "_",
415 })
416 .collect::<Vec<_>>()
417 .join(",")
418 ),
419 Stmt::Labeled { .. } => "Labeled".to_string(),
420 _ => "Other".to_string(),
421 };
422 eprintln!("CLASSIFY-OUT header={} -> {}", header, kind);
423 }
424 if self.used_labels.contains(&label) {
425 st = Stmt::Labeled {
426 label,
427 body: Box::new(st),
428 };
429 }
430 st
431 }
432 Region::Switch {
433 block,
434 selector,
435 cases,
436 default,
437 follow,
438 } => {
439 self.cur_block = block;
440 let sw_label = self.next_label();
441 self.switches.push(SwitchCtx {
442 follow,
443 label: sw_label.clone(),
444 loops_depth: self.loops.len(),
445 });
446 let mut case_groups = Vec::new();
447 for (vals, r) in cases {
448 let s = self.conv(r);
449 case_groups.push(crate::ir::stmt::CaseGroup {
450 labels: vals,
451 string_labels: vec![],
452 enum_labels: vec![],
453 raw_labels: vec![],
454 guard: None,
455 body: stmt_to_vec(s),
456 });
457 }
458 let default_stmt = default.map(|d| Box::new(self.conv(*d)));
459 self.switches.pop();
460 let mut head = self.block_stmts_no_term(block);
461 let mut sw = Stmt::Switch {
462 selector,
463 cases: case_groups,
464 default: default_stmt,
465 on_string: false,
466 };
467 if self.used_labels.contains(&sw_label) {
468 sw = Stmt::Labeled {
469 label: sw_label,
470 body: Box::new(sw),
471 };
472 }
473 head.push(sw);
474 if head.len() == 1 {
475 head.pop().unwrap()
476 } else {
477 Stmt::Block(head)
478 }
479 }
480 Region::Try {
481 group_idx,
482 body,
483 catches,
484 } => {
485 let mut body_stmt = self.conv(*body);
486 // Natural exits of the try body / handlers jump to the block
487 // right after the try span; that is structured fallthrough.
488 let gend = self.groups_end(group_idx);
489 let try_follow = gend.and_then(|e| self.cfg.block_at(e));
490 strip_trailing_goto(&mut body_stmt, try_follow);
491 let mut catch_stmts = Vec::new();
492 for (tys, _hblock, r) in catches {
493 let mut s = self.conv(*r);
494 strip_trailing_goto(&mut s, try_follow);
495 catch_stmts.push(Catch {
496 exc: tys,
497 var: u32::MAX, // assigned by the method pipeline (handler store)
498 var_name: None,
499 body: Box::new(s),
500 });
501 }
502 Stmt::Try {
503 body: Box::new(body_stmt),
504 catches: catch_stmts,
505 finally: None,
506 }
507 }
508 Region::Goto { target } => {
509 if crate::dbg_flag!("JCDC_DBG_GOTO") {
510 eprintln!(
511 "conv Goto target={} cur_block={} loops={:?} switches={} if_follows={:?}",
512 target,
513 self.cur_block,
514 self.loops
515 .iter()
516 .map(|l| (l.header, l.exits.len()))
517 .collect::<Vec<_>>(),
518 self.switches.len(),
519 self.if_follows
520 );
521 }
522 // A jump to an enclosing if's follow that TERMINATES
523 // (return/throw) can be inlined: the copy ends this path
524 // exactly like the jump would, without needing a label.
525 // Same for a jump to a LOOP EXIT that heads a pure
526 // terminator chain (stmt-bearing fall-through links
527 // ending in return/throw): resolve_goto's loop-exit arm
528 // would turn it into a bare `break`, silently dropping
529 // the chain's statements between the loop end and the
530 // shared return (jdk26 Bits.reserveMemory phase-1:
531 // `if (interrupted) <goto interrupt-block>; ` rendered
532 // as else-break and the Thread.currentThread()
533 // .interrupt() vanished — interruption lost). The
534 // inlined copy keeps the abrupt ending (no fall-out
535 // duplication); a genuine fall-out exit still breaks.
536 //
537 let inline_terminator = self.if_follows.contains(&target)
538 && matches!(self.results[target].term, Term::Return(_) | Term::Throw(_))
539 && !self.tail_chain_writes_final(target);
540 if inline_terminator {
541 // Copy the WHOLE terminator chain: the target's own
542 // statements plus every fall-through link down to
543 // the final return/throw (a single-block chain is
544 // the original if-follow case).
545 let mut v: Vec<Stmt> = Vec::new();
546 let mut t = target;
547 let mut guard = 0;
548 loop {
549 v.extend(self.results[t].stmts.clone());
550 match &self.results[t].term {
551 Term::Return(e) => {
552 v.push(Stmt::Return(e.clone()));
553 break;
554 }
555 Term::Throw(e) => {
556 v.push(Stmt::Throw(e.clone()));
557 break;
558 }
559 Term::Fallthrough | Term::Goto
560 if self.cfg.blocks[t].succ.len() == 1 && guard < 8 =>
561 {
562 t = self.cfg.blocks[t].succ[0];
563 guard += 1;
564 }
565 _ => break,
566 }
567 }
568 return if v.len() == 1 {
569 v.into_iter().next().unwrap()
570 } else {
571 Stmt::Block(v)
572 };
573 }
574 if let Some(j) = self.resolve_goto(target) {
575 match j {
576 Jump::Break(lbl) => {
577 if let Some(l) = &lbl {
578 self.used_labels.insert(l.clone());
579 }
580 Stmt::Break(lbl)
581 }
582 Jump::Continue(lbl) => {
583 if let Some(l) = &lbl {
584 self.used_labels.insert(l.clone());
585 }
586 Stmt::Continue(lbl)
587 }
588 Jump::ContinueVia(t, lbl) => {
589 if let Some(l) = &lbl {
590 self.used_labels.insert(l.clone());
591 }
592 let mut cv = if self.prev_copy.get() == Some(t) {
593 // Statements already emitted by the
594 // immediately preceding CopyStmts.
595 Vec::new()
596 } else {
597 self.results[t].stmts.clone()
598 };
599 cv.push(Stmt::Continue(lbl));
600 if cv.len() == 1 {
601 cv.into_iter().next().unwrap()
602 } else {
603 Stmt::Block(cv)
604 }
605 }
606 Jump::RawGoto(t) => {
607 if crate::dbg_flag!("JCDC_DBG_GOTO") {
608 eprintln!(
609 "RAWGOTO t={} copied_tails={:?} if_follows={:?} last={}",
610 t, self.copied_tails, self.if_follows, self.goto_is_last
611 );
612 }
613 // A Goto region is always the last part of its
614 // walk, so the jump either falls through to the
615 // enclosing merge or must be inlined.
616 let term_copy =
617 matches!(self.results[t].term, Term::Return(_) | Term::Throw(_))
618 && !self.tail_chain_writes_final(t)
619 && !self.is_retry_loop_header(t);
620 if term_copy {
621 let mut cv = self.results[t].stmts.clone();
622 match &self.results[t].term {
623 Term::Return(e) => cv.push(Stmt::Return(e.clone())),
624 Term::Throw(e) => cv.push(Stmt::Throw(e.clone())),
625 _ => {}
626 }
627 if cv.len() == 1 {
628 cv.into_iter().next().unwrap()
629 } else {
630 Stmt::Block(cv)
631 }
632 } else if self
633 .loops
634 .iter()
635 .any(|l| l.exits.contains(&t) || l.header == t)
636 && (self.goto_is_last || self.if_follows.contains(&t))
637 {
638 // The target is an ENCLOSING LOOP's exit:
639 // inside a loop body, falling out of an if
640 // continues the LOOP — the if-follow
641 // elision would silently drop the jump
642 // (jdk11 ObjectInputStream.readSerialData's
643 // finally-copy retry loop lost its `break`,
644 // the finally's while(true) never completed
645 // normally, and the tail went unreachable —
646 // 无法访问的语句 x2 trees). Materialize the
647 // break (innermost enclosing loop).
648 Stmt::Break(None)
649 } else if !matches!(
650 self.results[t].term,
651 Term::Return(_) | Term::Throw(_)
652 ) && !self.results[t].stmts.is_empty()
653 && !self.copied_tails.contains(&t)
654 && self.prev_copy.get() != Some(t)
655 && self
656 .expect_next
657 .get()
658 .map(|nx| {
659 // The next emission is a copy that
660 // STARTS PAST t (at a successor of t
661 // already in copied_tails): eliding
662 // would silently skip t's own
663 // statements, and no emitted copy
664 // covers them. (sj17 receive:
665 // Goto{16=SET}, expect=Some(17),
666 // 17 copied, 16 not.) If t itself is
667 // in copied_tails, an emitted full
668 // copy already includes t's stmts —
669 // inlining would double-emit (w26
670 // receive: Goto{14}, 14 copied).
671 let mut x = t;
672 for _ in 0..8 {
673 if x == nx {
674 return x != t;
675 }
676 if self.copied_tails.contains(&x) {
677 return false;
678 }
679 match self.cfg.blocks[x].succ.first() {
680 Some(&n) => x = n,
681 None => return false,
682 }
683 }
684 false
685 })
686 .unwrap_or(false)
687 && (self
688 .if_follows
689 .iter()
690 .any(|f| self.cfg.blocks[t].succ.contains(f)))
691 {
692 // Statement-bearing target FIRST: eliding
693 // (goto_is_last / expect / copy-tail) would
694 // silently skip t's own statements when the
695 // next emission is a copy of t's SUCCESSOR
696 // rather than t itself (jdk17
697 // DatagramChannelImpl.receive: Goto{16=
698 // `sender = sourceSocketAddress()`} elided
699 // via reaches_copy_tail(16->17) into the
700 // arm's Basic(17) copy — the n==0&&isOpen
701 // path returned a null sender). The inline
702 // below emits t's statements and then falls
703 // through to the same continuation.
704 let cv = self.results[t].stmts.clone();
705 if cv.len() == 1 {
706 cv.into_iter().next().unwrap()
707 } else {
708 Stmt::Block(cv)
709 }
710 } else if self.goto_is_last
711 || self.expect_next.get() == Some(t)
712 || self.reaches_copy_tail(t)
713 {
714 // Natural fallthrough reaches the same
715 // continuation the jump targeted.
716 Stmt::Block(vec![])
717 } else if !matches!(
718 self.results[t].term,
719 Term::Return(_) | Term::Throw(_)
720 ) && self.prev_copy.get() != Some(t)
721 && (self
722 .if_follows
723 .iter()
724 .any(|f| self.cfg.blocks[t].succ.contains(f)))
725 {
726 // The jump re-enters an already-structured
727 // block whose flow ends at an enclosing
728 // if-follow: inline its statements; the
729 // natural continuation matches the jump's.
730 let cv = self.results[t].stmts.clone();
731 if cv.len() == 1 {
732 cv.into_iter().next().unwrap()
733 } else {
734 Stmt::Block(cv)
735 }
736 } else {
737 Stmt::Goto(t as u32)
738 }
739 }
740 }
741 } else {
742 Stmt::Block(vec![])
743 }
744 }
745 }
746 }
747
748 /// Statements of a block including its terminal (return/throw), but NOT
749 /// the structural terminals (cond/switch/goto — handled by regions).
750 fn block_stmts(&mut self, block: usize) -> Stmt {
751 let mut v = self.block_stmts_no_term(block);
752 match &self.results[block].term {
753 Term::Return(e) => v.push(Stmt::Return(e.clone())),
754 Term::Throw(e) => v.push(Stmt::Throw(e.clone())),
755 Term::Jsr | Term::Ret => v.push(Stmt::Comment("// jsr/ret not supported".into())),
756 _ => {}
757 }
758 if v.len() == 1 {
759 v.pop().unwrap()
760 } else {
761 Stmt::Block(v)
762 }
763 }
764
765 fn block_stmts_no_term(&self, block: usize) -> Vec<Stmt> {
766 // Monitor markers are kept; synchronized reconstruction happens later.
767 self.results[block].stmts.clone()
768 }
769
770 /// Resolve a goto target against the scope stack.
771 fn resolve_goto(&mut self, target: usize) -> Option<Jump> {
772 // continue: innermost loop whose header == target
773 if let Some(i) = (0..self.loops.len())
774 .rev()
775 .find(|&i| self.loops[i].header == target)
776 {
777 let depth = self.loops.len() - 1 - i;
778 return Some(if depth == 0 {
779 Jump::Continue(None)
780 } else {
781 Jump::Continue(Some(self.loops[i].label.clone()))
782 });
783 }
784 // break out of a loop whose exit set contains the target; pick the
785 // OUTERMOST such loop — an exit of nested loops (e.g. `break outer`)
786 // belongs to every enclosing loop's exit set, and breaking only the
787 // innermost would wrongly re-enter the outer loop.
788 if let Some(li) = (0..self.loops.len()).find(|&i| self.loops[i].exits.contains(&target)) {
789 // RESTART-STUB exit: the target chains through statement-free
790 // stubs back into an enclosing-or-self loop HEADER — the jump
791 // re-iterates THAT loop, it does not break out of anything
792 // (jdk11/17/26 ConcurrentLinkedQueue.poll's p==q arm `goto
793 // restartFromHead`: exit 12 is the `goto 0` stub; rendering
794 // break+break fell to the post-loop tail and returned the
795 // STALE item instead of restarting from head). The broad
796 // walk-side continue-stub precedence was reverted once
797 // (Pattern.clazz: switch-case goto stubs must stay
798 // RawGoto-elided so the emitter re-appends case breaks) —
799 // this form is scoped to genuine loop exits with no open
800 // switch on the stack.
801 if self.switches.is_empty() {
802 let mut x = target;
803 let mut guard = 0;
804 let header_hit = loop {
805 if let Some(hi) = self.loops.iter().position(|l| l.header == x) {
806 if hi <= li {
807 break Some(hi);
808 }
809 }
810 if guard >= 8 {
811 break None;
812 }
813 let r = &self.results[x];
814 let stub = r.stmts.is_empty()
815 && r.out_stack.is_empty()
816 && matches!(r.term, Term::Fallthrough | Term::Goto)
817 && self.cfg.blocks[x].succ.len() == 1;
818 if !stub {
819 break None;
820 }
821 x = self.cfg.blocks[x].succ[0];
822 guard += 1;
823 };
824 if let Some(hi) = header_hit {
825 let depth = self.loops.len() - 1 - hi;
826 return Some(if depth == 0 {
827 Jump::Continue(None)
828 } else {
829 Jump::Continue(Some(self.loops[hi].label.clone()))
830 });
831 }
832 }
833 // switches opened after that loop intercept a plain `break`
834 let crosses_switch = !self.switches.is_empty();
835 return Some(if li + 1 == self.loops.len() && !crosses_switch {
836 Jump::Break(None)
837 } else {
838 Jump::Break(Some(self.loops[li].label.clone()))
839 });
840 }
841 if let Some(si) = (0..self.switches.len())
842 .rev()
843 .find(|&i| self.switches[i].follow == Some(target))
844 {
845 return Some(
846 if si + 1 == self.switches.len()
847 && self.loops.len() == self.switches[si].loops_depth
848 {
849 Jump::Break(None)
850 } else {
851 Jump::Break(Some(self.switches[si].label.clone()))
852 },
853 );
854 }
855 // Natural flow to the enclosing if's merge point: no statement.
856 // Only the INNERMOST open follow qualifies — eliding a jump to an
857 // outer follow would silently skip the statements between (e.g. a
858 // shared throw at the end of an else-branch).
859 if self.if_follows.last() == Some(&target) {
860 return None;
861 }
862 // Inside a loop, a jump to an already-structured block that flows
863 // back into the loop is a CONTINUE of the innermost such loop: the
864 // target's statements were emitted earlier in this body, and the
865 // natural continuation after this region is the loop bottom (an
866 // if-follow or a fall-out exit), never the target. Eliding it as
867 // fallthrough (the RawGoto `goto_is_last` arm) silently dropped the
868 // iteration: jdk11/17 InstantPrinterParser.format's second loop
869 // copy emitted `if (i >= fractionalDigits) {}` with no way out --
870 // the digit append + back edge vanished (semantically an infinite
871 // loop; javac: 缺少返回语句 on the method tail).
872 // A target that is a loop EXIT keeps its RawGoto resolution (the
873 // conv arm materializes the break): Pattern.clazz's switch-case
874 // `break` targets sit on the loop-bottom path AND name exits —
875 // continuing there made every switch exit abrupt and the post-
876 // switch tail unreachable (无法访问的语句 x3 trees).
877 let is_any_exit = self.loops.iter().any(|l| l.exits.contains(&target));
878 if !is_any_exit && self.switches.is_empty() {
879 // STUB-TARGET continue: a statement-free confluence stub that
880 // can reach an enclosing header re-iterates that loop (its own
881 // flow was emitted earlier in this body copy; IPP's can_reach
882 // targets t=43/18/96/89/114/21/7 are all stubs).
883 // HEADER-ADJACENT statement-bearing target: the jump re-runs a
884 // shared tail whose statements were NOT emitted on this path —
885 // inline them and continue (jdk11 BigInteger.nextProbablePrime
886 // sieve arms: Goto{result=result.add(TWO)} flowing straight to
887 // the header; a bare Continue skipped the advance and left
888 // while(true) as the method's only completion: 缺少返回语句).
889 // Statement-bearing targets with a BRANCHING or statement
890 // bearing chain to the header keep RawGoto resolution (huc
891 // getInputStream0's clone/getValue tails flow through shared
892 // forward code — continuing there would skip it).
893 let mut via_label: Option<Option<String>> = None;
894 if self.results[target].stmts.is_empty() {
895 if let Some(i) = (0..self.loops.len()).rev().find(|&i| {
896 crate::structure::can_reach_cfg(self.cfg, target, self.loops[i].header, 4096)
897 }) {
898 let depth = self.loops.len() - 1 - i;
899 via_label = Some(if depth == 0 {
900 None
901 } else {
902 Some(self.loops[i].label.clone())
903 });
904 }
905 } else {
906 let mut x = target;
907 for _ in 0..8 {
908 if let Some(hi) = self.loops.iter().position(|l| l.header == x) {
909 let depth = self.loops.len() - 1 - hi;
910 via_label = Some(if depth == 0 {
911 None
912 } else {
913 Some(self.loops[hi].label.clone())
914 });
915 break;
916 }
917 if !self.results[x].stmts.is_empty() && x != target {
918 break;
919 }
920 if self.cfg.blocks[x].succ.len() != 1 {
921 break;
922 }
923 x = self.cfg.blocks[x].succ[0];
924 }
925 }
926 if let Some(lbl) = via_label {
927 return Some(if self.results[target].stmts.is_empty() {
928 Jump::Continue(lbl)
929 } else {
930 Jump::ContinueVia(target, lbl)
931 });
932 }
933 }
934 // Inside a loop, a forward escape that cannot reach back into the
935 // loop is a break of the innermost loop.
936 if !self.loops.is_empty()
937 && !crate::structure::can_reach_cfg(
938 self.cfg,
939 target,
940 self.loops[self.loops.len() - 1].header,
941 4096,
942 )
943 {
944 return Some(Jump::Break(None));
945 }
946 // Unstructured jump.
947 Some(Jump::RawGoto(target))
948 }
949
950 /// True if `a` post-dominates `b` (every path from b exits through a).
951 /// True when a jump to `t` is equivalent to natural fallthrough: `t`
952 /// (or the end of a chain of statement-free blocks starting at `t`) is
953 /// a copied shared tail or an open if-follow that the enclosing flow
954 /// will emit next anyway.
955 /// True when `t` is the head of a javac retry loop: some exception
956 /// handler protecting `t` has no normal preds and flows straight back
957 /// to `t` (`catch (InterruptedException e) { ..; goto head }`). A Goto
958 /// to such a header re-executes the protected body — inlining the
959 /// body's terminator as a "shared tail copy" drops it out of its try
960 /// (jdk26 Future.exceptionNow: the IE catch got `get(); throw ISE`
961 /// unprotected — 未报告的异常错误 InterruptedException).
962 fn is_retry_loop_header(&self, t: usize) -> bool {
963 self.cfg.exc_edges.iter().any(|e| {
964 e.from == t
965 && e.to != t
966 && self.cfg.blocks[e.to].pred.is_empty()
967 && self.cfg.blocks[e.to].succ.len() == 1
968 && self.cfg.blocks[e.to].succ[0] == t
969 })
970 }
971
972 fn reaches_copy_tail(&self, t: usize) -> bool {
973 let mut x = t;
974 for _ in 0..8 {
975 if self.copied_tails.contains(&x) || self.if_follows.contains(&x) {
976 return true;
977 }
978 if !self.results[x].stmts.is_empty() {
979 return false;
980 }
981 match &self.results[x].term {
982 Term::Fallthrough | Term::Goto => match self.cfg.blocks[x].succ.first() {
983 Some(&n) => x = n,
984 None => return false,
985 },
986 _ => return false,
987 }
988 }
989 false
990 }
991
992 #[allow(dead_code)]
993 fn postdominates(&self, a: usize, b: usize, universe: &HashSet<usize>) -> bool {
994 // BFS from b avoiding a; if no exit block is reachable, a post-dominates.
995 let mut seen = HashSet::default();
996 let mut q = std::collections::VecDeque::new();
997 q.push_back(b);
998 seen.insert(b);
999 while let Some(x) = q.pop_front() {
1000 for &s in &self.cfg.blocks[x].succ {
1001 if s == a {
1002 continue;
1003 }
1004 if !universe.contains(&s) {
1005 continue;
1006 }
1007 if seen.insert(s) {
1008 q.push_back(s);
1009 }
1010 }
1011 if self.cfg.blocks[x].succ.is_empty() && x != a && x != b {
1012 // reached an exit without passing through a
1013 return false;
1014 }
1015 }
1016 // also treat blocks leaving the universe as exits
1017 true
1018 }
1019
1020 /// Classify a loop region into while / do-while / infinite and assemble
1021 /// the final statement.
1022 fn classify_loop(&mut self, header: usize, body_stmt: Stmt, ctx: LoopCtx) -> Stmt {
1023 let term = self.results[header].term.clone();
1024 let succs = self.cfg.blocks[header].succ.clone();
1025
1026 match term {
1027 Term::Cond { cond } if succs.len() == 2 => {
1028 let taken = succs[1];
1029 let fall = succs[0];
1030 if taken == header && ctx.exits.contains(&fall) {
1031 // Self-loop: the header block holds both the body and
1032 // the trailing conditional back edge → do-while. The
1033 // fallthrough side is the loop exit. (When the fall
1034 // side is INTERIOR the self-edge is an in-body retry
1035 // jump of a loop with no normal completion at all —
1036 // rotating it into a do-while bottom invents an exit
1037 // path: jdk11/17/26 DSAParameterGenerator.generatePandQ's
1038 // `if (!resultQ.isProbablePrime(..)) goto <Q-gen>`
1039 // self-retry falls through into the P-search interior;
1040 // the fabricated do-while fell out to the method end —
1041 // 缺少返回语句. Those shapes take the interior-diamond
1042 // while(true) arm below.)
1043 // A compound (multi-test) trailing condition folds first:
1044 // `if(c1)continue; .. if(cN)continue; else exit;` becomes
1045 // `do{body}while(c1||..||cN); exit`.
1046 if let Some((stmts, c, exit)) = extract_compound_do_while(&body_stmt) {
1047 // An extracted exit that is the loop's OWN unlabeled
1048 // break must be dropped: the do-while condition false
1049 // already exits, and re-emitting the break OUTSIDE the
1050 // loop is a stray `break;` (compile error). SESE's
1051 // explicit `Goto{header}` -> continue makes self-loop
1052 // bodies hit this shape (`if (c) continue; else break;`).
1053 // return/throw/labeled-break exits stay (real fall-out).
1054 if matches!(exit, Stmt::Break(None)) {
1055 return Stmt::DoWhile {
1056 body: Box::new(Stmt::Block(stmts)),
1057 cond: c,
1058 };
1059 }
1060 return Stmt::Block(vec![
1061 Stmt::DoWhile {
1062 body: Box::new(Stmt::Block(stmts)),
1063 cond: c,
1064 },
1065 exit,
1066 ]);
1067 }
1068 let (stmts, c) = match extract_trailing_do_while(&body_stmt) {
1069 Some(x) => x,
1070 None => (stmt_to_vec(body_stmt), cond.clone()),
1071 };
1072 return Stmt::DoWhile {
1073 body: Box::new(Stmt::Block(stmts)),
1074 cond: c,
1075 };
1076 }
1077 if fall == header && ctx.exits.contains(&taken) {
1078 // Inverted self-loop: `while (!(c)) body` — the taken
1079 // side exits, the fallthrough loops back.
1080 let inner2 = strip_trailing_continue(body_stmt);
1081 return Stmt::While {
1082 cond: negate(cond.clone()),
1083 body: Box::new(inner2),
1084 };
1085 }
1086 let taken_is_exit = ctx.exits.contains(&taken);
1087 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1088 eprintln!(
1089 "CLASSIFY2 header={} taken={} fall={} exits={:?}",
1090 header, taken, fall, ctx.exits
1091 );
1092 }
1093 if !taken_is_exit && !ctx.exits.contains(&fall) {
1094 // Multi-block loop condition. The header's fallthrough
1095 // may be a pure condition-continuation test block whose
1096 // taken side is the loop exit and whose fall side rejoins
1097 // the body (`taken`). javac compiles `while (cH || !cB)`
1098 // exactly this way:
1099 // H: if cH goto body; B: if cB goto exit; body: ..; goto H
1100 // Fold B into the while condition. Without this the body
1101 // walk re-emits H and B as empty `if`s inside a
1102 // `while(true)`, dropping the exit and leaving an infinite
1103 // loop plus an unreachable trailing statement.
1104 if self.results[fall].stmts.is_empty() {
1105 if let Term::Cond { cond: cb } = &self.results[fall].term {
1106 let bs = self.cfg.blocks[fall].succ.clone();
1107 if bs.len() == 2 && ctx.exits.contains(&bs[1]) && bs[0] == taken {
1108 let combined = Expr::Bin {
1109 op: BinOp::LogOr,
1110 l: Box::new(cond.clone()),
1111 r: Box::new(negate(cb.clone())),
1112 ty: None,
1113 };
1114 // The body is the converted region
1115 // remainder, NOT the taken block's raw
1116 // statements: when `taken` is wrapped in
1117 // regions (a try/catch group around the
1118 // body call), the raw-stmt body silently
1119 // drops them (jdk11 SocketChannelImpl
1120 // .implCloseSelectableChannel: the
1121 // wait()-loop's try/catch(Interrupted
1122 // Exception) region vanished — bare
1123 // wait() — 未报告的异常错误). The
1124 // leading guard If is the condition being
1125 // folded here; strip it and keep the rest.
1126 let mut parts = stmt_to_vec(body_stmt);
1127 if matches!(parts.first(), Some(Stmt::If { .. })) {
1128 parts.remove(0);
1129 }
1130 let body = if parts.is_empty() {
1131 Stmt::Block(self.results[taken].stmts.clone())
1132 } else {
1133 let rest = if parts.len() == 1 {
1134 parts.pop().unwrap()
1135 } else {
1136 Stmt::Block(parts)
1137 };
1138 strip_trailing_continue(rest)
1139 };
1140 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1141 eprintln!(
1142 "CLASSIFY-CONDCHAIN header={} B={} body={} exits={:?}",
1143 header, fall, taken, ctx.exits
1144 );
1145 }
1146 return Stmt::While {
1147 cond: combined,
1148 body: Box::new(body),
1149 };
1150 }
1151 }
1152 }
1153 // Neither successor leaves the loop: the header's Cond
1154 // is an interior diamond (e.g. a ternary inside an
1155 // infinite `for(;;)` whose back edge targets the header
1156 // top), not an exit test. Keep it in the body.
1157 return Stmt::While {
1158 cond: Expr::Const(ConstVal::Int(1)),
1159 body: Box::new(body_stmt),
1160 };
1161 }
1162 // The body walk re-emitted the header's own If region; unwrap
1163 // it so the loop reads `while (C) { else-part }`.
1164 let (inner, exit_stmts) = split_leading_if(&body_stmt, &cond);
1165 // A top test the structurer wrapped in a TRY (the header
1166 // block sits inside its handler's protected range — jdk26
1167 // Bits.reserveMemory's backoff retry loop tests
1168 // tryReserveOrClean(..) throws InterruptedException under
1169 // its own catch(IE) retry handler): rotating the test into
1170 // `while (C)` evaluates the call OUTSIDE every handler —
1171 // 未报告的异常错误InterruptedException. split_leading_if
1172 // deliberately cannot reach through the Try (the rotated
1173 // cond would still be unprotected), so keep the test where
1174 // the bytecode has it and stay on while(true). The earlier
1175 // implCloseSelectableChannel guard (raw-stmt bodies losing
1176 // the leading If's statements) does not apply: the If lives
1177 // intact inside the Try.
1178
1179 if crate::dbg_flag!("JCDC_DBG_LOOP") {
1180 eprintln!(
1181 "CLASSIFY header={} taken_is_exit={} exit_stmts={}",
1182 header,
1183 taken_is_exit,
1184 exit_stmts.len()
1185 );
1186 }
1187 let inner = strip_trailing_continue(inner);
1188 let while_cond = if taken_is_exit {
1189 negate(cond.clone())
1190 } else {
1191 cond.clone()
1192 };
1193 let plain_exit = exit_stmts.is_empty()
1194 || matches!(exit_stmts.as_slice(), [Stmt::Break(_)])
1195 || matches!(exit_stmts.as_slice(), [Stmt::Block(b)] if b.is_empty());
1196 if taken_is_exit && plain_exit {
1197 match try_protected_dowhile(inner, &cond) {
1198 Ok(dw) => dw,
1199 Err(inner) => Stmt::While {
1200 cond: while_cond,
1201 body: Box::new(inner),
1202 },
1203 }
1204 } else if taken_is_exit {
1205 // Exit branch runs statements before leaving:
1206 // while (true) { if (C_exit) { stmts; break; } body }
1207 // `cond` IS the exit test here (the taken side exits —
1208 // negate(cond) inverted the guard: jdk17 CHM
1209 // comparableClassFor emitted `if (i < len) return null`
1210 // inside while(true), inverting the loop). The break is
1211 // only appended when the exit statements do not already
1212 // terminate (return null + break = unreachable stmt).
1213 let mut ex = exit_stmts;
1214 let already_term = matches!(
1215 ex.last(),
1216 Some(Stmt::Return(_)) | Some(Stmt::Throw(_)) | Some(Stmt::Break(_))
1217 );
1218 if !already_term {
1219 ex.push(Stmt::Break(None));
1220 }
1221 let guard = Stmt::If {
1222 cond: cond.clone(),
1223 then_stmt: Box::new(Stmt::Block(ex)),
1224 else_stmt: None,
1225 };
1226 let mut v = vec![guard];
1227 v.extend(stmt_to_vec(inner));
1228 Stmt::While {
1229 cond: Expr::Const(ConstVal::Int(1)),
1230 body: Box::new(Stmt::Block(v)),
1231 }
1232 } else {
1233 // Unusual orientation: the fallthrough side exits.
1234 if exit_stmts.is_empty()
1235 || matches!(exit_stmts.as_slice(), [Stmt::Break(_)])
1236 || matches!(exit_stmts.as_slice(), [Stmt::Block(b)] if b.is_empty())
1237 {
1238 // then-side was the exit scaffolding; body is `inner`
1239 match try_protected_dowhile(inner, &cond) {
1240 Ok(dw) => dw,
1241 Err(inner) => Stmt::While {
1242 cond: while_cond,
1243 body: Box::new(inner),
1244 },
1245 }
1246 } else {
1247 // then-side carries the body ending with the back
1248 // edge; fallthrough exits.
1249 let body2 = strip_trailing_continue(stmts_to_stmt(exit_stmts));
1250 Stmt::While {
1251 cond: while_cond,
1252 body: Box::new(body2),
1253 }
1254 }
1255 }
1256 }
1257 Term::Goto | Term::Fallthrough => {
1258 // Possibly do-while: the body ends with a conditional jump
1259 // back to the header (`if (c) continue;` or inverted), or a
1260 // COMPOUND run of such tests ending in the loop exit.
1261 if let Some((stmts, c, exit)) = extract_compound_do_while(&body_stmt) {
1262 // Same guard as the self-loop compound path above: an
1263 // extracted exit that is the loop's OWN unlabeled break
1264 // must be dropped — the do-while condition false already
1265 // exits, and a `break;` re-emitted OUTSIDE the loop is a
1266 // stray (it also makes stmt_terminates treat the sequence
1267 // as terminated, so prune_unreachable drops the real
1268 // tail: jdk26 Resolver.bind `return this` — 缺少返回语句).
1269 // SESE's rotated do-whiles hit this shape: the tail test
1270 // block becomes `if (c) continue; else break;`.
1271 // return/throw/labeled-break exits stay (real fall-out).
1272 if matches!(exit, Stmt::Break(None)) {
1273 return Stmt::DoWhile {
1274 body: Box::new(Stmt::Block(stmts)),
1275 cond: c,
1276 };
1277 }
1278 return Stmt::Block(vec![
1279 Stmt::DoWhile {
1280 body: Box::new(Stmt::Block(stmts)),
1281 cond: c,
1282 },
1283 exit,
1284 ]);
1285 }
1286 if let Some((stmts, c)) = extract_trailing_do_while(&body_stmt) {
1287 return Stmt::DoWhile {
1288 body: Box::new(Stmt::Block(stmts)),
1289 cond: c,
1290 };
1291 }
1292 Stmt::While {
1293 cond: Expr::Const(ConstVal::Int(1)),
1294 body: Box::new(body_stmt),
1295 }
1296 }
1297 _ => Stmt::While {
1298 cond: Expr::Const(ConstVal::Int(1)),
1299 body: Box::new(body_stmt),
1300 },
1301 }
1302 }
1303
1304 /// True if `target` is inside the loop currently being classified
1305 /// (approximated: target can reach the header again).
1306 #[allow(dead_code)]
1307 fn loop_contains(&self, header: usize, target: usize) -> bool {
1308 can_reach(self.cfg, target, header, 8192)
1309 }
1310
1311 #[allow(dead_code)]
1312 fn loop_exit_contains(&self, header: usize, target: usize) -> bool {
1313 // The loop currently being classified is the innermost on the stack
1314 // BEFORE it was popped; ctx.exits was consumed. Recompute cheaply:
1315 // target is an exit if it is not reachable-back-to-header within the
1316 // loop members — approximated by checking the parent scope recorded
1317 // in loops stack is empty here; use cfg: header's own successors that
1318 // leave... For the header itself, an exit is any successor that is
1319 // not dominated by header OR is not in the natural loop. Simple
1320 // approximation: a successor s is "in loop" if s can reach header
1321 // again via normal edges.
1322 can_reach(self.cfg, target, header, 4096).not()
1323 }
1324}
1325
1326#[allow(dead_code)]
1327trait NotExt {
1328 fn not(self) -> bool;
1329}
1330impl NotExt for bool {
1331 fn not(self) -> bool {
1332 !self
1333 }
1334}
1335
1336#[allow(dead_code)]
1337fn can_reach(cfg: &Cfg, from: usize, to: usize, budget: usize) -> bool {
1338 if from == to {
1339 return true;
1340 }
1341 let mut seen = HashSet::default();
1342 let mut q = std::collections::VecDeque::new();
1343 q.push_back(from);
1344 seen.insert(from);
1345 let mut n = 0;
1346 while let Some(b) = q.pop_front() {
1347 n += 1;
1348 if n > budget {
1349 return false;
1350 }
1351 for &s in &cfg.blocks[b].succ {
1352 if s == to {
1353 return true;
1354 }
1355 if seen.insert(s) {
1356 q.push_back(s);
1357 }
1358 }
1359 }
1360 false
1361}
1362
1363/// If the body starts with the loop header's own `If` (same condition
1364/// expression), return (else-branch content, then-branch content).
1365/// Otherwise return (body, []).
1366fn stmts_to_stmt(v: Vec<Stmt>) -> Stmt {
1367 match v.len() {
1368 0 => Stmt::Block(vec![]),
1369 1 => v.into_iter().next().unwrap(),
1370 _ => Stmt::Block(v),
1371 }
1372}
1373
1374/// When the loop's top test lives inside a Try in `inner` (the header
1375/// block sits in its handler's protected range — jdk26
1376/// Bits.reserveMemory's backoff retry loop tests
1377/// `tryReserveOrClean(..) throws InterruptedException` under its own
1378/// catch(IE) handler), the `while (C)` rotation would evaluate C
1379/// OUTSIDE every handler — 未报告的异常错误InterruptedException.
1380/// Rewrite as a do-while instead: inject `break` into the embedded
1381/// If's EMPTY arm (the exit side — the exit blocks live outside the
1382/// loop region as its continuation siblings, so the empty arm is the
1383/// fall-out) and loop unconditionally. The sibling tail then stays
1384/// reachable as the do-while's natural completion. Returns None when
1385/// the test is not try-wrapped (plain rotation applies) or when the
1386/// shape is ambiguous (both If arms materialized).
1387fn try_protected_dowhile(inner: Stmt, cond: &Expr) -> Result<Stmt, Stmt> {
1388 // The exit side of the embedded test: an Empty region conversion
1389 // (empty block / missing else) or an ALREADY-materialized plain
1390 // `break` (the merged-span shape walks the sleep region as the
1391 // then-arm and breaks on the exit side directly).
1392 fn is_empty(s: &Stmt) -> bool {
1393 match s {
1394 Stmt::Block(v) => v.is_empty() || (v.len() == 1 && is_empty(&v[0])),
1395 Stmt::Break(None) => false,
1396 _ => false,
1397 }
1398 }
1399 fn inject(s: &mut Stmt, cond: &Expr) -> bool {
1400 match s {
1401 Stmt::If {
1402 cond: c,
1403 then_stmt,
1404 else_stmt,
1405 } if c == cond => {
1406 let then_empty = is_empty(then_stmt);
1407 let else_empty = else_stmt.as_ref().map(|e| is_empty(e)).unwrap_or(true);
1408 if then_empty && !else_empty {
1409 *then_stmt = Box::new(Stmt::Break(None));
1410 true
1411 } else if else_empty && !then_empty {
1412 *else_stmt = Some(Box::new(Stmt::Break(None)));
1413 true
1414 } else {
1415 false
1416 }
1417 }
1418 Stmt::Try { body, .. } | Stmt::TryWithResources { body, .. } => inject(body, cond),
1419 Stmt::Block(v) => v.first_mut().map(|f| inject(f, cond)).unwrap_or(false),
1420 Stmt::Labeled { body, .. } | Stmt::Synchronized { body, .. } => inject(body, cond),
1421 _ => false,
1422 }
1423 }
1424 let mut b = inner;
1425 if inject(&mut b, cond) {
1426 Ok(Stmt::DoWhile {
1427 body: Box::new(b),
1428 cond: Expr::Const(ConstVal::Int(1)),
1429 })
1430 } else {
1431 Err(b)
1432 }
1433}
1434
1435fn split_leading_if(body: &Stmt, cond: &Expr) -> (Stmt, Vec<Stmt>) {
1436 let items = match body {
1437 Stmt::Block(v) => v,
1438 other => {
1439 return match other {
1440 Stmt::If {
1441 cond: c,
1442 then_stmt,
1443 else_stmt,
1444 } if c == cond => (
1445 else_stmt
1446 .as_ref()
1447 .map(|e| (**e).clone())
1448 .unwrap_or(Stmt::Block(vec![])),
1449 stmt_to_vec((**then_stmt).clone()),
1450 ),
1451 _ => (other.clone(), vec![]),
1452 };
1453 }
1454 };
1455 if let Some(first) = items.first() {
1456 if let Stmt::If {
1457 cond: c,
1458 then_stmt,
1459 else_stmt,
1460 } = first
1461 {
1462 if c == cond {
1463 let inner = if items.len() == 1 {
1464 else_stmt
1465 .as_ref()
1466 .map(|e| (**e).clone())
1467 .unwrap_or(Stmt::Block(vec![]))
1468 } else {
1469 let mut rest = vec![else_stmt
1470 .as_ref()
1471 .map(|e| (**e).clone())
1472 .unwrap_or(Stmt::Block(vec![]))];
1473 rest.extend(items[1..].iter().cloned());
1474 Stmt::Block(rest)
1475 };
1476 return (inner, stmt_to_vec((**then_stmt).clone()));
1477 }
1478 }
1479 }
1480 (body.clone(), vec![])
1481}
1482
1483/// Remove a trailing `continue;` (loop back-edge) from a statement list.
1484fn strip_trailing_continue(s: Stmt) -> Stmt {
1485 match s {
1486 Stmt::Block(mut v) => {
1487 while matches!(v.last(), Some(Stmt::Continue(None))) {
1488 v.pop();
1489 }
1490 if v.len() == 1 && matches!(v[0], Stmt::Block(_)) {
1491 let inner = match v.pop().unwrap() {
1492 Stmt::Block(inner) => inner,
1493 _ => unreachable!(),
1494 };
1495 return strip_trailing_continue(Stmt::Block(inner));
1496 }
1497 Stmt::Block(v)
1498 }
1499 Stmt::Continue(None) => Stmt::Block(vec![]),
1500 other => other,
1501 }
1502}
1503
1504pub fn negate(e: Expr) -> Expr {
1505 match e {
1506 Expr::Un { op: UnOp::Not, e } => *e,
1507 // De Morgan for the short-circuit operators: !(A && B) =
1508 // !A || !B, !(A || B) = !A && !B — recursing through negate
1509 // keeps comparison operands cleanly inverted. An op-only swap
1510 // (the historical invert() LogAnd<->LogOr mapping) silently
1511 // changed the condition's meaning: Arrays.equals' folded
1512 // `a == null || a2 == null` inverted to `a == null && a2 ==
1513 // null`, NPE-ing the a2==null path.
1514 Expr::Bin {
1515 op: op @ (crate::ir::expr::BinOp::LogAnd | crate::ir::expr::BinOp::LogOr),
1516 l,
1517 r,
1518 ty,
1519 } => {
1520 let swapped = if matches!(op, crate::ir::expr::BinOp::LogAnd) {
1521 crate::ir::expr::BinOp::LogOr
1522 } else {
1523 crate::ir::expr::BinOp::LogAnd
1524 };
1525 Expr::Bin {
1526 op: swapped,
1527 l: Box::new(negate(*l)),
1528 r: Box::new(negate(*r)),
1529 ty,
1530 }
1531 }
1532 Expr::Bin { op, l, r, ty } => {
1533 if let Some(inv) = op.invert() {
1534 Expr::Bin { op: inv, l, r, ty }
1535 } else {
1536 Expr::Un {
1537 op: UnOp::Not,
1538 e: Box::new(Expr::Bin { op, l, r, ty }),
1539 }
1540 }
1541 }
1542 other => Expr::Un {
1543 op: UnOp::Not,
1544 e: Box::new(other),
1545 },
1546 }
1547}
1548
1549/// True for a bare `continue;` (or a block wrapping just one).
1550fn is_continue_stmt(s: &Stmt) -> bool {
1551 matches!(s, Stmt::Continue(None))
1552 || matches!(s, Stmt::Block(v) if v.len() == 1 && matches!(v[0], Stmt::Continue(None)))
1553}
1554
1555/// Detect a COMPOUND trailing do-while condition: the body ends with a run of
1556/// `if (c1) continue;` ... `if (cN) continue; else <exit>;`. javac emits this
1557/// for `do { body } while (c1 || ... || cN);` followed by the loop exit when
1558/// the condition is a short-circuit `||` whose tests each jump back to the
1559/// body head (e.g. `Random.internalNextInt`: `while (r < origin || r >=
1560/// bound)`). Fold the run into a single do-while whose condition is the `||`
1561/// of the tests, returning the body statements, that condition, and the exit
1562/// action (`<exit>`) to emit right after the loop.
1563fn extract_compound_do_while(body: &Stmt) -> Option<(Vec<Stmt>, Expr, Stmt)> {
1564 // Flatten nested straight-line blocks: the loop body may group the first
1565 // condition test(s) inside a nested Block (the self-loop header's region)
1566 // while later tests are siblings. Splicing pure sequence blocks is safe.
1567 fn flatten_seq(s: &Stmt) -> Vec<Stmt> {
1568 match s {
1569 Stmt::Block(v) => {
1570 let mut out = Vec::new();
1571 for x in v {
1572 out.extend(flatten_seq(x));
1573 }
1574 out
1575 }
1576 other => vec![other.clone()],
1577 }
1578 }
1579 let mut stmts = flatten_seq(body);
1580 // Unnest the rendered if/else-if CHAIN spine: javac's bottom-tested
1581 // `if (c1) continue; if (c2) continue; ... else exit;` disjunct run
1582 // converts to ONE nested If whose else arms hold the remaining
1583 // tests — opaque to the backwards scan below, which would fold only
1584 // c1 and push the whole c2..cN chain out of the loop as the "exit"
1585 // (jdk26 ML_DSA.signInternal: `do { expandMask.. } while
1586 // (vectorNormBound(z,..));` with the c2/hint chain — and its
1587 // continue/break — stranded after the loop, continue 在 loop 外部
1588 // ×2). Splitting each continue-then If back into siblings restores
1589 // the flat run; semantics are identical (the then arm is a bare
1590 // continue with no fallthrough, and the LAST else is preserved as
1591 // the final statement's else = the loop's exit route).
1592 {
1593 let mut flat: Vec<Stmt> = Vec::new();
1594 for st in stmts.into_iter() {
1595 let mut cur = st;
1596 loop {
1597 match cur {
1598 Stmt::If {
1599 cond,
1600 then_stmt,
1601 else_stmt: Some(e),
1602 } if is_continue_stmt(&then_stmt) => {
1603 flat.push(Stmt::If {
1604 cond,
1605 then_stmt,
1606 else_stmt: None,
1607 });
1608 cur = *e;
1609 }
1610 other => {
1611 flat.push(other);
1612 break;
1613 }
1614 }
1615 }
1616 }
1617 stmts = flat;
1618 }
1619 let n = stmts.len();
1620 if n < 2 {
1621 return None;
1622 }
1623 // Last statement: `if (c_last) continue; else <exit>` — the exit is the
1624 // loop's fall-out (a return/throw/break), NOT another continue.
1625 let (c_last, exit) = match &stmts[n - 1] {
1626 Stmt::If {
1627 cond,
1628 then_stmt,
1629 else_stmt: Some(e),
1630 } if is_continue_stmt(then_stmt) && !is_continue_stmt(e) => (cond.clone(), (**e).clone()),
1631 _ => return None,
1632 };
1633 // Walk backwards over preceding `if (ci) continue;` (no else) tests.
1634 let mut conds = vec![c_last];
1635 let mut i = n - 1;
1636 while i >= 1 {
1637 match &stmts[i - 1] {
1638 Stmt::If {
1639 cond,
1640 then_stmt,
1641 else_stmt: None,
1642 } if is_continue_stmt(then_stmt) => {
1643 conds.push(cond.clone());
1644 i -= 1;
1645 }
1646 _ => break,
1647 }
1648 }
1649 let body_stmts: Vec<Stmt> = stmts[..i].to_vec();
1650 // cond = c1 || c2 || ... || c_last (conds currently reversed).
1651 let mut it = conds.into_iter().rev();
1652 let mut cond = it.next().unwrap();
1653 for c in it {
1654 cond = Expr::Bin {
1655 op: BinOp::LogOr,
1656 l: Box::new(c),
1657 r: Box::new(cond),
1658 ty: None,
1659 };
1660 }
1661 Some((body_stmts, cond, exit))
1662}
1663
1664/// Detect a trailing do-while condition at the end of a loop body:
1665/// `if (c) continue;` → cond c, or `if (c) {} else continue;` → cond !c.
1666fn extract_trailing_do_while(body: &Stmt) -> Option<(Vec<Stmt>, Expr)> {
1667 let mut stmts = match body {
1668 Stmt::Block(v) => v.clone(),
1669 other => vec![other.clone()],
1670 };
1671 if stmts.is_empty() {
1672 return None;
1673 }
1674 let n = stmts.len() - 1;
1675 if let Stmt::If {
1676 cond,
1677 then_stmt,
1678 else_stmt,
1679 } = &stmts[n]
1680 {
1681 let then_is_cont = |s: &Stmt| {
1682 matches!(s, Stmt::Continue(None))
1683 || matches!(s, Stmt::Block(v) if v.len() == 1 && matches!(v[0], Stmt::Continue(None)))
1684 };
1685 if then_is_cont(then_stmt) && else_stmt.is_none() {
1686 let c = cond.clone();
1687 stmts.pop();
1688 return Some((stmts, c));
1689 }
1690 if else_stmt.as_ref().map(|e| then_is_cont(e)).unwrap_or(false)
1691 && (matches!(&**then_stmt, Stmt::Block(v) if v.is_empty()))
1692 {
1693 let c = negate(cond.clone());
1694 stmts.pop();
1695 return Some((stmts, c));
1696 }
1697 }
1698 None
1699}
1700
1701/// If the body ends with `if (c) continue;` (Continue(None), no else),
1702/// strip it and return (body without trailing if, c).
1703#[allow(dead_code)]
1704fn extract_trailing_continue_if(body: &Stmt) -> Option<(Vec<Stmt>, Expr)> {
1705 let mut stmts = match body {
1706 Stmt::Block(v) => v.clone(),
1707 other => vec![other.clone()],
1708 };
1709 if stmts.is_empty() {
1710 return None;
1711 }
1712 let n = stmts.len() - 1;
1713 // Direct trailing `if (c) continue;`.
1714 let is_direct = match &stmts[n] {
1715 Stmt::If {
1716 then_stmt,
1717 else_stmt: None,
1718 ..
1719 } => {
1720 matches!(&**then_stmt, Stmt::Continue(None))
1721 || matches!(&**then_stmt, Stmt::Block(tb) if tb.len() == 1 && matches!(tb[0], Stmt::Continue(None)))
1722 }
1723 _ => false,
1724 };
1725 if is_direct {
1726 if let Stmt::If { cond, .. } = stmts.pop().unwrap() {
1727 return Some((stmts, cond));
1728 }
1729 }
1730 // Descend into a trailing block.
1731 if matches!(stmts.last(), Some(Stmt::Block(_))) {
1732 let last = stmts.pop().unwrap();
1733 if let Some((inner, c)) = extract_trailing_continue_if(&last) {
1734 if !inner.is_empty() {
1735 stmts.push(Stmt::Block(inner));
1736 }
1737 return Some((stmts, c));
1738 } else {
1739 stmts.push(last);
1740 }
1741 }
1742 None
1743}
1744
1745/// Separate the first Basic block's statements from the rest of a body.
1746/// (Bodies built by `walk` start with the header's statements.)
1747#[allow(dead_code)]
1748fn split_first_block(body: Stmt) -> (Stmt, Option<usize>) {
1749 // The body is whatever the walk produced; we keep it intact — header
1750 // statements were already woven in by conv(Basic). Return as-is.
1751 (body, None)
1752}
1753
1754fn stmt_to_vec(s: Stmt) -> Vec<Stmt> {
1755 match s {
1756 Stmt::Block(v) => v,
1757 other => vec![other],
1758 }
1759}