Skip to main content

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}