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