Skip to main content

jdc_core/
convert.rs

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