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