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