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