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