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