Skip to main content

jdc_core/
structure.rs

1//! Control flow structuring: CFG of basic blocks → nested region tree.
2//!
3//! Strategy:
4//! 1. Iterative dominator computation (Cooper-Harvey-Kennedy).
5//! 2. Exception ranges grouped by (start, end) span → try regions carved out
6//!    first; bodies and handlers are structured recursively.
7//! 3. Inside each flow scope, walk blocks from the entry:
8//!    - loop header (back-edge target that dominates the source) → natural
9//!      loop region,
10//!    - conditional terminal → If region (branches structured up to the
11//!      immediate post-dominator),
12//!    - switch terminal → Switch region with per-case sub-scopes,
13//!    - forward goto within scope → continue walking at the target,
14//!    - edges that leave the scope → Goto nodes (resolved to
15//!      break/continue/labels in the conversion pass).
16
17use std::collections::{HashMap, HashSet, VecDeque};
18
19use crate::cfg::Cfg;
20use crate::ir::build::{BlockResult, SwitchTargets, Term};
21use crate::ir::expr::Expr;
22
23// ---------------------------------------------------------------------------
24// Dominators & flow queries
25// ---------------------------------------------------------------------------
26
27/// Method-invariant inputs of `immediate_postdom` (see postdom_ipdom).
28/// Frozen at first use; every input is immutable after construction, so
29/// the cache cannot drift from the per-call recompute it replaces.
30#[derive(Clone)]
31struct PostdomCtx {
32    exempt: HashSet<usize>,
33    terminators: HashSet<usize>,
34    final_writers: HashSet<usize>,
35    abrupt_only: HashSet<usize>,
36    stmt_counts: Vec<usize>,
37}
38
39#[derive(Clone, Debug)]
40pub struct DomInfo {
41    pub idom: Vec<usize>,
42}
43
44/// Perf instrumentation: how many times the walk recomputed dominators
45/// (and over how many blocks). Off by default — the unconditional atomics
46/// contended on 7M calls/run.
47pub static DOM_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
48pub static DOM_BLOCKS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
49pub static DOM_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
50
51/// Enable dominator-recompute counters (diagnostics).
52pub fn set_dom_counters(on: bool) {
53    DOM_ON.store(on, std::sync::atomic::Ordering::Relaxed);
54}
55
56impl DomInfo {
57    pub fn dominates(&self, a: usize, b: usize) -> bool {
58        let n = self.idom.len();
59        if a >= n || b >= n {
60            return false;
61        }
62        let mut cur = b;
63        let mut guard = 0;
64        loop {
65            if cur == a {
66                return true;
67            }
68            let next = self.idom[cur];
69            if next == cur || guard > n + 1 {
70                return false;
71            }
72            guard += 1;
73            cur = next;
74        }
75    }
76}
77
78pub fn reverse_postorder(cfg: &Cfg, entry: usize, universe: &HashSet<usize>) -> Vec<usize> {
79    let mut visited = vec![false; cfg.blocks.len()];
80    let mut out = Vec::new();
81    if !universe.contains(&entry) {
82        return out;
83    }
84    let mut stack: Vec<(usize, usize)> = vec![(entry, 0)];
85    visited[entry] = true;
86    while let Some((b, idx)) = stack.last_mut() {
87        let b = *b;
88        if *idx < cfg.blocks[b].succ.len() {
89            let s = cfg.blocks[b].succ[*idx];
90            *idx += 1;
91            if !visited[s] && universe.contains(&s) {
92                visited[s] = true;
93                stack.push((s, 0));
94            }
95        } else {
96            out.push(b);
97            stack.pop();
98        }
99    }
100    out.reverse();
101    out
102}
103
104pub fn compute_dominators(cfg: &Cfg, universe: &HashSet<usize>, entry: usize) -> DomInfo {
105    if DOM_ON.load(std::sync::atomic::Ordering::Relaxed) {
106        DOM_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
107        DOM_BLOCKS.fetch_add(
108            cfg.blocks.len() as u64,
109            std::sync::atomic::Ordering::Relaxed,
110        );
111    }
112    let n = cfg.blocks.len();
113    let mut idom = vec![usize::MAX; n];
114    if universe.contains(&entry) {
115        idom[entry] = entry;
116    }
117    let rpo = reverse_postorder(cfg, entry, universe);
118    let mut rpo_num = vec![usize::MAX; n];
119    for (i, &b) in rpo.iter().enumerate() {
120        rpo_num[b] = i;
121    }
122    let mut changed = true;
123    while changed {
124        changed = false;
125        for &b in &rpo {
126            if b == entry {
127                continue;
128            }
129            let mut new_idom = usize::MAX;
130            for &p in &cfg.blocks[b].pred {
131                if rpo_num[p] == usize::MAX || idom[p] == usize::MAX {
132                    continue;
133                }
134                new_idom = if new_idom == usize::MAX {
135                    p
136                } else {
137                    intersect(new_idom, p, &idom, &rpo_num)
138                };
139            }
140            if new_idom != usize::MAX && idom[b] != new_idom {
141                idom[b] = new_idom;
142                changed = true;
143            }
144        }
145    }
146    for i in 0..n {
147        if idom[i] == usize::MAX {
148            idom[i] = i;
149        }
150    }
151    DomInfo { idom }
152}
153
154fn intersect(mut a: usize, mut b: usize, idom: &[usize], rpo_num: &[usize]) -> usize {
155    let mut guard = 0;
156    while a != b && guard < 1_000_000 {
157        guard += 1;
158        while rpo_num[a] > rpo_num[b] {
159            a = idom[a];
160        }
161        while rpo_num[b] > rpo_num[a] {
162            b = idom[b];
163        }
164    }
165    a
166}
167
168thread_local! {
169    /// Set while a SESE consumed-arrival copy runs: there the copy IS the
170    /// arm's only emission route (the consumed merge has no sibling owner
171    /// part in this layout), so the shared-tail-confluence barrier must
172    /// not truncate it (jdk11/17/26 Pattern.family: the else arm's copy of
173    /// the b13 merge tail came out stage-2-less — the bar excluded the
174    /// 12-pred stage-2 switch head — and the method fell off its end,
175    /// 缺少返回语句 ×3 trees). Walk-side copy sites never set this.
176    pub static COPY_ALLOW_CONFLUENCE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
177}
178
179/// True when `from` reaches `to` through normal-flow successors without
180/// expanding any `barred` block (`to` itself may be barred — reaching it
181/// is the success condition). Used by PARKCHAIN's relaxed coherence.
182pub(crate) fn bypass_flows_to(cfg: &Cfg, from: usize, to: usize, barred: &HashSet<usize>) -> bool {
183    if from == to {
184        return true;
185    }
186    let mut seen: HashSet<usize> = HashSet::new();
187    let mut q: VecDeque<usize> = VecDeque::new();
188    q.push_back(from);
189    seen.insert(from);
190    while let Some(x) = q.pop_front() {
191        for &s in &cfg.blocks[x].succ {
192            if s == to {
193                return true;
194            }
195            if barred.contains(&s) || seen.contains(&s) {
196                continue;
197            }
198            seen.insert(s);
199            q.push_back(s);
200        }
201    }
202    false
203}
204
205thread_local! {
206    /// Per-method copy-budget override installed by the method size guard
207    /// in classdec (pathological copy explosion retry); consumed by
208    /// Structurer::with_diamonds at construction.
209    pub static BUDGET_OVERRIDE: std::cell::Cell<Option<u32>> = const { std::cell::Cell::new(None) };
210}
211
212/// Install/clear the per-method copy-budget override (see BUDGET_OVERRIDE).
213pub fn set_budget_override(v: Option<u32>) {
214    BUDGET_OVERRIDE.with(|c| c.set(v));
215}
216
217thread_local! {
218    /// Per-method walk visit budget (None = off). Bounds CALL COUNT: the
219    /// walk degrades to a Goto at the entry once the budget is spent —
220    /// conversion stays valid.
221    static WALK_VISIT_OVERRIDE: std::cell::Cell<Option<u64>> =
222        const { std::cell::Cell::new(None) };
223}
224
225/// Install/clear the per-method walk visit budget.
226pub fn set_walk_visit_budget(v: Option<u64>) {
227    WALK_VISIT_OVERRIDE.with(|c| c.set(v));
228}
229
230thread_local! {
231    /// Per-method walk wall-clock deadline (None = off). The visit budget
232    /// is deterministic but only bounds CALL COUNT — one pathological
233    /// method (Telegram SendMessagesHelper.sendMessage, 1734 blocks)
234    /// spends ~6ms per visit in scope/set construction, so even a tight
235    /// visit budget ran minutes. The deadline cuts the walk at a fixed
236    /// time; degradation is a Goto, same as the visit budget.
237    static WALK_DEADLINE: std::cell::Cell<Option<std::time::Instant>> =
238        const { std::cell::Cell::new(None) };
239}
240
241/// Install/clear the per-method walk deadline.
242pub fn set_walk_deadline(d: Option<std::time::Instant>) {
243    WALK_DEADLINE.with(|c| c.set(d));
244}
245
246/// Walk() calls consumed by the last method (feature "visit-stats").
247#[cfg(feature = "visit-stats")]
248thread_local! {
249    pub static WALK_VISITS_TOTAL: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
250}
251
252#[cfg(feature = "visit-stats")]
253pub fn walk_visits_consumed() -> u64 {
254    WALK_VISITS_TOTAL.with(|c| c.get())
255}
256
257#[cfg(feature = "visit-stats")]
258pub fn reset_visit_stats() {
259    WALK_VISITS_TOTAL.with(|c| c.set(0));
260}
261
262/// Blocks normally reachable from `entry` without entering `stop`.
263pub fn reachable_within(cfg: &Cfg, entry: usize, stop: &HashSet<usize>) -> HashSet<usize> {
264    let mut seen = HashSet::new();
265    if stop.contains(&entry) {
266        return seen;
267    }
268    let mut q = VecDeque::new();
269    q.push_back(entry);
270    seen.insert(entry);
271    while let Some(b) = q.pop_front() {
272        for &s in &cfg.blocks[b].succ {
273            if !stop.contains(&s) && seen.insert(s) {
274                q.push_back(s);
275            }
276        }
277    }
278    seen
279}
280
281/// Compute immediate post-dominators over `universe` via the standard
282/// Cooper-Harvey-Kennedy iterative dataflow on the REVERSED cfg, rooted at a
283/// single sentinel VIRTUAL EXIT (`vx`) that every block leaving the universe
284/// (a terminator, or an edge out of `universe`) flows to. Returns
285/// (vx, block_id -> ipdom_id). The single sentinel root makes the reverse RPO
286/// well-defined and the post-dominator forest a single tree, so the CHK
287/// `intersect` walk always terminates (it climbs toward `vx`).
288pub(crate) fn compute_postdominators(
289    cfg: &Cfg,
290    universe: &HashSet<usize>,
291) -> (usize, HashMap<usize, usize>) {
292    let vx = cfg.blocks.len();
293    let leaves_universe = |b: usize| -> bool {
294        cfg.blocks[b].succ.is_empty() || cfg.blocks[b].succ.iter().any(|s| !universe.contains(s))
295    };
296    let succs_of = |b: usize| -> Vec<usize> {
297        let mut v: Vec<usize> = cfg.blocks[b]
298            .succ
299            .iter()
300            .copied()
301            .filter(|s| universe.contains(s))
302            .collect();
303        if leaves_universe(b) {
304            v.push(vx);
305        }
306        v
307    };
308    // Reverse-post-order over the reversed graph rooted at vx (vx's reversed
309    // successors are the exit blocks; b's reversed successors are its forward
310    // preds).
311    let mut order: Vec<usize> = Vec::new();
312    {
313        let mut visited: HashSet<usize> = HashSet::new();
314        visited.insert(vx);
315        let mut stack: Vec<usize> = Vec::new();
316        let mut state: HashMap<usize, usize> = HashMap::new();
317        let mut seeds: Vec<usize> = universe
318            .iter()
319            .copied()
320            .filter(|&b| leaves_universe(b))
321            .collect();
322        seeds.sort_unstable_by_key(|b| cfg.blocks[*b].start);
323        for s in seeds.iter().rev() {
324            if visited.insert(*s) {
325                stack.push(*s);
326            }
327        }
328        while let Some(&n) = stack.last() {
329            let idx = *state.entry(n).or_insert(0);
330            let ps: Vec<usize> = cfg.blocks[n]
331                .pred
332                .iter()
333                .copied()
334                .filter(|p| universe.contains(p))
335                .collect();
336            if idx < ps.len() {
337                *state.get_mut(&n).unwrap() += 1;
338                let p = ps[idx];
339                if visited.insert(p) {
340                    stack.push(p);
341                }
342            } else {
343                order.push(n);
344                stack.pop();
345            }
346        }
347        order.reverse();
348        order.push(vx);
349    }
350    let mut rpo_num: HashMap<usize, usize> = HashMap::new();
351    for (i, &b) in order.iter().enumerate() {
352        rpo_num.insert(b, i);
353    }
354    let mut ipdom: HashMap<usize, usize> = HashMap::new();
355    ipdom.insert(vx, vx);
356    for &b in universe.iter() {
357        ipdom.insert(b, vx);
358    }
359    let cap = universe.len() + 2;
360    let intersect = |mut a: usize, mut b: usize, ipdom: &HashMap<usize, usize>| -> usize {
361        let mut guard = 0usize;
362        while a != b {
363            guard += 1;
364            if guard > cap {
365                return vx;
366            }
367            let na = rpo_num.get(&a).copied().unwrap_or(0);
368            let nb = rpo_num.get(&b).copied().unwrap_or(0);
369            if na < nb {
370                a = *ipdom.get(&a).unwrap_or(&vx);
371            } else {
372                b = *ipdom.get(&b).unwrap_or(&vx);
373            }
374        }
375        a
376    };
377    let mut changed = true;
378    let mut iter = 0;
379    while changed && iter < 64 {
380        changed = false;
381        iter += 1;
382        for &b in order.iter().rev() {
383            if b == vx {
384                continue;
385            }
386            let ss = succs_of(b);
387            if ss.is_empty() {
388                continue;
389            }
390            let mut new_ipdom = ss[0];
391            for &s in &ss[1..] {
392                new_ipdom = intersect(new_ipdom, s, &ipdom);
393            }
394            if ipdom.get(&b).copied().unwrap_or(vx) != new_ipdom {
395                ipdom.insert(b, new_ipdom);
396                changed = true;
397            }
398        }
399    }
400    (vx, ipdom)
401}
402
403/// Immediate post-dominator of `entry` within `universe`, approximated as
404/// the nearest reconvergence point: the block (other than entry) reachable
405/// from ALL of entry's in-universe successors with the smallest total BFS
406/// distance. Blocks whose in-universe successors are empty are exits.
407pub fn immediate_postdom(
408    cfg: &Cfg,
409    results: &[BlockResult],
410    universe: &HashSet<usize>,
411    entry: usize,
412    group_owned: &HashSet<usize>,
413    stmt_counts: &[usize],
414    body_group_of: &HashMap<usize, usize>,
415    entry_group: Option<usize>,
416    terminators: &HashSet<usize>,
417    abrupt_only: &HashSet<usize>,
418    final_writers: &HashSet<usize>,
419) -> Option<usize> {
420    let succs: Vec<usize> = cfg.blocks[entry]
421        .succ
422        .iter()
423        .copied()
424        .filter(|s| universe.contains(s))
425        .collect();
426    if succs.len() < 2 {
427        return None;
428    }
429    // BFS distances from each successor. The entry block acts as a barrier:
430    // paths that loop back through it do not constitute reconvergence.
431    let mut dists: Vec<HashMap<usize, u32>> = Vec::with_capacity(succs.len());
432    for &s0 in &succs {
433        let mut d: HashMap<usize, u32> = HashMap::new();
434        let mut q: VecDeque<(usize, u32)> = VecDeque::new();
435        d.insert(s0, 0);
436        q.push_back((s0, 0));
437        while let Some((b, db)) = q.pop_front() {
438            for &s in &cfg.blocks[b].succ {
439                if s != entry && universe.contains(&s) && !d.contains_key(&s) {
440                    d.insert(s, db + 1);
441                    q.push_back((s, db + 1));
442                }
443            }
444        }
445        dists.push(d);
446    }
447    // Candidates: blocks present in all BFS maps (except entry itself), and
448    // NOT a direct successor of entry. A direct successor is one of the
449    // branches, never the merge: it is "reachable from all successors" only
450    // because it reaches itself (distance 0). Rejecting it is the compound
451    // `if (A || B) then;` fix (the `then` body is a successor that the BFS
452    // heuristic otherwise picks over the true follow further out). Skipped for
453    // self-loops (a loop header is its own successor; there the nearest
454    // confluence is the loop exit and must stay).
455    // Candidates: blocks present in all BFS maps (except entry itself).
456    // A DIRECT SUCCESSOR of the entry is one of the branches, never the
457    // merge: it is "reachable from all successors" only because another
458    // branch jumps to it (if-else-if chains over a shared statement
459    // block). Choosing it as the follow renders the branch an EMPTY arm
460    // and parks the block after the chain, where every bypassing path
461    // falls through it — jdk11 OCSPResponse SingleResponse's ctor parked
462    // the shared `revocationReason = UNSPECIFIED` (a blank-final write
463    // no copy route may duplicate) after the reason-range chain and the
464    // values[reason] arm double-assigned (可能已分配变量 x2 trees);
465    // jdk11 JarFile.getBytes' readNBytes arm fell into the parked
466    // readAllBytes (double read). The successor stays a candidate when it
467    // is the entry's ONLY out (degenerate) or a statement-free stub (a
468    // transparent trampoline to the real merge, common in javac output).
469    let self_loop = cfg.blocks[entry].succ.iter().any(|&x| x == entry);
470    let mut best: Option<(u32, usize)> = None;
471    let mut any_rejected = false;
472    for (&cand, &d0) in dists[0].iter() {
473        if cand == entry {
474            continue;
475        }
476        let mut rejected = false;
477        if !self_loop && succs.iter().any(|&sc| sc == cand) {
478            // A "bare goto" stub: exactly one machine instruction, the
479            // terminator itself. (Not "no statements": a block may carry
480            // stack leftovers — a lone `getstatic` before its `goto` — with
481            // no statements either, and the false-merge rejection below is
482            // calibrated on the instruction-level shape.)
483            let stmt_free = cfg.blocks[cand].ins_len == 1
484                && results
485                    .get(cand)
486                    .map(|r| matches!(r.term, Term::Goto))
487                    .unwrap_or(false);
488            // A statement-bearing successor is only a FALSE merge when
489            // another path BYPASSES it (compound-if then-blocks, the
490            // OCSP shared-assign block): some successor reaches one of
491            // cand's own successors without stepping on cand. When every
492            // route to cand's tails goes through cand, it is the genuine
493            // immediate merge even though a branch jumps straight to it
494            // (`if (c) goto S; stmts; S:` — javac's bottom-exit loops and
495            // goto-merge diamonds; a blanket rejection spun Legacy6's
496            // loop nest into a timeout).
497            // Never reject the FIRST successor of a two-way (COND) entry:
498            // that is the branch's own taken target, and rejecting it
499            // re-routes the guard walk across the try carve-out (huc
500            // getInputStream0 lost its inner catch when the taken target
501            // at pc 813 was rejected as a false merge).
502            let is_fall = succs.len() == 2 && succs[0] == cand;
503            let no_tc = crate::dbg_flag!("JCDC_DBG_NOTC");
504            let no_tcf = crate::dbg_flag!("JCDC_DBG_NOTCF");
505            let no_lr = crate::dbg_flag!("JCDC_DBG_NOLR");
506            // Same-try-body exemption: rejecting a cand that sits in the
507            // SAME protected body as the entry re-routes the guard walk
508            // across the carve-out (huc getInputStream0's inner catch
509            // dissolved into a bare try when its taken target at pc 813
510            // was rejected from the guard at pc 498 inside the same
511            // 283..1853 body). A cand in a DIFFERENT group — or in no
512            // group — keeps the ordinary rules: keytool doCommands' TWR
513            // epilogue blocks must stay rejectable so each case arm gets
514            // its own per-arrival try copy; exempting them wholesale
515            // wrapped the giant shared region in one FileOutputStream TWR
516            // whose javac expansion overflowed (try 语句的代码过长 x93).
517            let same_body =
518                entry_group.is_some() && body_group_of.get(&cand) == entry_group.as_ref();
519            let stmts_at_least_8 = stmt_counts.get(cand).copied().unwrap_or(0) >= 8;
520            // Reachability set below cand: the bypass probe must see the
521            // WHOLE downstream flow, not just cand's immediate succs —
522            // SSLConfiguration's clinit ternary scaffolding (cond ? a : b
523            // over blank-final assigns) re-converges several blocks below
524            // the taken target (head 7 -> arms 8/9 -> merge 12 -> next
525            // property 14): the fall arm's route 8 -> 13 -> 14 bypasses 9
526            // entirely, but 14 is not in succ(9) = {10, 11}, so the narrow
527            // probe kept 9 as the follow, the else walk nested property 3/4
528            // inside the client-default arm and duplicated the blank-final
529            // assigns (可能已分配/可能尚未初始化 x4 w26).
530            // Entry-barred reachability below cand: a loop wraparound
531            // re-entering the entry's own branch is NOT cand's tail
532            // territory (Legacy6 loops(): the inner-loop backedge cand 13
533            // reaches block 12 only via 13 -> 9 -> entry 10 -> 11 -> 12,
534            // and counting that confluence rejected the backedge block
535            // and spun the walk; SSLConfiguration's merge 14 stays
536            // visible — 9 -> 10 -> 12 -> 14 never crosses entry 7).
537            // Depth-capped (4 hops): the false-merge families are tight
538            // diamonds whose tail re-joins the sibling flow within a few
539            // blocks. Uncapped, a long if-chain's then-tail reaches the
540            // whole downstream method and EVERY chain COND rejects
541            // (jdk26 IndicConjunctBreak isExtend: 1300+ rejects, each
542            // re-routed follow re-walked the 4K-bytecode chain — the
543            // structurer spun at 100% CPU).
544            let mut cand_reach: HashSet<usize> = HashSet::new();
545            if !crate::dbg_flag!("JCDC_DBG_NOWIDE") {
546                let mut q: VecDeque<(usize, u32)> = VecDeque::new();
547                for &sx in &cfg.blocks[cand].succ {
548                    if sx != entry && cand_reach.insert(sx) {
549                        q.push_back((sx, 1));
550                    }
551                }
552                while let Some((b, db)) = q.pop_front() {
553                    if db >= 4 {
554                        continue;
555                    }
556                    for &nx in &cfg.blocks[b].succ {
557                        if nx != entry && cand_reach.insert(nx) {
558                            q.push_back((nx, db + 1));
559                        }
560                    }
561                }
562            } else {
563                cand_reach.extend(cfg.blocks[cand].succ.iter().copied());
564            }
565            let bypass = !stmt_free
566                && !is_fall
567                && !same_body
568                && !group_owned.contains(&cand)
569                && succs.iter().any(|&s0| {
570                    let mut seen: HashSet<usize> = HashSet::new();
571                    let mut q: VecDeque<(usize, bool)> = VecDeque::new();
572                    q.push_back((s0, false));
573                    seen.insert(s0);
574                    while let Some((b, indep)) = q.pop_front() {
575                        if b == cand {
576                            continue;
577                        }
578                        // b is an INDEPENDENT segment block when it is not
579                        // the sibling start itself and lies outside cand's
580                        // tail territory.
581                        let b_indep = indep
582                            || (b != s0 && !cand_reach.contains(&b));
583                        for &nx in &cfg.blocks[b].succ {
584                            if nx == cand || seen.contains(&nx) {
585                                continue;
586                            }
587                            if cand_reach.contains(&nx) {
588                                // Large shared tails (>= 8 statements) skip
589                                // the independence requirement: deduping a
590                                // big epilogue is worth the re-route even
591                                // when the sibling steps straight into the
592                                // tail (keytool doCommands' 22-statement
593                                // TWR epilogues rejected from 18 switch
594                                // arms render the compact compilable 6K
595                                // shape; with the requirement they parked
596                                // per-arm copies and javac's TWR codegen
597                                // overflowed again, 64 errors). Small
598                                // statement tails keep the requirement
599                                // (IndicConjunctBreak's 3-statement chain
600                                // returns).
601                                // Re-entry into cand's tail counts as a
602                                // bypass only when the route brought its
603                                // own flow first (SSLConfiguration clinit:
604                                // the fall arm owns block 13 before
605                                // re-joining at the diamond merge 14 — the
606                                // nested-property shape double-assigned the
607                                // blank finals, 可能已分配/可能尚未初始化 x4
608                                // w26) AND the confluence is not a quiet
609                                // terminator (AnnotationType: a route dying
610                                // in its own return exits before any parked
611                                // copy of the shared tail — benign;
612                                // OCSPResponse: the values[reason] arm flows
613                                // on into a live Goto-stub confluence and
614                                // would double-assign — harmful). A sibling
615                                // that steps straight into cand's
616                                // continuation is the ordinary if-chain
617                                // merge (jdk26 IndicConjunctBreak isExtend:
618                                // counting those rejected all 1300+ chain
619                                // CONDs and spun the structurer at 100%
620                                // CPU).
621                                if (b_indep || stmts_at_least_8)
622                                    && (!terminators.contains(&nx) || no_tcf)
623                                {
624                                    return true;
625                                }
626                                seen.insert(nx);
627                                q.push_back((nx, b_indep));
628                                continue;
629                            }
630                            seen.insert(nx);
631                            q.push_back((nx, b_indep));
632                        }
633                    }
634                    false
635                })
636                // A TERMINATOR cand (shared throw/return block) with a
637                // route that skips it entirely is always a false merge:
638                // parking it after the chain puts an unconditional
639                // throw/return between the skip-path's arm and its
640                // continuation, and the arm's jump elides to a
641                // fallthrough INTO the parked terminator (DHKeyExchange
642                // DHEPossessionGenerator clinit: the size<1024 ||
643                // size>8192 || (&0x3f)!=0 OR-chain over ONE shared
644                // throw; the valid path's goto landed after it, the
645                // parked throw made the try always-throw and the
646                // continuation 无法访问的语句). The succ-confluence probe
647                // above cannot see this: a throw has no successors.
648                // Rejection gives every guard arm its per-arrival copy
649                // and lets the skip path flow past. AnnotationType's
650                // shared tail is NOT a terminator (it falls into the
651                // ctor Return), so its benign parked shape stays.
652                || (!no_tc
653                    && terminators.contains(&cand)
654                    // A final-WRITING terminator must stay parkable:
655                    // every copy route refuses final-writers
656                    // (terminator_writes_final), so rejecting one strands
657                    // the tail with no renderable home and the assigns
658                    // vanish (java/lang/String's compress-path ctors lost
659                    // the shared `value = toBytes; coder = UTF16; return`
660                    // tail, 可能尚未初始化变量value). The parked shape is the
661                    // only renderable form there — and it compiles, since
662                    // the skipping arms exit via their own returns.
663                    && !final_writers.contains(&cand)
664                    && succs.iter().any(|&s0| {
665                        let mut seen: HashSet<usize> = HashSet::new();
666                        let mut q: VecDeque<usize> = VecDeque::new();
667                        q.push_back(s0);
668                        seen.insert(s0);
669                        while let Some(b) = q.pop_front() {
670                            if b == cand {
671                                continue;
672                            }
673                            for &nx in &cfg.blocks[b].succ {
674                                if seen.contains(&nx) {
675                                    continue;
676                                }
677                                // The skipping route must end in a LIVE
678                                // continuation, not an abrupt exit: a route
679                                // that dies in its own throw never needed
680                                // cand (java/util/ResourceBundle loadBundle's
681                                // shared `return bundle` tail was rejected
682                                // because the switch's default-throw arm
683                                // could not reach it — the parked return
684                                // after the loop is the correct shape, and
685                                // rejecting it dropped the method's final
686                                // return, 缺少返回语句 x3 trees). DHKey's
687                                // valid path skips the parked throw via a
688                                // Goto stub into the Return — a live route.
689                                // A skip route whose confluence DIES
690                                // (a terminator, or a stub whose every
691                                // successor is one) never falls into the
692                                // parked tail, so parking a shared RETURN
693                                // is safe there (CHM.equals: the loop's
694                                // `return false` splits into iconst_0 |
695                                // ireturn — the confluence stub is one hop
696                                // before the Return, invisible to a plain
697                                // terminator check — and cannot reach the
698                                // method-final `return true` at pc 211;
699                                // rejecting it lost the tail, 缺少返回语句).
700                                // DHKeyExchange's shared THROW stays
701                                // rejected because its valid path skips
702                                // via a Goto stub into the Return — the
703                                // stub itself is the confluence and the
704                                // route is live at that point.
705                                let dying = terminators.contains(&nx)
706                                    || abrupt_only.contains(&nx);
707                                // A shared THROW keeps its rejection even
708                                // against a dying route: the valid path
709                                // that skips the throw must not find it
710                                // parked between the chain and its own
711                                // exit (DHKeyExchange clinit — the Goto
712                                // stub into the Return is abrupt-only, so
713                                // the dying guard would park the throw and
714                                // the valid key-size path would execute
715                                // it: 无法访问的语句 p11, silent wrong-throw
716                                // elsewhere).
717                                let throw_cand = cfg.blocks[cand].ins_len > 0
718                                    && matches!(
719                                        results.get(cand).map(|r| &r.term),
720                                        Some(Term::Throw(_))
721                                    );
722                                if !can_reach_cfg(cfg, nx, cand, 4096)
723                                    && (no_lr || !dying || throw_cand)
724                                {
725                                    if crate::dbg_flag!("JCDC_DBG_PDJ") {
726                                        eprintln!("PDJ-SKIP cand={} cand_pc={} cand_term={} s0={} s0_pc={} nx={} nx_pc={} nx_term={} no_lr={}",
727                                            cand, cfg.blocks[cand].start, terminators.contains(&cand),
728                                            s0, cfg.blocks[s0].start,
729                                            nx, cfg.blocks[nx].start,
730                                            terminators.contains(&nx), no_lr);
731                                    }
732                                    return true;
733                                }
734                                seen.insert(nx);
735                                q.push_back(nx);
736                            }
737                        }
738                        false
739                    }));
740            if bypass {
741                if crate::dbg_flag!("JCDC_DBG_PDJ") {
742                    eprintln!(
743                        "PDJ-REJECT entry_pc={} cand={} cand_pc={} cand_stmts={}",
744                        cfg.blocks[entry].start,
745                        cand,
746                        cfg.blocks[cand].start,
747                        cfg.blocks[cand].ins_len as usize
748                    );
749                }
750                rejected = true;
751                any_rejected = true;
752            }
753        }
754        let mut total = d0;
755        let mut ok = true;
756        for d in &dists[1..] {
757            match d.get(&cand) {
758                Some(x) => total += x,
759                None => {
760                    ok = false;
761                    break;
762                }
763            }
764        }
765        if ok && !rejected {
766            let better = match best {
767                None => true,
768                Some((bd, bc)) => {
769                    total < bd || (total == bd && cfg.blocks[cand].start < cfg.blocks[bc].start)
770                }
771            };
772            if better {
773                best = Some((total, cand));
774            }
775        }
776    }
777    // Re-score when a candidate was rejected: the plain BFS lets one
778    // branch's paths run THROUGH a sibling branch target, so ternary
779    // scaffolding INSIDE the rejected arm scores nearer than the true
780    // re-confluence (SSLConfiguration clinit: with the taken target 9
781    // rejected, block 10 — still inside the same ? : diamond — won on
782    // distance 2+1 over the real merge 14 at 2+3, and the else walk
783    // nested property 3/4 inside the client-default arm again). With
784    // siblings barred, only genuine post-diamond confluences survive
785    // (14 from both sides). When no candidate survives (DHKey's shared
786    // throw: the sibling side has no successors at all), fall back to
787    // the plain non-rejected best.
788    // Locality fence: rescore only when the plain non-rejected best is
789    // NEAR (a tight diamond whose scaffolding outscores the true merge —
790    // SSLConfiguration clinit, plain best 10 at total 3 vs the real
791    // merge 14). In long if-chains the sibling-barred maps only meet at
792    // the method-final tail (jdk26 IndicConjunctBreak isExtend: entry 0's
793    // rescore picked block 622 at pc 4114 as the follow, spanning the
794    // whole 4K-bytecode chain inside one If — every nested COND re-walked
795    // it and the structurer spun at 100% CPU). A far plain best means
796    // there is no tight diamond to repair; keep it.
797    if any_rejected
798        && !crate::dbg_flag!("JCDC_DBG_NOWIDE")
799        && best.map(|(d, _)| d).unwrap_or(u32::MAX) <= 8
800    {
801        let mut dists2: Vec<HashMap<usize, u32>> = Vec::with_capacity(succs.len());
802        for (i, &s0) in succs.iter().enumerate() {
803            let mut d: HashMap<usize, u32> = HashMap::new();
804            let mut q: VecDeque<(usize, u32)> = VecDeque::new();
805            d.insert(s0, 0);
806            q.push_back((s0, 0));
807            while let Some((b, db)) = q.pop_front() {
808                for &s in &cfg.blocks[b].succ {
809                    if s == entry || !universe.contains(&s) || d.contains_key(&s) {
810                        continue;
811                    }
812                    if succs.iter().enumerate().any(|(j, &sb)| j != i && sb == s) {
813                        continue;
814                    }
815                    d.insert(s, db + 1);
816                    q.push_back((s, db + 1));
817                }
818            }
819            dists2.push(d);
820        }
821        let mut best2: Option<(u32, usize)> = None;
822        for (&cand, &d0) in dists2[0].iter() {
823            if cand == entry || succs.iter().any(|&sc| sc == cand) {
824                // rejected candidates stay rejected; direct successors are
825                // branches, never the merge (the bar above already keeps
826                // them out of the maps, this guards the s0 self-entry).
827                continue;
828            }
829            let mut total = d0;
830            let mut ok = true;
831            for d in &dists2[1..] {
832                match d.get(&cand) {
833                    Some(x) => total += x,
834                    None => {
835                        ok = false;
836                        break;
837                    }
838                }
839            }
840            if ok {
841                let better = match best2 {
842                    None => true,
843                    Some((bd, bc)) => {
844                        total < bd || (total == bd && cfg.blocks[cand].start < cfg.blocks[bc].start)
845                    }
846                };
847                if better {
848                    best2 = Some((total, cand));
849                }
850            }
851        }
852        if let Some(b2) = best2 {
853            if b2.0 <= 8 {
854                if crate::dbg_flag!("JCDC_DBG_PDJ") {
855                    eprintln!(
856                        "PDJ-RESCORE entry_pc={} pick={} pick_pc={} plain={:?}",
857                        cfg.blocks[entry].start,
858                        b2.1,
859                        cfg.blocks[b2.1].start,
860                        best.map(|(_, c)| c)
861                    );
862                }
863                return Some(b2.1);
864            }
865            // The barred maps only met far downstream (a long chain's
866            // final tail): not a diamond merge — keep the plain best.
867        }
868    }
869    best.map(|(_, c)| c)
870}
871
872// ---------------------------------------------------------------------------
873// Try groups
874// ---------------------------------------------------------------------------
875
876#[derive(Debug, Clone)]
877pub struct TryGroup {
878    pub start: u32,
879    pub end: u32,
880    /// (handler pc, catch type) in exception-table order.
881    pub handlers: Vec<(u32, Option<String>)>,
882    pub ranges: Vec<usize>,
883}
884
885pub fn group_exceptions(cfg: &Cfg) -> Vec<TryGroup> {
886    group_exceptions_with(cfg, None)
887}
888
889/// True when any expression in these statements calls
890/// `Throwable.addSuppressed` — the fingerprint of try-with-resources
891/// close scaffolding.
892fn stmts_mention_addsuppressed(stmts: &[crate::ir::stmt::Stmt]) -> bool {
893    fn ex(e: &crate::ir::expr::Expr) -> bool {
894        use crate::ir::expr::Expr as E;
895        match e {
896            E::Method {
897                name, owner, args, ..
898            } => {
899                name == "addSuppressed"
900                    || owner.as_deref().map(ex).unwrap_or(false)
901                    || args.iter().any(ex)
902            }
903            E::Field { owner: Some(o), .. } => ex(o),
904            E::Bin { l, r, .. }
905            | E::Assign {
906                target: l,
907                value: r,
908                ..
909            } => ex(l) || ex(r),
910            E::Cond { c, t, f } => ex(c) || ex(t) || ex(f),
911            E::Un { e: x, .. }
912            | E::Cast { e: x, .. }
913            | E::InstanceOf { e: x, .. }
914            | E::PreIncDec { e: x, .. }
915            | E::PostIncDec { e: x, .. } => ex(x),
916            E::ArrayIndex { array, index } => ex(array) || ex(index),
917            E::New { args, .. } | E::AnonNew { args, .. } => args.iter().any(ex),
918            E::NewArray { dims, init, .. } => {
919                dims.iter().any(ex) || init.as_ref().map(|v| v.iter().any(ex)).unwrap_or(false)
920            }
921            E::StringConcat(parts) => parts
922                .iter()
923                .any(|pp| matches!(pp, crate::ir::expr::ConcatPart::Str(x) if ex(x))),
924            E::Invokedynamic { args, .. } => args.iter().any(ex),
925            E::Lambda(l) => l.captures.iter().any(ex),
926            _ => false,
927        }
928    }
929    fn st(s: &crate::ir::stmt::Stmt) -> bool {
930        use crate::ir::stmt::Stmt as S;
931        match s {
932            S::Block(v) => v.iter().any(st),
933            S::ExprStmt(e) => ex(e),
934            S::LocalDef { init: Some(e), .. } => ex(e),
935            S::Return(e) => e.as_ref().map(ex).unwrap_or(false),
936            S::Throw(e) => ex(e),
937            S::If {
938                cond,
939                then_stmt,
940                else_stmt,
941            } => ex(cond) || st(then_stmt) || else_stmt.as_deref().map(st).unwrap_or(false),
942            S::While { cond, body } | S::DoWhile { body, cond } => ex(cond) || st(body),
943            S::For {
944                init,
945                cond,
946                update,
947                body,
948            } => {
949                init.iter().any(st)
950                    || cond.as_ref().map(ex).unwrap_or(false)
951                    || update.iter().any(ex)
952                    || st(body)
953            }
954            S::ForEach { iterable, body, .. } => ex(iterable) || st(body),
955            S::Switch {
956                selector,
957                cases,
958                default,
959                ..
960            } => {
961                ex(selector)
962                    || cases.iter().any(|c| c.body.iter().any(st))
963                    || default.as_deref().map(st).unwrap_or(false)
964            }
965            S::Try {
966                body,
967                catches,
968                finally,
969            }
970            | S::TryWithResources {
971                body,
972                catches,
973                finally,
974                ..
975            } => {
976                st(body)
977                    || catches.iter().any(|c| st(&c.body))
978                    || finally.as_deref().map(st).unwrap_or(false)
979            }
980            S::Synchronized { lock, body } => ex(lock) || st(body),
981            S::Labeled { body, .. } => st(body),
982            S::Assert { cond, msg } => ex(cond) || msg.as_ref().map(ex).unwrap_or(false),
983            _ => false,
984        }
985    }
986    stmts.iter().any(st)
987}
988
989pub fn group_exceptions_with(
990    cfg: &Cfg,
991    results: Option<&Vec<crate::ir::build::BlockResult>>,
992) -> Vec<TryGroup> {
993    let mut groups: Vec<TryGroup> = Vec::new();
994    for (ri, r) in cfg.exc_ranges.iter().enumerate() {
995        if let Some(g) = groups
996            .iter_mut()
997            .find(|g| g.start == r.start && g.end == r.end)
998        {
999            g.handlers.push((r.handler, r.catch_type.clone()));
1000            g.ranges.push(ri);
1001        } else {
1002            groups.push(TryGroup {
1003                start: r.start,
1004                end: r.end,
1005                handlers: vec![(r.handler, r.catch_type.clone())],
1006                ranges: vec![ri],
1007            });
1008        }
1009    }
1010    groups.sort_by_key(|g| (g.start, std::cmp::Reverse(g.end)));
1011    // javac splits one protected region into several exception ranges that
1012    // share identical handlers (e.g. around every `return` inside a
1013    // synchronized block). Merge adjacent such spans into one group so the
1014    // region structures as a single try. The handler's self-protection
1015    // range (start == handler pc) never merges into its own group.
1016    fn handler_key(g: &TryGroup) -> Vec<(u32, Option<String>)> {
1017        let mut v: Vec<(u32, Option<String>)> =
1018            g.handlers.iter().map(|(h, t)| (*h, t.clone())).collect();
1019        v.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
1020        v.dedup();
1021        v
1022    }
1023    loop {
1024        let mut merged_any = false;
1025        'outer: for i in 0..groups.len() {
1026            for j in (i + 1)..groups.len() {
1027                let (a, b) = if groups[i].start <= groups[j].start {
1028                    (i, j)
1029                } else {
1030                    (j, i)
1031                };
1032                if handler_key(&groups[a]) != handler_key(&groups[b]) {
1033                    continue;
1034                }
1035                if groups[b].start > groups[a].end.saturating_add(4) {
1036                    // Same-handler ranges separated by MORE than 4 bytes
1037                    // still belong to one try when every block strictly
1038                    // between them is a terminator (no normal succ):
1039                    // those are the handler's own inline finally copies
1040                    // (`unlock; return false`) that javac excludes from
1041                    // the protected ranges (jdk17 LinkedBlockingQueue
1042                    // .offer(E,long,TimeUnit): ranges (37,59)+(67,122)
1043                    // split around the 8-byte return-false copy — the
1044                    // unmerged first range made the try body {3,4,5},
1045                    // cutting the wait loop's backedge block out of the
1046                    // body universe; the loop exit got inlined as an
1047                    // if-arm inside the loop and the post-loop return
1048                    // was stranded in it — 缺少返回语句 x2 methods).
1049                    // The gap must not swallow ANOTHER group's handler:
1050                    // TWR nesting (try(a){try(b){..}}) puts the inner
1051                    // handler entry exactly at the seam between the outer
1052                    // resource's split ranges (feat Exceptions
1053                    // .tryWithResources2: outer ranges (10,56)+(62,78)
1054                    // share handler 78, and 62 is the inner group's
1055                    // handler — merging collapsed the nested TWR into one
1056                    // span with an empty catch(Throwable), 缺少返回语句
1057                    // across the whole features battery). A foreign
1058                    // handler INSIDE the gap fails the same way.
1059                    let own_handlers: Vec<u32> =
1060                        groups[a].handlers.iter().map(|(h, _)| *h).collect();
1061                    let seam_clear_of_foreign_handlers = !cfg.exc_ranges.iter().any(|r| {
1062                        !own_handlers.contains(&r.handler)
1063                            && r.handler > groups[a].end
1064                            && r.handler <= groups[b].start
1065                    });
1066                    // The gap blocks themselves must be pure terminator
1067                    // flow (the handler's inline finally copies —
1068                    // `unlock; return false`) with no TWR close
1069                    // scaffolding (addSuppressed calls indicate resource
1070                    // copies whose removal from the exception topology
1071                    // misplaces the closes).
1072                    let gap_terminator_only = seam_clear_of_foreign_handlers
1073                        && cfg
1074                            .blocks
1075                            .iter()
1076                            .filter(|bl| bl.ins_len != 0)
1077                            .filter(|bl| bl.start >= groups[a].end && bl.end <= groups[b].start)
1078                            .all(|bl| {
1079                                bl.succ.is_empty()
1080                                    && results
1081                                        .map(|rs| !stmts_mention_addsuppressed(&rs[bl.id].stmts))
1082                                        .unwrap_or(true)
1083                            });
1084                    if !gap_terminator_only {
1085                        continue;
1086                    }
1087                }
1088                if groups[a]
1089                    .handlers
1090                    .iter()
1091                    .any(|(h, _)| *h == groups[b].start)
1092                {
1093                    continue;
1094                }
1095                let new_start = groups[a].start.min(groups[b].start);
1096                let new_end = groups[a].end.max(groups[b].end);
1097                let mut ranges = groups[a].ranges.clone();
1098                ranges.extend(groups[b].ranges.iter().copied());
1099                groups[a].start = new_start;
1100                groups[a].end = new_end;
1101                groups[a].ranges = ranges;
1102                groups.remove(b);
1103                merged_any = true;
1104                break 'outer;
1105            }
1106        }
1107        if !merged_any {
1108            break;
1109        }
1110    }
1111    // HANDLER-PROTECTION NESTING: when one group's HANDLER code is itself
1112    // protected by a SUBSET of that group's handlers, javac has split the
1113    // source-level `try { while (true) { try { .. } catch (Exception e) {
1114    // .. may throw I/N .. } } } catch (I) { .. } catch (N) { .. }` into a
1115    // body range plus a handler-protection range (jdk26 javax.crypto.KDF
1116    // .chooseProvider: A=(91,162)[Exception@162, IAPE@258, NSAE@263] +
1117    // B=(162,258)[IAPE, NSAE] — B protects the catch(Exception) body's
1118    // getNext retry, whose NSAE the source-level outer catch converts to
1119    // IAPE("No provider supports this input", lastException); the flat
1120    // rendering loses that conversion — the documented KDF behavioral
1121    // divergence). Reconstruct: OUTER=(A.start, B.end)[B's handlers] +
1122    // INNER=(A.start, A.end)[A's handlers minus B's].
1123    loop {
1124        let mut did = false;
1125        'hp: for i in 0..groups.len() {
1126            for j in 0..groups.len() {
1127                if i == j {
1128                    continue;
1129                }
1130                let x = groups[i].clone();
1131                let y = groups[j].clone();
1132                // Y must start AT one of X's handler pcs (Y protects X's
1133                // handler code) and extend strictly past X.
1134                if !x.handlers.iter().any(|(h, _)| *h == y.start) {
1135                    continue;
1136                }
1137                if y.start < x.end || y.start > x.end.saturating_add(8) {
1138                    continue;
1139                }
1140                if y.end <= x.end {
1141                    continue;
1142                }
1143                // Y's handler set is a strict subset of X's (the shared
1144                // outer catches); X keeps at least one inner-only handler.
1145                if !y.handlers.iter().all(|h| x.handlers.contains(h)) {
1146                    continue;
1147                }
1148                // Y's handlers must ALL be typed catches: a catch-all
1149                // (None = java.lang.Throwable) protecting X's handler
1150                // code is javac's FINALLY desugaring (the catch body's
1151                // inline finally copy), not a source-level outer try —
1152                // merging there hoists the finally above the catch and
1153                // swallows the loop tail (feat Exceptions.loopTry: the
1154                // `++i` increment vanished inside the merged topology —
1155                // infinite loop, run timeout ×6 releases). KDF's outer
1156                // IAPE/NSAE catches are typed; finally copies never are.
1157                if !y.handlers.iter().all(|(_, t)| t.is_some()) {
1158                    continue;
1159                }
1160                let inner_handlers: Vec<(u32, Option<String>)> = x
1161                    .handlers
1162                    .iter()
1163                    .filter(|h| !y.handlers.contains(h))
1164                    .cloned()
1165                    .collect();
1166                if inner_handlers.is_empty() {
1167                    continue;
1168                }
1169                // Split X's exc-range indices: ranges whose handler is
1170                // inner-only belong to INNER, the rest to OUTER.
1171                let mut inner_ranges = Vec::new();
1172                let mut outer_ranges: Vec<usize> = y.ranges.clone();
1173                for &ri in x.ranges.iter() {
1174                    let h = cfg.exc_ranges[ri].handler;
1175                    let t = &cfg.exc_ranges[ri].catch_type;
1176                    if y.handlers.iter().any(|(yh, yt)| *yh == h && *yt == *t) {
1177                        outer_ranges.push(ri);
1178                    } else {
1179                        inner_ranges.push(ri);
1180                    }
1181                }
1182                // No foreign handler may live strictly inside the inner
1183                // span (it would belong to a deeper nest the walk must
1184                // keep owning) or inside the protection gap.
1185                let foreign_inside = cfg.exc_ranges.iter().any(|r| {
1186                    let owned_by_x_or_y = x.handlers.iter().any(|(h, _)| *h == r.handler)
1187                        || y.handlers.iter().any(|(h, _)| *h == r.handler);
1188                    !owned_by_x_or_y && r.start >= x.start && r.end <= y.end
1189                });
1190                if foreign_inside {
1191                    continue;
1192                }
1193                let mut outer = y.clone();
1194                outer.start = x.start;
1195                outer.ranges = outer_ranges;
1196                let mut inner = x.clone();
1197                inner.handlers = inner_handlers;
1198                inner.ranges = inner_ranges;
1199                let lo = i.min(j);
1200                let hi = i.max(j);
1201                groups.remove(hi);
1202                groups.remove(lo);
1203                groups.push(outer);
1204                groups.push(inner);
1205                did = true;
1206                break 'hp;
1207            }
1208        }
1209        if !did {
1210            break;
1211        }
1212    }
1213    groups.sort_by_key(|g| (g.start, std::cmp::Reverse(g.end)));
1214    groups
1215}
1216
1217// ---------------------------------------------------------------------------
1218// Region tree
1219// ---------------------------------------------------------------------------
1220
1221#[derive(Debug, Clone)]
1222pub enum Region {
1223    /// Statements of one basic block; terminal handled by the enclosing region.
1224    Basic {
1225        block: usize,
1226    },
1227    Seq(Vec<Region>),
1228    If {
1229        /// block carrying the conditional terminal
1230        block: usize,
1231        cond: Expr,
1232        /// taken (true) branch
1233        then_r: Box<Region>,
1234        /// fallthrough (false) branch
1235        else_r: Box<Region>,
1236        follow: Option<usize>,
1237        /// Folded value-diamond: (then value, else value) — both branches
1238        /// are pure expression pushes that merge on the operand stack.
1239        ternary: Option<(Expr, Expr)>,
1240    },
1241    Loop {
1242        header: usize,
1243        body: Box<Region>,
1244        members: HashSet<usize>,
1245        exits: Vec<usize>,
1246    },
1247    Switch {
1248        block: usize,
1249        selector: Expr,
1250        cases: Vec<(Vec<i64>, Region)>,
1251        default: Option<Box<Region>>,
1252        follow: Option<usize>,
1253    },
1254    Try {
1255        group_idx: usize,
1256        body: Box<Region>,
1257        /// (catch types [multi-catch merged], handler block, region) in table order
1258        catches: Vec<(Vec<String>, usize, Box<Region>)>,
1259    },
1260    /// Jump to a block outside the current region (resolved later).
1261    Goto {
1262        target: usize,
1263    },
1264    /// Duplicate of an already-claimed pure block's statements (shared
1265    /// value block reached from multiple branches).
1266    CopyStmts {
1267        block: usize,
1268    },
1269    Empty,
1270}
1271
1272impl Region {
1273    /// Top-level parts of this region (the Seq's elements, or the region
1274    /// itself as a single part).
1275    fn iter_parts(&self) -> &[Region] {
1276        match self {
1277            Region::Seq(v) => v.as_slice(),
1278            _ => std::slice::from_ref(self),
1279        }
1280    }
1281
1282    /// True when ANY flow path leaving this region does so via a Goto to
1283    /// a target other than `taken` — i.e. the path BYPASSES the parked
1284    /// follow block `taken` and would fall into the parked sibling
1285    /// statements at render time instead of reaching its real target.
1286    /// Regions ending without a Goto (Basic/CopyStmts/Empty/Loop/Try
1287    /// fallout) leave the arm by natural fallthrough INTO the parked
1288    /// chain — exactly the empty-arm contract, no bypass.
1289    fn bypasses_exempt(
1290        r: &Region,
1291        taken: usize,
1292        exempt: &std::collections::HashSet<usize>,
1293    ) -> bool {
1294        match r {
1295            Region::Goto { target } => *target != taken && !exempt.contains(target),
1296            Region::Seq(v) => v
1297                .last()
1298                .map(|l| Region::bypasses_exempt(l, taken, exempt))
1299                .unwrap_or(false),
1300            Region::If { then_r, else_r, .. } => {
1301                Region::bypasses_exempt(then_r, taken, exempt)
1302                    || Region::bypasses_exempt(else_r, taken, exempt)
1303            }
1304            Region::Try { body, catches, .. } => {
1305                Region::bypasses_exempt(body, taken, exempt)
1306                    || catches
1307                        .iter()
1308                        .any(|(_, _, h)| Region::bypasses_exempt(h, taken, exempt))
1309            }
1310            _ => false,
1311        }
1312    }
1313
1314    /// Collect loop headers/exits and Switch follows embedded in a region
1315    /// tree: Gotos targeting them resolve to continue/break statements at
1316    /// conversion — jumps that never fall through, so they are not
1317    /// parked-chain bypasses (completing after them would emit
1318    /// unreachable code).
1319    fn jump_targets(r: &Region, out: &mut std::collections::HashSet<usize>) {
1320        match r {
1321            Region::Loop {
1322                header,
1323                body,
1324                exits,
1325                ..
1326            } => {
1327                out.insert(*header);
1328                out.extend(exits.iter().copied());
1329                Region::jump_targets(body, out);
1330            }
1331            Region::Switch {
1332                cases,
1333                default,
1334                follow,
1335                ..
1336            } => {
1337                if let Some(f) = follow {
1338                    out.insert(*f);
1339                }
1340                for (_, cr) in cases {
1341                    Region::jump_targets(cr, out);
1342                }
1343                if let Some(d) = default {
1344                    Region::jump_targets(d, out);
1345                }
1346            }
1347            Region::Seq(v) => {
1348                for x in v {
1349                    Region::jump_targets(x, out);
1350                }
1351            }
1352            Region::If { then_r, else_r, .. } => {
1353                Region::jump_targets(then_r, out);
1354                Region::jump_targets(else_r, out);
1355            }
1356            Region::Try { body, catches, .. } => {
1357                Region::jump_targets(body, out);
1358                for (_, _, h) in catches {
1359                    Region::jump_targets(h, out);
1360                }
1361            }
1362            _ => {}
1363        }
1364    }
1365}
1366
1367pub struct Structurer<'a> {
1368    pub cfg: &'a Cfg,
1369    pub results: &'a Vec<BlockResult>,
1370    /// Blocks whose operand stack was folded from a pure value diamond.
1371    pub diamond_merges: std::collections::HashSet<usize>,
1372    /// Folded diamond regions: merge block -> (root header, absorbed blocks).
1373    /// When the walk reaches a root header, the whole region collapses: the
1374    /// absorbed blocks are claimed and the walk continues at the merge.
1375    pub fold_regions: HashMap<usize, (usize, HashSet<usize>)>,
1376    /// Reverse index: root header -> merge block.
1377    pub fold_root_to_merge: HashMap<usize, usize>,
1378    /// Heads of shared-tail regions that were copy-walked into a scope
1379    /// (a RawGoto targeting one is natural fallthrough at conversion).
1380    pub copied_tails: HashSet<usize>,
1381    /// Headers of loops currently being structured (nesting barriers).
1382    pub loops_stack: Vec<usize>,
1383    /// Nesting depth of structure_switch case walks: >0 while walking
1384    /// inside another switch's case regions. A stop-confluence follow of
1385    /// a NESTED switch crosses construct boundaries (it is typically the
1386    /// ENCLOSING switch's own follow) — passing it as this switch's
1387    /// follow makes conversion resolve the case-tail goto to a bare
1388    /// `break` that binds to the WRONG (inner) switch.
1389    pub switch_depth: usize,
1390    /// Innermost switch case-arm walk context: (case head block, that
1391    /// switch's region follow, selector is a Java 21+ typeSwitch pattern
1392    /// switch). A nested switch whose stop-confluence equals the ENCLOSING
1393    /// pattern switch's follow may keep the plain-break binding when it is
1394    /// the LAST construct of the case arm AND the arm walk is the case-arm
1395    /// walk itself: the pattern-switch restoration appends the case's
1396    /// terminal `break;`, which lands on the outer follow == the
1397    /// confluence, so both bindings are equivalent and the plain one keeps
1398    /// the appended outer break reachable (ClassPrinterImpl.toYaml/toXml).
1399    /// Classic switches get NO appended break (case fall-through) — the
1400    /// crossing nulling must stay for them (bkeyword's inner default
1401    /// `return '?'` must not degrade to `break` + fall-through).
1402    pub case_arm_ctx: Vec<(usize, Option<usize>, bool)>,
1403    /// Loop headers detected at the SESE method level (including
1404    /// exception-edge back edges from no-normal-pred retry handlers).
1405    /// Walk-based sub-builders consult this so a retry `goto header`
1406    /// keeps loop precedence and resolves to `continue` (jdk26
1407    /// Future.exceptionNow).
1408    pub sese_loop_headers: std::collections::HashSet<usize>,
1409    /// Subset of sese_loop_headers discovered via EXCEPTION-mediated back
1410    /// edges (handler-flow retry gotos). The walk loop branch trusts only
1411    /// this subset: normal back edges must keep failing the scoped
1412    /// dominance check (the loop belongs to an enclosing scope then —
1413    /// structuring it from the inner scope degraded ThreadPoolExecutor /
1414    /// the blocking-queue family: int-boolean conditions, catch-less
1415    /// tries x6 per tree).
1416    pub sese_exc_retry_headers: std::collections::HashSet<usize>,
1417    /// Current `walk` recursion depth (hang guard for pathological methods
1418    /// whose shared-tail / branch decomposition does not converge).
1419    walk_depth: usize,
1420    /// Remaining walk visits when a budget is armed (u64::MAX = off).
1421    walk_visits_left: std::cell::Cell<u64>,
1422    /// Walk wall-clock deadline (see set_walk_deadline); None = off.
1423    walk_deadline: Option<std::time::Instant>,
1424    /// Method-invariant rejection sets for postdom_ipdom, computed once.
1425    /// Every input is constructor-built and never mutated afterwards
1426    /// (handler_group/groups are filled only in the constructor,
1427    /// results/cfg are borrows, final_fields stays empty in the
1428    /// Structurer) — see postdom_ipdom.
1429    postdom_ctx: std::cell::OnceCell<PostdomCtx>,
1430    /// Per-method ticket budget for COPY-producing mechanisms
1431    /// (copy_walk, PARKCHAIN chain completions, per-arrival fills).
1432    /// Nested copies multiply: jdk8 java.awt.Toolkit.eventDispatched's
1433    /// 14-deep event-mask if-chain inside synchronized blocks drove
1434    /// 2^14 = 17792 duplications of the dispatch chain (47MB single
1435    /// method render, ~1GB transient allocations, 5.2s). Golden shapes
1436    /// Measured per-method demands: golden shapes <25 (dci ~8, huc
1437    /// ~10, keytool epilogue ~22); jdk26 IndicConjunctBreak's SESE path
1438    /// needs 457 + 257 on its two heaviest methods (legit shared-tail
1439    /// work — cutting it loses returns); Toolkit.eventDispatched wants
1440    /// 17792+ (exponential). Default 512 covers the legit demand; the
1441    /// METHOD SIZE GUARD in classdec (450KB) re-renders any overflowing
1442    /// method at halved budgets, so the exponential cases collapse
1443    /// (Toolkit 47.7MB -> 330KB, compilable) without touching the legit
1444    /// big renders (keytool doCommands 420KB). Exhausted budget degrades
1445    /// to the historical Goto paths (conversion resolves them to
1446    /// elision/break/continue). JCDC_COPY_BUDGET overrides; 0 disables
1447    /// copying entirely.
1448    copy_budget: std::cell::Cell<u32>,
1449    pub groups: Vec<TryGroup>,
1450    /// Outermost group index owning each body block.
1451    pub body_group: HashMap<usize, usize>,
1452    /// Group index owning each handler head block.
1453    pub handler_group: HashMap<usize, usize>,
1454    /// Final fields of the emitting class: SESE must not duplicate a
1455    /// shared terminator block that assigns one (a final accepts exactly
1456    /// one assignment per path — jdk17 Long$LongCache clinit tail
1457    /// `cache = archivedCache; return;` copied into a branch =
1458    /// "variable cache might already have been assigned").
1459    pub final_fields: std::collections::HashSet<String>,
1460    /// Groups whose structure_try is currently on the stack: the
1461    /// group_here fallback must not re-fire a group inside its OWN body
1462    /// walk (structure_try passes `nested` — which excludes the group —
1463    /// as the body's active, so the primary find already declines there).
1464    structuring_groups: std::cell::RefCell<Vec<usize>>,
1465}
1466
1467impl<'a> Structurer<'a> {
1468    /// Remove a trailing `Goto{target}` from a region (at any tail
1469    /// position: end of a sequence, or the tail of an if/else branch).
1470    /// A trailing `Goto{s}` also strips when `s` is a statement-free
1471    /// Fallthrough/Goto chain into `target`: the body walk hops such
1472    /// stubs via copy_walk (the stub is not in the body universe), and
1473    /// the copy must go when the real continuation is the stripped
1474    /// target — otherwise the post-try statement executes twice (jdk26
1475    /// UntrustedCertificates clinit: `algorithm = getProperty` inside the
1476    /// try AND after it — 可能已分配变量algorithm on the blank final).
1477    fn strip_trailing_goto_to(&self, r: &mut Region, target: usize) {
1478        self.strip_trailing_goto_chain(r, target, &mut HashSet::new())
1479    }
1480
1481    fn strip_trailing_goto_chain(&self, r: &mut Region, target: usize, seen: &mut HashSet<usize>) {
1482        match r {
1483            Region::Goto { target: t } if *t == target => *r = Region::Empty,
1484            Region::Goto { target: t } if self.is_stmt_free_chain_to(*t, target, seen) => {
1485                *r = Region::Empty
1486            }
1487            Region::Seq(v) => {
1488                if let Some(last) = v.last_mut() {
1489                    self.strip_trailing_goto_chain(last, target, seen);
1490                }
1491                if matches!(v.last(), Some(Region::Empty)) {
1492                    v.pop();
1493                }
1494            }
1495            Region::If { then_r, else_r, .. } => {
1496                self.strip_trailing_goto_chain(then_r, target, seen);
1497                self.strip_trailing_goto_chain(else_r, target, seen);
1498            }
1499            _ => {}
1500        }
1501    }
1502
1503    /// True when `t` lies at/after the span end of one of the ACTIVE
1504    /// groups: it is that group's post-try continuation, owned by the
1505    /// enclosing walk (structure_try strips the trailing Goto and the
1506    /// scope continues there). Copy-walking it from inside the body
1507    /// duplicates the continuation statement (jdk26
1508    /// UntrustedCertificates clinit: `algorithm = getProperty` inside the
1509    /// try AND after it — 可能已分配变量algorithm on the blank final).
1510    fn is_active_group_continuation(
1511        &self,
1512        cur: usize,
1513        t: usize,
1514        claimed: &HashSet<usize>,
1515        active: &[usize],
1516    ) -> bool {
1517        let ts = self.cfg.blocks[t].start;
1518        // A CLAIMED block that STARTS another group's protected span,
1519        // arrived at from OUTSIDE that span, is never a bare continuation:
1520        // its group was already structured by a different arm, no walk
1521        // will ever re-emit it, and the arrival must carry its own copy.
1522        // Deferring to an unrelated active group's owner walk strands it
1523        // when that owner's universe excludes it (jdk26
1524        // StructuredTaskScopeImpl.join: the timeoutExpired else arm's
1525        // arrival at the tail-try head deferred to the awaitAll group's
1526        // continuation, whose arm universe lacked the tail — the
1527        // `return joiner.result()` epilogue vanished from the else path,
1528        // 缺少返回语句). UNCLAIMED group heads keep the old deferral:
1529        // structure_try fires in whichever walk's universe holds them.
1530        if let Some(&ogi) = self.body_group.get(&t) {
1531            let g = &self.groups[ogi];
1532            let cs = self.cfg.blocks[cur].start;
1533            let cur_in_span = cs >= g.start && (cs as u32) < (g.end as u32);
1534            if g.start == ts && claimed.contains(&t) && !cur_in_span {
1535                return false;
1536            }
1537        }
1538        active.iter().any(|&gi| {
1539            ts >= self.groups[gi].end
1540                && self.body_group.get(&t) != Some(&gi)
1541                && !self.handler_group.contains_key(&t)
1542        })
1543    }
1544
1545    /// True when `from` reaches `to` through statement-free
1546    /// Fallthrough/Goto blocks (pure jump stubs).
1547    fn is_stmt_free_chain_to(&self, from: usize, to: usize, seen: &mut HashSet<usize>) -> bool {
1548        let mut x = from;
1549        for _ in 0..8 {
1550            if x == to {
1551                return true;
1552            }
1553            if !seen.insert(x) {
1554                return false;
1555            }
1556            if !self.results[x].stmts.is_empty() {
1557                return false;
1558            }
1559            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
1560                return false;
1561            }
1562            let succs = self.cfg.blocks[x].succ.clone();
1563            if succs.len() != 1 {
1564                return false;
1565            }
1566            x = succs[0];
1567        }
1568        false
1569    }
1570
1571    /// Strip a trailing handler `Goto{t}` that merges into the post-try
1572    /// flow: t starts at/after the group end and is not a handler head.
1573    fn strip_handler_exit_goto(&self, r: &mut Region, end_pc: u32) {
1574        let ok = |t: usize| {
1575            self.cfg
1576                .blocks
1577                .get(t)
1578                .map(|b| b.start >= end_pc)
1579                .unwrap_or(false)
1580                && !self.is_handler(t)
1581        };
1582        match r {
1583            Region::Goto { target: t } if ok(*t) => *r = Region::Empty,
1584            Region::Seq(v) => {
1585                if let Some(last) = v.last_mut() {
1586                    self.strip_handler_exit_goto(last, end_pc);
1587                }
1588                if matches!(v.last(), Some(Region::Empty)) {
1589                    v.pop();
1590                }
1591            }
1592            Region::If { then_r, else_r, .. } => {
1593                self.strip_handler_exit_goto(then_r, end_pc);
1594                self.strip_handler_exit_goto(else_r, end_pc);
1595            }
1596            _ => {}
1597        }
1598    }
1599
1600    /// Blocks reachable from this group's handler entries whose EVERY
1601    /// normal pred stays within handler flow: the handler's private tail
1602    /// (`if (interrupted) selfInterrupt(); throw t;` — jdk11 AQS
1603    /// .acquireQueued). They are NOT the post-try continuation: treating
1604    /// them as one emits the handler tail as a top-level sibling after the
1605    /// try (未报告的异常错误Throwable on the leaked `throw t`) and strips it
1606    /// out of the handler universe. A merge with any pred from normal code
1607    /// (jdk26 AlgorithmId.getName's shared return tail) is NOT handler-flow.
1608    pub(crate) fn handler_flow_only(&self, gi: usize) -> HashSet<usize> {
1609        let hf = self.handler_flow_only_inner(gi);
1610        if crate::dbg_flag!("JCDC_DBG_HF") {
1611            let g = &self.groups[gi];
1612            let mut hs: Vec<usize> = hf.iter().copied().collect();
1613            hs.sort();
1614            eprintln!(
1615                "HF gi={} span=({},{}) handlers={:?} hf={:?}",
1616                gi, g.start, g.end, g.handlers, hs
1617            );
1618        }
1619        hf
1620    }
1621
1622    fn handler_flow_only_inner(&self, gi: usize) -> HashSet<usize> {
1623        let g = &self.groups[gi];
1624        let mut hf: HashSet<usize> = HashSet::new();
1625        let mut q: VecDeque<usize> = VecDeque::new();
1626        for (h, _) in &g.handlers {
1627            if let Some(hb) = self.cfg.block_at(*h) {
1628                q.push_back(hb);
1629            }
1630        }
1631        let in_body = |b: usize| matches!(self.body_group.get(&b), Some(&og) if og == gi);
1632        while let Some(b) = q.pop_front() {
1633            for &s in &self.cfg.blocks[b].succ {
1634                if hf.contains(&s) || in_body(s) || self.handler_group.contains_key(&s) {
1635                    continue;
1636                }
1637                // Never classify a LOOP HEADER as handler-flow-only: a
1638                // loop is an independent construct that some scope must
1639                // structure — when the fixpoint swallowed one, every
1640                // hf-keyed consumer inherited the whole loop as
1641                // "private tail" (jdk17 Files.createDirectories: the
1642                // parent-check handler's hf ran 10→12→16→17 absorbing
1643                // the iterator loop, restrict_handler_branch then
1644                // truncated the if-arm walk to universe {16,3}, and the
1645                // loop plus its `return child` tail were never
1646                // structured at the top level — 缺少返回语句 x2 trees).
1647                // Stopping AT the header keeps the handler's real
1648                // private blocks (Module's goto-pending stub, accept's
1649                // assert-throw/return chain, acquireQueued's
1650                // selfInterrupt+athrow) while the loop's enclosing scope
1651                // structures it and the post-If continuation copies
1652                // handle the rest.
1653                if self.sese_loop_headers.contains(&s) || self.loops_stack.contains(&s) {
1654                    continue;
1655                }
1656                if self.cfg.blocks[s]
1657                    .pred
1658                    .iter()
1659                    .all(|p| hf.contains(p) || in_body(*p) || self.handler_group.contains_key(p))
1660                {
1661                    hf.insert(s);
1662                    q.push_back(s);
1663                }
1664            }
1665        }
1666        hf
1667    }
1668
1669    /// A handler scope's COND branch walk must stay inside the handler's
1670    /// OWN flow: sub_scope's group expansion keys off body_group
1671    /// membership of a block, and a handler-adjacent block owned by an
1672    /// ENCLOSING try body widens the branch to the whole enclosing span --
1673    /// the post-handler continuation re-walked with the handler's (empty)
1674    /// group visibility emits its nested tries BARE (jdk17
1675    /// SignerInfo.verify's catch(Exception) debug arm swallowed the rest
1676    /// of the method: the initVerifyWithParam multi-catch copy lost its
1677    /// try -- 未报告的异常错误InvalidAlgorithmParameterException). Blocks
1678    /// outside the handler flow fall to the Goto arms instead;
1679    /// strip_handler_exit_goto renders forward ones as the natural
1680    /// fallthrough into the outer continuation.
1681    fn restrict_handler_branch(&self, sub: &mut HashSet<usize>, entry: usize) {
1682        let Some(&gi) = self.handler_group.get(&entry) else {
1683            return;
1684        };
1685        let hf = self.handler_flow_only(gi);
1686        // Handler-EXCLUSIVE blocks: normal flow from the method entry,
1687        // barred at EVERY handler head, never reaches them — their only
1688        // entry is through exception flow. No normal-path walk ever
1689        // emits them, so dropping them from the arm strands them in no
1690        // region: jdk17 Files.createDirectories' outermost
1691        // NoSuchFileException retry level lost the iterator loop +
1692        // `return dir` tail (blocks past a loop header are invisible to
1693        // the hf fixpoint — the header's pred chain crosses the
1694        // break-stub merge that body flow also reaches, and the
1695        // loop-header skip stops the fixpoint at the header itself);
1696        // SESE's bottom fallback then re-peeled the orphaned loop
1697        // without its exit return — 缺少返回语句 x2 trees. Loops whose
1698        // header is handler-exclusive structure normally inside the arm
1699        // walk (structure_loop keys off sese_loop_headers). A pred-based
1700        // closure cannot compute this: the loop header and its body
1701        // preds form a cycle that never bootstraps.
1702        let mut normal_reach: HashSet<usize> = HashSet::new();
1703        let mut q: VecDeque<usize> = VecDeque::new();
1704        if !self.handler_group.contains_key(&self.cfg.entry) {
1705            q.push_back(self.cfg.entry);
1706            normal_reach.insert(self.cfg.entry);
1707        }
1708        while let Some(b) = q.pop_front() {
1709            for &s in &self.cfg.blocks[b].succ {
1710                if normal_reach.contains(&s) || self.handler_group.contains_key(&s) {
1711                    continue;
1712                }
1713                normal_reach.insert(s);
1714                q.push_back(s);
1715            }
1716        }
1717        sub.retain(|b| {
1718            *b == entry
1719                || hf.contains(b)
1720                || !normal_reach.contains(b)
1721                || self.handler_group.get(b) == Some(&gi)
1722        });
1723        sub.insert(entry);
1724    }
1725
1726    /// True when block `n` is the post-try continuation of some group
1727    /// currently being structured (or active in this walk): the owner
1728    /// walk emits it at the right level after the Try region, so a
1729    /// claimed-arrival Goto must stay a bare Goto (conversion strips it
1730    /// as the try-follow fallthrough) instead of an inline copy. The
1731    /// forward scan tolerates already-claimed blocks: by the time a
1732    /// sibling body path arrives at `n`, an earlier arm may have walked
1733    /// it (jdk11 Module.loadModuleInfoClass: block pc 20 was claimed by
1734    /// the if-then arm walk, yet it is gi=1's cont — copying at the
1735    /// fallthrough arrival duplicated the close+return chain into the
1736    /// body and defeated twr_j11's fold).
1737    fn is_cont_of_active_group(
1738        &self,
1739        n: usize,
1740        stop: &HashSet<usize>,
1741        claimed: &HashSet<usize>,
1742        active: &[usize],
1743    ) -> bool {
1744        let scan = |a: usize| -> bool {
1745            let gend = self.groups[a].end;
1746            for nb in self.cfg.blocks.iter() {
1747                if (nb.start as u32) < gend as u32 {
1748                    continue;
1749                }
1750                if stop.contains(&nb.id) {
1751                    return false;
1752                }
1753                if nb.id == n {
1754                    return true;
1755                }
1756                if !claimed.contains(&nb.id) && !self.is_handler(nb.id) {
1757                    return false;
1758                }
1759            }
1760            false
1761        };
1762        active.iter().copied().any(scan)
1763            || self.structuring_groups.borrow().iter().copied().any(scan)
1764    }
1765
1766    /// First unclaimed non-handler block at or after `pc` that the outer
1767    /// walk will actually continue at. `exclude` carries the enclosing
1768    /// scope's barriers (loop exits, follows): a barrier block is never the
1769    /// outer continuation (the post-Try `next` search skips stop blocks),
1770    /// so naming one here would strip the try body's trailing `Goto` for a
1771    /// continuation that never happens -- the jump silently vanishes and
1772    /// the path falls into whatever the scope walks next (jdk17
1773    /// ResourceBundle.loadBundle: the break stub `goto 323` at the
1774    /// protected-span end was stripped because cont resolved to the stub
1775    /// itself, a loop exit in stop; the break path then fell into the null
1776    /// path's `continue` -- 无法访问的语句 on the real tail return).
1777    fn continuation_after(
1778        &self,
1779        pc: u32,
1780        universe: &HashSet<usize>,
1781        claimed: &HashSet<usize>,
1782        exclude: &HashSet<usize>,
1783    ) -> Option<usize> {
1784        // Blocks are in ascending-start order; the scan STOPS at the first
1785        // barrier (exclude) block: a continuation can never lie beyond a
1786        // barrier — flow reaching it leaves this scope (loop-exit break,
1787        // enclosing follow), and picking a block past it misroutes the
1788        // post-try walk (jdk26 Future.resultNow).
1789        for nb in self.cfg.blocks.iter() {
1790            if nb.start < pc {
1791                continue;
1792            }
1793            if exclude.contains(&nb.id) {
1794                return None;
1795            }
1796            if universe.contains(&nb.id) && !claimed.contains(&nb.id) && !self.is_handler(nb.id) {
1797                return Some(nb.id);
1798            }
1799        }
1800        None
1801    }
1802}
1803
1804/// Strip a trailing `Goto{target}` from a region (at any tail position:
1805/// end of a sequence, or the tail of an if/else branch).
1806/// True when every flow path out of this region ends in a terminator
1807/// (return/throw): the region cannot complete normally, so a following
1808/// continuation would be unreachable (javac: 无法访问的语句). Goto/Empty
1809/// are conservative negatives (a Goto may resolve to a fallthrough).
1810/// `active` minus the groups the current walk scope already structured.
1811fn active_filtered(active: &[usize], structured: &HashSet<usize>) -> Vec<usize> {
1812    if structured.is_empty() {
1813        return active.to_vec();
1814    }
1815    active
1816        .iter()
1817        .copied()
1818        .filter(|g| !structured.contains(g))
1819        .collect()
1820}
1821
1822/// True when the region's terminal edge is a Goto to one of the loop's
1823/// own non-handler exits (a `break` onto live post-loop flow).
1824fn region_ends_at_live_exit(r: &Region, exits: &[usize], st: &Structurer) -> bool {
1825    let t = match r {
1826        Region::Goto { target } => *target,
1827        Region::Seq(v) => match v.last() {
1828            Some(last) => return region_ends_at_live_exit(last, exits, st),
1829            None => return false,
1830        },
1831        _ => return false,
1832    };
1833    exits.contains(&t) && !st.is_handler(t)
1834}
1835
1836/// True when the region tree contains a Basic/CopyStmts of `b` (a
1837/// matexit-materialized exit cascade re-emits the exit block inside the
1838/// body; the rotated-do-while rebuild must not duplicate it).
1839fn region_mentions_block(r: &Region, b: usize) -> bool {
1840    match r {
1841        Region::Basic { block } | Region::CopyStmts { block } => *block == b,
1842        Region::Seq(v) => v.iter().any(|x| region_mentions_block(x, b)),
1843        Region::Loop { body, .. } => region_mentions_block(body, b),
1844        Region::If { then_r, else_r, .. } => {
1845            region_mentions_block(then_r, b) || region_mentions_block(else_r, b)
1846        }
1847        Region::Try { body, catches, .. } => {
1848            region_mentions_block(body, b)
1849                || catches.iter().any(|(_, _, r)| region_mentions_block(r, b))
1850        }
1851        Region::Switch { cases, default, .. } => {
1852            cases.iter().any(|(_, c)| region_mentions_block(c, b))
1853                || default
1854                    .as_ref()
1855                    .map(|d| region_mentions_block(d, b))
1856                    .unwrap_or(false)
1857        }
1858        _ => false,
1859    }
1860}
1861
1862pub(crate) fn region_terminates(r: &Region, results: &[crate::ir::build::BlockResult]) -> bool {
1863    region_terminates_ex(r, results, &[])
1864}
1865
1866/// Like `region_terminates`, but a `Goto` into `handler_exits` (loop exits
1867/// that are exception-handler entries) also counts as terminating: inside
1868/// the finally-retry loop such a Goto converts to `break`, and the exit it
1869/// targets is the bytecode's explicit pending-exception rethrow — the
1870/// enclosing Java finally propagates it implicitly, so NOTHING may be
1871/// rendered after the loop (the continuation would copy the wrong exit's
1872/// throw onto the break path). A break toward a NON-handler exit is a real
1873/// loop completion and must NOT suppress the continuation.
1874pub(crate) fn region_terminates_ex(
1875    r: &Region,
1876    results: &[crate::ir::build::BlockResult],
1877    handler_exits: &[usize],
1878) -> bool {
1879    match r {
1880        Region::CopyStmts { block } | Region::Basic { block } => matches!(
1881            results[*block].term,
1882            crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
1883        ),
1884        // A Goto into a handler exit counts (see fn doc); so does a Goto
1885        // whose TARGET is a shared return/throw terminator — conversion
1886        // inlines those at the arrival site (term copy), so the flow is
1887        // abrupt here too (TempFileHelper.create's SE catch
1888        // `if (dir != tmpdir) <goto throw-e>; ...`).
1889        Region::Goto { target } => {
1890            handler_exits.contains(target)
1891                || matches!(
1892                    results[*target].term,
1893                    crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
1894                )
1895        }
1896        Region::Seq(v) => v
1897            .last()
1898            .map(|x| region_terminates_ex(x, results, handler_exits))
1899            .unwrap_or(false),
1900        Region::If { then_r, else_r, .. } => {
1901            region_terminates_ex(then_r, results, handler_exits)
1902                && region_terminates_ex(else_r, results, handler_exits)
1903        }
1904        // A try completes abruptly when its body does and every catch
1905        // does (no finally — with one, the finally's completion governs;
1906        // stay conservative). This is exact for region trees: a Goto
1907        // part names its block's terminal edge, and an If part is
1908        // abrupt only when BOTH branches are (jdk11/17
1909        // TempFileHelper.create: the for(;;) retry body =
1910        // try(generatePath){both IPE arms throw} + try(create){4 abrupt
1911        // catch arms + if/else of two areturns} — the old `_ => false`
1912        // made body_done miss it, the bottom-tested natural_follow
1913        // fallback named the IN-TRY areturns as the loop's follow, and
1914        // the top chain re-emitted `return Files.createDirectory(..)`
1915        // after the non-completing loop — 无法访问的语句 x2 trees).
1916        Region::Try { body, catches, .. } => {
1917            region_terminates_ex(body, results, handler_exits)
1918                && catches
1919                    .iter()
1920                    .all(|(_, _, c)| region_terminates_ex(c, results, handler_exits))
1921        }
1922        _ => false,
1923    }
1924}
1925
1926/// Can a rendered Switch region complete normally (flow out of its
1927/// follow)? JLS 14.11: yes when the selector can match no case (no
1928/// default region rendered), or when the default arm does not complete
1929/// abruptly, or when any case arm does not (its `break` exits the
1930/// switch). An arm whose terminal edge is `Goto{follow}` IS the break —
1931/// normal completion — even when the follow block itself is a shared
1932/// return/throw terminator (region_terminates would call that Goto
1933/// abrupt: the term-copy inlining applies to gotos ACROSS scopes, not
1934/// to a switch's own follow binding). region_terminates is `_ => false`
1935/// for nested Switch, so those conservatively count as completing.
1936fn switch_completes_normally(
1937    r: &Region,
1938    follow: usize,
1939    results: &[crate::ir::build::BlockResult],
1940) -> bool {
1941    fn breaks_out(r: &Region, follow: usize) -> bool {
1942        match r {
1943            Region::Goto { target } => *target == follow,
1944            Region::Seq(v) => v.last().map(|x| breaks_out(x, follow)).unwrap_or(false),
1945            Region::Empty => true,
1946            _ => false,
1947        }
1948    }
1949    fn arm_abrupt(r: &Region, follow: usize, results: &[crate::ir::build::BlockResult]) -> bool {
1950        !breaks_out(r, follow) && region_terminates(r, results)
1951    }
1952    match r {
1953        Region::Switch { cases, default, .. } => {
1954            (match default.as_deref() {
1955                None => true,
1956                Some(d) => !arm_abrupt(d, follow, results),
1957            }) || cases.iter().any(|(_, c)| !arm_abrupt(c, follow, results))
1958        }
1959        _ => !region_terminates(r, results),
1960    }
1961}
1962
1963/// Remove a trailing `Goto` to a sibling case head (Java fallthrough).
1964fn strip_fallthrough_goto(r: Region, heads: &HashSet<usize>) -> Region {
1965    match r {
1966        Region::Goto { target } if heads.contains(&target) => Region::Empty,
1967        Region::Seq(mut v) => {
1968            if let Some(last) = v.last() {
1969                if let Region::Goto { target } = last {
1970                    if heads.contains(target) {
1971                        v.pop();
1972                    }
1973                }
1974            }
1975            match v.len() {
1976                0 => Region::Empty,
1977                1 => v.pop().unwrap(),
1978                _ => Region::Seq(v),
1979            }
1980        }
1981        other => other,
1982    }
1983}
1984
1985/// Debug helper: first block id a region starts at (usize::MAX if none).
1986pub(crate) fn region_head_block(r: &Region) -> usize {
1987    match r {
1988        Region::Basic { block } => *block,
1989        Region::Seq(v) => v.first().map(region_head_block).unwrap_or(usize::MAX),
1990        Region::If { block, .. } => *block,
1991        Region::Loop { header, .. } => *header,
1992        Region::Switch { block, .. } => *block,
1993        _ => usize::MAX,
1994    }
1995}
1996
1997/// Debug helper: compact variant shape of a region for traces.
1998fn region_shape(r: &Region) -> String {
1999    match r {
2000        Region::Basic { block } => format!("Basic({})", block),
2001        Region::Seq(v) => format!(
2002            "Seq{}[{}]",
2003            v.len(),
2004            v.iter().map(region_shape).collect::<Vec<_>>().join(",")
2005        ),
2006        Region::If { block, .. } => format!("If({})", block),
2007        Region::Loop { header, exits, .. } => format!("Loop({} exits={:?})", header, exits),
2008        Region::Switch {
2009            block,
2010            cases,
2011            default,
2012            ..
2013        } => format!(
2014            "Switch({} cases={} def={})",
2015            block,
2016            cases.len(),
2017            default.is_some()
2018        ),
2019        Region::Goto { target } => format!("Goto({})", target),
2020        Region::Empty => "Empty".to_string(),
2021        Region::Try { group_idx, .. } => format!("Try(g{})", group_idx),
2022        Region::CopyStmts { block } => format!("Copy({})", block),
2023    }
2024}
2025
2026impl<'a> Structurer<'a> {
2027    pub fn new(cfg: &'a Cfg, results: &'a Vec<BlockResult>) -> Structurer<'a> {
2028        Self::with_diamonds(cfg, results, Default::default(), Default::default())
2029    }
2030
2031    pub fn with_diamonds(
2032        cfg: &'a Cfg,
2033        results: &'a Vec<BlockResult>,
2034        diamond_merges: std::collections::HashSet<usize>,
2035        fold_regions: HashMap<usize, (usize, HashSet<usize>)>,
2036    ) -> Structurer<'a> {
2037        let groups = group_exceptions_with(cfg, Some(results));
2038        let mut body_group = HashMap::new();
2039        let mut handler_group = HashMap::new();
2040        // Groups are sorted outer-first (start asc, end desc); later
2041        // (more nested) groups overwrite so each block maps to its
2042        // INNERMOST containing try body.
2043        for (gi, g) in groups.iter().enumerate() {
2044            for b in &cfg.blocks {
2045                if b.ins_len == 0 {
2046                    continue;
2047                }
2048                if b.start >= g.start && b.end <= g.end.max(g.start + 1) {
2049                    body_group.insert(b.id, gi);
2050                }
2051            }
2052            for (hpc, _) in &g.handlers {
2053                if let Some(hb) = cfg.block_at(*hpc) {
2054                    handler_group.entry(hb).or_insert(gi);
2055                }
2056            }
2057        }
2058        // Reverse index: fold root -> merge block.
2059        let mut fold_root_to_merge = HashMap::new();
2060        for (&merge, (root, _vis)) in &fold_regions {
2061            fold_root_to_merge.insert(*root, merge);
2062        }
2063        Self::from_parts(cfg, results, groups, diamond_merges, fold_regions)
2064    }
2065
2066    /// Constructor for callers that already computed the exception groups
2067    /// (e.g. to share them with the Converter): skips the internal
2068    /// `group_exceptions_with` recompute. The body/handler maps derive
2069    /// from the passed groups exactly as `with_diamonds` derives them.
2070    pub fn with_precomputed_groups(
2071        cfg: &'a Cfg,
2072        results: &'a Vec<BlockResult>,
2073        groups: Vec<TryGroup>,
2074        diamond_merges: std::collections::HashSet<usize>,
2075        fold_regions: HashMap<usize, (usize, HashSet<usize>)>,
2076    ) -> Structurer<'a> {
2077        Self::from_parts(cfg, results, groups, diamond_merges, fold_regions)
2078    }
2079
2080    /// Shared constructor tail: the group-derived indexes and the walk
2081    /// guards (visit budget + deadline read from their thread-locals —
2082    /// both None in jcdc, armed per-method by ddc).
2083    fn from_parts(
2084        cfg: &'a Cfg,
2085        results: &'a Vec<BlockResult>,
2086        groups: Vec<TryGroup>,
2087        diamond_merges: std::collections::HashSet<usize>,
2088        fold_regions: HashMap<usize, (usize, HashSet<usize>)>,
2089    ) -> Structurer<'a> {
2090        let mut body_group = HashMap::new();
2091        let mut handler_group = HashMap::new();
2092        // Groups are sorted outer-first (start asc, end desc); later
2093        // (more nested) groups overwrite so each block maps to its
2094        // INNERMOST containing try body.
2095        for (gi, g) in groups.iter().enumerate() {
2096            for b in &cfg.blocks {
2097                if b.ins_len == 0 {
2098                    continue;
2099                }
2100                if b.start >= g.start && b.end <= g.end.max(g.start + 1) {
2101                    body_group.insert(b.id, gi);
2102                }
2103            }
2104            for (hpc, _) in &g.handlers {
2105                if let Some(hb) = cfg.block_at(*hpc) {
2106                    handler_group.entry(hb).or_insert(gi);
2107                }
2108            }
2109        }
2110        // Reverse index: fold root -> merge block.
2111        let mut fold_root_to_merge = HashMap::new();
2112        for (&merge, (root, _vis)) in &fold_regions {
2113            fold_root_to_merge.insert(*root, merge);
2114        }
2115        Structurer {
2116            cfg,
2117            results,
2118            groups,
2119            body_group,
2120            handler_group,
2121            diamond_merges,
2122            fold_regions,
2123            fold_root_to_merge,
2124            copied_tails: HashSet::new(),
2125            loops_stack: Vec::new(),
2126            switch_depth: 0,
2127            case_arm_ctx: Vec::new(),
2128            sese_loop_headers: std::collections::HashSet::new(),
2129            sese_exc_retry_headers: std::collections::HashSet::new(),
2130            walk_depth: 0,
2131            walk_visits_left: std::cell::Cell::new(
2132                WALK_VISIT_OVERRIDE.with(|c| c.get()).unwrap_or(u64::MAX),
2133            ),
2134            walk_deadline: WALK_DEADLINE.with(|c| c.get()),
2135            copy_budget: std::cell::Cell::new(
2136                BUDGET_OVERRIDE
2137                    .with(|c| c.get())
2138                    .or_else(|| crate::dbg_value!("JCDC_COPY_BUDGET", u32))
2139                    .unwrap_or(512),
2140            ),
2141            final_fields: HashSet::new(),
2142            postdom_ctx: std::cell::OnceCell::new(),
2143            structuring_groups: std::cell::RefCell::new(Vec::new()),
2144        }
2145    }
2146
2147    /// Immediate post-dominator of `entry` within `universe`. Delegates to the
2148    /// O(n) `immediate_postdom` (BFS nearest-confluence with successor-candidate
2149    /// rejection); kept as a `&mut self` method for call-site convenience.
2150    pub(crate) fn postdom_ipdom(
2151        &mut self,
2152        universe: &HashSet<usize>,
2153        entry: usize,
2154    ) -> Option<usize> {
2155        // The exempt/terminators/final_writers/abrupt_only/stmt_counts
2156        // sets below are METHOD-INVARIANT — every input is built in the
2157        // constructor and never mutated afterwards (handler_group and
2158        // groups are constructor-only, results/cfg are borrows, and
2159        // final_fields stays empty inside the Structurer, so
2160        // terminator_writes_final always returns false here) — yet this
2161        // method rebuilt them all on EVERY call, and walk_inner consults
2162        // it at every IF decision. Compute once, reuse forever; the
2163        // computed values are byte-identical to the per-call recompute.
2164        let ctx = self.postdom_ctx.get_or_init(|| {
2165            // Successor-candidate rejection exempts every GROUP-OWNED
2166            // block (whole try bodies and handler heads, not just
2167            // group starts): rejecting an in-body successor re-routes
2168            // the COND walk across the carve-out boundary and
2169            // dissolved HttpURLConnection.getInputStream0's inner
2170            // try/catch, while the statement-block rejection outside
2171            // groups stays.
2172            let mut exempt: HashSet<usize> = self.handler_group.keys().copied().collect();
2173            for g in &self.groups {
2174                if let Some(b) = self.cfg.block_at(g.start) {
2175                    exempt.insert(b);
2176                }
2177            }
2178            let terminators: HashSet<usize> = (0..self.results.len())
2179                .filter(|&b| self.is_terminator_block(b))
2180                .collect();
2181            let final_writers: HashSet<usize> = (0..self.results.len())
2182                .filter(|&b| self.terminator_writes_final(b))
2183                .collect();
2184            // Blocks whose every CFG exit is abrupt (return/throw or
2185            // none): a route landing there dies before any parked
2186            // merge, so it never skips a shared RETURN tail.
2187            let mut abrupt_only: HashSet<usize> = HashSet::new();
2188            for b in 0..self.results.len() {
2189                let term_abrupt = matches!(
2190                    self.results[b].term,
2191                    crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
2192                );
2193                let succs_abrupt = !self.cfg.blocks[b].succ.is_empty()
2194                    && self.cfg.blocks[b]
2195                        .succ
2196                        .iter()
2197                        .all(|&x| terminators.contains(&x));
2198                if term_abrupt || succs_abrupt {
2199                    abrupt_only.insert(b);
2200                }
2201            }
2202            let stmt_counts: Vec<usize> = self.results.iter().map(|r| r.stmts.len()).collect();
2203            PostdomCtx {
2204                exempt,
2205                terminators,
2206                final_writers,
2207                abrupt_only,
2208                stmt_counts,
2209            }
2210        });
2211        let entry_group = self.body_group.get(&entry).copied();
2212        immediate_postdom(
2213            self.cfg,
2214            self.results,
2215            universe,
2216            entry,
2217            &ctx.exempt,
2218            &ctx.stmt_counts,
2219            &self.body_group,
2220            entry_group,
2221            &ctx.terminators,
2222            &ctx.abrupt_only,
2223            &ctx.final_writers,
2224        )
2225    }
2226
2227    pub(crate) fn is_handler(&self, b: usize) -> bool {
2228        self.handler_group.contains_key(&b)
2229    }
2230
2231    fn term(&self, b: usize) -> &Term {
2232        &self.results[b].term
2233    }
2234
2235    /// Sub-scope for a branch walk: reachable region minus already-claimed
2236    /// blocks (e.g. the surrounding loop header reached via a back edge).
2237    /// Branch region for a dual-terminator stop exit (both If targets are
2238    /// terminator blocks in `stop` — the check tail of javac's finally
2239    /// retry idiom, jdk11 ObjectInputStream.readSerialData copy2:
2240    /// `if (t != null) throw t; <rethrow pending>`). Inside a loop: a
2241    /// Goto resolves to `break` — for a handler-entry exit the pending
2242    /// exception is already in flight and the enclosing Java finally
2243    /// rethrows it implicitly, so breaking out of the retry loop IS the
2244    /// source semantics (the explicit `throw pending` copy would be
2245    /// stripped as in-flight scaffolding, leaving an empty branch that
2246    /// traps the loop forever → the whole post-finally flow 无法访问).
2247    /// Outside a loop: inline the throw (the handler's own rethrow tail).
2248    fn dual_terminator_branch(&self, target: usize, active: &[usize]) -> Region {
2249        let _ = active;
2250        if !self.loops_stack.is_empty() && self.is_pending_rethrow(target) {
2251            // Inside the finally-retry loop, an exit that rethrows the
2252            // PENDING exception (the local a handler entry stored) is the
2253            // bytecode's explicit in-flight rethrow — the enclosing Java
2254            // finally propagates it implicitly, so `break` out of the
2255            // retry loop is the source-equivalent exit (the copied
2256            // `throw pending` is out of lexical scope in the finally AND
2257            // strip_inflight_throws eats it, leaving an empty branch that
2258            // traps the loop forever — jdk11 ObjectInputStream
2259            // .readSerialData 无法访问的语句). Any other terminator exit
2260            // is a real source throw (the ThreadDeath override `throw t`
2261            // — t is a plain method local, not a handler store): inline.
2262            return Region::Goto { target };
2263        }
2264        Region::CopyStmts { block: target }
2265    }
2266
2267    /// True when `b`'s terminal is `throw v` where some exception-handler
2268    /// entry block stores `v` (the in-flight/pending exception slot).
2269    fn is_pending_rethrow(&self, b: usize) -> bool {
2270        let var = match &self.results[b].term {
2271            crate::ir::build::Term::Throw(Expr::Local { var, .. }) => *var,
2272            _ => return false,
2273        };
2274        // The store must be the CATCH-PARAMETER store itself (the handler
2275        // entry's incoming stack is the null exception placeholder): the
2276        // retry idiom's accumulator `t` is also handler-stored, but from a
2277        // Local (`catch (ThreadDeath e) { t = e; }`) — inlining `throw t`
2278        // must stay (it is the source's ThreadDeath override), only the
2279        // pending-slot rethrow becomes the implicit-propagation break.
2280        self.cfg.exc_edges.iter().any(|e| {
2281            self.results[e.to].stmts.iter().any(|s| match s {
2282                crate::ir::stmt::Stmt::LocalDef { var: v, init, .. } => {
2283                    *v == var && matches!(init, Some(crate::ir::expr::Expr::Const(crate::ir::expr::ConstVal::Null)))
2284                }
2285                crate::ir::stmt::Stmt::ExprStmt(crate::ir::expr::Expr::Assign { target, value, .. }) => {
2286                    matches!(target.as_ref(), crate::ir::expr::Expr::Local { var: v, .. } if *v == var)
2287                        && matches!(&**value, crate::ir::expr::Expr::Const(crate::ir::expr::ConstVal::Null))
2288                }
2289                _ => false,
2290            })
2291        })
2292    }
2293
2294    /// Is `t` the header of a natural loop containing a back edge from `cur`-side
2295    /// flow? Approximation used by the walk's back-edge arm: `t` dominates the
2296    /// edge source (the arm's entry condition) AND some predecessor path of the
2297    /// current region loops — but the simple, robust signal here is: the Goto arm
2298    /// only runs when `dom.dominates(t, cur)` already held (see caller), so a
2299    /// terminator `t` that also has an incoming exc-handler back edge is a retry
2300    /// loop header. Callers pass the structurer for cfg access.
2301    fn ctx_is_loop_header(s: &Structurer, t: usize) -> bool {
2302        // EXACT retry idiom only: t's protected range is caught by a handler
2303        // with no normal preds whose sole out-edge returns to t (Future
2304        // .exceptionNow: range (40,57) caught at 57, `goto 40`). Broader
2305        // "any handler flows to t" shapes matched shared RETURN tails whose
2306        // arrivals legitimately need the terminator copy (jdk26 Resolver
2307        // 缺少返回语句 x2 regression).
2308        s.cfg.exc_edges.iter().any(|e| {
2309            e.from == t
2310                && e.to != t
2311                && s.cfg.blocks[e.to].pred.is_empty()
2312                && s.cfg.blocks[e.to].succ.len() == 1
2313                && s.cfg.blocks[e.to].succ[0] == t
2314        }) && matches!(
2315            s.results[t].term,
2316            crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
2317        )
2318    }
2319
2320    fn sub_scope(
2321        &self,
2322        from: usize,
2323        universe: &HashSet<usize>,
2324        stop: &HashSet<usize>,
2325        claimed: &HashSet<usize>,
2326    ) -> HashSet<usize> {
2327        let mut sub = reachable_within(self.cfg, from, stop);
2328        // Restrict to the current region's universe so branch walks cannot
2329        // escape their scope (e.g. out of a try body into the loop header).
2330        // Blocks inside a try group whose start is in the universe are kept
2331        // even if their predecessors are outside: their flow is carved out
2332        // by the Try region and continues at the group's end.
2333        let mut eff = universe.clone();
2334        // The walk root itself seeds the group expansion even when it lies
2335        // OUTSIDE the passed universe: a protected branch target from a
2336        // smaller group's body walk (jdk11 SocketChannelImpl.finishConnect's
2337        // `if (!isConnected())` then-arm — block pc 39 belongs to the
2338        // readLock/writeLock finally groups (39..174)/(39..167)/(46..105)
2339        // but the If sits inside group (14..23), whose body universe is
2340        // {2,3}; owner_scope then filtered everything and the expansion
2341        // loop had nothing to key off) got sub = {from} alone — the arm
2342        // emitted Goto{pc 46} and the ENTIRE connect-retry body (loop,
2343        // endFinishConnect, return true) vanished, leaving the
2344        // catch(IOException) try body unable to throw (不能抛出异常错误) and
2345        // the blocking/connected locals dangling. The holds_start guard
2346        // still blocks mid-group roots from ballooning (getInputStream0).
2347        eff.insert(from);
2348        let seed = eff.clone();
2349        for &b in &seed {
2350            if let Some(&gi) = self.body_group.get(&b) {
2351                let g = &self.groups[gi];
2352                // Expand to the full span ONLY when the scope holds the
2353                // group's START block: a walk rooted mid-group (a
2354                // handler-restricted arm at a single continuation block,
2355                // jdk11 getInputStream0's RT-catch arm -> block 33) must
2356                // not balloon to the whole enclosing span — it dragged the
2357                // entire method body into the catch with the handler's
2358                // (nested-less) group visibility, emitting the reflection
2359                // try's call bare (未报告的异常错误NoSuchFieldException).
2360                let holds_start = self
2361                    .cfg
2362                    .blocks
2363                    .iter()
2364                    .any(|nb| nb.start == g.start && seed.contains(&nb.id));
2365                if !holds_start {
2366                    continue;
2367                }
2368                for nb in &self.cfg.blocks {
2369                    if nb.ins_len != 0 && nb.start >= g.start && nb.end <= g.end.max(g.start + 1) {
2370                        eff.insert(nb.id);
2371                    }
2372                }
2373            }
2374        }
2375        sub.retain(|b| eff.contains(b) && !claimed.contains(b) && !self.is_handler(*b));
2376        sub.insert(from);
2377        sub
2378    }
2379
2380    /// `cur` is a loop header if some in-universe, unclaimed predecessor has
2381    /// an edge back to `cur` and `cur` dominates it, or `cur` self-loops.
2382    /// True when the normal edge `p -> cur` closes a cycle at `cur`:
2383    /// either `cur` dominates `p` in the scope's normal-flow dominator
2384    /// tree, or `p` is exception-only reachable (compute_dominators'
2385    /// RPO walks normal succ edges only, so handler-flow blocks keep
2386    /// the idom[p]==p sentinel) and `cur` reaches `p` over the
2387    /// exc-augmented CFG — the catch-and-retry back edge (jdk11
2388    /// AbstractClassLoaderValue.putIfAbsent: the Throwable handler's
2389    /// `goto H` is the retry loop's only inbound edge to H besides the
2390    /// True when `p` is a normally-unreachable handler-flow root in this
2391    /// scope's dominator tree (its idom is itself and it is not the scope
2392    /// entry): every path to it runs through an exception edge — the
2393    /// signature of a catch body's first block (or a handler-only island).
2394    fn dom_is_handler_root(&self, p: usize, dom: &DomInfo, entry: usize) -> bool {
2395        dom.idom[p] == p && p != entry
2396    }
2397
2398    /// True when normal flow from block `p` re-enters the pc span
2399    /// [start_pc, end_pc) — the HP-merge retry check: a handler-flow
2400    /// source whose goto lands back inside the protected span is the
2401    /// catch body's `continue` (loop back edge), not a forward merge.
2402    fn flows_back_into_span(&self, p: usize, start_pc: u32, end_pc: u32) -> bool {
2403        let mut seen: HashSet<usize> = HashSet::new();
2404        let mut q: Vec<usize> = self.cfg.blocks[p].succ.clone();
2405        let mut budget = 512usize;
2406        while let Some(b) = q.pop() {
2407            if budget == 0 || seen.contains(&b) {
2408                if budget == 0 {
2409                    return false;
2410                }
2411                continue;
2412            }
2413            budget -= 1;
2414            seen.insert(b);
2415            let bs = self.cfg.blocks[b].start;
2416            if bs >= start_pc && bs < end_pc {
2417                return true;
2418            }
2419            q.extend(self.cfg.blocks[b].succ.iter().copied());
2420        }
2421        false
2422    }
2423
2424    /// pre-loop init; jdk11/17 ObjectInputStream$1.run's superclass
2425    /// walk update block). The scope's dom root is excluded: its idom
2426    /// is itself too, and root->cur with cur reaching the root is just
2427    /// the enclosing scope's circulation.
2428    fn closes_back_edge(
2429        &self,
2430        cur: usize,
2431        p: usize,
2432        dom: &DomInfo,
2433        entry: usize,
2434        use_exc: bool,
2435    ) -> bool {
2436        if p == cur {
2437            return true;
2438        }
2439        if dom.dominates(cur, p) {
2440            return true;
2441        }
2442        if !use_exc {
2443            return false;
2444        }
2445        // Exclusions: `p == entry` is the scope root's forward edge
2446        // (root→cur with cur reaching the root is the enclosing scope's
2447        // circulation, not a loop at cur), and an exc-only `cur` that is
2448        // not the root is a mid-cycle handler-flow member (jdk11
2449        // Process.waitFor's sleep block — the genuine header sits two
2450        // hops up). The root itself (cur == entry, idom[root]==root by
2451        // construction) stays eligible: jdk26 Future.exceptionNow's
2452        // finally-group body walk is rooted AT the retry header.
2453        if dom.idom[p] == p && p != entry {
2454            let root_walk = cur == entry;
2455            if root_walk {
2456                // A sub-scope root whose sentinel pred is the sub-scope's
2457                // own branch head is an if-ARM walk: the head-to-root
2458                // "cycle" runs out through the ENCLOSING loop's iterator
2459                // (jdk11 FileDescriptor.closeAll: spurious
2460                // while(true){addSuppressed;break} arm wrappers). The
2461                // genuine root-walk retry header (jdk26
2462                // Future.exceptionNow's finally-group body walk rooted AT
2463                // the protected-span start) has its back-edge source as a
2464                // DIRECT exception successor: require that exact edge.
2465                if !self
2466                    .cfg
2467                    .exc_edges
2468                    .iter()
2469                    .any(|e| e.from == cur && e.to == p)
2470                {
2471                    return false;
2472                }
2473            } else if dom.idom[cur] == cur {
2474                // Exc-only mid-cycle member (jdk11 Process.waitFor's
2475                // sleep block): the genuine header sits upstream.
2476                return false;
2477            }
2478            let mut xs: HashMap<usize, Vec<usize>> = HashMap::new();
2479            for e in &self.cfg.exc_edges {
2480                xs.entry(e.from).or_default().push(e.to);
2481            }
2482            return can_reach_cfg_barred(self.cfg, &xs, cur, p, &HashSet::new(), 8192);
2483        }
2484        false
2485    }
2486
2487    fn is_loop_header(
2488        &self,
2489        cur: usize,
2490        universe: &HashSet<usize>,
2491        dom: &DomInfo,
2492        entry: usize,
2493        use_exc: bool,
2494    ) -> bool {
2495        // Enclosing-loop circulation artifact: a walk entry whose only
2496        // successor is an ENCLOSING loop header (a `st = -1; goto head`
2497        // case stub) reaches every in-universe block through that header,
2498        // so the scope-rooted dominator makes `cur` dominate its own
2499        // switch-dispatch preds and the back-edge test fires on a cycle
2500        // that belongs to the enclosing loop — a spurious single-member
2501        // `while (true) { st = -1; continue L1; }` (jdk26 xml
2502        // impl.Parser.xml's default-case wrappers: the switch lost its
2503        // default semantics, sibling cases fell through, and the
2504        // post-switch tail went unreachable — 无法访问的语句). The pred
2505        // only counts when it is reachable from `cur` WITHOUT passing
2506        // through the enclosing header (a genuine loop of `cur`).
2507        let single_enclosing_succ = self.cfg.blocks[cur].succ.len() == 1 && {
2508            let x = self.cfg.blocks[cur].succ[0];
2509            x != cur
2510                && (self.loops_stack.contains(&x)
2511                    || self.sese_loop_headers.contains(&x)
2512                    || self.sese_exc_retry_headers.contains(&x))
2513        };
2514        // Exc-only-reachable preds (handlers and their downstream blocks)
2515        // never get a normal-flow dominator: compute_dominators' RPO walks
2516        // normal succ edges only, so such a block keeps the idom[p]==p
2517        // sentinel and every dominates() query on it is false. When it
2518        // carries a NORMAL edge back into `cur`, that is a genuine back
2519        // edge of a catch-and-retry loop whose cycle runs through a throw
2520        // (jdk11/17 ObjectInputStream$1.run: `for (cl = subcl; cl !=
2521        // ObjectInputStream.class; cl = cl.getSuperclass())` — the update
2522        // block's only inbound edge is try2's exception edge; walk missed
2523        // the loop, copy_walk unrolled 5 nested copies and the tail
2524        // `return TRUE` was lost — 缺少返回语句). Verify the cycle on the
2525        // exc-augmented CFG (same view structure_loop's membership scan
2526        // uses). The scope's dom root is excluded: idom[root]==root too,
2527        // and root→cur with cur⇝root is just the enclosing loop's normal
2528        // circulation, not a loop at `cur`.
2529        for &p in &self.cfg.blocks[cur].pred {
2530            if p == cur {
2531                return true;
2532            }
2533            // In-scope arrivals only: a pred outside this walk's
2534            // universe belongs to a sibling region (a handler arm being
2535            // structured by structure_try). Its back edge is the retry
2536            // loop of the ENCLOSING scope — claiming it here wraps the
2537            // protected block in a spurious inner while(true) whose
2538            // header never lands in loops_stack when the handler arm
2539            // walks, so the retry `goto head` lost its continue (jdk26
2540            // Future.exceptionNow: catch(IE){interrupted=true} fell off
2541            // the method — 缺少返回语句). The dominance branch never saw
2542            // such preds either (out-of-universe preds have sentinel
2543            // idoms); the exc-augmented branch must keep the same view.
2544            if universe.contains(&p) && self.closes_back_edge(cur, p, dom, entry, use_exc) {
2545                if single_enclosing_succ {
2546                    let x = self.cfg.blocks[cur].succ[0];
2547                    let mut barriers: HashSet<usize> = HashSet::new();
2548                    barriers.insert(x);
2549                    if !can_reach_cfg_barred(self.cfg, &HashMap::new(), cur, p, &barriers, 8192) {
2550                        continue;
2551                    }
2552                }
2553                return true;
2554            }
2555        }
2556        false
2557    }
2558
2559    /// Structure the whole method.
2560    pub fn structure_method(&mut self) -> Region {
2561        // From-scratch SESE/dominator-tree structurer (rewrite), gated so the
2562        // default path is the verified `walk` baseline.
2563        let dbg_regions = crate::dbg_flag!("JCDC_DBG_REGIONS");
2564        if crate::dbg_flag!("JCDC_SESE") {
2565            let r = self.structure_method_sese();
2566            if dbg_regions {
2567                eprintln!("REGIONS_SESE {:#?}", r);
2568            }
2569            return r;
2570        }
2571        let r = self.structure_method_walk();
2572        if crate::dbg_flag!("JCDC_DBG_REGIONS") {
2573            eprintln!("REGIONS_WALK {:#?}", r);
2574        }
2575        r
2576    }
2577
2578    /// The verified walk baseline, ungated: the SESE hybrid fallback in
2579    /// method.rs runs it on a fresh Structurer to compare emission counts.
2580    pub fn structure_method_walk(&mut self) -> Region {
2581        let universe: HashSet<usize> = self
2582            .cfg
2583            .blocks
2584            .iter()
2585            .filter(|b| b.ins_len != 0)
2586            .map(|b| b.id)
2587            .collect();
2588
2589        // Exc-mediated retry headers must be known to the walk too: its
2590        // loop-structuring branch and back-edge scans consult
2591        // sese_exc_retry_headers, which only the SESE precompute used to
2592        // fill — in walk-only mode catch-and-retry loops unrolled into
2593        // nested try copies (ois auditSubclass 缺少返回语句 x2 trees).
2594        // sese_loop_headers stays EMPTY here on purpose: feeding walk the
2595        // full natural-header set regressed 6/6/6 (the 1969e0bb lesson —
2596        // walk-side consumers must use the exc-only subset).
2597        {
2598            let idom = compute_dominators(self.cfg, &universe, self.cfg.entry);
2599            let mut lh: HashSet<usize> = HashSet::new();
2600            self.precompute_exc_retry(&universe, &idom, &mut lh);
2601        }
2602
2603        let top_groups: Vec<usize> = (0..self.groups.len())
2604            .filter(|gi| {
2605                let g = &self.groups[*gi];
2606                !self.groups.iter().enumerate().any(|(oj, og)| {
2607                    oj != *gi
2608                        && og.start <= g.start
2609                        && og.end >= g.end
2610                        && (og.start != g.start || og.end != g.end)
2611                })
2612            })
2613            .collect();
2614
2615        let mut claimed = HashSet::new();
2616        self.walk(
2617            self.cfg.entry,
2618            &universe,
2619            &HashSet::new(),
2620            &top_groups,
2621            &mut claimed,
2622            true,
2623        )
2624    }
2625
2626    /// Walk a scope starting at `entry`.
2627    ///
2628    /// * `universe` — blocks eligible for inclusion,
2629    /// * `stop`     — exclusive boundary,
2630    /// * `active`   — try groups that may start inside this scope,
2631    /// * `claimed`  — blocks consumed (in/out; pre-seed for loop bodies),
2632    /// * `allow_claimed_entry` — process `entry` even if already claimed
2633    ///   (loop headers are pre-claimed).
2634    pub(crate) fn walk(
2635        &mut self,
2636        entry: usize,
2637        universe: &HashSet<usize>,
2638        stop: &HashSet<usize>,
2639        active: &[usize],
2640        claimed: &mut HashSet<usize>,
2641        allow_claimed_entry: bool,
2642    ) -> Region {
2643        // Hang guard: pathological methods (e.g. keytool's `doCommands`, with
2644        // thousands of blocks and a long shared-tail chain) can drive unbounded
2645        // `walk` recursion. Cap the depth; beyond it, fall back to a Goto at
2646        // the entry so conversion still emits a valid jump instead of hanging
2647        // or overflowing the stack.
2648        if self.walk_depth >= 256 {
2649            return Region::Goto { target: entry };
2650        }
2651        // Wall-clock guard: bounds the visit-COST (not just the count) of
2652        // pathological explorations — same Goto degradation as the depth
2653        // guard, so conversion stays valid. None = off (jcdc never arms it).
2654        if let Some(dl) = self.walk_deadline {
2655            if std::time::Instant::now() >= dl {
2656                return Region::Goto { target: entry };
2657            }
2658        }
2659        // Visit budget: same degradation class once the exploration ran
2660        // long. u64::MAX = off.
2661        {
2662            let left = self.walk_visits_left.get();
2663            if left == 0 {
2664                return Region::Goto { target: entry };
2665            }
2666            self.walk_visits_left.set(left - 1);
2667            #[cfg(feature = "visit-stats")]
2668            {
2669                WALK_VISITS_TOTAL.with(|c| c.set(c.get() + 1));
2670            }
2671        }
2672        self.walk_depth += 1;
2673        let r = self.walk_inner(entry, universe, stop, active, claimed, allow_claimed_entry);
2674        self.walk_depth -= 1;
2675        r
2676    }
2677
2678    fn walk_inner(
2679        &mut self,
2680        entry: usize,
2681        universe: &HashSet<usize>,
2682        stop: &HashSet<usize>,
2683        active: &[usize],
2684        claimed: &mut HashSet<usize>,
2685        allow_claimed_entry: bool,
2686    ) -> Region {
2687        if !universe.contains(&entry) {
2688            return Region::Empty;
2689        }
2690        let dom = compute_dominators(self.cfg, universe, entry);
2691        // When the entry was pre-claimed (loop header), skip the loop-header
2692        // check for the entry block itself to avoid re-structuring the same
2693        // loop recursively.
2694        let entry_preclaimed = claimed.contains(&entry);
2695        if crate::dbg_flag!("JCDC_DBG_IF") {
2696            eprintln!(
2697                "WALK entry={} universe={:?} stop={:?} claimed={:?}",
2698                entry, universe, stop, claimed
2699            );
2700        }
2701        let mut parts: Vec<Region> = Vec::new();
2702        let mut cur = entry;
2703        let mut first = true;
2704        let mut guard = 0usize;
2705        // Groups this scope has already structured: their continuations
2706        // are OWNED by this walk from structure_try's `cont` step on —
2707        // the active-group-continuation guards must no longer defer to
2708        // an owner that IS this scope (jdk11/17 TimeZone.setDefaultZone:
2709        // the post-try fall-out `goto tail` was elided as "the owner
2710        // will emit it" and the shared `return tz` tail vanished from
2711        // the try's normal path — 缺少返回语句 x2 trees).
2712        let mut structured_here: HashSet<usize> = HashSet::new();
2713        let _ = &mut first;
2714        loop {
2715            guard += 1;
2716            if guard > 100_000 || stop.contains(&cur) || !universe.contains(&cur) {
2717                break;
2718            }
2719            if claimed.contains(&cur) && !(first && allow_claimed_entry) {
2720                // Flow re-entered an already structured block.
2721                if crate::dbg_flag!("JCDC_DBG_IF") {
2722                    eprintln!(
2723                        "GOTO-CLAIMED cur={} entry={} parts={}",
2724                        cur,
2725                        entry,
2726                        parts.len()
2727                    );
2728                }
2729                if !parts.is_empty() {
2730                    if self.is_terminator_block(cur) && !stop.contains(&cur) {
2731                        parts.push(Region::CopyStmts { block: cur });
2732                    } else if !stop.contains(&cur) && !self.loops_stack.contains(&cur) {
2733                        match self.copy_walk(cur, stop, active, entry) {
2734                            Some(r) => parts.push(r),
2735                            None => {
2736                                // copy_walk refused (typically the
2737                                // re-entry guard: `cur` flows back into
2738                                // this walk's entry region). When `cur`
2739                                // is a STATEMENT-BEARING back-edge
2740                                // preheader — its flow re-enters an
2741                                // open loop header — a bare Goto
2742                                // resolves to `continue` and SILENTLY
2743                                // DROPS cur's statements at this
2744                                // arrival (jdk26 ClassPrinterImpl
2745                                // toYaml/toXml BLOCK case: the shared
2746                                // toYaml(ternary-indent, ..) merge
2747                                // block was claimed by the sibling
2748                                // arm's copy; the indent+1 arm lost
2749                                // the call and left a dangling
2750                                // `stack0 = indent + 1`). Copy the
2751                                // statements and keep the Goto for the
2752                                // continue (KeyStore backedge-stub
2753                                // pattern).
2754                                if !self.results[cur].stmts.is_empty()
2755                                    && self.cfg.blocks[cur].succ.iter().any(|&s2| {
2756                                        self.loops_stack.contains(&s2)
2757                                            || self.sese_loop_headers.contains(&s2)
2758                                            || can_reach_cfg(self.cfg, s2, entry, 4096)
2759                                    })
2760                                {
2761                                    parts.push(Region::Seq(vec![
2762                                        Region::CopyStmts { block: cur },
2763                                        Region::Goto { target: cur },
2764                                    ]));
2765                                } else if self.results[cur].stmts.is_empty()
2766                                    && matches!(self.results[cur].term, Term::Fallthrough)
2767                                    && self.cfg.blocks[cur].succ.len() == 1
2768                                    && universe.contains(&self.cfg.blocks[cur].succ[0])
2769                                    && !stop.contains(&self.cfg.blocks[cur].succ[0])
2770                                    && parts.iter().any(|p| {
2771                                        region_head_block(p) == self.cfg.blocks[cur].succ[0]
2772                                    })
2773                                {
2774                                    // Fall-into-sibling: `cur` is a
2775                                    // statement-free fallthrough whose
2776                                    // successor is THIS walk's immediate
2777                                    // next part — an if-else chain's
2778                                    // inner block boundary (jdk17
2779                                    // JarFile.getBytes: the readNBytes
2780                                    // A-arm's tail merge block 6 starts
2781                                    // the B-arm copy that follows the
2782                                    // chain; the plain Goto{6} was
2783                                    // elided as a natural fallthrough
2784                                    // and the A-arm ran straight into
2785                                    // readAllBytes — double read +
2786                                    // spurious EOFException). Materialize
2787                                    // the jump so the sibling copy is
2788                                    // skipped.
2789                                    parts.push(Region::Goto { target: cur });
2790                                } else {
2791                                    parts.push(Region::Goto { target: cur });
2792                                }
2793                            }
2794                        }
2795                    } else {
2796                        parts.push(Region::Goto { target: cur });
2797                    }
2798                } else if self.loops_stack.contains(&cur) || self.sese_loop_headers.contains(&cur) {
2799                    // An EMPTY region arriving at an enclosing loop header
2800                    // IS the back edge (jdk26 Future.exceptionNow's
2801                    // `catch (InterruptedException e) { interrupted =
2802                    // true; }` retry — the handler's only out-edge is
2803                    // `goto head`): emit the Goto so conversion resolves
2804                    // it to `continue`. Dropping it lets the handler fall
2805                    // out of the try silently.
2806                    parts.push(Region::Goto { target: cur });
2807                }
2808                break;
2809            }
2810            if !first && self.is_handler(cur) && active.is_empty() {
2811                // Handler head reached by a stray normal edge; it belongs to
2812                // its Try region.
2813                break;
2814            }
2815            first = false;
2816            let b = &self.cfg.blocks[cur];
2817            if b.ins_len == 0 {
2818                match b.succ.first().copied() {
2819                    Some(n) => {
2820                        cur = n;
2821                        continue;
2822                    }
2823                    None => break,
2824                }
2825            }
2826
2827            // Try group starting exactly here? Match by span (the block may
2828            // belong to a more deeply nested group in body_group); active
2829            // lists are ordered outer-first so the first match is the
2830            // outermost group starting here.
2831            let group_here = active
2832                .iter()
2833                .find(|&&gi| {
2834                    self.groups[gi].start == b.start
2835                        && self.groups[gi].end >= b.end
2836                        && !self.handler_group.contains_key(&cur)
2837                })
2838                .copied()
2839                .or_else(|| {
2840                    // A group starting at `cur` that `active` does not
2841                    // carry: under SESE only OUTERMOST groups are active at
2842                    // the top level, and handler-arm scopes inherit the
2843                    // handler's (span-limited) visibility — a nested group
2844                    // reached there was emitted BARE, losing its try
2845                    // (jdk11 HttpURLConnection.getInputStream0: the RT-catch
2846                    // arm re-walked the continuation and the reflection
2847                    // try/catch(IllegalAccessException|NoSuchFieldException)
2848                    // vanished — 未报告的异常错误). Carve it out unless an
2849                    // ACTIVE group strictly enclosing `cur` owns the
2850                    // carve-out (the intact enclosing body walk case).
2851                    let covered_by_active = self.groups.iter().enumerate().any(|(oj, og)| {
2852                        active.contains(&oj)
2853                            && og.start <= b.start
2854                            && og.end > b.start
2855                            && !(og.start == b.start && og.end >= b.end)
2856                    });
2857                    if covered_by_active || self.handler_group.contains_key(&cur) {
2858                        return None;
2859                    }
2860                    self.groups
2861                        .iter()
2862                        .enumerate()
2863                        .filter(|(gi, g)| {
2864                            g.start == b.start
2865                                && g.end >= b.end
2866                                && !self.structuring_groups.borrow().contains(gi)
2867                        })
2868                        .max_by_key(|(_, g)| g.end)
2869                        .map(|(gi, _)| gi)
2870                });
2871            // The group starts at a LOOP HEADER whose back edge lies
2872            // OUTSIDE the protected span (`for (;;) { try { ... } catch
2873            // { ... } ...retry... }` — jdk26 ClassValue.getFromHashMap):
2874            // the loop is the enclosing construct. Structuring the try
2875            // first consumes the header and leaves the back edge to be
2876            // duplicated or dropped (walk: silent lost retry loop +
2877            // missing return; SESE: 3× unrolled copies). Let the
2878            // loop-header branch below win; the body walk re-finds this
2879            // group at its start. try{while} keeps group-first: its back
2880            // edge is INSIDE the protected span.
2881            let group_here = group_here.filter(|&gi| {
2882                entry_preclaimed
2883                    || !(self.is_loop_header(cur, universe, &dom, entry, true)
2884                        || self.sese_loop_headers.contains(&cur)
2885                        // Exc-retry headers count too: the retry loop of
2886                        // `for(;;){try{..}catch{.. goto head}}` wraps the
2887                        // try even when the try's protected span starts
2888                        // AT the header (jdk26 Future.exceptionNow: the
2889                        // finally-group body walk must structure the loop
2890                        // around the inner IE/EE try so the IE handler's
2891                        // `goto head` resolves to `continue` against
2892                        // loops_stack — without it the inner group won,
2893                        // a degenerate while(true){get();throw} absorbed
2894                        // the protected block, and catch(IE){interrupted
2895                        // =true} fell off the method — 缺少返回语句).
2896                        || self.sese_exc_retry_headers.contains(&cur))
2897                    || !self.cfg.blocks[cur].pred.iter().any(|&p| {
2898                        // No universe filter: an exc-mediated retry back
2899                        // edge's source is handler flow — carved OUT of
2900                        // this body walk's universe by construction
2901                        // (Future.exceptionNow's IE handler at pc 57).
2902                        p != cur
2903                            && self.closes_back_edge(cur, p, &dom, entry, true)
2904                            && (self.cfg.blocks[p].end <= self.groups[gi].start
2905                                || self.cfg.blocks[p].start >= self.groups[gi].end
2906                                // HANDLER-PROTECTION topology: the source
2907                                // is normally-unreachable handler flow
2908                                // (its dominator set is just itself) that
2909                                // flows BACK into the protected span —
2910                                // the HP-merge's outer group spans the
2911                                // handler code, so the retry edge is
2912                                // span-internal yet still handler flow
2913                                // (jdk26 javax.crypto.KDF chooseProvider:
2914                                // OUTER=(91,258)[IAPE,NSAE] + INNER=
2915                                // (91,162)[Exception]; the catch body's
2916                                // getNext retry `goto 91` (b30) lives
2917                                // inside the outer span — without the
2918                                // loop-wins veto here the handler walk
2919                                // copy-unrolls the retry loop 4-deep
2920                                // instead of resolving `continue`).
2921                                || (self.dom_is_handler_root(p, &dom, entry)
2922                                    && self.flows_back_into_span(
2923                                        p,
2924                                        self.groups[gi].start,
2925                                        self.groups[gi].end,
2926                                    )))
2927                    })
2928            });
2929            if crate::dbg_flag!("JCDC_DBG_GRP") {
2930                eprintln!("GRPDEC cur={} start={} end={} group_here={:?} active={:?} hg={} structuring={:?}",
2931                    cur, b.start, b.end, group_here, active,
2932                    self.handler_group.contains_key(&cur),
2933                    self.structuring_groups.borrow().clone());
2934            }
2935            if let Some(gi) = group_here {
2936                let outer_universe = universe.clone();
2937                let try_region = self.structure_try(gi, universe, &outer_universe, claimed, stop);
2938                parts.push(try_region);
2939                structured_here.insert(gi);
2940                let gend = self.groups[gi].end;
2941                let hf_next = self.handler_flow_only(gi);
2942                let next = self.cfg.blocks.iter().find(|nb| {
2943                    nb.start >= gend
2944                        && universe.contains(&nb.id)
2945                        && !stop.contains(&nb.id)
2946                        && !claimed.contains(&nb.id)
2947                        && !self.is_handler(nb.id)
2948                        && !hf_next.contains(&nb.id)
2949                });
2950                match next {
2951                    Some(nb) => {
2952                        cur = nb.id;
2953                        continue;
2954                    }
2955                    None => break,
2956                }
2957            }
2958
2959            // Loop header? (skip for a pre-claimed entry: that loop is the
2960            // one we are currently structuring)
2961            let at_preclaimed_entry = entry_preclaimed && cur == entry && parts.is_empty();
2962            // sese_loop_headers carries the exception-mediated retry
2963            // headers (handler-flow back edges invisible to this scope's
2964            // normal dominance): a try-group body walk that reaches the
2965            // header of an enclosing `for(;;){try{..}catch{..continue}}`
2966            // must structure the loop, else the handler's `goto head`
2967            // copy_walks the head into nested duplicate tries (jdk26
2968            // DateTimeFormatter.parseBest unrolled 5 deep, the
2969            // loop-exhausted `throw DateTimeException` lost —
2970            // 缺少返回语句 x3 trees).
2971            if !at_preclaimed_entry
2972                && (self.is_loop_header(cur, universe, &dom, entry, true)
2973                    || self.sese_exc_retry_headers.contains(&cur))
2974            {
2975                let loop_r = self.structure_loop(cur, universe, stop, active, claimed, &dom);
2976                // A body that cannot complete normally (both arms of its
2977                // tail check inline the loop's terminator exits — the
2978                // finally-retry dual-throw shape, jdk11 ObjectInputStream
2979                // .readSerialData) makes any post-loop continuation
2980                // unreachable (无法访问的语句): neither continue at a
2981                // follow nor copy a terminator exit after it.
2982                let body_done = match &loop_r {
2983                    Region::Loop { body, exits, .. } => {
2984                        let handler_exits: Vec<usize> = exits
2985                            .iter()
2986                            .copied()
2987                            .filter(|e| self.is_handler(*e))
2988                            .collect();
2989                        region_terminates_ex(body, self.results, &handler_exits)
2990                            // A terminal Goto to the loop's OWN non-handler
2991                            // exit converts to `break` (the exit binding
2992                            // wins over term-copy inlining), and the break
2993                            // lands on live post-loop flow — the body DOES
2994                            // complete normally even when the exit block is
2995                            // a shared return terminator (jdk11
2996                            // KeyStore$Builder$2$1.run: break → the
2997                            // `getCalled = true; return ks` tail; the
2998                            // term-copy abrupt reading suppressed the
2999                            // continuation and the CBH path fell off the
3000                            // method — 缺少返回语句 x2 trees).
3001                            && !region_ends_at_live_exit(body, exits, self)
3002                    }
3003                    _ => false,
3004                };
3005                let next = if body_done {
3006                    None
3007                } else {
3008                    self.next_after_loop(&loop_r, universe, stop, claimed)
3009                };
3010                let loop_exits = match &loop_r {
3011                    Region::Loop { exits, .. } => exits.clone(),
3012                    _ => Vec::new(),
3013                };
3014                // ROTATED DO-WHILE REBUILD: the loop's continuation is a
3015                // BACKWARD exit — a claimed statement block BEFORE the
3016                // header that this walk already emitted as the pre-loop
3017                // part (parts tail = [Basic{n}, Loop]). javac rotated
3018                // `do { <n stmts> while (c) {..} } while (c2);` into
3019                // `<n stmts>; while(..){.. goto n ..}`: the in-body
3020                // Goto{n} resolved against the inner exits as a `break`
3021                // landing on the post-loop re-copy of n, which dead-ends
3022                // off the method (jdk11/17 URLClassPath$JarLoader
3023                // .getResource: the refetch-true back edge broke out,
3024                // the do-top copy fell off the if — 缺少返回语句).
3025                // Wrap [Basic{n}, Loop] into an OUTER for(;;) region
3026                // headed at n: conversion's header-continue check
3027                // (which precedes the exits-break check) then resolves
3028                // every Goto{n} inside the body to a labeled continue
3029                // of the outer loop, and classify_loop renders the
3030                // Fallthrough-headed outer as while(true). Skip when a
3031                // matexit cascade already materialized n inside the
3032                // body (its copies would duplicate the outer Basic).
3033                if let Some(n) = next {
3034                    if claimed.contains(&n)
3035                        && self.cfg.blocks[n].start < self.cfg.blocks[cur].start
3036                        && !self.is_handler(n)
3037                        && matches!(loop_r, Region::Loop { .. })
3038                        && !region_mentions_block(&loop_r, n)
3039                        && matches!(parts.last(), Some(Region::Basic { block }) if *block == n)
3040                    {
3041                        let do_top = parts.pop().unwrap();
3042                        let mut members = match &loop_r {
3043                            Region::Loop { members, .. } => members.clone(),
3044                            _ => HashSet::new(),
3045                        };
3046                        members.insert(n);
3047                        parts.push(Region::Loop {
3048                            header: n,
3049                            body: Box::new(Region::Seq(vec![do_top, loop_r])),
3050                            members,
3051                            exits: Vec::new(),
3052                        });
3053                        if crate::dbg_flag!("JCDC_DBG_LOOP") {
3054                            eprintln!("ROTATION header={} do-top={}", cur, n);
3055                        }
3056                        break;
3057                    }
3058                }
3059                parts.push(loop_r);
3060                match next {
3061                    Some(n) => {
3062                        cur = n;
3063                        continue;
3064                    }
3065                    None => {
3066                        // The loop exits into a block this walk cannot
3067                        // continue at (claimed by a sibling branch or out
3068                        // of the universe). When that block is a shared
3069                        // terminator (return/throw), copy it here so this
3070                        // path does not silently fall through.
3071                        if !body_done {
3072                            for &e in &loop_exits {
3073                                if !stop.contains(&e) && self.is_terminator_block(e) {
3074                                    parts.push(Region::CopyStmts { block: e });
3075                                    break;
3076                                }
3077                            }
3078                        }
3079                        // An exit that is an ENCLOSING loop's barrier (in
3080                        // `stop`) must still be materialized as a Goto: it
3081                        // converts to the labeled break of the enclosing
3082                        // loop. Dropping it lets the enclosing loop body
3083                        // end in an implicit continue — the normal
3084                        // completion of the inner loop re-entered the
3085                        // outer instead of leaving it (jdk11
3086                        // FutureTask.removeWaiter spun forever; javac:
3087                        // 无法访问的语句 on the tail return once the
3088                        // continue-stub walks were filtered).
3089                        if !body_done
3090                            && parts
3091                                .last()
3092                                .map(|r| !matches!(r, Region::Goto { .. }))
3093                                .unwrap_or(true)
3094                        {
3095                            if let Some(&e) = loop_exits
3096                                .iter()
3097                                .filter(|e| {
3098                                    stop.contains(e)
3099                                        && !self.loops_stack.contains(e)
3100                                        && universe.contains(e)
3101                                })
3102                                .min_by_key(|e| self.cfg.blocks[**e].start)
3103                            {
3104                                parts.push(Region::Goto { target: e });
3105                            }
3106                        }
3107                        break;
3108                    }
3109                }
3110            }
3111
3112            // Folded diamond root: collapse the whole region — claim the
3113            // absorbed blocks and continue at the merge. The folded ternary
3114            // already lives in the merge's input stack.
3115            if let Some(&merge) = self.fold_root_to_merge.get(&cur) {
3116                if let Some((_root, vis)) = self.fold_regions.get(&merge) {
3117                    if !self.results[cur].stmts.is_empty() {
3118                        parts.push(Region::Basic { block: cur });
3119                    }
3120                    claimed.extend(vis.iter().copied());
3121                    claimed.insert(merge);
3122                    claimed.insert(cur);
3123                    if crate::dbg_flag!("JCDC_DBG_IF") {
3124                        eprintln!("FOLD-COLLAPSE root={} merge={}", cur, merge);
3125                    }
3126                    cur = merge;
3127                    // The merge itself must still be processed (it is now
3128                    // claimed, so allow it explicitly on the next iteration).
3129                    first = false;
3130                    // Process the merge block in this same walk: temporarily
3131                    // allowed because we just claimed it; jump back through
3132                    // the loop top with the claimed-entry exception.
3133                    // (Handled below by processing `merge` directly.)
3134                    // Continue the loop; the claimed check at top would
3135                    // break, so instead process merge via a sub-walk splice:
3136                    // simplest is to unclaim merge and let normal flow claim
3137                    // it.
3138                    claimed.remove(&merge);
3139                    continue;
3140                }
3141            }
3142
3143            claimed.insert(cur);
3144            if crate::dbg_flag!("JCDC_DBG_CLAIM") {
3145                eprintln!(
3146                    "CLAIM blk={} entry={} term_is_cond={}",
3147                    cur,
3148                    entry,
3149                    matches!(self.term(cur), Term::Cond { .. })
3150                );
3151            }
3152            match self.term(cur).clone() {
3153                Term::Cond { cond } => {
3154                    let succs = b.succ.clone();
3155                    if succs.len() != 2 {
3156                        // Degenerate conditional (both branches jump to the
3157                        // same block, e.g. javac 7's empty `continue`):
3158                        // emit the statements and walk on into the single
3159                        // successor.
3160                        parts.push(Region::Basic { block: cur });
3161                        match succs.first().copied() {
3162                            Some(nxt)
3163                                if universe.contains(&nxt)
3164                                    && !stop.contains(&nxt)
3165                                    && !claimed.contains(&nxt) =>
3166                            {
3167                                cur = nxt;
3168                                continue;
3169                            }
3170                            _ => break,
3171                        }
3172                    }
3173                    let (fall, taken) = (succs[0], succs[1]);
3174                    let mut pd = self.postdom_ipdom(universe, cur);
3175                    // A shared return/throw block is a poor If-follow: the
3176                    // branch that flows into it should carry its own copy of
3177                    // the terminator, and jumps past it from the other
3178                    // branch stay natural fallthrough. Only treat it as the
3179                    // follow when BOTH branches land on it (the If truly
3180                    // merges there).
3181                    // EXCEPTION — the terminator carries blank-final field
3182                    // assignments: those must execute EXACTLY once on every
3183                    // path, and every copy route (CopyStmts, term copy,
3184                    // SWTAIL) refuses final-writers, so no arm can absorb
3185                    // the tail. Nulling the follow lets the FIRST arm's
3186                    // walk flow into the merge and claim it, and the
3187                    // sibling arms fall off without the assignments
3188                    // (jdk11 LocaleProviderAdapter clinit:
3189                    // adapterPreference/NONEXISTENT_ADAPTER assigned only
3190                    // in the typeList.isEmpty() arm; jdk26
3191                    // ObjectInputFilter$Config clinit:
3192                    // invalidFactoryMessage — 可能尚未初始化变量).
3193                    if let Some(p) = pd {
3194                        if self.is_terminator_block(p)
3195                            && p != taken
3196                            && p != fall
3197                            && !self.terminator_writes_final(p)
3198                        {
3199                            pd = None;
3200                        }
3201                    }
3202
3203                    // Appendix fold: when the post-dominator is unwalkable
3204                    // (None or claimed), the value-diamond continuation
3205                    // between here and the enclosing barrier block M is an
3206                    // "appendix". Fold it into this If's follow so the walk
3207                    // continues at M instead of emitting raw Gotos into
3208                    // already-claimed merge blocks.
3209                    let mut follow = pd;
3210                    let follow_walkable = follow
3211                        .map(|f| {
3212                            universe.contains(&f) && !stop.contains(&f) && !claimed.contains(&f)
3213                        })
3214                        .unwrap_or(false);
3215                    // Never fold an appendix at a pre-claimed loop header:
3216                    // the stop set there is the loop exit, and the header's
3217                    // branch-out must stay a loop exit, not an If follow.
3218                    if !follow_walkable && !at_preclaimed_entry {
3219                        let m = self.appendix_target(cur, universe, stop, claimed);
3220                        if crate::dbg_flag!("JCDC_DBG_IF") {
3221                            eprintln!("COND cur={} pd={:?} unwalkable appendix->{:?}", cur, pd, m);
3222                        }
3223                        if let Some(m) = m {
3224                            follow = Some(m);
3225                        }
3226                    }
3227                    let mut bstop = stop.clone();
3228                    if let Some(f) = follow {
3229                        bstop.insert(f);
3230                    }
3231
3232                    // Chained boolean/ternary: both branch targets lead
3233                    // (through pure value-push blocks) to a common merge
3234                    // whose input stack was folded into ternaries during
3235                    // block building. The true merge is the end of the
3236                    // pure-value tail chain (the post-dominator may be a
3237                    // push block itself when the diamond carries trailing
3238                    // values). Skip the scaffolding and continue at the merge.
3239                    let mut diamond_jump: Option<usize> = None;
3240                    {
3241                        // Candidate merges: nearest block reachable from BOTH
3242                        // branches (true confluence), then the taken-side
3243                        // pure chain end, then the post-dominator.
3244                        let mut cands: Vec<usize> = Vec::new();
3245                        {
3246                            // Deterministic candidate order (HashSet
3247                            // iteration varies per process).
3248                            let mut dms: Vec<usize> = self
3249                                .diamond_merges
3250                                .iter()
3251                                .copied()
3252                                .filter(|dm| {
3253                                    *dm != cur && universe.contains(dm) && !stop.contains(dm)
3254                                })
3255                                .collect();
3256                            dms.sort_unstable_by_key(|b| self.cfg.blocks[*b].start);
3257                            cands.extend(dms);
3258                        }
3259                        if let Some(m) = self.branch_confluence(fall, taken, universe) {
3260                            if !cands.contains(&m) {
3261                                cands.push(m);
3262                            }
3263                        }
3264                        if let Some(m) = self.pure_chain_end(taken, universe, stop, claimed) {
3265                            if !cands.contains(&m) {
3266                                cands.push(m);
3267                            }
3268                        }
3269                        if let Some(m) = pd {
3270                            if !cands.contains(&m) {
3271                                cands.push(m);
3272                            }
3273                        }
3274                        for m in cands {
3275                            if m == cur {
3276                                continue;
3277                            }
3278                            let mut vis_t: HashSet<usize> = HashSet::new();
3279                            let mut vis_f: HashSet<usize> = HashSet::new();
3280                            if self.diamond_side(taken, m, universe, &bstop, claimed, &mut vis_t, 0)
3281                                && self
3282                                    .diamond_side(fall, m, universe, &bstop, claimed, &mut vis_f, 0)
3283                            {
3284                                let mut vis = vis_t;
3285                                vis.extend(vis_f);
3286                                // the merge block itself stays available as
3287                                // the next walk position
3288                                vis.remove(&m);
3289                                claimed.extend(vis.iter().copied());
3290                                diamond_jump = Some(m);
3291                                break;
3292                            }
3293                        }
3294                    }
3295                    if let Some(m) = diamond_jump {
3296                        // The header block's own statements (if any) still
3297                        // execute; only its conditional terminal is folded
3298                        // into the merge value.
3299                        if !self.results[cur].stmts.is_empty() {
3300                            parts.push(Region::Basic { block: cur });
3301                        }
3302                        cur = m;
3303                        continue;
3304                    }
3305                    // No ipdom, no appendix, no value diamond — but when
3306                    // the fall side is a bare statement-free jump STUB
3307                    // and the TAKEN arm's open flow crosses past the
3308                    // stub into its target, that target is the real
3309                    // follow: an if/ELSE pyramid dangles the taken arm's
3310                    // fall-out inside the else arm's territory (jdk26
3311                    // UnixFileSystemProvider.newDirectoryStream x3
3312                    // trees: `catch (UnixException x) { ...;
3313                    // x.rethrowAsIOException(dir); }` falls through
3314                    // into the dfd1/SecureDirectoryStream tail owned by
3315                    // the else arm — the catch fell off the if/else and
3316                    // the method fell off its end — 缺少返回语句).
3317                    // Scoped tight: stub-only fall sides (the sequential-
3318                    // if shape javac emits: `if (c) goto join; goto T`
3319                    // with T the dead stub target), non-terminator
3320                    // target, and a taken-arm route to it that bypasses
3321                    // the stub (barriers exclude the stub, the cond and
3322                    // both branch heads, so the ONLY qualifying route
3323                    // crosses over). Natural diamonds keep their ipdom
3324                    // and never reach this branch.
3325                    if follow.is_none() {
3326                        let fall_is_stub = self.results[fall].stmts.is_empty()
3327                            && matches!(self.results[fall].term, Term::Goto)
3328                            && self.cfg.blocks[fall].succ.len() == 1;
3329                        if fall_is_stub {
3330                            let fh = self.cfg.blocks[fall].succ[0];
3331                            // fh must not START a try group: the follow
3332                            // continuation may be walked in a scope where
3333                            // that (nested) group is not visible, and the
3334                            // carve-out would be lost — jdk11
3335                            // ReflectionFactory.getReplaceResolveFor-
3336                            // Serialization's inner try{setAccessible;
3337                            // unreflect}catch(IAE) dissolved into a bare
3338                            // unreflect (未报告的异常错误 x3 trees).
3339                            let fh_starts_group = self
3340                                .groups
3341                                .iter()
3342                                .any(|g| g.start == self.cfg.blocks[fh].start);
3343                            if fh != cur
3344                                && fh != taken
3345                                && !fh_starts_group
3346                                && !stop.contains(&fh)
3347                                && !claimed.contains(&fh)
3348                                && !self.is_terminator_block(fh)
3349                                && !self.is_handler(fh)
3350                                && !self.loops_stack.contains(&fh)
3351                            {
3352                                let exc_succ: HashMap<usize, Vec<usize>> =
3353                                    self.cfg.exc_edges.iter().fold(HashMap::new(), |mut m, e| {
3354                                        m.entry(e.from).or_default().push(e.to);
3355                                        m
3356                                    });
3357                                let mut barriers: HashSet<usize> = HashSet::new();
3358                                barriers.insert(fh);
3359                                barriers.insert(cur);
3360                                barriers.insert(fall);
3361                                barriers.insert(taken);
3362                                for l in &self.loops_stack {
3363                                    barriers.insert(*l);
3364                                }
3365                                barriers.extend(stop.iter().copied());
3366                                if can_reach_cfg_barred(
3367                                    self.cfg, &exc_succ, taken, fh, &barriers, 8192,
3368                                ) {
3369                                    follow = Some(fh);
3370                                    bstop.insert(fh);
3371                                }
3372                            }
3373                        }
3374                    }
3375                    if crate::dbg_flag!("JCDC_DBG_IF") {
3376                        let tb = self.cfg.block_at(self.cfg.blocks[taken].start);
3377                        eprintln!("COND cur={} pc={} fall_pc={} taken_pc={} pd={:?} follow={:?} f_pc={:?} t_id={:?} t_univ={} t_claim={} t_stop={} bstop={:?}",
3378                            cur, self.cfg.blocks[cur].start,
3379                            self.cfg.blocks[fall].start, self.cfg.blocks[taken].start,
3380                            pd, follow, follow.map(|f| self.cfg.blocks[f].start),
3381                            tb, tb.map(|b| universe.contains(&b)).unwrap_or(false),
3382                            tb.map(|b| claimed.contains(&b)).unwrap_or(false),
3383                            tb.map(|b| stop.contains(&b)).unwrap_or(false),
3384                            { let mut v: Vec<u32> = bstop.iter().map(|b| self.cfg.blocks[*b].start).collect(); v.sort(); v });
3385                    }
3386                    if crate::dbg_flag!("JCDC_DBG_IF") {
3387                        // absorb_pure is &mut and CLAIMS the absorbed block
3388                        // — calling it here for the printout pre-claimed the
3389                        // taken arm and flipped the real decision below
3390                        // (JarFile.getBytes rendered a different shape under
3391                        // JCDC_DBG_IF than in production). Debug must be
3392                        // pure observation.
3393                        eprintln!(
3394                            "THENDECIDE cur={} taken={} fall={} follow={:?} stop_t={} stop_f={} term_t={} term_f={} univ_t={} claim_t={}",
3395                            cur, taken, fall, follow, stop.contains(&taken), stop.contains(&fall),
3396                            self.is_terminator_block(taken), self.is_terminator_block(fall),
3397                            universe.contains(&taken), claimed.contains(&taken)
3398                        );
3399                    }
3400                    let then_r = if Some(taken) == follow && !stop.contains(&taken) {
3401                        Region::Empty
3402                    } else if Some(taken) == follow
3403                        && stop.contains(&taken)
3404                        && self.is_terminator_block(taken)
3405                        && !self.loops_stack.is_empty()
3406                        && self.loops_stack[self.loops_stack.len() - 1] != cur
3407                    {
3408                        // Loop-bottom exit test with a TERMINATOR exit
3409                        // (`if (i >= n) <return-tail>; else <digit; goto
3410                        // head>;`): the then arm must materialize as
3411                        // `break`. Leaving it Empty relies on the tail copy
3412                        // after the loop, but a `while (true)` body without
3413                        // any break is a JLS non-completing loop —
3414                        // prune_unreachable then deletes the tail copy
3415                        // (jdk11/17 InstantPrinterParser.format's second
3416                        // loop copy lost `append('Z'); return true` —
3417                        // 缺少返回语句). classify_loop's split_leading_if
3418                        // still rotates a HEADER test into the while
3419                        // condition (cur != header guard keeps top-tested
3420                        // shapes on the old Empty path).
3421                        Region::Goto { target: taken }
3422                    } else if Some(taken) == follow
3423                        && stop.contains(&taken)
3424                        && !self.is_terminator_block(taken)
3425                    {
3426                        // The "follow" is an enclosing barrier (loop exit):
3427                        // the branch is a jump out, not a fallthrough.
3428                        // Terminator exits stay Empty — the return/throw is
3429                        // emitted after the loop. UNLESS the exit chains into
3430                        // an already-claimed terminator (restart-loop guarded
3431                        // case: breaking lands after the loop where nothing
3432                        // remains; jdk26 DecimalFormat case 6).
3433                        if !self.loops_stack.contains(&taken)
3434                            && self.stop_chain_to_claimed_terminator(taken, claimed)
3435                        {
3436                            // `taken` is the if-follow and sits in bstop;
3437                            // copy_walk's reachable_within would exclude the
3438                            // entry itself — lift it for the copy.
3439                            let mut cstop = bstop.clone();
3440                            cstop.remove(&taken);
3441                            match self.copy_walk(taken, &cstop, active, cur) {
3442                                Some(r) => r,
3443                                None => Region::Goto { target: taken },
3444                            }
3445                        } else {
3446                            Region::Goto { target: taken }
3447                        }
3448                    } else if let Some(absorbed) =
3449                        self.absorb_pure(taken, universe, &bstop, claimed, active)
3450                    {
3451                        absorbed
3452                    } else if universe.contains(&taken)
3453                        && !bstop.contains(&taken)
3454                        && !claimed.contains(&taken)
3455                        && !(self.is_handler(taken) && active.is_empty())
3456                    {
3457                        let branch_universe = self.owner_scope(taken, universe);
3458                        let mut sub = self.sub_scope(taken, &branch_universe, &bstop, claimed);
3459                        self.restrict_handler_branch(&mut sub, entry);
3460                        self.walk(taken, &sub, &bstop, active, claimed, false)
3461                    } else if !stop.contains(&taken)
3462                        && !claimed.contains(&taken)
3463                        && !self.is_terminator_block(taken)
3464                        && self.cfg.exc_edges.iter().any(|e| e.from == taken)
3465                    {
3466                        // A PROTECTED branch target outside this scope's
3467                        // universe (an enclosing try body split it into
3468                        // another owner group): walking it gives the flow
3469                        // its own try carve-out and materializes its
3470                        // statements. Region::Empty here silently deleted
3471                        // the branch AND left the group unstructured —
3472                        // jdk17 HttpURLConnection.getOutputStream's
3473                        // `return getOutputStream0()` arm vanished and the
3474                        // doPrivileged call lost its
3475                        // catch(PrivilegedActionException)
3476                        // (未报告的异常错误 x2 methods, sj17 tree).
3477                        let branch_universe = self.owner_scope(taken, universe);
3478                        let mut sub = self.sub_scope(taken, &branch_universe, &bstop, claimed);
3479                        self.restrict_handler_branch(&mut sub, entry);
3480                        self.walk(taken, &sub, &bstop, active, claimed, false)
3481                    } else {
3482                        // Target is the follow (empty), or already structured
3483                        // (loop header → continue; loop exit → break).
3484                        if self.is_terminator_block(taken)
3485                            && !stop.contains(&taken)
3486                            && !self.terminator_writes_final(taken)
3487                        {
3488                            // Shared return/throw block: inline a copy at
3489                            // this branch (safe — terminators have no
3490                            // outgoing flow). Loop-exit terminators (in
3491                            // `stop`) must stay jumps so they resolve to
3492                            // break/while conditions.
3493                            Region::CopyStmts { block: taken }
3494                        } else if claimed.contains(&taken) {
3495                            if !stop.contains(&taken)
3496                                && !bstop.contains(&taken)
3497                                && !self.loops_stack.contains(&taken)
3498                                && !self.terminator_writes_final(taken)
3499                            {
3500                                match self.copy_walk(taken, &bstop, active, cur) {
3501                                    Some(r) => r,
3502                                    None => Region::Goto { target: taken },
3503                                }
3504                            } else {
3505                                Region::Goto { target: taken }
3506                            }
3507                        } else if stop.contains(&taken) && !self.is_terminator_block(taken) {
3508                            // Jump to an enclosing loop's exit (or other
3509                            // barrier): keep it as a Goto so conversion
3510                            // resolves it to `break` — UNLESS the exit chains
3511                            // into an already-claimed terminator (the
3512                            // restart-loop guarded case: breaking would land
3513                            // after the loop where nothing remains and the
3514                            // body is lost; jdk26 DecimalFormat case 6).
3515                            if !self.loops_stack.contains(&taken)
3516                                && self.stop_chain_to_claimed_terminator(taken, claimed)
3517                            {
3518                                let mut cstop = bstop.clone();
3519                                cstop.remove(&taken);
3520                                match self.copy_walk(taken, &cstop, active, cur) {
3521                                    Some(r) => r,
3522                                    None => Region::Goto { target: taken },
3523                                }
3524                            } else {
3525                                Region::Goto { target: taken }
3526                            }
3527                        } else if self.is_terminator_block(taken)
3528                            && stop.contains(&taken)
3529                            && self.is_terminator_block(fall)
3530                            && stop.contains(&fall)
3531                        {
3532                            self.dual_terminator_branch(taken, active)
3533                        } else if self.is_terminator_block(taken)
3534                            && stop.contains(&taken)
3535                            && !self.loops_stack.is_empty()
3536                            && !self.is_terminator_block(fall)
3537                            && stop.contains(&fall)
3538                            && can_reach_cfg(self.cfg, fall, taken, 4096)
3539                        {
3540                            // A TERMINATOR loop-exit target as a mid-body
3541                            // branch whose FALL sibling also flows to that
3542                            // same terminator (both arms are alternate
3543                            // routes to one shared exit): Empty would let
3544                            // the then-arm fall into the REST OF THE BODY
3545                            // instead of the exit emission (jdk11/17/26
3546                            // ConcurrentLinkedQueue.poll: the p==h
3547                            // cas-success arm targeting the shared
3548                            // `return item` block rendered empty and fell
3549                            // into the advance code — the dequeued item was
3550                            // silently lost until the next poll; offer's
3551                            // TAIL-cas arm looped instead of returning
3552                            // true). Emit the jump; conversion resolves it
3553                            // against the loop exits (break) exactly like
3554                            // the sibling arm's chain-end goto. The
3555                            // fall-reaches-taken convergence requirement
3556                            // keeps single-exit body tails (Locale/Pattern
3557                            // hair-trigger shapes) on the historical Empty
3558                            // path.
3559                            Region::Goto { target: taken }
3560                        } else {
3561                            Region::Empty
3562                        }
3563                    };
3564                    let else_r = if Some(fall) == follow && !stop.contains(&fall) {
3565                        if crate::dbg_flag!("JCDC_DBG_IF") {
3566                            eprintln!("IF cur={} else Empty (fall==follow {})", cur, fall);
3567                        }
3568                        Region::Empty
3569                    } else if Some(fall) == follow
3570                        && stop.contains(&fall)
3571                        && self.is_terminator_block(fall)
3572                        && !self.loops_stack.is_empty()
3573                        && self.loops_stack[self.loops_stack.len() - 1] != cur
3574                    {
3575                        // Loop-bottom exit test, inverted orientation: the
3576                        // FALL side is the terminator exit (see the taken
3577                        // side).
3578                        Region::Goto { target: fall }
3579                    } else if Some(fall) == follow
3580                        && stop.contains(&fall)
3581                        && !self.is_terminator_block(fall)
3582                    {
3583                        // The "follow" is an enclosing barrier (loop exit):
3584                        // the branch is a jump out, not a fallthrough.
3585                        // Terminator exits stay Empty — the return/throw is
3586                        // emitted after the loop. See the taken side for the
3587                        // claimed-terminator-chain exception.
3588                        if !self.loops_stack.contains(&fall)
3589                            && self.stop_chain_to_claimed_terminator(fall, claimed)
3590                        {
3591                            let mut cstop = bstop.clone();
3592                            cstop.remove(&fall);
3593                            match self.copy_walk(fall, &cstop, active, cur) {
3594                                Some(r) => r,
3595                                None => Region::Goto { target: fall },
3596                            }
3597                        } else {
3598                            Region::Goto { target: fall }
3599                        }
3600                    } else if let Some(absorbed) =
3601                        self.absorb_pure(fall, universe, &bstop, claimed, active)
3602                    {
3603                        absorbed
3604                    } else if Some(taken) == follow
3605                        && !stop.contains(&taken)
3606                        && universe.contains(&taken)
3607                        && !claimed.contains(&taken)
3608                        && !self.is_terminator_block(taken)
3609                        && !self.results[taken].stmts.is_empty()
3610                        && !self.loops_stack.contains(&taken)
3611                        && !(self.is_handler(taken) && active.is_empty())
3612                        && !bstop.contains(&fall)
3613                        && !claimed.contains(&fall)
3614                        && universe.contains(&fall)
3615                        && !(self.is_handler(fall) && active.is_empty())
3616                    {
3617                        // PARKED-ELSE COMPLETION (the systemic empty-arm
3618                        // family): `taken` is the If's follow AND a
3619                        // statement-bearing in-universe block — the
3620                        // if-else-if chain shape javac emits for a shared
3621                        // else body (`if (t1) goto B; if (t2) goto B; A;
3622                        // goto M; B: ...; M: ...`). The then-arm renders
3623                        // Region::Empty (correct: it falls into B's
3624                        // statements parked after the chain), but the ELSE
3625                        // arm's flow continues PAST B — its terminal
3626                        // Goto{M} is elided at conversion (goto_is_last)
3627                        // and the arm falls INTO the parked B statements:
3628                        // double execution on the else path and B skipped
3629                        // on its real predecessors' path (jdk17
3630                        // JarFile.getBytes: the readNBytes A-arm ran
3631                        // straight into readAllBytes; Pattern.clazz's
3632                        // Bound arm, DatagramChannelImpl.receive,
3633                        // SocketChannelImpl, HttpURLConnection carry the
3634                        // same latent shape). Complete the else arm with
3635                        // its OWN copy of the parked chain — exactly what
3636                        // the GOTO-FT/GOTO-CLAIMED shared-tail arrivals
3637                        // already do for claimed targets: per-arrival
3638                        // copies are the bytecode semantics.
3639                        let branch_universe = self.owner_scope(fall, universe);
3640                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
3641                        self.restrict_handler_branch(&mut sub, entry);
3642                        let arm = self.walk(fall, &sub, &bstop, active, claimed, false);
3643                        // BYPASS DISCRIMINATOR: completion is needed only
3644                        // when some arm path's terminal flow skips the
3645                        // parked follow (a trailing Goto targeting
3646                        // something other than `taken`, at any nesting
3647                        // depth — JarFile's A-arm ends at Goto{epilogue
3648                        // 11}, not the parked B 6). Exempt targets are
3649                        // TRUE JUMPS, never fallthrough bypasses: loop
3650                        // headers/exits and switch follows embedded in
3651                        // the arm (resolve to continue/break), the
3652                        // enclosing loops_stack and case_arm_ctx
3653                        // follows, and this scope's barriers (bstop —
3654                        // enclosing loop exits resolve to break at
3655                        // conversion; Files.walkFileTree's TERMINATE
3656                        // arm ends at Goto{loop-exit} and inverted when
3657                        // wrongly completed). NOT exempt: an active
3658                        // group's post-try continuation when the parked
3659                        // follow's statements still sit between the arm
3660                        // and it at render time (JarFile's b11 — the
3661                        // owner emits the follow chain BEFORE the cont,
3662                        // so the bare fallthrough would cross the parked
3663                        // B statements). The ordinary empty-then `if`
3664                        // (every arm path flows into the follow
3665                        // naturally) needs no copy — completing it would
3666                        // duplicate the tail through every such if in
3667                        // the corpus (25-class matrix churn).
3668                        let mut jump_exempt: HashSet<usize> = HashSet::new();
3669                        Region::jump_targets(&arm, &mut jump_exempt);
3670                        jump_exempt.extend(self.loops_stack.iter().copied());
3671                        jump_exempt.extend(self.case_arm_ctx.iter().filter_map(|c| c.1));
3672                        jump_exempt.extend(bstop.iter().copied());
3673                        let bypass = Region::bypasses_exempt(&arm, taken, &jump_exempt);
3674                        // The parked chain: statement blocks flowing from
3675                        // `taken` up to the blocks this arm already copied
3676                        // (their per-arrival copies render inside the arm;
3677                        // the sibling emits the rest).
3678                        let mut chain: HashSet<usize> = HashSet::new();
3679                        if bypass {
3680                            let mut b = taken;
3681                            for _ in 0..64 {
3682                                if chain.contains(&b)
3683                                    || b == cur
3684                                    // bstop holds the follow == the chain
3685                                    // head itself; only deeper barriers
3686                                    // (loop exits) end the chain.
3687                                    || (b != taken && bstop.contains(&b))
3688                                    || self.loops_stack.contains(&b)
3689                                    || self.is_handler(b)
3690                                    || claimed.contains(&b)
3691                                    || self.terminator_writes_final(b)
3692                                {
3693                                    break;
3694                                }
3695                                if arm.iter_parts().iter().any(|p| region_head_block(p) == b) {
3696                                    break;
3697                                }
3698                                let term = self.results[b].term.clone();
3699                                let stmts_ok = !self.results[b].stmts.is_empty()
3700                                    || matches!(term, Term::Fallthrough | Term::Goto);
3701                                if !stmts_ok {
3702                                    break;
3703                                }
3704                                chain.insert(b);
3705                                match term {
3706                                    Term::Fallthrough | Term::Goto
3707                                        if self.cfg.blocks[b].succ.len() == 1 =>
3708                                    {
3709                                        b = self.cfg.blocks[b].succ[0];
3710                                    }
3711                                    _ => break,
3712                                }
3713                            }
3714                        }
3715                        if chain.is_empty() {
3716                            if crate::dbg_flag!("JCDC_DBG_IF") {
3717                                eprintln!(
3718                                    "PARKCHAIN cur={} chain=EMPTY arm={}",
3719                                    cur,
3720                                    region_shape(&arm)
3721                                );
3722                            }
3723                            arm
3724                        } else {
3725                            // The chain walk must stay a SIMPLE tail
3726                            // reproduction: universe = chain + forward
3727                            // flow closure under the FULL parent
3728                            // barriers (stop ∪ bstop minus the chain
3729                            // itself, plus every enclosing loop header —
3730                            // back edges into open loops belong to the
3731                            // loop's own structuring, never to an arm
3732                            // copy: huc getInputStream0's follow 41
3733                            // flows around the auth retry loop and the
3734                            // unbarriered closure restructured
3735                            // Loop(41)+5 regions inside the else arm).
3736                            let mut cu: HashSet<usize> = chain.clone();
3737                            {
3738                                let mut q: Vec<usize> = chain.iter().copied().collect();
3739                                let mut seen: HashSet<usize> = chain.clone();
3740                                while let Some(b) = q.pop() {
3741                                    for &s2 in &self.cfg.blocks[b].succ {
3742                                        if bstop.contains(&s2)
3743                                            || stop.contains(&s2)
3744                                            || self.loops_stack.contains(&s2)
3745                                        {
3746                                            continue;
3747                                        }
3748                                        if seen.insert(s2) {
3749                                            cu.insert(s2);
3750                                            q.push(s2);
3751                                        }
3752                                    }
3753                                }
3754                            }
3755                            let mut cstop: HashSet<usize> = bstop.union(stop).copied().collect();
3756                            cstop.extend(self.loops_stack.iter().copied());
3757                            cstop.retain(|x| !chain.contains(x));
3758                            // SCRATCH claimed set: a rejected completion
3759                            // must leave no claims behind (an abandoned
3760                            // exploratory walk claiming the follow would
3761                            // degrade the parent's parked-sibling arrival
3762                            // to a bare elided Goto — the very statement
3763                            // loss the completion exists to prevent).
3764                            // copied_tails must roll back too: conversion's
3765                            // reaches_copy_tail elides RawGotos targeting
3766                            // copy-walked tails, and the exploratory walk's
3767                            // copy_walks poisoned that set for the parent's
3768                            // own (identical-shape) walk.
3769                            let saved_claims = claimed.clone();
3770                            let mut scratch = claimed.clone();
3771                            let saved_tails = self.copied_tails.clone();
3772                            let saved_groups = self.structuring_groups.borrow().clone();
3773                            let saved_loops = self.loops_stack.clone();
3774                            let saved_switch_depth = self.switch_depth;
3775                            let saved_case_ctx = self.case_arm_ctx.clone();
3776                            let saved_walk_depth = self.walk_depth;
3777                            let taken_r = if self.take_copy_ticket() {
3778                                self.walk(taken, &cu, &cstop, active, &mut scratch, false)
3779                            } else {
3780                                Region::Empty
3781                            };
3782                            if crate::dbg_flag!("JCDC_DBG_IF") {
3783                                eprintln!(
3784                                    "PARKCHAIN cur={} chain={:?} cu={} cstop={:?} taken_r={}",
3785                                    cur,
3786                                    chain,
3787                                    cu.len(),
3788                                    cstop,
3789                                    region_shape(&taken_r)
3790                                );
3791                            }
3792                            // Accept only SIMPLE completions (linear
3793                            // blocks, copied tails, elision-safe gotos,
3794                            // plain if-diamonds). A Loop/Try/Switch in
3795                            // the result means the parked flow is
3796                            // structural territory the PARENT walk must
3797                            // own — back off and keep the historical
3798                            // shape (adding a copy anyway lost huc's
3799                            // serverAuthentication.addToCache: the arm
3800                            // claimed the follow, the parent's arrival
3801                            // degraded to a bare elided Goto).
3802                            fn simple_completion(r: &Region) -> bool {
3803                                match r {
3804                                    Region::Basic { .. }
3805                                    | Region::CopyStmts { .. }
3806                                    | Region::Empty
3807                                    | Region::Goto { .. } => true,
3808                                    Region::Seq(v) => v.iter().all(simple_completion),
3809                                    Region::If { then_r, else_r, .. } => {
3810                                        simple_completion(then_r) && simple_completion(else_r)
3811                                    }
3812                                    _ => false,
3813                                }
3814                            }
3815                            // CONTINUATION COHERENCE: the chain copy's
3816                            // own walk must end where the bypassing arm
3817                            // path jumps (its continuation walk starts at
3818                            // the bypass target). When they diverge (huc
3819                            // getInputStream0: bypass Goto{117} but the
3820                            // parked 109-chain continues into the auth
3821                            // retry loop territory), splicing the copy
3822                            // into the arm flips conv's goto_is_last for
3823                            // the arm's internal gotos (a nested elided
3824                            // Goto{108} materialized as Continue) and
3825                            // prune_unreachable then ate the tail copy
3826                            // (serverAuthentication.addToCache lost).
3827                            let mut bts: Vec<usize> = Vec::new();
3828                            let coherent = {
3829                                fn chain_cont(r: &Region) -> Option<usize> {
3830                                    match r {
3831                                        Region::Goto { target } => Some(*target),
3832                                        Region::Seq(v) => v.last().and_then(chain_cont),
3833                                        _ => None,
3834                                    }
3835                                }
3836                                fn bypass_targets(r: &Region, out: &mut Vec<usize>) {
3837                                    match r {
3838                                        Region::Goto { target } => out.push(*target),
3839                                        Region::Seq(v) => {
3840                                            if let Some(l) = v.last() {
3841                                                bypass_targets(l, out);
3842                                            }
3843                                        }
3844                                        Region::If { then_r, else_r, .. } => {
3845                                            bypass_targets(then_r, out);
3846                                            bypass_targets(else_r, out);
3847                                        }
3848                                        Region::Try { body, catches, .. } => {
3849                                            bypass_targets(body, out);
3850                                            for (_, _, h) in catches {
3851                                                bypass_targets(h, out);
3852                                            }
3853                                        }
3854                                        _ => {}
3855                                    }
3856                                }
3857                                bypass_targets(&arm, &mut bts);
3858                                // A copy ending in a Goto to somewhere
3859                                // OTHER than the bypass target re-routes
3860                                // the path away from the arm's real
3861                                // continuation (huc). A copy with NO
3862                                // trailing Goto (it ends inside a
3863                                // terminator or a full tail walk past
3864                                // the target — JarFile's chain flows
3865                                // through b11 into the close/return
3866                                // epilogue) preserves it.
3867                                //
3868                                // REACHABILITY RELAXATION: a bypass
3869                                // target that flows to the copy's own
3870                                // continuation WITHOUT crossing the
3871                                // chain barriers shares that
3872                                // continuation — coherent when the arm
3873                                // gets a per-arrival fill (routed2
3874                                // below). jdk11 HttpURLConnection
3875                                // getInputStream0: arm-tail Goto{105}
3876                                // flows 105→106→114 == chain_cont; the
3877                                // strict equality refused the copy and
3878                                // the short-clone arm fell through the
3879                                // sibling else into getRootPath (the
3880                                // source skips it on the path-
3881                                // shortening branch).
3882                                match chain_cont(&taken_r) {
3883                                    Some(c) => bts.iter().all(|t| {
3884                                        *t == c || bypass_flows_to(self.cfg, *t, c, &cstop)
3885                                    }),
3886                                    None => true,
3887                                }
3888                            };
3889                            // PER-BYPASS-TARGET ROUTING (the dci receive
3890                            // misroute cure): when the arm holds exit
3891                            // Gotos to chain-MID blocks (b12/b13's
3892                            // `goto 15` = the SKIP path around the parked
3893                            // `sender = sourceSocketAddress()` block 14),
3894                            // those Gotos elide at conversion (15 is an
3895                            // inner If's follow) and fall into the APPENDED
3896                            // copy's head — the skip path executed the
3897                            // parked SET. Slice the copy at each bypass
3898                            // target's block and splice it over that
3899                            // target's Goto, so every arm path enters the
3900                            // chain exactly where its bytecode jumped:
3901                            // skip paths get the from-15 suffix, the
3902                            // fall-into path keeps the full from-14 copy.
3903                            // Only offered when the appended copy has no
3904                            // trailing Goto of its own (chain_cont=None —
3905                            // a trailing-Goto copy must keep the strict
3906                            // all-bts-equal-cont coherence above).
3907                            if crate::dbg_flag!("JCDC_DBG_IF") {
3908                                eprintln!(
3909                                    "PARKC-ARM cur={} taken={} bts={:?} arm={}",
3910                                    cur,
3911                                    taken,
3912                                    bts,
3913                                    region_shape(&arm)
3914                                );
3915                            }
3916                            fn fill_bypass(
3917                                st: &mut Structurer,
3918                                r: &mut Region,
3919                                copy: &Region,
3920                                taken: usize,
3921                                cu2: &HashSet<usize>,
3922                                cstop: &HashSet<usize>,
3923                                active: &[usize],
3924                                claimed: &HashSet<usize>,
3925                                filled: &mut usize,
3926                                failed: &mut bool,
3927                                top: bool,
3928                            ) {
3929                                match r {
3930                                    Region::Seq(v) => {
3931                                        let n = v.len();
3932                                        for i in 0..n {
3933                                            let is_last = i + 1 == n;
3934                                            if is_last {
3935                                                let goto_t = match &v[i] {
3936                                                    Region::Goto { target } => Some(*target),
3937                                                    _ => None,
3938                                                };
3939                                                if let Some(t) = goto_t {
3940                                                    if t == taken && !top {
3941                                                        // Deep If-arm tail jumping to the
3942                                                        // chain head: own copy of the chain.
3943                                                        if st.take_copy_ticket() {
3944                                                            v[i] = copy.clone();
3945                                                            *filled += 1;
3946                                                        }
3947                                                        continue;
3948                                                    }
3949                                                    if t != taken
3950                                                        && bypass_flows_to(st.cfg, t, taken, cstop)
3951                                                    {
3952                                                        // Arm tail flowing into the chain:
3953                                                        // fresh walk from t; drop a
3954                                                        // preceding CopyStmts{t} (the fresh
3955                                                        // Basic(t) supersedes it).
3956                                                        let mut sc = claimed.clone();
3957                                                        let sub = if st.take_copy_ticket() {
3958                                                            st.walk(
3959                                                                t, cu2, cstop, active, &mut sc,
3960                                                                false,
3961                                                            )
3962                                                        } else {
3963                                                            Region::Empty
3964                                                        };
3965                                                        if !matches!(sub, Region::Empty)
3966                                                            && simple_completion_local2(&sub)
3967                                                            && region_terminates_ex(
3968                                                                &sub,
3969                                                                st.results,
3970                                                                &[],
3971                                                            )
3972                                                        {
3973                                                            v[i] = sub;
3974                                                            if n >= 2 {
3975                                                                if let Region::CopyStmts { block } =
3976                                                                    &v[i - 1]
3977                                                                {
3978                                                                    if *block == t {
3979                                                                        v[i - 1] = Region::Empty;
3980                                                                    }
3981                                                                }
3982                                                            }
3983                                                            *filled += 1;
3984                                                        } else {
3985                                                            *failed = true;
3986                                                        }
3987                                                        continue;
3988                                                    }
3989                                                }
3990                                            }
3991                                            fill_bypass(
3992                                                st, &mut v[i], copy, taken, cu2, cstop, active,
3993                                                claimed, filled, failed, false,
3994                                            );
3995                                        }
3996                                    }
3997                                    Region::If { then_r, else_r, .. } => {
3998                                        fill_bypass(
3999                                            st, then_r, copy, taken, cu2, cstop, active, claimed,
4000                                            filled, failed, false,
4001                                        );
4002                                        fill_bypass(
4003                                            st, else_r, copy, taken, cu2, cstop, active, claimed,
4004                                            filled, failed, false,
4005                                        );
4006                                    }
4007                                    Region::Try { body, catches, .. } => {
4008                                        fill_bypass(
4009                                            st, body, copy, taken, cu2, cstop, active, claimed,
4010                                            filled, failed, false,
4011                                        );
4012                                        for (_, _, h) in catches.iter_mut() {
4013                                            fill_bypass(
4014                                                st, h, copy, taken, cu2, cstop, active, claimed,
4015                                                filled, failed, false,
4016                                            );
4017                                        }
4018                                    }
4019                                    _ => {}
4020                                }
4021                            }
4022                            fn simple_completion_local2(r: &Region) -> bool {
4023                                match r {
4024                                    Region::Basic { .. }
4025                                    | Region::CopyStmts { .. }
4026                                    | Region::Empty
4027                                    | Region::Goto { .. } => true,
4028                                    Region::Seq(v) => v.iter().all(simple_completion_local2),
4029                                    Region::If { then_r, else_r, .. } => {
4030                                        simple_completion_local2(then_r)
4031                                            && simple_completion_local2(else_r)
4032                                    }
4033                                    _ => false,
4034                                }
4035                            }
4036                            let routed = {
4037                                coherent
4038                                    && !matches!(taken_r, Region::Empty)
4039                                    // The copy must end abruptly (its own
4040                                    // return/throw): a filled skip arm must
4041                                    // not fall out of the copy into
4042                                    // whatever follows the If.
4043                                    && region_terminates_ex(&taken_r, self.results, &[])
4044                                    && {
4045                                        fn has_deep_empty(r: &Region, top: bool) -> bool {
4046                                            match r {
4047                                                Region::Empty => !top,
4048                                                Region::Seq(v) => {
4049                                                    let n = v.len();
4050                                                    v.iter().enumerate().any(|(k, x)| {
4051                                                        has_deep_empty(x, top && k + 1 == n)
4052                                                    })
4053                                                }
4054                                                Region::If { then_r, else_r, .. } => {
4055                                                    has_deep_empty(then_r, false)
4056                                                        || has_deep_empty(else_r, false)
4057                                                }
4058                                                Region::Try { body, catches, .. } => {
4059                                                    has_deep_empty(body, false)
4060                                                        || catches.iter().any(|(_, _, h)| {
4061                                                            has_deep_empty(h, false)
4062                                                        })
4063                                                }
4064                                                _ => false,
4065                                            }
4066                                        }
4067                                        has_deep_empty(&arm, true)
4068                                    }
4069                            };
4070                            if matches!(taken_r, Region::Empty)
4071                                || !simple_completion(&taken_r)
4072                                || !coherent
4073                            {
4074                                // Full state rollback: the exploratory walk
4075                                // mutates Structurer state beyond `claimed`
4076                                // (copied_tails feeds conversion's
4077                                // reaches_copy_tail elision; the group/loop/
4078                                // switch stacks feed scope decisions) — huc
4079                                // getInputStream0 lost addToCache with only
4080                                // claimed+tails restored.
4081                                self.copied_tails = saved_tails;
4082                                *claimed = saved_claims;
4083                                *self.structuring_groups.borrow_mut() = saved_groups;
4084                                self.loops_stack = saved_loops;
4085                                self.switch_depth = saved_switch_depth;
4086                                self.case_arm_ctx = saved_case_ctx;
4087                                self.walk_depth = saved_walk_depth;
4088                                arm
4089                            } else {
4090                                claimed.extend(scratch.iter().copied());
4091                                // EMPTY-ARM ROUTING (the dci receive
4092                                // misroute cure): the bypass paths render
4093                                // as EMPTY If-arms (their Goto to the
4094                                // skip target elides at conversion because
4095                                // the target is an inner If's follow) and
4096                                // fall through the arm tail into the
4097                                // appended copy's head — executing the
4098                                // parked SET they must skip (jdk26
4099                                // DatagramChannelImpl.receive: n<0 and
4100                                // n==0&&!isOpen returned
4101                                // sourceSocketAddress() instead of the
4102                                // null sender). Give every arm-level
4103                                // Empty its own copy of the parked
4104                                // follow: the fall-into path then takes
4105                                // the appended copy and each skip path
4106                                // takes its in-arm copy — per-arrival
4107                                // copies, the codebase's standard
4108                                // shared-tail discipline. Gated: the
4109                                // follow must be claimed or parked (its
4110                                // canonical emission already exists, so
4111                                // the copies are per-arrival
4112                                // materializations, not double emission).
4113                                let mut arm = arm;
4114                                // SIBLING-INTERIOR ROUTING (the huc
4115                                // short-clone-arm cure): with relaxed
4116                                // coherence the arm may hold deep Goto
4117                                // tails whose target is the chain head
4118                                // (an If-arm's last statement jumping to
4119                                // `taken`) or flows to the chain's
4120                                // continuation (the arm's own back-edge-
4121                                // stub tail). Those Gotos would elide and
4122                                // fall into the WRONG sibling emission —
4123                                // give each its own per-arrival copy:
4124                                // Goto{taken} gets the chain copy clone;
4125                                // a flow-to-continuation tail gets a fresh
4126                                // scratch walk from its target (replacing
4127                                // a preceding CopyStmts pair) validated by
4128                                // simple completion + abrupt termination.
4129                                let routed2 = coherent
4130                                    && !routed
4131                                    && !matches!(taken_r, Region::Empty)
4132                                    && simple_completion(&taken_r);
4133                                if routed2 {
4134                                    let mut cu2 = cu.clone();
4135                                    for t in bts.iter() {
4136                                        cu2.insert(*t);
4137                                        for x in reachable_within(self.cfg, *t, &cstop) {
4138                                            cu2.insert(x);
4139                                        }
4140                                    }
4141                                    let mut filled2 = 0usize;
4142                                    let mut failed2 = false;
4143                                    fill_bypass(
4144                                        self,
4145                                        &mut arm,
4146                                        &taken_r,
4147                                        taken,
4148                                        &cu2,
4149                                        &cstop,
4150                                        active,
4151                                        claimed,
4152                                        &mut filled2,
4153                                        &mut failed2,
4154                                        true,
4155                                    );
4156                                    if crate::dbg_flag!("JCDC_DBG_IF") {
4157                                        eprintln!(
4158                                            "PARKCHAIN-ROUTE2 cur={} filled={} failed={}",
4159                                            cur, filled2, failed2
4160                                        );
4161                                    }
4162                                }
4163                                if routed {
4164                                    // SKIP-PATH ROUTING: a follow-EMPTY If
4165                                    // arm whose target is NOT the parked
4166                                    // chain head is a skip path around the
4167                                    // head (dci receive: `if (n != 0) {}`
4168                                    // and `else if (!isOpen()) {}` must
4169                                    // reach `return sender` WITHOUT
4170                                    // executing `sender =
4171                                    // sourceSocketAddress()`). Give each
4172                                    // such Empty its own per-arrival copy:
4173                                    // slice the chain copy from that
4174                                    // target's part when the copy's parts
4175                                    // expose it, else re-walk the target
4176                                    // on a scratch claim set. The
4177                                    // fall-INTO Empty (target == chain
4178                                    // head) stays empty and drops into the
4179                                    // appended copy.
4180                                    fn fill_skip_empties(
4181                                        st: &mut Structurer,
4182                                        r: &mut Region,
4183                                        copy: &Region,
4184                                        taken: usize,
4185                                        cu: &HashSet<usize>,
4186                                        cstop: &HashSet<usize>,
4187                                        active: &[usize],
4188                                        claimed: &HashSet<usize>,
4189                                        filled: &mut usize,
4190                                    ) {
4191                                        match r {
4192                                            Region::Seq(v) => {
4193                                                let n = v.len();
4194                                                for (i, x) in v.iter_mut().enumerate() {
4195                                                    // A Seq's LAST element
4196                                                    // is the fall-through
4197                                                    // tail into the
4198                                                    // appended copy — never
4199                                                    // replace it.
4200                                                    if i + 1 == n {
4201                                                        if !matches!(x, Region::Goto { .. }) {
4202                                                            fill_skip_empties(
4203                                                                st, x, copy, taken, cu, cstop,
4204                                                                active, claimed, filled,
4205                                                            );
4206                                                        }
4207                                                        continue;
4208                                                    }
4209                                                    fill_skip_empties(
4210                                                        st, x, copy, taken, cu, cstop, active,
4211                                                        claimed, filled,
4212                                                    );
4213                                                }
4214                                            }
4215                                            Region::Goto { target } if *target != taken => {
4216                                                // An elided follow-merge
4217                                                // jump to a skip target
4218                                                // (b13's `goto 15` rendered
4219                                                // empty because 15 is the
4220                                                // inner If's follow): give
4221                                                // it the suffix copy too.
4222                                                let sl = match copy {
4223                                                    Region::Seq(v) => v
4224                                                        .iter()
4225                                                        .position(|p| {
4226                                                            crate::structure::region_head_block(p)
4227                                                                == *target
4228                                                        })
4229                                                        .map(|i| {
4230                                                            if v.len() - i == 1 {
4231                                                                v[i].clone()
4232                                                            } else {
4233                                                                Region::Seq(v[i..].to_vec())
4234                                                            }
4235                                                        }),
4236                                                    other => {
4237                                                        if crate::structure::region_head_block(
4238                                                            other,
4239                                                        ) == *target
4240                                                        {
4241                                                            Some(other.clone())
4242                                                        } else {
4243                                                            None
4244                                                        }
4245                                                    }
4246                                                };
4247                                                let suffix = match sl {
4248                                                    Some(x) => Some(x),
4249                                                    None => {
4250                                                        let mut sc = claimed.clone();
4251                                                        let sub = if st.take_copy_ticket() {
4252                                                            st.walk(
4253                                                                *target, cu, cstop, active,
4254                                                                &mut sc, false,
4255                                                            )
4256                                                        } else {
4257                                                            Region::Empty
4258                                                        };
4259                                                        if !matches!(sub, Region::Empty)
4260                                                            && simple_completion_local(&sub)
4261                                                            && region_terminates_ex(
4262                                                                &sub,
4263                                                                st.results,
4264                                                                &[],
4265                                                            )
4266                                                        {
4267                                                            Some(sub)
4268                                                        } else {
4269                                                            None
4270                                                        }
4271                                                    }
4272                                                };
4273                                                if let Some(sfx) = suffix {
4274                                                    *r = sfx;
4275                                                    *filled += 1;
4276                                                }
4277                                            }
4278                                            Region::If {
4279                                                block,
4280                                                then_r,
4281                                                else_r,
4282                                                ..
4283                                            } => {
4284                                                let succ = st.cfg.blocks[*block].succ.clone();
4285                                                if succ.len() == 2 {
4286                                                    let pairs = [
4287                                                        (succ[1], then_r.as_mut()),
4288                                                        (succ[0], else_r.as_mut()),
4289                                                    ];
4290                                                    for (tgt, armr) in pairs {
4291                                                        if matches!(*armr, Region::Empty) {
4292                                                            if tgt == taken {
4293                                                                continue; // fall-into path
4294                                                            }
4295                                                            // suffix copy from tgt
4296                                                            let sl = match copy {
4297                                                                Region::Seq(v) => v
4298                                                                    .iter()
4299                                                                    .position(|p| {
4300                                                                        crate::structure::region_head_block(p) == tgt
4301                                                                    })
4302                                                                    .map(|i| {
4303                                                                        if v.len() - i == 1 {
4304                                                                            v[i].clone()
4305                                                                        } else {
4306                                                                            Region::Seq(v[i..].to_vec())
4307                                                                        }
4308                                                                    }),
4309                                                                other => {
4310                                                                    if crate::structure::region_head_block(other) == tgt {
4311                                                                        Some(other.clone())
4312                                                                    } else {
4313                                                                        None
4314                                                                    }
4315                                                                }
4316                                                            };
4317                                                            let suffix = match sl {
4318                                                                Some(x) => Some(x),
4319                                                                None => {
4320                                                                    let mut sc = claimed.clone();
4321                                                                    let sub = if st
4322                                                                        .take_copy_ticket()
4323                                                                    {
4324                                                                        st.walk(
4325                                                                            tgt, cu, cstop, active,
4326                                                                            &mut sc, false,
4327                                                                        )
4328                                                                    } else {
4329                                                                        Region::Empty
4330                                                                    };
4331                                                                    if !matches!(sub, Region::Empty)
4332                                                                        && simple_completion_local(
4333                                                                            &sub,
4334                                                                        )
4335                                                                        && region_terminates_ex(
4336                                                                            &sub,
4337                                                                            st.results,
4338                                                                            &[],
4339                                                                        )
4340                                                                    {
4341                                                                        Some(sub)
4342                                                                    } else {
4343                                                                        None
4344                                                                    }
4345                                                                }
4346                                                            };
4347                                                            if let Some(sfx) = suffix {
4348                                                                *armr = sfx;
4349                                                                *filled += 1;
4350                                                            }
4351                                                        } else {
4352                                                            fill_skip_empties(
4353                                                                st, armr, copy, taken, cu, cstop,
4354                                                                active, claimed, filled,
4355                                                            );
4356                                                        }
4357                                                    }
4358                                                }
4359                                            }
4360                                            Region::Try { body, catches, .. } => {
4361                                                fill_skip_empties(
4362                                                    st, body, copy, taken, cu, cstop, active,
4363                                                    claimed, filled,
4364                                                );
4365                                                for (_, _, h) in catches.iter_mut() {
4366                                                    fill_skip_empties(
4367                                                        st, h, copy, taken, cu, cstop, active,
4368                                                        claimed, filled,
4369                                                    );
4370                                                }
4371                                            }
4372                                            _ => {}
4373                                        }
4374                                    }
4375                                    fn simple_completion_local(r: &Region) -> bool {
4376                                        match r {
4377                                            Region::Basic { .. }
4378                                            | Region::CopyStmts { .. }
4379                                            | Region::Empty
4380                                            | Region::Goto { .. } => true,
4381                                            Region::Seq(v) => v.iter().all(simple_completion_local),
4382                                            Region::If { then_r, else_r, .. } => {
4383                                                simple_completion_local(then_r)
4384                                                    && simple_completion_local(else_r)
4385                                            }
4386                                            _ => false,
4387                                        }
4388                                    }
4389                                    let mut filled = 0usize;
4390                                    fill_skip_empties(
4391                                        self,
4392                                        &mut arm,
4393                                        &taken_r,
4394                                        taken,
4395                                        &cu,
4396                                        &cstop,
4397                                        active,
4398                                        claimed,
4399                                        &mut filled,
4400                                    );
4401                                    if crate::dbg_flag!("JCDC_DBG_IF") {
4402                                        eprintln!(
4403                                            "PARKCHAIN-ROUTE cur={} filled={} skip-empties",
4404                                            cur, filled
4405                                        );
4406                                    }
4407                                }
4408                                Region::Seq(vec![arm, taken_r])
4409                            }
4410                        }
4411                    } else if universe.contains(&fall)
4412                        && !bstop.contains(&fall)
4413                        && !claimed.contains(&fall)
4414                        && !(self.is_handler(fall) && active.is_empty())
4415                    {
4416                        let branch_universe = self.owner_scope(fall, universe);
4417                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
4418                        self.restrict_handler_branch(&mut sub, entry);
4419                        if crate::dbg_flag!("JCDC_DBG_IF") {
4420                            eprintln!("IF cur={} else walk fall={} sub={:?}", cur, fall, sub);
4421                        }
4422                        self.walk(fall, &sub, &bstop, active, claimed, false)
4423                    } else if self.is_terminator_block(fall)
4424                        && !stop.contains(&fall)
4425                        && !self.terminator_writes_final(fall)
4426                    {
4427                        Region::CopyStmts { block: fall }
4428                    } else if claimed.contains(&fall) {
4429                        if !stop.contains(&fall)
4430                            && !bstop.contains(&fall)
4431                            && !self.loops_stack.contains(&fall)
4432                            && !self.terminator_writes_final(fall)
4433                            && !self.shared_tail_confluence(fall)
4434                        {
4435                            if let Some(r) = self.copy_walk(fall, &bstop, active, cur) {
4436                                r
4437                            } else {
4438                                if crate::dbg_flag!("JCDC_DBG_IF") {
4439                                    eprintln!("IF cur={} else Goto(claimed) fall={}", cur, fall);
4440                                }
4441                                Region::Goto { target: fall }
4442                            }
4443                        } else {
4444                            if crate::dbg_flag!("JCDC_DBG_IF") {
4445                                eprintln!("IF cur={} else Goto(claimed) fall={}", cur, fall);
4446                            }
4447                            Region::Goto { target: fall }
4448                        }
4449                    } else if self.is_terminator_block(fall)
4450                        && !stop.contains(&fall)
4451                        && !self.terminator_writes_final(fall)
4452                    {
4453                        Region::CopyStmts { block: fall }
4454                    } else if stop.contains(&fall) && !self.is_terminator_block(fall) {
4455                        // Jump to an enclosing loop's exit (or other
4456                        // barrier): keep it as a Goto so conversion
4457                        // resolves it to `break` — unless the exit chains
4458                        // into an already-claimed terminator (see the
4459                        // taken side; jdk26 DecimalFormat guarded case).
4460                        if !self.loops_stack.contains(&fall)
4461                            && self.stop_chain_to_claimed_terminator(fall, claimed)
4462                        {
4463                            let mut cstop = bstop.clone();
4464                            cstop.remove(&fall);
4465                            match self.copy_walk(fall, &cstop, active, cur) {
4466                                Some(r) => r,
4467                                None => Region::Goto { target: fall },
4468                            }
4469                        } else {
4470                            Region::Goto { target: fall }
4471                        }
4472                    } else if self.is_terminator_block(fall)
4473                        && stop.contains(&fall)
4474                        && self.is_terminator_block(taken)
4475                        && stop.contains(&taken)
4476                    {
4477                        // Dual-terminator stop exits: see the taken side
4478                        // (jdk11 ObjectInputStream.readSerialData).
4479                        self.dual_terminator_branch(fall, active)
4480                    } else if !stop.contains(&fall)
4481                        && !claimed.contains(&fall)
4482                        && self.cfg.exc_edges.iter().any(|e| e.from == fall)
4483                    {
4484                        // Protected branch target outside the scope — see
4485                        // the taken side (getOutputStream p==null arm).
4486                        let branch_universe = self.owner_scope(fall, universe);
4487                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
4488                        self.restrict_handler_branch(&mut sub, entry);
4489                        self.walk(fall, &sub, &bstop, active, claimed, false)
4490                    } else {
4491                        if crate::dbg_flag!("JCDC_DBG_IF") {
4492                            eprintln!(
4493                                "IF cur={} else Empty fall={} univ={} bstop={} handler={}",
4494                                cur,
4495                                fall,
4496                                universe.contains(&fall),
4497                                bstop.contains(&fall),
4498                                self.is_handler(fall)
4499                            );
4500                        }
4501                        Region::Empty
4502                    };
4503                    // Value-diamond detection: both branches are pure
4504                    // (no statements, no exits) and leave exactly one value.
4505                    // A branch may be `Basic` or `Seq[Basic, Goto{follow}]`
4506                    // (javac often uses an explicit goto to the merge).
4507                    fn pure_block(
4508                        r: &crate::ir::build::BlockResult,
4509                        succs: &[usize],
4510                        follow: Option<usize>,
4511                    ) -> bool {
4512                        r.stmts.is_empty()
4513                            && r.out_stack.len() == 1
4514                            && match &r.term {
4515                                crate::ir::build::Term::Fallthrough => true,
4516                                crate::ir::build::Term::Goto => {
4517                                    follow.map(|f| succs == [f]).unwrap_or(false)
4518                                }
4519                                _ => false,
4520                            }
4521                    }
4522                    fn region_pure_block(
4523                        rg: &Region,
4524                        results: &Vec<BlockResult>,
4525                        cfg: &Cfg,
4526                        follow: Option<usize>,
4527                    ) -> Option<usize> {
4528                        match rg {
4529                            Region::Basic { block } => {
4530                                if pure_block(&results[*block], &cfg.blocks[*block].succ, follow) {
4531                                    Some(*block)
4532                                } else {
4533                                    None
4534                                }
4535                            }
4536                            Region::Seq(v) if v.len() == 2 => {
4537                                if let (Region::Basic { block }, Region::Goto { target }) =
4538                                    (&v[0], &v[1])
4539                                {
4540                                    if follow == Some(*target)
4541                                        && pure_block(
4542                                            &results[*block],
4543                                            &cfg.blocks[*block].succ,
4544                                            follow,
4545                                        )
4546                                    {
4547                                        return Some(*block);
4548                                    }
4549                                }
4550                                None
4551                            }
4552                            _ => None,
4553                        }
4554                    }
4555                    let ternary = if follow.is_some() {
4556                        match (
4557                            region_pure_block(&then_r, self.results, self.cfg, follow),
4558                            region_pure_block(&else_r, self.results, self.cfg, follow),
4559                        ) {
4560                            (Some(tb), Some(fb)) => Some((
4561                                self.results[tb].out_stack[0].clone(),
4562                                self.results[fb].out_stack[0].clone(),
4563                            )),
4564                            _ => None,
4565                        }
4566                    } else {
4567                        None
4568                    };
4569                    if ternary.is_some() {
4570                        // consume the branch blocks (Basic or Seq[Basic,Goto])
4571                        for rg in [&then_r, &else_r] {
4572                            match rg {
4573                                Region::Basic { block } => {
4574                                    claimed.insert(*block);
4575                                }
4576                                Region::Seq(v) => {
4577                                    for x in v {
4578                                        if let Region::Basic { block } = x {
4579                                            claimed.insert(*block);
4580                                        }
4581                                    }
4582                                }
4583                                _ => {}
4584                            }
4585                        }
4586                    }
4587                    parts.push(Region::If {
4588                        block: cur,
4589                        cond,
4590                        then_r: Box::new(then_r),
4591                        else_r: Box::new(else_r),
4592                        follow,
4593                        ternary,
4594                    });
4595                    match follow {
4596                        Some(f)
4597                            if universe.contains(&f)
4598                                && !stop.contains(&f)
4599                                && !claimed.contains(&f) =>
4600                        {
4601                            cur = f;
4602                            continue;
4603                        }
4604                        Some(f)
4605                            if universe.contains(&f)
4606                                && !stop.contains(&f)
4607                                && !self.loops_stack.contains(&f)
4608                                && !can_reach_cfg(self.cfg, f, cur, 4096) =>
4609                        {
4610                            // The merge block was already structured inside
4611                            // one branch (a shared tail). Every branch exit
4612                            // re-executes the whole tail, so re-walk it
4613                            // with a fresh claimed set (a structural copy)
4614                            // and splice the region in here.
4615                            //
4616                            // UNLESS the follow is a loop BACKEDGE STUB
4617                            // (`reset(); goto head`): the fresh walk would
4618                            // re-walk the ENTIRE loop from the header
4619                            // (fresh claimed knows nothing) and paste the
4620                            // loop body into this branch (jdk11 KeyStore
4621                            // .getInstance(File)'s IOException catch
4622                            // swallowed the provider loop with an
4623                            // unprotected Security.getImpl — 未报告的异常
4624                            // 错误NoSuchProviderException). Copy the stub's
4625                            // own statements and attach an explicit Goto
4626                            // to the header — conversion resolves it to
4627                            // `continue`; a fall-through must NOT reach
4628                            // the head (it would re-run the loop test).
4629                            let stub_header = if matches!(self.results[f].term, Term::Goto)
4630                                && self.cfg.blocks[f].succ.len() == 1
4631                            {
4632                                let t = self.cfg.blocks[f].succ[0];
4633                                self.loops_stack
4634                                    .iter()
4635                                    .chain(self.sese_loop_headers.iter())
4636                                    .find(|&&h| h == t || self.is_stmt_free_chain_to_block(t, h))
4637                                    .copied()
4638                            } else {
4639                                None
4640                            };
4641                            if let Some(h) = stub_header {
4642                                parts.push(Region::Seq(vec![
4643                                    Region::CopyStmts { block: f },
4644                                    Region::Goto { target: h },
4645                                ]));
4646                                break;
4647                            }
4648                            let mut barriers = stop.clone();
4649                            barriers.insert(cur);
4650                            let tail_universe = reachable_within(self.cfg, f, &barriers);
4651                            let mut fresh: HashSet<usize> = HashSet::new();
4652                            let r = self.walk(f, &tail_universe, &stop, active, &mut fresh, true);
4653                            self.copied_tails.insert(f);
4654                            parts.push(r);
4655                            break;
4656                        }
4657                        _ => break,
4658                    }
4659                }
4660                Term::Switch {
4661                    selector,
4662                    targets,
4663                    default: _,
4664                } => {
4665                    // A switch's follow is the confluence of the cases that
4666                    // do NOT terminate (return/throw). `immediate_postdom`
4667                    // would yield None whenever any case always exits (e.g.
4668                    // a throwing default), losing break resolution for the
4669                    // remaining cases.
4670                    let follow = self
4671                        .postdom_ipdom(universe, cur)
4672                        .or_else(|| self.switch_follow(cur, universe))
4673                        // Inside a copied/shared-tail region the confluence
4674                        // sits in `stop` (the enclosing flow owns it), so
4675                        // the universe-bound searches miss it and every
4676                        // case's terminal goto degrades to fall-through
4677                        // (jdk11 Calendar.createCalendar catch-copy switch
4678                        // lost its breaks). A stop block that ALL live
4679                        // cases escape to is still the logical follow for
4680                        // break resolution.
4681                        .or_else(|| self.switch_stop_confluence(cur, universe, stop));
4682                    // CROSSING follow: a stop-confluence of a NESTED
4683                    // switch belongs to an enclosing construct (jdk26 xml
4684                    // impl.Parser.bappend: the inner switch(ch)'s cases
4685                    // break to the OUTER switch(mode)'s follow — passing
4686                    // it as the inner follow resolved the default's goto
4687                    // to a bare `break` binding the inner switch, case
4688                    // 105 fell through into case 99 and the outer tail
4689                    // went unreachable). With follow=None the conversion's
4690                    // labeled-break logic binds it to the outer switch
4691                    // (`break L1`); the confluence still bounds the case
4692                    // walks via `stop`. Calendar (depth 0) keeps Some(f).
4693                    // EXCEPTION — when every route past the built switch
4694                    // lands on the confluence anyway (the switch is the
4695                    // last construct of an abruptly-completing outer case
4696                    // arm), the plain inner-break binding is equivalent
4697                    // AND renderable: the labeled one makes the inner
4698                    // switch non-completing and the outer case's appended
4699                    // `break;` unreachable (ClassPrinterImpl.toYaml/toXml
4700                    // 无法访问的语句 x2 trees). Rebuild with the follow.
4701                    let crossing = match follow {
4702                        Some(f) if stop.contains(&f) && self.switch_depth > 0 => Some(f),
4703                        _ => None,
4704                    };
4705                    let region_follow = match crossing {
4706                        Some(_) => None,
4707                        None => follow,
4708                    };
4709                    self.switch_depth += 1;
4710                    let claimed_save = claimed.clone();
4711                    let mut sw = self.structure_switch(
4712                        cur,
4713                        selector.clone(),
4714                        &targets,
4715                        universe,
4716                        stop,
4717                        region_follow,
4718                        active,
4719                        claimed,
4720                    );
4721                    if let Some(f) = crossing {
4722                        // What this scope does RIGHT AFTER the switch must
4723                        // already land on `f`: either the scope's earlier
4724                        // parts complete abruptly (nothing can fall through
4725                        // the switch), or the walk's own next-step rule
4726                        // continues exactly at `f` (an in-universe follow).
4727                        // Anything else means the bare-break binding drops
4728                        // flow into the enclosing continuation, re-running
4729                        // code the bytecode skipped (bappend's outer tail,
4730                        // bkeyword's case fall-through losing `return '?'`).
4731                        let cont_dead = parts
4732                            .last()
4733                            .map(|p| region_terminates(p, self.results))
4734                            .unwrap_or(false);
4735                        let cont_is_f = universe.contains(&f)
4736                            && !stop.contains(&f)
4737                            && !claimed_save.contains(&f);
4738                        // This walk IS a pattern-switch case arm and the
4739                        // confluence is that switch's follow: the arm
4740                        // renderer appends the case's terminal `break;`
4741                        // (restore_one_switch), landing on the outer
4742                        // follow == f — the plain-break binding is
4743                        // equivalent and keeps the appended break
4744                        // reachable (ClassPrinterImpl.toYaml/toXml).
4745                        let cont_via_case_break = self
4746                            .case_arm_ctx
4747                            .last()
4748                            .map(|&(head, ef, pat)| head == entry && pat && ef == Some(f))
4749                            .unwrap_or(false);
4750                        let bindable = (cont_dead || cont_is_f || cont_via_case_break)
4751                            && self.switch_follow_bindable(&sw, cur, &targets, f);
4752                        if crate::dbg_flag!("JCDC_DBG_SWF") {
4753                            eprintln!("SWBIND cur={} f={} cont_dead={} cont_is_f={} cont_case={} bindable={}",
4754                                cur, f, cont_dead, cont_is_f, cont_via_case_break, bindable);
4755                        }
4756                        if bindable {
4757                            *claimed = claimed_save;
4758                            sw = self.structure_switch(
4759                                cur,
4760                                selector,
4761                                &targets,
4762                                universe,
4763                                stop,
4764                                Some(f),
4765                                active,
4766                                claimed,
4767                            );
4768                        }
4769                    }
4770                    self.switch_depth -= 1;
4771                    // A follow already CLAIMED by a sibling arm (a shared
4772                    // terminator tail copied there first) still binds this
4773                    // switch's `break`s — but nothing after the switch
4774                    // renders the tail, so a normally-completing switch
4775                    // fell off the enclosing arm (jdk11 MethodTypeForm
4776                    // .canonicalize: the Void.TYPE lookupswitch's default
4777                    // `goto return-null` became `break` with the tail
4778                    // claimed by the tableswitch arm — 缺少返回语句). Copy
4779                    // the terminator at the confluence, mirroring the
4780                    // GOTO-CLAIMED shared-tail rule. Only when the switch
4781                    // can complete normally (JLS 14.11: an open default
4782                    // route, a non-terminating default, or any
4783                    // non-terminating case) — else the copy is unreachable
4784                    // (无法访问的语句).
4785                    if let Some(f) = follow {
4786                        if crate::dbg_flag!("JCDC_DBG_SWF") {
4787                            eprintln!("SWTAIL cur={} f={} claimed={} stop={} loops={} term={} loophdr={} finw={} completes={}",
4788                                cur, f, claimed.contains(&f), stop.contains(&f),
4789                                self.loops_stack.contains(&f), self.is_terminator_block(f),
4790                                Self::ctx_is_loop_header(self, f), self.terminator_writes_final(f),
4791                                switch_completes_normally(&sw, f, self.results));
4792                        }
4793                        if claimed.contains(&f)
4794                            && !stop.contains(&f)
4795                            && !self.loops_stack.contains(&f)
4796                            && self.is_terminator_block(f)
4797                            && !Self::ctx_is_loop_header(self, f)
4798                            && !self.terminator_writes_final(f)
4799                            && switch_completes_normally(&sw, f, self.results)
4800                        {
4801                            parts.push(sw);
4802                            parts.push(Region::CopyStmts { block: f });
4803                            break;
4804                        }
4805                    }
4806                    parts.push(sw);
4807                    match follow {
4808                        Some(f)
4809                            if universe.contains(&f)
4810                                && !stop.contains(&f)
4811                                && !claimed.contains(&f) =>
4812                        {
4813                            cur = f;
4814                            continue;
4815                        }
4816                        _ => break,
4817                    }
4818                }
4819                Term::Goto => {
4820                    let t = b.succ.first().copied();
4821                    match t {
4822                        Some(t)
4823                            if universe.contains(&t)
4824                                && !stop.contains(&t)
4825                                && !claimed.contains(&t)
4826                                && !(self.is_handler(t) && active.is_empty()) =>
4827                        {
4828                            // Forward jump within scope: keep this block's
4829                            // statements, continue walking at the target.
4830                            parts.push(Region::Basic { block: cur });
4831                            cur = t;
4832                            continue;
4833                        }
4834                        Some(t)
4835                            if universe.contains(&t)
4836                                && !stop.contains(&t)
4837                                && claimed.contains(&t)
4838                                && dom.dominates(t, cur) =>
4839                        {
4840                            // Back edge into an already-structured block:
4841                            // emit statements + goto (→ continue/break).
4842                            parts.push(Region::Basic { block: cur });
4843                            if self.is_terminator_block(t)
4844                                && !stop.contains(&t)
4845                                && !self.loops_stack.contains(&t)
4846                                && !Self::ctx_is_loop_header(self, t)
4847                            {
4848                                // A shared TERMINATOR tail (return/throw) is
4849                                // copied at each arrival — but NOT when the
4850                                // target is a LOOP HEADER: the back edge is
4851                                // a `continue`, and copying a header whose
4852                                // body ends in a throw inlines the body into
4853                                // the handler unprotected (jdk26 Future
4854                                // .exceptionNow: the InterruptedException
4855                                // retry `goto 40` copied `get(); throw ISE`
4856                                // into the catch — 未报告的异常错误
4857                                // InterruptedException).
4858                                parts.push(Region::CopyStmts { block: t });
4859                            } else if !stop.contains(&t)
4860                                && !self.loops_stack.contains(&t)
4861                                && !Self::ctx_is_loop_header(self, t)
4862                            {
4863                                match self.copy_walk(t, stop, active, cur) {
4864                                    Some(r) => parts.push(r),
4865                                    None => parts.push(Region::Goto { target: t }),
4866                                }
4867                            } else {
4868                                parts.push(Region::Goto { target: t });
4869                            }
4870                            break;
4871                        }
4872                        Some(t) => {
4873                            if crate::dbg_flag!("JCDC_DBG_IF") {
4874                                eprintln!(
4875                                    "GOTO-FALL cur={} t={} univ={} stop={} claimed={} entry={}",
4876                                    cur,
4877                                    t,
4878                                    universe.contains(&t),
4879                                    stop.contains(&t),
4880                                    claimed.contains(&t),
4881                                    entry
4882                                );
4883                            }
4884
4885                            parts.push(Region::Basic { block: cur });
4886                            if self.is_terminator_block(t)
4887                                && !stop.contains(&t)
4888                                && !Self::ctx_is_loop_header(self, t)
4889                                && !self.terminator_writes_final(t)
4890                            {
4891                                // Shared terminator tail — but NOT a retry
4892                                // loop header (a handler-entry `goto head`
4893                                // re-executes the protected body; copying a
4894                                // throw-terminated header inlines it
4895                                // unprotected: jdk26 Future.exceptionNow
4896                                // catch(IE) got `get(); throw ISE` —
4897                                // 未报告的异常错误 InterruptedException).
4898                                parts.push(Region::CopyStmts { block: t });
4899                            } else if self.walk_depth == 1
4900                                && claimed.contains(&t)
4901                                && self.copied_tails.contains(&t)
4902                                && self.cfg_all_paths_terminate(t)
4903                                && !stop.contains(&t)
4904                                && !Self::ctx_is_loop_header(self, t)
4905                                && !self.terminator_writes_final(t)
4906                            {
4907                                // Method-final epilogue recovery: the
4908                                // target was claimed by copy/handler walks
4909                                // whose owner scopes are FINISHED (the
4910                                // active-group deferral below would strand
4911                                // it — jdk11/17 Resource.getBytes: the
4912                                // shared `if (interrupted) interrupt();
4913                                // return b;` selector was claimed inside
4914                                // the finally renders, the method-level
4915                                // stub arrival deferred to long-finished
4916                                // inner groups, and the method ended right
4917                                // after the finally — 缺少返回语句). Only at
4918                                // method depth (keytool's per-case return
4919                                // tails arrive inside switch-arm walks),
4920                                // only at an already-copied tail (fresh
4921                                // tails stay under the deferral discipline
4922                                // — SSLEngineImpl's try-containing arrival
4923                                // completes normally and fails the
4924                                // all-paths-terminate probe anyway).
4925                                match self.copy_walk(t, stop, active, cur) {
4926                                    Some(r) => parts.push(r),
4927                                    None => parts.push(Region::Goto { target: t }),
4928                                }
4929                            } else if !stop.contains(&t)
4930                                && !self.loops_stack.contains(&t)
4931                                && !Self::ctx_is_loop_header(self, t)
4932                                && !self.shared_tail_confluence(t)
4933                                && !self.is_active_group_continuation(
4934                                    cur,
4935                                    t,
4936                                    claimed,
4937                                    &active_filtered(active, &structured_here),
4938                                )
4939                            {
4940                                match self.copy_walk(t, stop, active, cur) {
4941                                    Some(r) => parts.push(r),
4942                                    None => parts.push(Region::Goto { target: t }),
4943                                }
4944                            } else {
4945                                parts.push(Region::Goto { target: t });
4946                            }
4947                            break;
4948                        }
4949                        None => {
4950                            parts.push(Region::Basic { block: cur });
4951                            break;
4952                        }
4953                    }
4954                }
4955                Term::Return(_) | Term::Throw(_) | Term::Ret | Term::Jsr => {
4956                    parts.push(Region::Basic { block: cur });
4957                    break;
4958                }
4959                Term::Fallthrough => {
4960                    parts.push(Region::Basic { block: cur });
4961                    match b.succ.first().copied() {
4962                        Some(n)
4963                            if universe.contains(&n)
4964                                && !stop.contains(&n)
4965                                && !(self.is_handler(n) && active.is_empty()) =>
4966                        {
4967                            cur = n;
4968                            continue;
4969                        }
4970                        Some(n) if self.is_handler(n) && active.is_empty() => break,
4971                        Some(n) => {
4972                            if crate::dbg_flag!("JCDC_DBG_IF") {
4973                                eprintln!(
4974                                    "GOTO-FT cur={} n={} univ={} stop={} claimed={} entry={}",
4975                                    cur,
4976                                    n,
4977                                    universe.contains(&n),
4978                                    stop.contains(&n),
4979                                    claimed.contains(&n),
4980                                    entry
4981                                );
4982                            }
4983                            // Shared-tail arrival: `n` is CLAIMED (hence
4984                            // out of this walk's universe — sub_scope
4985                            // retains unclaimed blocks only). A bare Goto
4986                            // here is elided at conversion as the walk's
4987                            // last part (goto_is_last), silently severing
4988                            // this path's flow into the shared tail (jdk26
4989                            // DatagramChannelImpl.innerJoin's IPv4 arm:
4990                            // `key = new Type4(..)` fell off the method
4991                            // without the `registry.add(key); return key;`
4992                            // copy — 缺少返回语句 x3 trees). Per-arrival
4993                            // copies are exactly the bytecode semantics.
4994                            // Shared-tail arrival: `n` is CLAIMED, so no
4995                            // walk here will ever emit it again. A bare
4996                            // Goto is elided at conversion as the walk's
4997                            // last part (goto_is_last), silently severing
4998                            // this path's flow into the tail (jdk26
4999                            // DatagramChannelImpl.innerJoin's IPv4 arm:
5000                            // `key = new Type4(..)` fell off the method
5001                            // without the `registry.add(key); return key;`
5002                            // copy — 缺少返回语句 x3 trees) — UNLESS `n`
5003                            // is an ACTIVE group's post-try continuation:
5004                            // the owner walk emits it at the right level
5005                            // after the Try region (jdk11 Module
5006                            // .loadModuleInfoClass's in.close() block is
5007                            // gi=1's cont — copying it here duplicated the
5008                            // close+return chain into the body, defeated
5009                            // twr_j11's try-with-resources fold, and the
5010                            // pending-rethrow scaffolding leaked —
5011                            // 未报告的异常错误Throwable x2 trees). The
5012                            // conversion's strip_trailing_goto(try_follow)
5013                            // renders the Goto as the natural fallthrough.
5014                            let owned_cont = self.is_cont_of_active_group(
5015                                n,
5016                                stop,
5017                                claimed,
5018                                &active_filtered(active, &structured_here),
5019                            );
5020                            if claimed.contains(&n)
5021                                && !owned_cont
5022                                && !stop.contains(&n)
5023                                && !self.loops_stack.contains(&n)
5024                                && !self.is_handler(n)
5025                                && !self.terminator_writes_final(n)
5026                            {
5027                                if self.is_terminator_block(n) {
5028                                    parts.push(Region::CopyStmts { block: n });
5029                                    break;
5030                                }
5031                                if let Some(r) = self.copy_walk(n, stop, active, cur) {
5032                                    parts.push(r);
5033                                    break;
5034                                }
5035                            }
5036                            parts.push(Region::Goto { target: n });
5037                            break;
5038                        }
5039                        None => break,
5040                    }
5041                }
5042            }
5043        }
5044        if crate::dbg_flag!("JCDC_DBG_IF") {
5045            eprintln!(
5046                "WALK END entry={} nparts={} claimed={:?}",
5047                entry,
5048                parts.len(),
5049                claimed
5050            );
5051        }
5052        match parts.len() {
5053            0 => Region::Empty,
5054            1 => parts.pop().unwrap(),
5055            _ => Region::Seq(parts),
5056        }
5057    }
5058
5059    /// Scope for a branch starting at `b`: when `b` belongs to a try group
5060    /// body, exclude blocks owned by OTHER (sibling) groups — they are
5061    /// structured by their own Try regions — but keep unowned blocks (the
5062    /// post-try continuation flow) and nested groups of `gi`.
5063    fn owner_scope(&self, b: usize, universe: &HashSet<usize>) -> HashSet<usize> {
5064        match self.body_group.get(&b) {
5065            Some(gi) => {
5066                let g = &self.groups[*gi];
5067                let mut out: HashSet<usize> = universe
5068                    .iter()
5069                    .copied()
5070                    .filter(|x| match self.body_group.get(x) {
5071                        None => true,
5072                        Some(og) => {
5073                            *og == *gi
5074                                || (self.groups[*og].start >= g.start
5075                                    && self.groups[*og].end <= g.end)
5076                                // Blocks owned by a group ENCLOSING `gi`
5077                                // are the arm's post-try continuation, not
5078                                // a sibling's territory: a nested try whose
5079                                // protected span ends before the enclosing
5080                                // monitor/group span leaves its monitorexit
5081                                // + return tail owned by the outer group,
5082                                // and filtering it stranded the tail in no
5083                                // region (jdk26 ZipFile.getComment: the
5084                                // `return zipCoder.toString(comment)` value
5085                                // block walked alone, its Goto into the
5086                                // tail elided at conversion — empty try
5087                                // body AND a missing return).
5088                                || (self.groups[*og].start <= g.start
5089                                    && self.groups[*og].end >= g.end)
5090                                // Blocks owned by a LATER sibling group
5091                                // that the arm's own flow runs INTO are
5092                                // not foreign territory either: the arm
5093                                // walk structures that group when flow
5094                                // arrives (structure_try fires at its
5095                                // start). Filtering them severs the flow
5096                                // mid-chain — every arrival at the
5097                                // dropped block dangles as an
5098                                // exits-resolved break (jdk11/17
5099                                // SeedGenerator ThreadedSeedGenerator.run:
5100                                // the spin arm's scope was rooted at the
5101                                // thread-create try; the spin body's
5102                                // `synchronized(this){}` monitorexit
5103                                // block is owned by its own tiny sync
5104                                // group starting INSIDE the spin loop, so
5105                                // it was stripped — the latch increment
5106                                // block past it left the spin's member
5107                                // set, the body's back edge resolved to
5108                                // `break`, and the 250ms entropy spin
5109                                // rendered as `while (cond) { break; }`
5110                                // with the latch lost). A later sibling
5111                                // whose start the arm cannot reach stays
5112                                // filtered (no arrival ever dangles).
5113                                || (self.groups[*og].start >= g.end
5114                                    && self.arm_flow_reaches(
5115                                        b,
5116                                        *og,
5117                                        universe,
5118                                    ))
5119                        }
5120                    })
5121                    .collect();
5122                self.expand_orphan_group_tails(&mut out, b);
5123                out
5124            }
5125            None => {
5126                let mut out = universe.clone();
5127                self.expand_orphan_group_tails(&mut out, b);
5128                out
5129            }
5130        }
5131    }
5132
5133    /// True when normal flow from `from` (within `universe`) reaches the
5134    /// START block of `target_gi`: the arm walk will arrive there and
5135    /// structure the group itself.
5136    fn arm_flow_reaches(&self, from: usize, target_gi: usize, universe: &HashSet<usize>) -> bool {
5137        let gstart = self.groups[target_gi].start;
5138        let mut start_blk: Option<usize> = None;
5139        for nb in &self.cfg.blocks {
5140            if nb.start == gstart {
5141                start_blk = Some(nb.id);
5142                break;
5143            }
5144        }
5145        let Some(target) = start_blk else {
5146            return false;
5147        };
5148        let mut seen: HashSet<usize> = HashSet::new();
5149        let mut q: VecDeque<usize> = VecDeque::new();
5150        q.push_back(from);
5151        seen.insert(from);
5152        let mut guard = 0;
5153        while let Some(b) = q.pop_front() {
5154            guard += 1;
5155            if guard > 1024 {
5156                return false;
5157            }
5158            for &s in &self.cfg.blocks[b].succ {
5159                if s == target {
5160                    return true;
5161                }
5162                if seen.contains(&s) || !universe.contains(&s) {
5163                    continue;
5164                }
5165                // Plain normal-flow BFS: handler blocks are unreachable
5166                // here by construction (exception-only in-edges), and a
5167                // kept-but-never-arrived block is harmless (the arm walk
5168                // only structures what its flow reaches).
5169                seen.insert(s);
5170                q.push_back(s);
5171            }
5172        }
5173        false
5174    }
5175
5176    /// A sibling-group-owned block that is the normal-flow target of a
5177    /// kept in-scope block, lies PAST every group active over that block,
5178    /// and whose group has no owner in scope, is an ORPHAN TAIL: no walk
5179    /// can ever structure it (the owner_scope filter strips it from every
5180    /// arm universe, so every arrival dangles as an elided raw Goto).
5181    /// Adopt it — with its whole group span and that group's own tail
5182    /// closure — so the arriving arm walks and structures it (jdk26
5183    /// StructuredTaskScopeImpl.join: both timeoutExpired arms ended in
5184    /// Goto{tail}, the tail's `try { return joiner.result(); } catch
5185    /// (Throwable e) { throw new FailedException(e); }` was owned by a
5186    /// sibling group present in NO arm's scope — the epilogue vanished
5187    /// from the render entirely, 缺少返回语句).
5188    fn expand_orphan_group_tails(&self, out: &mut HashSet<usize>, from: usize) {
5189        let structuring = self.structuring_groups.borrow().clone();
5190        let mut frontier: Vec<usize> = vec![from];
5191        let mut seen: HashSet<usize> = HashSet::new();
5192        let mut guard = 0;
5193        while let Some(b) = frontier.pop() {
5194            guard += 1;
5195            if guard > 512 || !seen.insert(b) {
5196                continue;
5197            }
5198            if !out.contains(&b) {
5199                continue;
5200            }
5201            for &t in &self.cfg.blocks[b].succ {
5202                if out.contains(&t) {
5203                    frontier.push(t);
5204                    continue;
5205                }
5206                let Some(&ogi) = self.body_group.get(&t) else {
5207                    continue;
5208                };
5209                let og = &self.groups[ogi];
5210                // The owner group is STILL PENDING in this scope — its
5211                // start block is held by `out` and structure_try has not
5212                // consumed it yet: that walk structures the group when
5213                // flow arrives and its post-try continuation emits the
5214                // tail at the right level. Adopting it into an arm
5215                // universe claims the shared tail early and the
5216                // continuation search then finds it claimed (jdk17/26
5217                // PrintStream.format x2 trees: the else arm adopted the
5218                // synchronized group's monitorexit chain plus the
5219                // post-try `return this`, cont resolved to None inside
5220                // BOTH nested tries — 缺少返回语句 x2 methods). Groups
5221                // already structured by an ancestor (STS join's tail
5222                // try, structured by the sibling arm) keep adopting:
5223                // their tail will never be emitted elsewhere.
5224                let start_held = self
5225                    .cfg
5226                    .blocks
5227                    .iter()
5228                    .any(|nb| nb.start == og.start && out.contains(&nb.id));
5229                if structuring.contains(&ogi) || start_held {
5230                    continue;
5231                }
5232                // Past every group active over `b`: a target inside an
5233                // active group's span is that group's own continuation —
5234                // the owner walk handles it (ZipFile.getComment's
5235                // enclosing-group tail; Module.loadModuleInfoClass's
5236                // post-try cont) — never an orphan.
5237                let inside_active = out.iter().any(|&x| {
5238                    self.body_group.get(&x) == Some(&ogi)
5239                        || self.handler_group.get(&x) == Some(&ogi)
5240                });
5241                if inside_active {
5242                    continue;
5243                }
5244                if self.groups.iter().enumerate().any(|(gi, g)| {
5245                    out.iter().any(|&x| self.body_group.get(&x) == Some(&gi))
5246                        && g.start <= og.start
5247                        && g.end >= og.end
5248                }) {
5249                    continue;
5250                }
5251                // Adopt the full span + handler heads (structure_try
5252                // rebuilds handlers itself) + the group's own tail
5253                // closure beyond the span end.
5254                let mut adopt: Vec<usize> = self
5255                    .cfg
5256                    .blocks
5257                    .iter()
5258                    .filter(|nb| {
5259                        nb.ins_len != 0
5260                            && nb.start >= og.start
5261                            && nb.end <= og.end.max(og.start + 1)
5262                    })
5263                    .map(|nb| nb.id)
5264                    .collect();
5265                for (&hb, &hg) in self.handler_group.iter() {
5266                    if hg == ogi {
5267                        adopt.push(hb);
5268                    }
5269                }
5270                let mut x = og.end;
5271                for _ in 0..8 {
5272                    let Some(bid) = self.cfg.block_at(x) else {
5273                        break;
5274                    };
5275                    if self.body_group.contains_key(&bid) {
5276                        break;
5277                    }
5278                    adopt.push(bid);
5279                    let nxt = match self.results[bid].term {
5280                        Term::Fallthrough | Term::Goto if self.cfg.blocks[bid].succ.len() == 1 => {
5281                            self.cfg.blocks[bid].succ[0]
5282                        }
5283                        _ => break,
5284                    };
5285                    x = self.cfg.blocks[nxt].start;
5286                }
5287                for id in adopt {
5288                    out.insert(id);
5289                    frontier.push(id);
5290                }
5291            }
5292        }
5293    }
5294
5295    /// If `blk` is a pure value block (no statements, one stack value out,
5296    /// single successor) and its successor is not walkable here (stop,
5297    /// claimed, or out of universe), absorb the block into this scope:
5298    /// return Basic(blk) and mark it claimed. This materializes chained
5299    /// boolean diamonds (`a && b || c`) whose shared push blocks would
5300    /// otherwise be consumed silently by the first reaching branch.
5301    /// Confluence block of a switch's non-terminating case flows.
5302    /// The single stop/out-of-universe block that every live (non
5303    /// return/throw) case target escapes to. Used as a switch follow for
5304    /// break resolution when the real confluence lies beyond the current
5305    /// region's universe.
5306    pub(crate) fn switch_stop_confluence(
5307        &self,
5308        cur: usize,
5309        universe: &HashSet<usize>,
5310        stop: &HashSet<usize>,
5311    ) -> Option<usize> {
5312        let mut common: Option<usize> = None;
5313        let mut saw_live = false;
5314        for &t in &self.cfg.blocks[cur].succ {
5315            if self.is_terminator_block(t) {
5316                continue;
5317            }
5318            saw_live = true;
5319            // Forward BFS within the universe; collect the first blocks
5320            // that leave it (stop members or out-of-universe succs).
5321            let mut escapes: HashSet<usize> = HashSet::new();
5322            let mut seen: HashSet<usize> = HashSet::new();
5323            let mut q: Vec<usize> = vec![t];
5324            seen.insert(t);
5325            while let Some(b) = q.pop() {
5326                for &s2 in &self.cfg.blocks[b].succ {
5327                    if !universe.contains(&s2) || stop.contains(&s2) {
5328                        escapes.insert(s2);
5329                    } else if seen.insert(s2) {
5330                        q.push(s2);
5331                    }
5332                }
5333            }
5334            // A live target that IS itself a stop/out-of-universe block
5335            // (the default jumping straight to the confluence) escapes to
5336            // itself.
5337            if !universe.contains(&t) || stop.contains(&t) {
5338                escapes.clear();
5339                escapes.insert(t);
5340            }
5341            if escapes.is_empty() {
5342                // The case flow terminates ENTIRELY inside the universe
5343                // (every exit is a return/throw leaf — no edge leaves it):
5344                // an always-terminating case contributes no escape and
5345                // must not veto the confluence of the others (jdk26 xml
5346                // impl.Parser.bappend: the whitespace case's
5347                // if/else-if/else chain all-returns zeroed the escape set
5348                // and the whole-switch None made the inner switch's
5349                // `break`-to-outer-follow default unresolvable — the case
5350                // fell through into `case 99`, corrupting the outer
5351                // switch's completion and stranding its follow —
5352                // 无法访问的语句).
5353                continue;
5354            }
5355            if escapes.len() != 1 {
5356                return None;
5357            }
5358            let e = *escapes.iter().next().unwrap();
5359            match common {
5360                None => common = Some(e),
5361                Some(c) if c == e => {}
5362                Some(_) => return None,
5363            }
5364        }
5365        if saw_live {
5366            common
5367        } else {
5368            None
5369        }
5370    }
5371
5372    /// For a NESTED switch whose confluence `f` is a stop block owned by
5373    /// an enclosing construct (the CROSSING shape), decide whether `f` may
5374    /// still be passed as the switch's OWN follow (case arms then resolve
5375    /// to plain `break`s) or must be nulled (labeled breaks bind the
5376    /// enclosing switch). The plain-break binding is safe ONLY when every
5377    /// way flow could naturally continue past this switch inside the
5378    /// enclosing region lands on `f` anyway: otherwise a case the bytecode
5379    /// sends straight to `f` would fall out of the switch into that
5380    /// continuation and re-execute code the bytecode skipped (jdk26 xml
5381    /// impl.Parser.bappend: the inner switch(ch)'s cases break to the
5382    /// OUTER switch(mode)'s follow, jumping over the outer case's tail —
5383    /// nulling keeps `break L1`). When the switch is the LAST construct of
5384    /// an abruptly-completing case arm both bindings land at `f` and the
5385    /// plain inner break is the renderable one — binding the outer label
5386    /// makes the inner switch unable to complete normally and the
5387    /// pattern-switch restoration's appended outer-case `break;` becomes
5388    /// unreachable (jdk26 ClassPrinterImpl.toYaml/toXml: the MapNode/List
5389    /// inner ordinal switches have NO default stub — every arm `goto`s the
5390    /// shared return directly — so the inner switch rendered `default:
5391    /// break L1` + outer case `break;` — 无法访问的语句 x2 trees).
5392    pub(crate) fn switch_follow_bindable(
5393        &self,
5394        sw: &Region,
5395        block: usize,
5396        _targets: &SwitchTargets,
5397        f: usize,
5398    ) -> bool {
5399        let (cases, default) = match sw {
5400            Region::Switch { cases, default, .. } => (cases, default.as_deref()),
5401            _ => return false,
5402        };
5403        // The default arm's target comes from the terminator (the JVM
5404        // records it in the switch payload, DEX in the payload table).
5405        let default_pc = match self.results.get(block).map(|r| &r.term) {
5406            Some(Term::Switch {
5407                targets: SwitchTargets::Table { .. },
5408                default: Some(d),
5409                ..
5410            })
5411            | Some(Term::Switch {
5412                targets: SwitchTargets::Lookup { .. },
5413                default: Some(d),
5414                ..
5415            }) => *d,
5416            _ => return false,
5417        };
5418        // The default route: the rendered default region must itself bind
5419        // to `f`. `default_pc == f` is NOT automatically bindable: with
5420        // the follow passed, structure_switch filters the default region
5421        // away and unmatched-selector flow falls out of the switch into
5422        // the ENCLOSING continuation (bkeyword's outer case then fell
5423        // through into the next case, dropping the shared `return '?'`).
5424        let default_ok = match default {
5425            Some(d) => self.arm_binds_to(d, f),
5426            None => false,
5427        };
5428        // Case arms in emission order. An Empty group is a fallthrough
5429        // label: its route is the NEXT group's (checked in its own
5430        // iteration), or the default's when it is the last group.
5431        let n = cases.len();
5432        let cases_ok = cases.iter().enumerate().all(|(i, (_, r))| {
5433            if matches!(r, Region::Empty) {
5434                return i + 1 < n || default_ok;
5435            }
5436            let ok = self.arm_binds_to(r, f);
5437            if crate::dbg_flag!("JCDC_DBG_SWF") {
5438                eprintln!(
5439                    "SWBIND-CASE i={} block={} f={} ok={} region={:?}",
5440                    i,
5441                    region_head_block(r),
5442                    f,
5443                    ok,
5444                    region_shape(r)
5445                );
5446            }
5447            ok
5448        });
5449        if crate::dbg_flag!("JCDC_DBG_SWF") {
5450            eprintln!(
5451                "SWBIND-DEF block={} default_pc={} f={} default_ok={} cases_ok={}",
5452                block, default_pc, f, default_ok, cases_ok
5453            );
5454        }
5455        default_ok && cases_ok
5456    }
5457
5458    /// Every completion path of this arm region lands on `f`: a trailing
5459    /// goto to it, a block whose terminal edge is that goto, an if whose
5460    /// both branches bind, a loop whose every exit IS `f`, or a nested
5461    /// switch that is itself bindable.
5462    fn arm_binds_to(&self, r: &Region, f: usize) -> bool {
5463        match r {
5464            Region::Basic { .. } => true,
5465            Region::Goto { target } => *target == f,
5466            Region::Empty => false,
5467            Region::Seq(v) => v.last().map(|x| self.arm_binds_to(x, f)).unwrap_or(false),
5468            Region::If { then_r, else_r, .. } => {
5469                self.arm_binds_to(then_r, f) && self.arm_binds_to(else_r, f)
5470            }
5471            // The loop arm binds when ALL its exits are `f`: loop
5472            // completion falls out of the switch (follow == `f`) and any
5473            // in-body break targets an exit == `f` (ClassPrinterImpl's
5474            // BLOCK-case iterator loop exits straight to the shared
5475            // return — no default stub).
5476            Region::Loop { exits, .. } => !exits.is_empty() && exits.iter().all(|&e| e == f),
5477            Region::Switch { block, .. } => {
5478                if let Term::Switch { targets, .. } = &self.results[*block].term {
5479                    self.switch_follow_bindable(r, *block, targets, f)
5480                } else {
5481                    false
5482                }
5483            }
5484            _ => false,
5485        }
5486    }
5487
5488    /// True when `cur` flows through single-successor blocks to an
5489    /// already-claimed terminator: the chain is safe to copy inline because
5490    /// its endpoint was structured elsewhere (the restart-loop switch: the
5491    /// guard-pass body flows ONLY to the shared areturn every case region
5492    /// absorbed) and the copy is this path's sole continuation. Normal loop
5493    /// exits chain to UNCLAIMED continuations (the post-loop follow) and
5494    /// must stay jumps — inlining those rewrites breaks into returns and
5495    /// degrades while-cond loops (jdk26 ThreadPoolExecutor regression).
5496    /// True when a loop-exit target heads real CONTENT: a statement or a
5497    /// branch cascade, not a statement-free stub chain into a barrier
5498    /// (loop header / another exit / out of scope). Breaking to a stub
5499    /// exit loses nothing (Pattern.clazz's switch-case goto stubs must
5500    /// stay bare breaks). Breaking to a content exit drops its whole
5501    /// flow: jdk17 AQS.cleanQueue's `q.status < 0` arm breaks to the
5502    /// CAS-unlink cascade (casTail/casPrev/casNext/signalNext flowing to
5503    /// the outer restart stub) and the bare break silently deleted the
5504    /// phase (semantic, compile-clean).
5505    pub(crate) fn exit_has_content(&self, e: usize, stop: &HashSet<usize>) -> bool {
5506        let mut b = e;
5507        for _ in 0..16 {
5508            if !self.results[b].stmts.is_empty() {
5509                return true;
5510            }
5511            match &self.results[b].term {
5512                Term::Cond { .. } | Term::Switch { .. } => return true,
5513                Term::Return(_) | Term::Throw(_) => return false,
5514                Term::Fallthrough | Term::Goto if self.cfg.blocks[b].succ.len() == 1 => {
5515                    let n = self.cfg.blocks[b].succ[0];
5516                    if n == e || stop.contains(&n) || self.loops_stack.contains(&n) {
5517                        return false;
5518                    }
5519                    b = n;
5520                }
5521                _ => return false,
5522            }
5523        }
5524        false
5525    }
5526
5527    fn stop_chain_to_claimed_terminator(&self, cur: usize, claimed: &HashSet<usize>) -> bool {
5528        let mut x = cur;
5529        for _ in 0..8 {
5530            if matches!(self.results[x].term, Term::Return(_) | Term::Throw(_)) {
5531                // The restart-loop guarded case is a chain of EMPTY jump
5532                // stubs into an absorbed terminator — breaking would land
5533                // where nothing remains. A STATEMENT-BEARING claimed
5534                // terminator is the post-loop continuation tail (a shared
5535                // clinit tail claimed early by the first arrival): the
5536                // enclosing walk structures it after the loop and the
5537                // barrier Goto must stay a `break`. Copying the tail into
5538                // the exit test put the blank-final assignments INSIDE the
5539                // for-each loop (javac: 可能在 loop 中分配了变量 x18,
5540                // SecurityProviderConstants) and duplicated them into the
5541                // catch.
5542                return claimed.contains(&x) && self.results[x].stmts.is_empty();
5543            }
5544            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
5545                return false;
5546            }
5547            // An UNCLAIMED statement-bearing link means the chain is real
5548            // post-loop code, not absorbed scaffolding (see above).
5549            if !self.results[x].stmts.is_empty() && !claimed.contains(&x) {
5550                return false;
5551            }
5552            let succs = &self.cfg.blocks[x].succ;
5553            if succs.len() != 1 {
5554                return false;
5555            }
5556            let n = succs[0];
5557            if n == x || self.loops_stack.contains(&n) {
5558                return false;
5559            }
5560            x = n;
5561        }
5562        false
5563    }
5564
5565    fn switch_follow(&self, cur: usize, universe: &HashSet<usize>) -> Option<usize> {
5566        let mut dists: Vec<HashMap<usize, u32>> = Vec::new();
5567        for &s0 in &self.cfg.blocks[cur].succ {
5568            if !universe.contains(&s0) {
5569                continue;
5570            }
5571            let mut d: HashMap<usize, u32> = HashMap::new();
5572            let mut q: VecDeque<(usize, u32)> = VecDeque::new();
5573            d.insert(s0, 0);
5574            q.push_back((s0, 0));
5575            while let Some((b, db)) = q.pop_front() {
5576                for &s in &self.cfg.blocks[b].succ {
5577                    if s != cur && universe.contains(&s) && !d.contains_key(&s) {
5578                        d.insert(s, db + 1);
5579                        q.push_back((s, db + 1));
5580                    }
5581                }
5582            }
5583            // Cases that always terminate (return/throw with no flow
5584            // onward) contribute no reconvergence point.
5585            let terminates = d.len() <= 1
5586                && self.cfg.blocks[s0]
5587                    .succ
5588                    .iter()
5589                    .all(|s| !universe.contains(s));
5590            if !terminates {
5591                dists.push(d);
5592            }
5593        }
5594        if dists.is_empty() {
5595            return None;
5596        }
5597        if dists.len() == 1 {
5598            // Single non-terminating case flow: its exit out of the case
5599            // region is the switch follow.
5600            let d = &dists[0];
5601            // Multi-pred blocks INSIDE a nested switch's span are that
5602            // switch's own merge, not this switch's follow (jdk26 xml
5603            // impl.Parser.dtdsub: switch(ch)'s case '!' flow merges the
5604            // nested switch(bkeyword) at the shared `st = 1; goto head`
5605            // stub — taking it as the follow put `st = 1` after the inner
5606            // switch in case '<' and left the arm without its continue,
5607            // falling through into case '%' — unreachable code past the
5608            // all-continue arms plus a corrupted state machine, 无法访问
5609            // 的语句 x5 methods x3 trees). A nested-switch merge is
5610            // claimed by that switch's own follow resolution.
5611            let in_nested_switch = |c: usize, d: &HashMap<usize, u32>| -> bool {
5612                let preds = &self.cfg.blocks[c].pred;
5613                if preds.len() < 2 {
5614                    return false;
5615                }
5616                self.cfg.blocks.iter().any(|b| {
5617                    if b.id == cur
5618                        || !matches!(self.results[b.id].term, Term::Switch { .. })
5619                        || !d.contains_key(&b.id)
5620                    {
5621                        return false;
5622                    }
5623                    preds.iter().all(|&p| {
5624                        b.succ.iter().any(|&t| {
5625                            let mut seen: HashSet<usize> = HashSet::new();
5626                            let mut q: VecDeque<usize> = VecDeque::new();
5627                            q.push_back(t);
5628                            while let Some(x) = q.pop_front() {
5629                                if x == p {
5630                                    return true;
5631                                }
5632                                if !seen.insert(x) || !d.contains_key(&x) {
5633                                    continue;
5634                                }
5635                                for &sx in &self.cfg.blocks[x].succ {
5636                                    q.push_back(sx);
5637                                }
5638                            }
5639                            false
5640                        })
5641                    })
5642                })
5643            };
5644            // A RETURN/THROW merge whose preds all live inside this one
5645            // flow is the case's private terminator confluence, not the
5646            // switch's follow (jdk26 xml impl.Parser.bappend: the
5647            // whitespace case's if-chain arms all converge on the shared
5648            // `return` leaf; electing it follow made the switch "complete"
5649            // into a bogus `return;` and the case group fall through into
5650            // `case 99` — 无法访问的语句 on the outer tail). A shared
5651            // return that IS the lexical follow keeps preds from the
5652            // sibling (terminator-excluded) case flows and stays elected.
5653            // A candidate whose every flow-internal pred is a VALUE-STACK
5654            // diamond arm (statement-free, one stack value out, single
5655            // successor) is a folded-ternary merge INSIDE the case body,
5656            // not the switch follow (jdk26 xml impl.Parser.xml: case
5657            // 0xFEFF's `ch = val < 0 ? 0xFFFF : val` and `st = ch != '<'
5658            // ? -1 : 1` diamonds — electing the first merge split the
5659            // case body in half, the front fell through into `default`
5660            // and the back half went unreachable after the all-continue
5661            // switch — 无法访问的语句 x2 trees). In the single-flow
5662            // branch every other case terminates, so the true follow is
5663            // the flow's own exit; an interior diamond merge never is.
5664            let diamond_merge = |c: usize, d: &HashMap<usize, u32>| -> bool {
5665                let internal: Vec<usize> = self.cfg.blocks[c]
5666                    .pred
5667                    .iter()
5668                    .copied()
5669                    .filter(|p| d.contains_key(p))
5670                    .collect();
5671                !internal.is_empty()
5672                    && internal.iter().all(|&p| {
5673                        self.results[p].stmts.is_empty()
5674                            && self.results[p].out_stack.len() == 1
5675                            && self.cfg.blocks[p].succ.len() == 1
5676                    })
5677            };
5678            let private_terminator_merge = |c: usize, d: &HashMap<usize, u32>| -> bool {
5679                if !matches!(self.results[c].term, Term::Return(_) | Term::Throw(_)) {
5680                    return false;
5681                }
5682                if !self.cfg.blocks[c]
5683                    .pred
5684                    .iter()
5685                    .all(|p| d.contains_key(p) || *p == cur)
5686                {
5687                    return false;
5688                }
5689                // A SINGLE case target whose flow ends at this return IS
5690                // the lexical switch tail (`switch (x) { default: g(); }
5691                // return v;` — the default completes the switch into the
5692                // return): keep it as the follow. With two or more live
5693                // targets the return is one case's private confluence
5694                // (bappend's whitespace if-chain), not the follow.
5695                self.cfg.blocks[cur].succ.len() > 1
5696            };
5697            let mut best: Option<usize> = None;
5698            for &cand in d.keys() {
5699                if self.cfg.blocks[cand].pred.len() >= 2
5700                    && !in_nested_switch(cand, d)
5701                    && !diamond_merge(cand, d)
5702                    && !private_terminator_merge(cand, d)
5703                    && best
5704                        .map(|b| self.cfg.blocks[cand].start < self.cfg.blocks[b].start)
5705                        .unwrap_or(true)
5706                {
5707                    best = Some(cand);
5708                }
5709            }
5710            return best;
5711        }
5712        let mut best: Option<(u32, usize)> = None;
5713        for (&cand, &d0) in dists[0].iter() {
5714            if cand == cur {
5715                continue;
5716            }
5717            let mut total = d0;
5718            let mut ok = true;
5719            for d in &dists[1..] {
5720                match d.get(&cand) {
5721                    Some(x) => total += x,
5722                    None => {
5723                        ok = false;
5724                        break;
5725                    }
5726                }
5727            }
5728            if ok {
5729                let better = match best {
5730                    None => true,
5731                    Some((bd, bc)) => {
5732                        total < bd
5733                            || (total == bd
5734                                && self.cfg.blocks[cand].start < self.cfg.blocks[bc].start)
5735                    }
5736                };
5737                if better {
5738                    best = Some((total, cand));
5739                }
5740            }
5741        }
5742        best.map(|(_, c)| c)
5743    }
5744
5745    /// Re-walk an already-claimed region with a throwaway claimed set,
5746    /// producing a structural copy. Used when flow re-enters a shared
5747    /// region that Java cannot express with a jump (no labelable target):
5748    /// re-executing the blocks matches the bytecode's per-arrival
5749    /// semantics. Bounded by depth and a no-reentry check.
5750    /// Take one copy ticket; false when the per-method budget is spent
5751    /// (copy mechanisms must then decline and leave a Goto).
5752    fn take_copy_ticket(&self) -> bool {
5753        let b = self.copy_budget.get();
5754        if b == 0 {
5755            return false;
5756        }
5757        self.copy_budget.set(b - 1);
5758        true
5759    }
5760
5761    pub(crate) fn copy_walk(
5762        &mut self,
5763        t: usize,
5764        stop: &HashSet<usize>,
5765        active: &[usize],
5766        guard_against: usize,
5767    ) -> Option<Region> {
5768        use std::cell::Cell;
5769        thread_local! {
5770            static COPY_DEPTH: Cell<u32> = const { Cell::new(0) };
5771        }
5772        if COPY_DEPTH.with(|c| c.get()) >= 4 {
5773            return None;
5774        }
5775        if !self.take_copy_ticket() {
5776            return None;
5777        }
5778        if can_reach_cfg(self.cfg, t, guard_against, 4096) {
5779            return None;
5780        }
5781        // Targets that flow back into an enclosing loop (or are loop
5782        // exits) resolve to continue/break at conversion; never copy them.
5783        for h in &self.loops_stack {
5784            if *h == t || can_reach_cfg(self.cfg, t, *h, 4096) {
5785                return None;
5786            }
5787        }
5788        let mut barriers = stop.clone();
5789        barriers.extend(self.loops_stack.iter().copied());
5790        barriers.insert(guard_against);
5791        // Shared tail-confluence heads inside the closure become dynamic
5792        // barriers: the copy stops before them and defers to the owner
5793        // walk's canonical render (keytool doCommands' load epilogue was
5794        // re-rendered by every password-section copy whose closure flowed
5795        // into it — 12 inline survivors blew the 64K try-codegen limit).
5796        // Skip the pre-pass when t is itself a terminator (legit shared
5797        // return-tail copies never traverse a confluence head anyway).
5798        //
5799        // COPY_ALLOW_CONFLUENCE (SESE consumed-arrival copies: the copy IS
5800        // the arm's only emission route) crosses a barred confluence only
5801        // when its own forward closure is SMALL (≤8 blocks): jdk11/17/26
5802        // Pattern.family's stage-2 switch head and DecimalFormat.equals'
5803        // chain merge must ride along or the copied arm falls off the
5804        // method end (缺少返回语句), while keytool doCommands' 28-pred
5805        // load epilogue — a 22-statement TWR finish chain every case arm
5806        // flows into — stays barred: crossing it duplicated the epilogue
5807        // into every consumed-arrival copy (75 → 329 FileOutputStream
5808        // sites, 953KB → 3.2MB render, try 语句的代码过长 ×652 unmasked
5809        // the moment OCSPResponse's FLOW error stopped masking javac's
5810        // GENERATE phase).
5811        if !self.is_terminator_block(t) {
5812            let allow = COPY_ALLOW_CONFLUENCE.with(|c| c.get());
5813            let probe = reachable_within(self.cfg, t, &barriers);
5814            let extra: Vec<usize> = probe
5815                .iter()
5816                .copied()
5817                .filter(|&x| x != t && self.shared_tail_confluence(x))
5818                .filter(|&x| {
5819                    !allow
5820                        || !(matches!(self.results[x].term, crate::ir::build::Term::Switch { .. })
5821                            || self.confluence_closure_small(x, &barriers))
5822                })
5823                .collect();
5824            barriers.extend(extra);
5825        }
5826        let tu = reachable_within(self.cfg, t, &barriers);
5827        if !tu.contains(&t) {
5828            return None;
5829        }
5830        #[allow(unused_mut)]
5831        let mut tu = tu;
5832        COPY_DEPTH.with(|c| c.set(c.get() + 1));
5833        let mut fresh: HashSet<usize> = HashSet::new();
5834        // Try groups that START (and whose handler heads live) inside the
5835        // copied universe must fire inside the copy: SESE passes only the
5836        // top-level groups, so a copied loop-top try head re-walked from a
5837        // catch back-edge lost its try wrapper and emitted bare statements
5838        // (jdk11 ObjectStreamClass.getInheritableMethod: the retry
5839        // getDeclaredMethod call landed in the catch WITHOUT its
5840        // try/catch — "unreported exception NoSuchMethodException").
5841        // For walk callers `active` already covers the scope, so this is
5842        // a no-op there.
5843        let mut active2: Vec<usize> = active.to_vec();
5844        for gi in 0..self.groups.len() {
5845            if active2.contains(&gi) {
5846                continue;
5847            }
5848            let gs = self.groups[gi].start;
5849            let start_in = self
5850                .cfg
5851                .blocks
5852                .iter()
5853                .any(|b| b.start == gs && tu.contains(&b.id));
5854            if !start_in {
5855                continue;
5856            }
5857            // Pull the handler heads (and their forward flow up to the
5858            // group's end) into the copy universe: exception edges are
5859            // not followed by reachable_within, but structure_try needs
5860            // the handler blocks present to rebuild the catch.
5861            let heads: Vec<usize> = self
5862                .handler_group
5863                .iter()
5864                .filter(|(_, &g)| g == gi)
5865                .map(|(&hb, _)| hb)
5866                .collect();
5867            for h in heads {
5868                tu.insert(h);
5869            }
5870            active2.push(gi);
5871        }
5872        let r = self.walk(t, &tu, stop, &active2, &mut fresh, true);
5873        COPY_DEPTH.with(|c| c.set(c.get() - 1));
5874        self.copied_tails.insert(t);
5875        Some(r)
5876    }
5877
5878    /// True when the block ends in a return/throw (a shared terminator that
5879    /// can be safely duplicated at each arrival site).
5880    pub(crate) fn is_terminator_block(&self, b: usize) -> bool {
5881        matches!(self.results[b].term, Term::Return(_) | Term::Throw(_))
5882    }
5883
5884    /// True when `b` reaches some method Return through normal edges
5885    /// (bounded BFS): the block sits on a live route to the method end,
5886    /// i.e. it heads a method-tail region rather than a self-contained
5887    /// abrupt epilogue.
5888    fn reaches_any_return(&self, b: usize) -> bool {
5889        let mut seen: HashSet<usize> = HashSet::new();
5890        let mut q: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
5891        q.push_back(b);
5892        let mut budget = 4096;
5893        while let Some(x) = q.pop_front() {
5894            if budget == 0 {
5895                return false;
5896            }
5897            budget -= 1;
5898            if !seen.insert(x) {
5899                continue;
5900            }
5901            if matches!(self.results[x].term, Term::Return(_)) {
5902                return true;
5903            }
5904            for &sx in &self.cfg.blocks[x].succ {
5905                q.push_back(sx);
5906            }
5907        }
5908        false
5909    }
5910
5911    /// A SHARED TAIL CONFLUENCE: a many-pred normal merge head that sits
5912    /// on a live route to the method's final return (jdk11/26 keytool
5913    /// doCommands' keystore-load epilogue, block 376 `if (!token)` with
5914    /// 28 preds: every password-section arm/handler exit jumps to it, and
5915    /// the canonical owner walk renders it once after the dispatch
5916    /// chain). Per-arrival COPY contexts must not swallow such a head in
5917    /// their universe closures: each copy re-renders the whole ~92-line
5918    /// epilogue (12 survivors inflated the method past javac's 64K
5919    /// try-codegen limit — try 语句的代码过长 ×64/87). The copy's exit
5920    /// Goto resolves at conversion like every deferred shared merge.
5921    /// Terminator heads stay copyable (the established per-arrival shared
5922    /// RETURN-tail discipline — small, no outgoing flow, nothing to
5923    /// duplicate); handler heads and group-span starts are structural
5924    /// territory, excluded; the high pred floor keeps ordinary merges
5925    /// (loop selectors, small diamonds) out — copy-local merges have
5926    /// their preds inside the closure and close normally.
5927    pub(crate) fn shared_tail_confluence(&self, b: usize) -> bool {
5928        if self.is_terminator_block(b) || self.is_handler(b) {
5929            return false;
5930        }
5931        let bstart = self.cfg.blocks[b].start;
5932        if self.groups.iter().any(|g| g.start == bstart) {
5933            return false;
5934        }
5935        let normal_preds = self.cfg.blocks[b]
5936            .pred
5937            .iter()
5938            .filter(|p| !self.is_handler(**p))
5939            .count();
5940        normal_preds >= 8 && self.reaches_any_return(b)
5941    }
5942
5943    /// True when the forward closure of confluence `b` (within the given
5944    /// barriers) is small — ≤8 blocks. The COPY_ALLOW_CONFLUENCE crossing
5945    /// budget: small closures are completeness-critical merge stubs /
5946    /// stage heads whose loss strands the copied arm (Pattern.family,
5947    /// DecimalFormat.equals); big closures are shared finish chains whose
5948    /// per-arrival duplication overflows javac's 64K try-codegen limit
5949    /// (keytool doCommands' 22-statement TWR epilogue).
5950    fn confluence_closure_small(&self, b: usize, barriers: &HashSet<usize>) -> bool {
5951        let reach = reachable_within(self.cfg, b, barriers);
5952        reach.len() <= 8
5953    }
5954
5955    /// True when EVERY normal flow path out of `b` ends in a
5956    /// return/throw within a small budget: the block heads an abrupt
5957    /// completion chain (possibly via a cond whose arms both terminate,
5958    /// Resource.getBytes' `if (interrupted)` epilogue selector). Used by
5959    /// the post-try recovery scan — a tail that can complete normally is
5960    /// live fall-through flow some walk emits itself, and copying it
5961    /// duplicates statements or whole try-containing tails.
5962    fn cfg_all_paths_terminate(&self, b: usize) -> bool {
5963        let mut visited: HashSet<usize> = HashSet::new();
5964        let mut q: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
5965        q.push_back(b);
5966        while let Some(x) = q.pop_front() {
5967            if !visited.insert(x) {
5968                continue;
5969            }
5970            if visited.len() > 64 {
5971                return false;
5972            }
5973            match self.results[x].term {
5974                Term::Return(_) | Term::Throw(_) => {}
5975                Term::Fallthrough | Term::Goto => {
5976                    let succs = self.cfg.blocks[x].succ.clone();
5977                    if succs.len() != 1 {
5978                        return false;
5979                    }
5980                    q.push_back(succs[0]);
5981                }
5982                _ => {
5983                    let succs = self.cfg.blocks[x].succ.clone();
5984                    if succs.is_empty() {
5985                        return false;
5986                    }
5987                    for sx in succs {
5988                        q.push_back(sx);
5989                    }
5990                }
5991            }
5992        }
5993        true
5994    }
5995
5996    /// True when the block's statements assign a FINAL field of the
5997    /// emitting class: copying such a shared terminator at an extra
5998    /// arrival site duplicates the final's assignment (javac:
5999    /// 可能在 loop 中分配了变量 / 可能已分配变量 — jdk11
6000    /// SecurityProviderConstants clinit tail assigned its blank-final
6001    /// key sizes in the loop-exit copy AND the handler copy). Such
6002    /// arrivals emit a Goto instead — conversion's term_copy refuses it
6003    /// for the same final_fields reason and elides to the real tail the
6004    /// enclosing flow emits next.
6005    fn terminator_writes_final(&self, b: usize) -> bool {
6006        if self.final_fields.is_empty() {
6007            return false;
6008        }
6009        // Walk the LINEAR tail chain (single-succ Fall/Goto hops, bounded):
6010        // a shared tail whose blank-final write sits PAST the head block
6011        // still double-assigns when copied per-arrival (jdk11/26
6012        // URICertStore clinit: head `CA_ISS_ALLOW_ANY = allowAny` chains
6013        // into `certStoreCache = newSoftMemoryCache(185)` — the head-only
6014        // probe missed the second final and the tail copy landed inside
6015        // the debug-println branch alongside the canonical render —
6016        // 可能已分配变量 ×2). Branching chains stop (a final write on only
6017        // one route is not a guaranteed duplicate).
6018        let writes = |x: usize| {
6019            self.results[x].stmts.iter().any(|s| {
6020                matches!(
6021                    s,
6022                    crate::ir::stmt::Stmt::ExprStmt(crate::ir::expr::Expr::Assign { target, .. })
6023                        if matches!(
6024                            target.as_ref(),
6025                            crate::ir::expr::Expr::Field { name, .. }
6026                                if self.final_fields.contains(name)
6027                        )
6028                )
6029            })
6030        };
6031        let mut x = b;
6032        let mut seen: HashSet<usize> = HashSet::new();
6033        for _ in 0..8 {
6034            if !seen.insert(x) {
6035                return false;
6036            }
6037            if writes(x) {
6038                return true;
6039            }
6040            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6041                return false;
6042            }
6043            let succs = &self.cfg.blocks[x].succ;
6044            if succs.len() != 1 {
6045                return false;
6046            }
6047            x = succs[0];
6048        }
6049        false
6050    }
6051
6052    /// Unique enclosing barrier block reachable from `cur`'s branch region.
6053    /// Barriers = stop ∪ claimed. Used by the appendix fold: when exactly
6054    /// one *stop* block is reachable, it is the enclosing merge M.
6055    fn appendix_target(
6056        &self,
6057        cur: usize,
6058        universe: &HashSet<usize>,
6059        stop: &HashSet<usize>,
6060        claimed: &HashSet<usize>,
6061    ) -> Option<usize> {
6062        if stop.is_empty() {
6063            return None;
6064        }
6065        let mut barriers: HashSet<usize> = stop.union(claimed).copied().collect();
6066        barriers.remove(&cur);
6067        let mut hits: HashSet<usize> = HashSet::new();
6068        let mut seen: HashSet<usize> = HashSet::new();
6069        let _ = universe;
6070        let mut q: VecDeque<usize> = self.cfg.blocks[cur].succ.iter().copied().collect();
6071        while let Some(b) = q.pop_front() {
6072            if !seen.insert(b) {
6073                continue;
6074            }
6075            if barriers.contains(&b) {
6076                hits.insert(b);
6077                continue;
6078            }
6079            q.extend(self.cfg.blocks[b].succ.iter().copied());
6080        }
6081        let mut stop_hits = hits.iter().copied().filter(|h| stop.contains(h));
6082        let m = stop_hits.next()?;
6083        if stop_hits.next().is_some() {
6084            return None; // ambiguous
6085        }
6086        // Non-stop hits (claimed blocks) are fine: the appendix region
6087        // merges into them, and folding makes this If their owner.
6088        Some(m)
6089    }
6090
6091    fn absorb_pure(
6092        &mut self,
6093        blk: usize,
6094        universe: &HashSet<usize>,
6095        bstop: &HashSet<usize>,
6096        claimed: &mut HashSet<usize>,
6097        active: &[usize],
6098    ) -> Option<Region> {
6099        if !universe.contains(&blk) {
6100            return None;
6101        }
6102        // A block that STARTS a try group visible to this walk must be
6103        // WALKED, not absorbed: absorption inlines its statements into
6104        // the branch and silently drops the group's exception protection
6105        // (jdk17 HttpURLConnection.getOutputStream: the
6106        // doPrivilegedWithCombiner call absorbed out of its
6107        // try/catch(PrivilegedActionException) into the `if (p == null)`
6108        // else arm — 未报告的异常错误PrivilegedActionException).
6109        let blk_start = self.cfg.blocks[blk].start;
6110        if self
6111            .groups
6112            .iter()
6113            .enumerate()
6114            .any(|(gi, g)| g.start == blk_start && active.contains(&gi))
6115        {
6116            return None;
6117        }
6118        let dbg_absorb = crate::dbg_flag!("JCDC_DBG_ABSORB");
6119        let succ_walkable;
6120        {
6121            let b = &self.cfg.blocks[blk];
6122            let r = &self.results[blk];
6123            if b.ins_len == 0 || b.succ.len() != 1 {
6124                if dbg_absorb {
6125                    eprintln!(
6126                        "absorb EARLY blk={} ins={} succ={} out={}",
6127                        blk,
6128                        b.ins_len == 0,
6129                        b.succ.len(),
6130                        r.out_stack.len()
6131                    );
6132                }
6133                return None;
6134            }
6135            // Statements must be empty or only stack-merge stores.
6136            let stmts_ok = r.stmts.iter().all(|st| match st {
6137                crate::ir::stmt::Stmt::LocalDef { init: Some(_), .. } => true,
6138                crate::ir::stmt::Stmt::ExprStmt(crate::ir::expr::Expr::Assign {
6139                    target, ..
6140                }) => {
6141                    matches!(&**target, crate::ir::expr::Expr::Local { .. })
6142                }
6143                _ => false,
6144            });
6145            if !stmts_ok {
6146                return None;
6147            }
6148            // Only fallthrough blocks may be absorbed: an explicit goto to
6149            // a barrier is a real jump (loop continue / break) that must
6150            // stay a Region::Goto so it resolves to a jump statement.
6151            if !matches!(r.term, crate::ir::build::Term::Fallthrough) {
6152                return None;
6153            }
6154            let succ = b.succ[0];
6155            succ_walkable = universe.contains(&succ) && !bstop.contains(&succ);
6156        }
6157        if succ_walkable {
6158            if crate::dbg_flag!("JCDC_DBG_ABSORB") {
6159                eprintln!(
6160                    "absorb REJECT blk={} succ walkable (univ={}, bstop={})",
6161                    blk,
6162                    universe.contains(&self.cfg.blocks[blk].succ[0]),
6163                    bstop.contains(&self.cfg.blocks[blk].succ[0])
6164                );
6165            }
6166            return None; // normal flow continues; not our case
6167        }
6168        if crate::dbg_flag!("JCDC_DBG_ABSORB") {
6169            eprintln!(
6170                "absorb ACCEPT blk={} claimed={}",
6171                blk,
6172                claimed.contains(&blk)
6173            );
6174        }
6175        // The unwalkable successor is an already-CLAIMED block this arm's
6176        // normal walk would inline-copy (the shared `registry.add(key);
6177        // return key;` tail): absorbing blk ends the arm AT blk and the
6178        // path to the tail is silently severed — the enclosing walk
6179        // breaks at the if with follow=None and the method falls off
6180        // without a return on this path (jdk26
6181        // DatagramChannelImpl.innerJoin's IPv4 arm lost the tail after
6182        // `key = new Type4(..)` — 缺少返回语句 x3 trees). Refuse: the
6183        // normal walk branch emits Basic{blk} and, arriving at the
6184        // claimed successor, copies it per arrival — exactly the bytecode
6185        // semantics. Final-writing successors stay absorbed (copying
6186        // would double-assign the blank final — the UntrustedCertificates
6187        // discipline). NOTE: Module.loadModuleInfoClass's TWR fold needs
6188        // this refusal ABSENT when handler_flow_only carries the
6189        // ownership check — the working combination is this refusal +
6190        // the ORIGINAL f4472a5f fixpoint + the sole-entry private-tail
6191        // cont==hb strip (gate-matrix verified).
6192        {
6193            let succ = self.cfg.blocks[blk].succ[0];
6194            let succ_owned_cont = self.is_cont_of_active_group(succ, bstop, claimed, active);
6195            if claimed.contains(&succ)
6196                && !succ_owned_cont
6197                && !bstop.contains(&succ)
6198                && !self.loops_stack.contains(&succ)
6199                && !self.terminator_writes_final(succ)
6200            {
6201                if dbg_absorb {
6202                    eprintln!(
6203                        "absorb REJECT blk={} succ={} claimed inline-copy tail",
6204                        blk, succ
6205                    );
6206                }
6207                return None;
6208            }
6209        }
6210        // Shared pure blocks (reached from several branches) get their
6211        // statements duplicated into each branch — exactly the bytecode
6212        // semantics, since the block re-executes per arrival.
6213        if claimed.contains(&blk) {
6214            let r = &self.results[blk];
6215            if r.stmts.is_empty() {
6216                return Some(Region::Empty);
6217            }
6218            return Some(Region::CopyStmts { block: blk });
6219        }
6220        claimed.insert(blk);
6221        Some(Region::Basic { block: blk })
6222    }
6223
6224    /// Nearest block reachable from both `a` and `b` (min total BFS dist).
6225    fn branch_confluence(&self, a: usize, b: usize, universe: &HashSet<usize>) -> Option<usize> {
6226        let bfs = |s0: usize| -> HashMap<usize, u32> {
6227            let mut d: HashMap<usize, u32> = HashMap::new();
6228            let mut q: VecDeque<(usize, u32)> = VecDeque::new();
6229            d.insert(s0, 0);
6230            q.push_back((s0, 0));
6231            while let Some((x, dx)) = q.pop_front() {
6232                for &s in &self.cfg.blocks[x].succ {
6233                    if universe.contains(&s) && !d.contains_key(&s) {
6234                        d.insert(s, dx + 1);
6235                        q.push_back((s, dx + 1));
6236                    }
6237                }
6238            }
6239            d
6240        };
6241        let da = bfs(a);
6242        let db = bfs(b);
6243        let mut best: Option<(u32, usize)> = None;
6244        for (&c, &x) in &da {
6245            if let Some(&y) = db.get(&c) {
6246                let t = x + y;
6247                let better = match best {
6248                    None => true,
6249                    Some((bt, bc)) => {
6250                        t < bt || (t == bt && self.cfg.blocks[c].start < self.cfg.blocks[bc].start)
6251                    }
6252                };
6253                if better {
6254                    best = Some((t, c));
6255                }
6256            }
6257        }
6258        best.map(|(_, c)| c)
6259    }
6260
6261    /// Follow a chain of pure value-producing blocks (no statements, one
6262    /// stack value out, fallthrough/goto terminals) and return the first
6263    /// block that is NOT pure — the real merge point of a value diamond.
6264    fn pure_chain_end(
6265        &self,
6266        start: usize,
6267        universe: &HashSet<usize>,
6268        stop: &HashSet<usize>,
6269        claimed: &HashSet<usize>,
6270    ) -> Option<usize> {
6271        let mut cur = start;
6272        let mut steps = 0;
6273        loop {
6274            steps += 1;
6275            if steps > 10_000 || !universe.contains(&cur) || stop.contains(&cur) {
6276                return None;
6277            }
6278            let b = &self.cfg.blocks[cur];
6279            let r = &self.results[cur];
6280            let pure = b.ins_len != 0
6281                && r.stmts.is_empty()
6282                && !r.out_stack.is_empty()
6283                && matches!(
6284                    r.term,
6285                    crate::ir::build::Term::Fallthrough | crate::ir::build::Term::Goto
6286                )
6287                && b.succ.len() == 1;
6288            if !pure {
6289                return Some(cur);
6290            }
6291            if claimed.contains(&cur) {
6292                return None;
6293            }
6294            cur = b.succ[0];
6295        }
6296    }
6297
6298    /// Check that both `a` and `b` flow to `merge` through blocks that are
6299    /// pure value producers (no statements; terminals are fallthrough or
6300    /// goto), possibly via nested diamond headers. All traversed blocks are
6301    /// added to `visited` so the caller can claim them.
6302    #[allow(dead_code)]
6303    fn branches_are_diamond(
6304        &self,
6305        a: usize,
6306        b: usize,
6307        merge: usize,
6308        universe: &HashSet<usize>,
6309        stop: &HashSet<usize>,
6310        claimed: &HashSet<usize>,
6311        visited: &mut HashSet<usize>,
6312    ) -> bool {
6313        self.diamond_side(a, merge, universe, stop, claimed, visited, 0)
6314            && self.diamond_side(b, merge, universe, stop, claimed, visited, 0)
6315    }
6316
6317    fn diamond_side(
6318        &self,
6319        blk: usize,
6320        merge: usize,
6321        universe: &HashSet<usize>,
6322        stop: &HashSet<usize>,
6323        claimed: &HashSet<usize>,
6324        visited: &mut HashSet<usize>,
6325        depth: usize,
6326    ) -> bool {
6327        if depth > 32 {
6328            return false;
6329        }
6330        let mut cur = blk;
6331        let mut steps = 0;
6332        loop {
6333            steps += 1;
6334            if steps > 10_000 {
6335                return false;
6336            }
6337            if cur == merge {
6338                return true;
6339            }
6340            if stop.contains(&cur) || claimed.contains(&cur) || !universe.contains(&cur) {
6341                return false;
6342            }
6343            let b = &self.cfg.blocks[cur];
6344            if b.ins_len == 0 {
6345                match b.succ.first() {
6346                    Some(&n) => {
6347                        visited.insert(cur);
6348                        cur = n;
6349                        continue;
6350                    }
6351                    None => return false,
6352                }
6353            }
6354            match self.term(cur) {
6355                Term::Fallthrough | Term::Goto => {
6356                    let r = &self.results[cur];
6357                    // pure = no statements and exactly one successor (the
6358                    // value it leaves may be any stack depth >= 0)
6359                    if !r.stmts.is_empty() || b.succ.len() != 1 {
6360                        return false;
6361                    }
6362                    let n = b.succ[0];
6363                    visited.insert(cur);
6364                    cur = n;
6365                }
6366                Term::Cond { .. } if b.succ.len() == 2 => {
6367                    // nested diamond header: both sides must reach merge
6368                    if !self.results[cur].stmts.is_empty() {
6369                        return false;
6370                    }
6371                    visited.insert(cur);
6372                    let s0 = b.succ[0];
6373                    let s1 = b.succ[1];
6374                    let mut v2 = HashSet::new();
6375                    let ok =
6376                        self.diamond_side(s0, merge, universe, stop, claimed, &mut v2, depth + 1)
6377                            && self.diamond_side(
6378                                s1,
6379                                merge,
6380                                universe,
6381                                stop,
6382                                claimed,
6383                                &mut v2,
6384                                depth + 1,
6385                            );
6386                    if ok {
6387                        visited.extend(v2);
6388                        return true;
6389                    }
6390                    return false;
6391                }
6392                _ => return false,
6393            }
6394        }
6395    }
6396
6397    fn next_after_loop(
6398        &self,
6399        loop_r: &Region,
6400        universe: &HashSet<usize>,
6401        stop: &HashSet<usize>,
6402        claimed: &HashSet<usize>,
6403    ) -> Option<usize> {
6404        let exits = match loop_r {
6405            Region::Loop { exits, .. } => exits.clone(),
6406            _ => return None,
6407        };
6408        let _ = claimed;
6409        // Pre-stub-filter candidate set: the shared-collector test in
6410        // stub_chain_claims_continuation checks chain intermediates
6411        // against it.
6412        let pre: HashSet<usize> = exits
6413            .iter()
6414            .copied()
6415            .filter(|e| universe.contains(e) && !stop.contains(e))
6416            .collect();
6417        exits
6418            .into_iter()
6419            .filter(|e| universe.contains(e) && !stop.contains(e))
6420            // An exit that is a statement-free jump chain back into an
6421            // ENCLOSING loop is a `continue outer` stub — one of the inner
6422            // loop's conditional break arms, already resolved to a break
6423            // inside the body. Walking it as the straight-line continuation
6424            // re-emits the jump unconditionally after the inner loop
6425            // (jdk11 FutureTask.removeWaiter: the pred.thread/CAS-fail
6426            // `continue retry` stubs became an unconditional `continue`
6427            // after the inner while, the outer while(true) never completed
6428            // and the tail `return` went unreachable — 无法访问的语句 x3
6429            // trees). Real fall-out exits (post-loop code) are kept.
6430            .filter(|e| {
6431                !self.loops_stack.iter().any(|&h| {
6432                    h != *e
6433                        && self.is_stmt_free_chain_to_block(*e, h)
6434                        && !self.stub_chain_claims_continuation(*e, h, &pre)
6435                })
6436            })
6437            .min_by_key(|e| self.cfg.blocks[*e].start)
6438    }
6439
6440    /// True when `from` reaches `to` through statement-free
6441    /// Fallthrough/Goto blocks (walk-side twin of the strip helper).
6442    pub(crate) fn is_stmt_free_chain_to_block(&self, from: usize, to: usize) -> bool {
6443        let mut x = from;
6444        let mut seen: HashSet<usize> = HashSet::new();
6445        for _ in 0..8 {
6446            if x == to {
6447                return true;
6448            }
6449            if !seen.insert(x) {
6450                return false;
6451            }
6452            if !self.results[x].stmts.is_empty() {
6453                return false;
6454            }
6455            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6456                return false;
6457            }
6458            let succs = self.cfg.blocks[x].succ.clone();
6459            if succs.len() != 1 {
6460                return false;
6461            }
6462            x = succs[0];
6463        }
6464        false
6465    }
6466
6467    /// True when `from`'s statement-free jump chain into loop header `to`
6468    /// passes through a block that is ITSELF a continuation candidate for
6469    /// the just-structured loop (in-universe, non-stop, non-stub exit with
6470    /// multiple predecessors — a shared continue-collector). Such a chain
6471    /// is the loop's real backedge merge, not a disposable conditional
6472    /// break arm: filtering it lets a lower-priority candidate (a bare
6473    /// `return` escape) become the continuation and the loop body loses
6474    /// its trailing re-iteration (jdk17 AQS.cleanQueue: the traversal
6475    /// loop's exits are the return block and the `goto 171` stub; block
6476    /// 171 (`goto 0`) has 10 preds and is a candidate — dropping the stub
6477    /// picked the return, the outer for(;;) body lost its `continue`, and
6478    /// the do-while rotation relocated the CAS tail past the loop with
6479    /// bare continues — continue 在 loop 外部). Direct-header backedges
6480    /// (jdk11 FutureTask.removeWaiter's `goto retry` stubs, chain = the
6481    /// exit block alone) have no intermediate shared block and stay
6482    /// filtered: walking them re-emitted the conditional retry jump as an
6483    /// unconditional continue after the inner loop.
6484    pub(crate) fn stub_chain_claims_continuation(
6485        &self,
6486        from: usize,
6487        to: usize,
6488        cands: &HashSet<usize>,
6489    ) -> bool {
6490        let mut x = from;
6491        let mut seen: HashSet<usize> = HashSet::new();
6492        while x != to {
6493            if !seen.insert(x) {
6494                return false;
6495            }
6496            if x != from && cands.contains(&x) && self.cfg.blocks[x].pred.len() > 1 {
6497                return true;
6498            }
6499            if !self.results[x].stmts.is_empty() {
6500                return false;
6501            }
6502            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6503                return false;
6504            }
6505            let succs = &self.cfg.blocks[x].succ;
6506            if succs.len() != 1 {
6507                return false;
6508            }
6509            x = succs[0];
6510        }
6511        false
6512    }
6513
6514    fn structure_loop(
6515        &mut self,
6516        header: usize,
6517        universe: &HashSet<usize>,
6518        stop: &HashSet<usize>,
6519        active: &[usize],
6520        claimed: &mut HashSet<usize>,
6521        dom: &DomInfo,
6522    ) -> Region {
6523        // Dominators rooted at the loop header (the incoming dom is rooted
6524        // at the enclosing walk entry and would misjudge inner loops).
6525        let dom_owned = compute_dominators(self.cfg, universe, header);
6526        let _dom = &dom_owned;
6527        // Loop body = blocks forward-reachable from the header without
6528        // re-entering it, EXCLUDING:
6529        // * blocks not dominated by the header,
6530        // * the header's own exiting branch (direct successor that cannot
6531        //   loop back),
6532        // * break-outer targets (cannot loop back and the branching block
6533        //   only escapes through them),
6534        // * blocks whose way back to the header requires an ENCLOSING loop's
6535        //   back edge (e.g. the outer increment block of nested loops).
6536        let mut barriers: HashSet<usize> = HashSet::new();
6537        for l in &self.loops_stack {
6538            barriers.insert(*l);
6539        }
6540        barriers.insert(header);
6541        // Exception successors (NOT filtered by `universe`): the handler that
6542        // loops back is carved out of this region's universe because it is
6543        // structured separately by structure_try, yet it is real control flow.
6544        // A protected block whose normal successors all return/throw can still
6545        // reach the header through that handler. Without these edges the
6546        // membership reachability below misclassifies the try head as a loop
6547        // EXIT and hoists the whole try/catch out of the loop body.
6548        let mut exc_succ: HashMap<usize, Vec<usize>> = HashMap::new();
6549        for e in &self.cfg.exc_edges {
6550            exc_succ.entry(e.from).or_default().push(e.to);
6551        }
6552        let mut members: HashSet<usize> = HashSet::new();
6553        members.insert(header);
6554        {
6555            let mut q: VecDeque<usize> = VecDeque::new();
6556            q.push_back(header);
6557            while let Some(b) = q.pop_front() {
6558                for &s in &self.cfg.blocks[b].succ {
6559                    let dbg_lmem = crate::dbg_flag!("JCDC_DBG_LMEM");
6560                    if barriers.contains(&s) && s != header {
6561                        // enclosing loop header: never absorb it
6562                        if dbg_lmem {
6563                            eprintln!("LMEM h={} from={} s={} why=barrier", header, b, s);
6564                        }
6565                        continue;
6566                    }
6567                    if s == header || !universe.contains(&s) || stop.contains(&s) {
6568                        if dbg_lmem {
6569                            eprintln!(
6570                                "LMEM h={} from={} s={} why=hdr{} univ{} stop{} hndlr={} succ={:?}",
6571                                header,
6572                                b,
6573                                s,
6574                                s == header,
6575                                universe.contains(&s),
6576                                stop.contains(&s),
6577                                self.is_handler(s),
6578                                self.cfg.blocks[s].succ
6579                            );
6580                        }
6581                        continue;
6582                    }
6583                    if !dom.dominates(header, s) {
6584                        if dbg_lmem {
6585                            eprintln!(
6586                                "LMEM h={} from={} s={} why=dom preds={:?}",
6587                                header, b, s, self.cfg.blocks[s].pred
6588                            );
6589                        }
6590                        continue;
6591                    }
6592                    let reaches =
6593                        can_reach_cfg_barred(self.cfg, &exc_succ, s, header, &barriers, 8192);
6594                    if dbg_lmem && !reaches {
6595                        eprintln!(
6596                            "LMEM h={} from={} s={} reaches=false succ={:?}",
6597                            header, b, s, self.cfg.blocks[s].succ
6598                        );
6599                    }
6600                    if !reaches {
6601                        // `s` cannot loop back on its own. It is the loop's
6602                        // exit merge when the header branches to it directly
6603                        // or some predecessor reaches it as its dedicated
6604                        // escape jump. A body-internal terminator tail
6605                        // (return/throw whose predecessor only falls into
6606                        // it) stays a member.
6607                        let preds = self.cfg.blocks[s].pred.clone();
6608                        // A pred OUTSIDE this walk's universe is a dedicated
6609                        // escape by definition: its flow belongs to the
6610                        // enclosing scope, so `s` is a shared merge the loop
6611                        // must not own. Without this, a copy_walk scope
6612                        // rooted at a branch target (entry 24, single succ =
6613                        // the loop header) makes the header dominate the
6614                        // shared tail, and every in-scope pred reaches the
6615                        // header around it -- the tail was absorbed as a
6616                        // member, exits emptied, and the post-loop
6617                        // `append('Z'); return true` landed INSIDE the
6618                        // while(true) (jdk11/17 DateTimeFormatterBuilder
6619                        // InstantPrinterParser.format: 缺少返回语句).
6620                        let mut exit_edge = preds.iter().any(|&p| {
6621                            p == header
6622                                || !universe.contains(&p)
6623                                || !can_reach_avoiding(self.cfg, &exc_succ, p, header, s, 8192)
6624                        });
6625                        if !exit_edge {
6626                            // Confluence of dedicated escape jumps: every
6627                            // predecessor also branches to another exit
6628                            // candidate that cannot reach the header.
6629                            exit_edge = !preds.is_empty()
6630                                && preds.iter().all(|&p| {
6631                                    self.cfg.blocks[p].succ.iter().any(|&q| {
6632                                        q != s
6633                                            && !can_reach_cfg_barred(
6634                                                self.cfg, &exc_succ, q, header, &barriers, 8192,
6635                                            )
6636                                    })
6637                                });
6638                        }
6639                        if exit_edge || preds.is_empty() {
6640                            // Last chance: a protected terminator tail. The
6641                            // JVM splits `try { return f(); }` into a call
6642                            // block (inside the range, has an exc edge) and a
6643                            // bare `return` block starting exactly at the
6644                            // range end (no exc edge of its own). Such a
6645                            // return is body-internal — keep it when its
6646                            // predecessors are already members sharing a try
6647                            // range. Without this, the in-loop try's arm
6648                            // region ends at the call block and the return is
6649                            // lost.
6650                            let protected_tail = self.cfg.blocks[s].succ.is_empty()
6651                                && !preds.is_empty()
6652                                && preds.iter().all(|&p| {
6653                                    members.contains(&p)
6654                                        && self.cfg.exc_ranges.iter().any(|r| {
6655                                            r.start <= self.cfg.blocks[p].start
6656                                                && self.cfg.blocks[p].start < r.end
6657                                                && self.cfg.blocks[s].start <= r.end
6658                                                && self.cfg.blocks[s].start
6659                                                    > self.cfg.blocks[p].start
6660                                        })
6661                                });
6662                            if !protected_tail {
6663                                continue;
6664                            }
6665                        }
6666                    }
6667                    if members.insert(s) {
6668                        q.push_back(s);
6669                    }
6670                }
6671            }
6672        }
6673        let mut exits: Vec<usize> = Vec::new();
6674        for &m in &members {
6675            for &s in &self.cfg.blocks[m].succ {
6676                if !members.contains(&s) && !exits.contains(&s) {
6677                    exits.push(s);
6678                }
6679            }
6680        }
6681        exits.sort_by_key(|e| self.cfg.blocks[*e].start);
6682
6683        let mut inner_stop: HashSet<usize> = stop
6684            .iter()
6685            .copied()
6686            .filter(|s| members.contains(s))
6687            .collect();
6688        inner_stop.extend(exits.iter().copied());
6689        // NOTE: header is NOT in inner_stop — the walk starts there and the
6690        // claimed-guard stops re-entry (back edges become Goto{header}).
6691        if crate::dbg_flag!("JCDC_DBG_IF") {
6692            eprintln!(
6693                "structure_loop header={} members={:?} exits={:?}",
6694                header, members, exits
6695            );
6696        }
6697        if crate::dbg_flag!("JCDC_DBG_LOOP") {
6698            eprintln!(
6699                "LOOP header={} members={:?} inner_stop={:?}",
6700                header, members, inner_stop
6701            );
6702        }
6703        claimed.insert(header);
6704        self.loops_stack.push(header);
6705        let mut body = self.walk(header, &members, &inner_stop, active, claimed, true);
6706        if crate::dbg_flag!("JCDC_DBG_LOOP") {
6707            eprintln!("LOOP body region: {:#?}", body);
6708        }
6709        // Walk-side twin of the SESE loop-site call: orphaned
6710        // statement-bearing exits materialized into the breaking arms
6711        // (jdk26 java.nio.Bits.reserveMemory's phase-1 success path —
6712        // `tryReserveOrClean` true falls to the interrupted-check +
6713        // return epilogue (blocks 16-18) which the bare-break rendering
6714        // dropped entirely: the success path RE-LOOPED, re-reserving
6715        // memory forever; javac can't see it, the walk path structures
6716        // this method, so the SESE-only pass never ran).
6717        if !crate::dbg_flag!("JCDC_NO_MATEXIT") {
6718            let natural: HashSet<usize> = self.cfg.blocks[header]
6719                .succ
6720                .iter()
6721                .copied()
6722                .filter(|x| !members.contains(x) && *x != header)
6723                .collect();
6724            let reach_all: HashSet<usize> = (0..self.cfg.blocks.len()).collect();
6725            let lh: HashSet<usize> = HashSet::new();
6726            self.materialize_content_exits(
6727                &mut body,
6728                header,
6729                &exits,
6730                &members,
6731                &natural,
6732                &members,
6733                &self.loops_stack.clone(),
6734                &lh,
6735                active,
6736                stop,
6737                &reach_all,
6738                claimed,
6739                false,
6740            );
6741        }
6742        self.loops_stack.pop();
6743        claimed.extend(members.iter().copied());
6744        Region::Loop {
6745            header,
6746            body: Box::new(body),
6747            members,
6748            exits,
6749        }
6750    }
6751
6752    pub(crate) fn structure_switch(
6753        &mut self,
6754        block: usize,
6755        selector: Expr,
6756        targets: &SwitchTargets,
6757        universe: &HashSet<usize>,
6758        stop: &HashSet<usize>,
6759        follow: Option<usize>,
6760        active: &[usize],
6761        claimed: &mut HashSet<usize>,
6762    ) -> Region {
6763        if crate::dbg_flag!("JCDC_DBG_GOTO") {
6764            eprintln!(
6765                "SWITCHENTRY block={} start={} follow={:?} universe={} stop={:?} claimed={:?}",
6766                block,
6767                self.cfg.blocks[block].start,
6768                follow,
6769                universe.len(),
6770                stop,
6771                claimed
6772            );
6773        }
6774        let (pairs, default_pc): (Vec<(i64, u32)>, u32) =
6775            match (targets, self.results.get(block).map(|r| &r.term)) {
6776                (
6777                    SwitchTargets::Table { .. },
6778                    Some(Term::Switch {
6779                        targets: SwitchTargets::Table { low, targets: ts },
6780                        default,
6781                        ..
6782                    }),
6783                ) => (
6784                    ts.iter()
6785                        .enumerate()
6786                        .map(|(i, &t)| (*low as i64 + i as i64, t))
6787                        .collect(),
6788                    default.unwrap_or(0),
6789                ),
6790                (
6791                    SwitchTargets::Lookup { .. },
6792                    Some(Term::Switch {
6793                        targets: SwitchTargets::Lookup { pairs: ps },
6794                        default,
6795                        ..
6796                    }),
6797                ) => (
6798                    ps.iter().map(|(m, t)| (*m as i64, *t)).collect(),
6799                    default.unwrap_or(0),
6800                ),
6801                _ => (Vec::new(), 0),
6802            };
6803        // If the default target is the switch follow (confluence), there is
6804        // no explicit default region — flow just continues after the switch.
6805        let default_block = self.cfg.block_at(default_pc).filter(|d| Some(*d) != follow);
6806
6807        let mut case_groups: Vec<(Vec<i64>, usize, bool)> = Vec::new(); // (vals, block, is_follow)
6808        for (v, t) in &pairs {
6809            let Some(tb) = self.cfg.block_at(*t) else {
6810                continue;
6811            };
6812            if Some(tb) == default_block {
6813                continue;
6814            }
6815            if Some(tb) == follow {
6816                // Case that jumps straight to the confluence: empty body + break.
6817                if let Some(g) = case_groups.iter_mut().find(|(_, gb, f)| *gb == tb && *f) {
6818                    g.0.push(*v);
6819                } else {
6820                    case_groups.push((vec![*v], tb, true));
6821                }
6822                continue;
6823            }
6824            if let Some(g) = case_groups.iter_mut().find(|(_, gb, f)| *gb == tb && !*f) {
6825                g.0.push(*v);
6826            } else {
6827                case_groups.push((vec![*v], tb, false));
6828            }
6829        }
6830        case_groups.sort_by_key(|(_, b, _)| self.cfg.blocks[*b].start);
6831
6832        // Java switch case groups fall through when their body completes
6833        // normally: an arm region whose flow does NOT provably land on the
6834        // switch follow (a dangling Goto to a claimed sibling block that
6835        // conversion elides or inlines without its jump — jdk17/26
6836        // AbstractValidatingLambdaMetafactory ctor: case-7's
6837        // `if (targetClass == implClass && isPrivate)` middle branch jumped
6838        // to the already-claimed `implKind = 7` block whose Goto{follow}
6839        // the RawGoto stmt-inline consumed, so the arm fell through into
6840        // case 6/8 and re-assigned the blank finals implClass/implKind/
6841        // implIsInstanceMethod — 可能已分配变量 ×3) gets an explicit
6842        // trailing Goto{follow}, which conversion materializes as `break`.
6843        // Arms that bind already (trailing Goto{follow}, terminators,
6844        // both-branch if binds) and abrupt arms are untouched; when no
6845        // follow is known the historical shape is kept.
6846        fn bind_arm(st: &Structurer, r: Region, follow: Option<usize>) -> Region {
6847            let Some(f) = follow else { return r };
6848            if st.arm_binds_to(&r, f) || region_terminates(&r, st.results) {
6849                return r;
6850            }
6851            // A nested switch whose groups are ALL abrupt cannot complete
6852            // normally by JLS 14.11.1 (only the LAST group's normal
6853            // completion and reachable unlabeled breaks count — arm_binds
6854            // _to's nested-switch rule is deliberately conservative and
6855            // says "not bindable" there): an appended break after it is an
6856            // 无法访问的语句 (jdk internal xml Parser's `switch (wsskip())
6857            // { case: continue; case: panic(); default: break L11; }`).
6858            // The arm already cannot fall through — append nothing.
6859            fn abrupt_switch_at_end(r: &Region, results: &[crate::ir::build::BlockResult]) -> bool {
6860                fn all_abrupt(rr: &Region, results: &[crate::ir::build::BlockResult]) -> bool {
6861                    match rr {
6862                        Region::Switch { cases, default, .. } => {
6863                            // JLS 14.11.1: a switch CANNOT complete normally
6864                            // when its LAST rendered group is abrupt (earlier
6865                            // groups either end abruptly or fall THROUGH into
6866                            // their successor, which is not normal completion
6867                            // of the switch) and no unlabeled break targets
6868                            // it — those surface as Goto{this switch's own
6869                            // follow}, which structure_switch never leaves
6870                            // dangling inside an arm. default renders last.
6871                            match default {
6872                                Some(d) => all_abrupt(d, results),
6873                                None => cases
6874                                    .last()
6875                                    .map(|c| all_abrupt(&c.1, results))
6876                                    .unwrap_or(false),
6877                            }
6878                        }
6879                        Region::Seq(v) => match v.last() {
6880                            Some(last) => all_abrupt(last, results),
6881                            None => false,
6882                        },
6883                        Region::If { then_r, else_r, .. } => {
6884                            all_abrupt(then_r, results) && all_abrupt(else_r, results)
6885                        }
6886                        Region::Loop { .. } => true,
6887                        Region::Goto { .. } => true,
6888                        Region::Basic { block } => {
6889                            matches!(results[*block].term, Term::Return(_) | Term::Throw(_))
6890                        }
6891                        Region::CopyStmts { .. } => false,
6892                        Region::Empty => false,
6893                        // Try and any other shape: conservatively NOT
6894                        // all-abrupt — the appended break stays reachable.
6895                        _ => false,
6896                    }
6897                }
6898                // ONLY a nested switch at the arm's end qualifies — an
6899                // arm ending in a bare Goto to a non-follow block is the
6900                // dangling-jump case the append exists for (the Goto is
6901                // elided/inlined at conversion, so the arm falls through
6902                // in the rendered layout despite looking abrupt here).
6903                match r {
6904                    Region::Seq(v) => {
6905                        matches!(v.last(), Some(Region::Switch { .. }) if all_abrupt(v.last().unwrap(), results))
6906                    }
6907                    Region::Switch { .. } => all_abrupt(r, results),
6908                    _ => false,
6909                }
6910            }
6911            if abrupt_switch_at_end(&r, st.results) {
6912                return r;
6913            }
6914            Region::Seq(vec![r, Region::Goto { target: f }])
6915        }
6916        let mut case_stop = stop.clone();
6917        if let Some(f) = follow {
6918            case_stop.insert(f);
6919        }
6920        for (_, b, is_follow) in &case_groups {
6921            if !is_follow {
6922                case_stop.insert(*b);
6923            }
6924        }
6925        if let Some(d) = default_block {
6926            case_stop.insert(d);
6927        }
6928
6929        // All case/default head blocks: a trailing Goto to one of them is a
6930        // switch fallthrough (no statement in Java).
6931        let mut head_set: HashSet<usize> = HashSet::new();
6932        for (_, b, is_follow) in &case_groups {
6933            if !is_follow {
6934                head_set.insert(*b);
6935            }
6936        }
6937        if let Some(d) = default_block {
6938            head_set.insert(d);
6939        }
6940
6941        let mut cases = Vec::new();
6942        // Java 21+ pattern switch: the restoration pass re-appends each
6943        // labelled case's terminal `break;` — a case arm ending at a
6944        // nested switch lands on THIS switch's follow (see case_arm_ctx).
6945        let is_pattern = matches!(&selector, Expr::Invokedynamic { name, args, .. }
6946            if name == "typeSwitch" && !args.is_empty());
6947        for (vals, b, is_follow) in case_groups {
6948            if is_follow {
6949                // Empty case that breaks out to the confluence.
6950                cases.push((vals, Region::Goto { target: b }));
6951                continue;
6952            }
6953            if !universe.contains(&b) || claimed.contains(&b) {
6954                // A case jumping into an already-structured terminator tail
6955                // gets its own copy (per-case epilogues are javac's normal
6956                // return-path duplication).
6957                let copied = if !stop.contains(&b) && !self.loops_stack.contains(&b) {
6958                    let mut cstop = case_stop.clone();
6959                    cstop.remove(&b);
6960                    self.copy_walk(b, &cstop, active, block)
6961                } else {
6962                    None
6963                };
6964                if crate::dbg_flag!("JCDC_DBG_GOTO") {
6965                    eprintln!(
6966                        "SWCASE b={} univ={} claimed={} stop={} follow={:?} loops={:?} copied={}",
6967                        b,
6968                        universe.contains(&b),
6969                        claimed.contains(&b),
6970                        case_stop.contains(&b),
6971                        follow,
6972                        self.loops_stack,
6973                        copied.is_some()
6974                    );
6975                }
6976                cases.push((vals, copied.unwrap_or(Region::Goto { target: b })));
6977                continue;
6978            }
6979            // The case's own head must not be in its stop set.
6980            let mut cstop = case_stop.clone();
6981            cstop.remove(&b);
6982            let sub = self.sub_scope(b, universe, &cstop, claimed);
6983            self.case_arm_ctx.push((b, follow, is_pattern));
6984            let r = self.walk(b, &sub, &cstop, active, claimed, false);
6985            self.case_arm_ctx.pop();
6986            let r = strip_fallthrough_goto(r, &head_set);
6987            cases.push((vals, bind_arm(self, r, follow)));
6988        }
6989        let default = default_block.map(|d| {
6990            if !universe.contains(&d) || claimed.contains(&d) {
6991                let copied = if !stop.contains(&d) && !self.loops_stack.contains(&d) {
6992                    let mut cstop = case_stop.clone();
6993                    cstop.remove(&d);
6994                    self.copy_walk(d, &cstop, active, block)
6995                } else {
6996                    None
6997                };
6998                return Box::new(copied.unwrap_or(Region::Goto { target: d }));
6999            }
7000            let mut cstop = case_stop.clone();
7001            cstop.remove(&d);
7002            let sub = reachable_within(self.cfg, d, &cstop);
7003            self.case_arm_ctx.push((d, follow, is_pattern));
7004            let r = self.walk(d, &sub, &cstop, active, claimed, false);
7005            self.case_arm_ctx.pop();
7006            Box::new(bind_arm(self, strip_fallthrough_goto(r, &head_set), follow))
7007        });
7008        Region::Switch {
7009            block,
7010            selector,
7011            cases,
7012            default,
7013            follow,
7014        }
7015    }
7016
7017    pub(crate) fn structure_try(
7018        &mut self,
7019        gi: usize,
7020        universe: &HashSet<usize>,
7021        outer_universe: &HashSet<usize>,
7022        claimed: &mut HashSet<usize>,
7023        stop: &HashSet<usize>,
7024    ) -> Region {
7025        let g = self.groups[gi].clone();
7026        self.structuring_groups.borrow_mut().push(gi);
7027        let nested: Vec<usize> = (0..self.groups.len())
7028            .filter(|&j| j != gi && self.groups[j].start >= g.start && self.groups[j].end <= g.end)
7029            .collect();
7030        // Body universe includes nested groups' blocks so the walk reaches
7031        // their start and carves out inner Try regions.
7032        let body_universe: HashSet<usize> = universe
7033            .iter()
7034            .copied()
7035            .filter(|b| match self.body_group.get(b) {
7036                Some(ogi) => *ogi == gi || nested.contains(ogi),
7037                None => false,
7038            })
7039            .collect();
7040        if crate::dbg_flag!("JCDC_DBG_IF") {
7041            eprintln!(
7042                "structure_try gi={} span=({},{}) body_universe={:?} nested={:?}",
7043                gi, g.start, g.end, body_universe, nested
7044            );
7045        }
7046        // javac often excludes the final `areturn` from the protected span
7047        // (it cannot throw). When that trailing terminator's only entry is
7048        // the protected flow, it semantically belongs to the try body —
7049        // absorb it so `return f();` stays inside the try and the catches
7050        // remain legal.
7051        let mut body_universe = body_universe;
7052        if let Some(cb) = self.cfg.block_at(g.end) {
7053            // The absorbing tail must belong to THIS group's flow: a
7054            // loop-exit block parked at exactly g.end (jdk26
7055            // Future.resultNow's normal-path finally-if at pc 28, the
7056            // retry loop's break target) has the protected block as its
7057            // only pred but continues to the post-loop tail — absorbing it
7058            // hides the exit from the enclosing loop's continuation
7059            // (exits/natural_follow lose it) and strands `interrupt();
7060            // return result;` (缺少返回语句). Preds may be body blocks,
7061            // this group's handler heads, or handler-flow-only blocks
7062            // (the pending-rethrow copy shapes). Do NOT gate on
7063            // !stop.contains(cb): a shared return tail that is ALSO an
7064            // enclosing loop's exit must still absorb when its preds are
7065            // all group flow — refusing left ReflectionFactory
7066            // .getReplaceResolveForSerialization's copied inner-try
7067            // return as a Goto that resolved to `break` (the
7068            // IllegalAccessException catch lost its throwing call).
7069            // Future's exit block is ruled out by the pred test alone
7070            // (its succs continue past the group, so it is not a
7071            // terminator).
7072            let hf_own = self.handler_flow_only(gi);
7073            let pred_in_group_flow = |p: &usize| {
7074                self.body_group.get(p) == Some(&gi)
7075                    || self.handler_group.get(p) == Some(&gi)
7076                    || hf_own.contains(p)
7077                    || body_universe.contains(p)
7078            };
7079            if !body_universe.contains(&cb)
7080                && self.is_terminator_block(cb)
7081                && self.results[cb].stmts.is_empty()
7082                && !self.handler_group.contains_key(&cb)
7083                && !self.cfg.blocks[cb].pred.is_empty()
7084                && self.cfg.blocks[cb].pred.iter().all(pred_in_group_flow)
7085            {
7086                body_universe.insert(cb);
7087            }
7088        }
7089        // A protected VALUE PUSH whose consumer sits just past the span
7090        // must stay inside the try: javac excludes the monitorexit+areturn
7091        // tail from the protected range (jdk26 ZipFile.getComment's
7092        // `return zipCoder.toString(comment)`), so the body block ends in
7093        // Fallthrough with the value on the out-stack and the Return that
7094        // names the call lives outside — converting that split either
7095        // empties the try (checked catch illegal) or emits the throwing
7096        // call UNPROTECTED. Absorb the linear statements-free fallthrough
7097        // chain plus the single Return consumer. The return only runs
7098        // after the chain completes, so it is exception-equivalent to the
7099        // protected call; MonitorExit markers ride into the body where
7100        // reconstruct_synchronized strips them again.
7101        {
7102            let entry_blk = self
7103                .cfg
7104                .blocks
7105                .iter()
7106                .find(|b| b.start == g.start && body_universe.contains(&b.id))
7107                .map(|b| b.id);
7108            if let Some(eb) = entry_blk {
7109                let spans = |gi: usize, og: usize| {
7110                    self.groups[og].start <= self.groups[gi].start
7111                        && self.groups[og].end >= self.groups[gi].end
7112                };
7113                let ownable = |n: usize| match self.body_group.get(&n) {
7114                    None => true,
7115                    Some(&og) => {
7116                        og == gi
7117                            || (self.groups[og].start >= self.groups[gi].start
7118                                && self.groups[og].end <= self.groups[gi].end)
7119                            || spans(gi, og)
7120                    }
7121                };
7122                if matches!(self.results[eb].term, Term::Fallthrough)
7123                    && self.results[eb].out_stack.len() == 1
7124                {
7125                    let mut x = eb;
7126                    for _ in 0..4 {
7127                        let succs = self.cfg.blocks[x].succ.clone();
7128                        if succs.len() != 1 {
7129                            break;
7130                        }
7131                        let n = succs[0];
7132                        if body_universe.contains(&n)
7133                            || self.handler_group.contains_key(&n)
7134                            || !ownable(n)
7135                        {
7136                            break;
7137                        }
7138                        let r = &self.results[n];
7139                        // Monitor markers ride along (they are stripped by
7140                        // reconstruct_synchronized); any OTHER statement
7141                        // stops the chain — absorbing real code past the
7142                        // span would newly protect it (a checked catch
7143                        // would become illegal).
7144                        if !r.stmts.is_empty()
7145                            && !r.stmts.iter().all(|s| {
7146                                matches!(
7147                                    s,
7148                                    crate::ir::stmt::Stmt::MonitorExit(_)
7149                                        | crate::ir::stmt::Stmt::MonitorEnter(_)
7150                                )
7151                            })
7152                        {
7153                            break;
7154                        }
7155                        match &r.term {
7156                            Term::Fallthrough => {
7157                                body_universe.insert(n);
7158                                x = n;
7159                            }
7160                            Term::Return(_) => {
7161                                body_universe.insert(n);
7162                                break;
7163                            }
7164                            _ => break,
7165                        }
7166                    }
7167                }
7168            }
7169        }
7170        let mut outer_cont_some = false;
7171        let body = match self.cfg.block_at(g.start) {
7172            Some(entry) if body_universe.contains(&entry) => {
7173                // A group whose protected span starts AT an enclosing
7174                // loop's header arrives here with the header already
7175                // claimed (structure_loop / the SESE loop branch claim it
7176                // before walking the body, and the group-yield filter
7177                // deliberately lets the body walk re-find the group at
7178                // its start). The body walk must then tolerate the
7179                // claimed entry like structure_loop does — refusing it
7180                // collapsed the whole protected body into the back-edge
7181                // Goto and the try emitted as `try { continue; }`
7182                // (jdk11 HttpURLConnection$ErrorStream.getErrorStream:
7183                // the `len = is.read(..)` do-while body lost —
7184                // 在相应的try语句主体中不能抛出异常错误SocketTimeoutException).
7185                let allow = claimed.contains(&entry);
7186                let mut r = self.walk(
7187                    entry,
7188                    &body_universe,
7189                    &HashSet::new(),
7190                    &nested,
7191                    claimed,
7192                    allow,
7193                );
7194                // Flow leaving the try body to the post-try continuation is
7195                // natural fallthrough (the outer walk picks it up there).
7196                let cont = self
7197                    .continuation_after(g.end, outer_universe, claimed, stop)
7198                    .filter(|c| {
7199                        !self.handler_flow_only(gi).contains(c) && !body_universe.contains(c)
7200                    });
7201                outer_cont_some = cont.is_some();
7202                if crate::dbg_flag!("JCDC_DBG_IF") {
7203                    eprintln!("try gi={} cont={:?} universe_has_blocks_after_end={} outer_universe={:?} stop={:?}", gi, cont,
7204                        universe.iter().filter(|b| self.cfg.blocks[**b].start >= g.end).count(),
7205                        outer_universe, stop);
7206                }
7207                if let Some(c) = cont {
7208                    self.strip_trailing_goto_to(&mut r, c);
7209                }
7210                r
7211            }
7212            _ => Region::Empty,
7213        };
7214
7215        // Merge multi-catch: consecutive handlers with the same handler block
7216        // become one catch with multiple types.
7217        let mut merged_handlers: Vec<(Vec<String>, u32, usize)> = Vec::new(); // types, hpc, hb
7218        for (hpc, ty) in &g.handlers {
7219            let Some(hb) = self.cfg.block_at(*hpc) else {
7220                continue;
7221            };
7222            if self.handler_group.get(&hb) != Some(&gi) {
7223                continue;
7224            }
7225            if let Some(last) = merged_handlers.last_mut() {
7226                if last.2 == hb {
7227                    if let Some(t) = ty {
7228                        last.0.push(t.clone());
7229                    }
7230                    continue;
7231                }
7232            }
7233            merged_handlers.push((ty.clone().into_iter().collect(), *hpc, hb));
7234        }
7235        let mut catches = Vec::new();
7236        for (tys, _hpc, hb) in &merged_handlers {
7237            let mut hstop: HashSet<usize> = HashSet::new();
7238            for b in universe.iter().copied() {
7239                if self.body_group.get(&b) == Some(&gi) {
7240                    hstop.insert(b);
7241                }
7242            }
7243            for (h2, _) in &g.handlers {
7244                if let Some(hb2) = self.cfg.block_at(*h2) {
7245                    if hb2 != *hb {
7246                        hstop.insert(hb2);
7247                    }
7248                }
7249            }
7250            let mut huniverse = reachable_within(self.cfg, *hb, &hstop);
7251            if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7252                eprintln!(
7253                    "HUNIV0 gi={} hb={} hstop={:?} reach={:?}",
7254                    gi,
7255                    hb,
7256                    {
7257                        let mut v: Vec<usize> = hstop.iter().copied().collect();
7258                        v.sort();
7259                        v
7260                    },
7261                    {
7262                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7263                        v.sort();
7264                        v
7265                    }
7266                );
7267            }
7268            // Keep the handler walk out of try-body blocks that start before
7269            // this group's end (real protected code), and out of other
7270            // handlers' heads. Blocks past the group end may legitimately
7271            // belong to an enclosing group's span while being part of this
7272            // handler's flow (nested try-with-resources).
7273            huniverse.retain(|b| {
7274                let body_before_end = self
7275                    .body_group
7276                    .get(b)
7277                    .map(|_ogi| self.cfg.blocks[*b].start < g.end)
7278                    .unwrap_or(false);
7279                !body_before_end && (!self.handler_group.contains_key(b) || *b == *hb)
7280            });
7281            // Also exclude the post-try continuation flow: blocks reachable
7282            // from the group's end are shared with the normal path and must
7283            // not be absorbed into the handler. `block_at(g.end)` misses the
7284            // merge when the handler itself sits at the span end (javac
7285            // excludes the try body's trailing return from the protected
7286            // range: jdk26 AlgorithmId.getName span (27,62), areturn at 62,
7287            // handler astore at 63 -> block_at(62) resolves to the areturn,
7288            // and the `goto merge` tail from 63 drags the WHOLE shared
7289            // `if (o != null) return o.stdName(); else ...` merge into the
7290            // catch, leaving every other path without its return --
7291            // 缺少返回语句). Gate on the merge shape directly: an unclaimed
7292            // non-group block whose normal preds are already claimed by the
7293            // surrounding flow is the post-try merge -- the handler reaches
7294            // it only by jumping out, which strip_handler_exit_goto already
7295            // renders as the natural fallthrough.
7296            let hf = self.handler_flow_only(gi);
7297            // An enclosing loop's BACKEDGE STUB (a Goto block whose
7298            // single target is/leads to an open loop header — the
7299            // handler's `dataStream.reset(); goto head` continue tail)
7300            // is NOT a post-try merge: stripping it from the handler
7301            // universe leaves the handler's branch arms dangling
7302            // (Region::Empty) and the flow re-copies the whole loop body
7303            // into the catch (jdk11 KeyStore.getInstance(File)'s
7304            // IOException catch swallowed the provider loop with an
7305            // unprotected Security.getImpl — 未报告的异常错误
7306            // NoSuchProviderException; regression of the loop-header
7307            // circulation guard exposing this path).
7308            let backedge_stub = |b: usize| -> bool {
7309                if !matches!(self.results[b].term, Term::Goto) {
7310                    return false;
7311                }
7312                let succs = &self.cfg.blocks[b].succ;
7313                if succs.len() != 1 {
7314                    return false;
7315                }
7316                let t = succs[0];
7317                self.loops_stack
7318                    .iter()
7319                    .chain(self.sese_loop_headers.iter())
7320                    .any(|&h| h == t || self.is_stmt_free_chain_to_block(t, h))
7321            };
7322            let mut shared_merge: Vec<usize> = huniverse
7323                .iter()
7324                .copied()
7325                .filter(|b| {
7326                    *b != *hb
7327                        && !hf.contains(b)
7328                        && !claimed.contains(b)
7329                        && !backedge_stub(*b)
7330                        && self.body_group.get(b) != Some(&gi)
7331                        && self.handler_group.get(b) != Some(&gi)
7332                        && self.cfg.blocks[*b].pred.iter().any(|p| {
7333                            claimed.contains(p)
7334                                && !self.handler_group.contains_key(p)
7335                                && self.body_group.get(p) != Some(&gi)
7336                        })
7337                })
7338                .collect();
7339            // Deterministic pick: huniverse is a HashSet whose iteration
7340            // order varies per process, and shared_merge[0] selects the
7341            // post-try tail to strip. Without the sort, jdk8
7342            // JMXConnectorFactory.connect flip-flopped between the
7343            // correct nested-if shape and a broken else-if chain that
7344            // trapped the shared getProvider tail inside the IOException
7345            // catch (losing it on the null-classloader and normal paths).
7346            // Earliest-pc first: it is the flow confluence, and its
7347            // reachable tail is the superset covering later candidates.
7348            shared_merge.sort_by_key(|b| self.cfg.blocks[*b].start);
7349            if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7350                eprintln!(
7351                    "HUNIV1 gi={} hb={} shared_merge={:?} huniverse={:?}",
7352                    gi,
7353                    hb,
7354                    shared_merge,
7355                    {
7356                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7357                        v.sort();
7358                        v
7359                    }
7360                );
7361            }
7362            if !shared_merge.is_empty() {
7363                let tail = reachable_within(self.cfg, shared_merge[0], &HashSet::new());
7364                huniverse.retain(|b| !tail.contains(b) || *b == *hb || hf.contains(b));
7365                if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7366                    eprintln!("HUNIV2 gi={} after-strip={:?}", gi, {
7367                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7368                        v.sort();
7369                        v
7370                    });
7371                }
7372            }
7373            // The cont-tail exclusion must not fire when the block at the
7374            // span end IS the handler flow (javac excludes the body's
7375            // trailing return from the span, so the handler entry sits AT
7376            // g.end: jdk11 AQS.acquireQueued span (2,57) merged from
7377            // (2,37)+(38,57), handler at 57). Excluding reachable(handler)
7378            // there strips the handler's OWN tail (`selfInterrupt();
7379            // throw t;`) out of its universe -- the catch loses it and the
7380            // outer continuation emits it as a top-level sibling
7381            // (未报告的异常错误Throwable).
7382            if let Some(cont) = self.cfg.block_at(g.end) {
7383                if cont == *hb {
7384                    // The handler entry sits AT the span end (merged spans,
7385                    // javac excluding the body's trailing terminator). Keep
7386                    // the handler's PRIVATE tail — successors owned by no
7387                    // group and no other group's handler (AQS.acquireQueued
7388                    // `if (interrupted) selfInterrupt(); throw t;`) — and
7389                    // strip the rest: successors OWNED by an enclosing try
7390                    // are the shared post-try flow / inline finally copies
7391                    // (feat Exceptions nestedTry: the try body always
7392                    // throws, so the d/e/g/return chain is structurally
7393                    // handler-only but semantically the continuation —
7394                    // swallowing it into the catch snapshotted the return
7395                    // before the real finally g ran, output lost its g).
7396                    // Private-tail keep set: successors not owned by
7397                    // ANOTHER group (nor another group's handler) whose
7398                    // preds stay within the handler's own flow. Only for
7399                    // SOLE-ENTRY handlers (the body always completes
7400                    // abruptly — nestedTry); a multi-entry handler's
7401                    // reachable set mixes in the genuine shared
7402                    // continuation and stripping it starves the post-try
7403                    // walk (jdk Module TWR).
7404
7405                    // Body-flow reachability per enclosing group: a block
7406                    // owned by group og that og's own body flow REACHES
7407                    // (from its span-start block, through blocks it owns,
7408                    // without stepping on handler heads) will be re-emitted
7409                    // by og's body walk — stripping it from this handler's
7410                    // universe is safe (feat Exceptions nestedTry: the
7411                    // d/e/g continuation chain is gi=0 body flow). A block
7412                    // merely GEOGRAPHICALLY inside og's span but reachable
7413                    // only through handler flow is this handler's private
7414                    // tail — stripping loses it entirely (jdk11
7415                    // ServerSocketAdaptor.accept: the catch(Exception)'s
7416                    // assert-throw / `return sc.socket()` tail sits inside
7417                    // the enclosing sync group's span but its body flow
7418                    // never reaches it — stripped, the catch fell off the
7419                    // method end: 缺少返回语句).
7420                    let mut og_body_reach: HashMap<usize, HashSet<usize>> = HashMap::new();
7421                    {
7422                        let mut by_og: HashMap<usize, Vec<usize>> = HashMap::new();
7423                        for (b, og) in self.body_group.iter() {
7424                            by_og.entry(*og).or_default().push(*b);
7425                        }
7426                        for (og, _) in by_og {
7427                            // Normal-flow reachability from the group's
7428                            // body entry, bounded by the group's span and
7429                            // barred at handler heads: nested groups'
7430                            // blocks lie on the body flow (the walk
7431                            // carves them out but continues after), so
7432                            // the BFS must pass through them.
7433                            let g = &self.groups[og];
7434                            let mut reach: HashSet<usize> = HashSet::new();
7435                            if let Some(entry) = self.cfg.block_at(g.start) {
7436                                let mut q: VecDeque<usize> = VecDeque::new();
7437                                q.push_back(entry);
7438                                reach.insert(entry);
7439                                // Groups carved out of this span: the
7440                                // body walk resumes at their post-try
7441                                // continuation, so the BFS hops the same
7442                                // way — BUT never onto handler-flow-only
7443                                // blocks of THIS structure_try's group
7444                                // (accept's gi=0 body cont would land on
7445                                // the catch's private assert-throw tail;
7446                                // the hf_after filter skips it there, and
7447                                // letting the BFS see it would mark the
7448                                // tail re-emittable and strip it).
7449                                let mut conts: HashMap<usize, usize> = HashMap::new();
7450                                for (j, gj) in self.groups.iter().enumerate() {
7451                                    if j == og {
7452                                        continue;
7453                                    }
7454                                    if gj.start < g.start || gj.end > g.end.max(g.start + 1) {
7455                                        continue;
7456                                    }
7457                                    // The exclusion set must be the
7458                                    // CARVED group's own hf (that is what
7459                                    // the body walk's continuation_after
7460                                    // filter consults), not this
7461                                    // structure_try's hf.
7462                                    let hf_j = self.handler_flow_only(j);
7463                                    let mut cont: Option<usize> = None;
7464                                    for nb in self.cfg.blocks.iter() {
7465                                        if nb.start < gj.end || nb.ins_len == 0 {
7466                                            continue;
7467                                        }
7468                                        if self.handler_group.contains_key(&nb.id) {
7469                                            continue;
7470                                        }
7471                                        if hf_j.contains(&nb.id) {
7472                                            continue;
7473                                        }
7474                                        cont = Some(nb.id);
7475                                        break;
7476                                    }
7477                                    if let Some(c) = cont {
7478                                        for nb in self.cfg.blocks.iter() {
7479                                            if nb.ins_len != 0
7480                                                && nb.start >= g.start
7481                                                && nb.end <= gj.end.max(gj.start + 1)
7482                                                && !self.handler_group.contains_key(&nb.id)
7483                                            {
7484                                                conts.entry(nb.id).or_insert(c);
7485                                            }
7486                                        }
7487                                    }
7488                                }
7489                                while let Some(x) = q.pop_front() {
7490                                    let mut edges: Vec<usize> = self.cfg.blocks[x].succ.clone();
7491                                    if let Some(&c) = conts.get(&x) {
7492                                        edges.push(c);
7493                                    }
7494                                    for sx in edges {
7495                                        let sb = &self.cfg.blocks[sx];
7496                                        if sb.start >= g.start
7497                                            && sb.end <= g.end.max(g.start + 1)
7498                                            && !self.handler_group.contains_key(&sx)
7499                                            && reach.insert(sx)
7500                                        {
7501                                            q.push_back(sx);
7502                                        }
7503                                    }
7504                                }
7505                            }
7506                            og_body_reach.insert(og, reach);
7507                        }
7508                    }
7509                    let mut private: HashSet<usize> = HashSet::new();
7510                    {
7511                        let mut pq: VecDeque<usize> = VecDeque::new();
7512                        for &s0 in &self.cfg.blocks[*hb].succ {
7513                            pq.push_back(s0);
7514                        }
7515                        while let Some(x) = pq.pop_front() {
7516                            if private.contains(&x) || x == *hb {
7517                                continue;
7518                            }
7519                            match self.body_group.get(&x) {
7520                                Some(og) if *og != gi => {
7521                                    // Owned by another group: private only
7522                                    // when that group's body flow cannot
7523                                    // re-emit it.
7524                                    if og_body_reach
7525                                        .get(og)
7526                                        .map(|r| r.contains(&x))
7527                                        .unwrap_or(false)
7528                                    {
7529                                        continue;
7530                                    }
7531                                }
7532                                _ => {}
7533                            }
7534                            if self
7535                                .handler_group
7536                                .get(&x)
7537                                .map(|og| *og != gi)
7538                                .unwrap_or(false)
7539                            {
7540                                continue;
7541                            }
7542                            if self.cfg.blocks[x].pred.iter().all(|p| {
7543                                *p == *hb
7544                                    || private.contains(p)
7545                                    || self.handler_group.get(p) == Some(&gi)
7546                            }) {
7547                                private.insert(x);
7548                                for &s1 in &self.cfg.blocks[x].succ {
7549                                    pq.push_back(s1);
7550                                }
7551                            }
7552                        }
7553                    }
7554                    let sole_entry_hb = self.cfg.blocks[*hb]
7555                        .pred
7556                        .iter()
7557                        .all(|p| self.handler_group.contains_key(p));
7558                    let tail = reachable_within(self.cfg, cont, &HashSet::new());
7559                    if sole_entry_hb {
7560                        huniverse.retain(|b| !tail.contains(b) || private.contains(b));
7561                    }
7562                    // Multi-entry cont==hb: no strip (the f4472a5f shape —
7563                    // the shared_merge machinery and outer walks handle it).
7564                } else if outer_cont_some
7565                    && !self.handler_group.contains_key(&cont)
7566                    && !hf.contains(&cont)
7567                {
7568                    // Strip the span-end block's reachable tail from the
7569                    // handler universe ONLY when the outer walk will emit
7570                    // a post-try continuation: the tail is shared with
7571                    // the normal path then. When cont is None (every
7572                    // block past the span end is already claimed — the
7573                    // body walk's follow chain absorbed the span-end
7574                    // goto and the method-final tail), stripping orphans
7575                    // the tail entirely: SSLSessionContextImpl
7576                    // getDefaults' body walk claimed the span-end
7577                    // `goto 234` (pc 205) and the final `return 20480`
7578                    // (pc 234); the catch-else arm's term-copy route was
7579                    // the only emitter left, and the strip starved it —
7580                    // 缺少返回语句, sj17 walk-only since HEAD (SESE renders
7581                    // the tail; javac masked it behind OCSP's DA error
7582                    // until the rejection suite fixed that).
7583                    let tail = reachable_within(self.cfg, cont, &HashSet::new());
7584                    huniverse.retain(|b| !tail.contains(b) || hf.contains(b));
7585                }
7586            }
7587            huniverse.insert(*hb);
7588            // Exception groups nested inside the handler region (e.g. a
7589            // synchronized block within a catch) must be visible to the
7590            // handler walk so they get carved out as their own Try regions.
7591            let hmin = huniverse
7592                .iter()
7593                .map(|b| self.cfg.blocks[*b].start)
7594                .min()
7595                .unwrap_or(u32::MAX);
7596            let hmax = huniverse
7597                .iter()
7598                .map(|b| self.cfg.blocks[*b].end)
7599                .max()
7600                .unwrap_or(0);
7601            let h_active: Vec<usize> = (0..self.groups.len())
7602                .filter(|&j| {
7603                    if j == gi {
7604                        return false;
7605                    }
7606                    let gj = &self.groups[j];
7607                    if gj.start < hmin || gj.end > hmax {
7608                        return false;
7609                    }
7610                    // Handler self-protection ranges (the handler guards its
7611                    // own monitorexit) are not separate regions.
7612                    !gj.handlers.iter().all(|(h, _)| *h == gj.start)
7613                })
7614                .collect();
7615            let mut r = self.walk(*hb, &huniverse, &HashSet::new(), &h_active, claimed, false);
7616            // Handler exits into the post-try flow: any forward target that
7617            // is not part of a try body is a natural merge (the outer walk
7618            // emits those blocks after this region).
7619            self.strip_handler_exit_goto(&mut r, g.end);
7620            catches.push((tys.clone(), *hb, Box::new(r)));
7621        }
7622        self.structuring_groups.borrow_mut().retain(|&x| x != gi);
7623        Region::Try {
7624            group_idx: gi,
7625            body: Box::new(body),
7626            catches,
7627        }
7628    }
7629}
7630
7631/// Reachability from `from` to `to` via normal edges, with a work budget.
7632pub fn can_reach_cfg(cfg: &Cfg, from: usize, to: usize, budget: usize) -> bool {
7633    if from == to {
7634        return true;
7635    }
7636    let mut seen = std::collections::HashSet::new();
7637    let mut q = std::collections::VecDeque::new();
7638    q.push_back(from);
7639    seen.insert(from);
7640    let mut n = 0;
7641    while let Some(b) = q.pop_front() {
7642        n += 1;
7643        if n > budget {
7644            return false;
7645        }
7646        for &s in &cfg.blocks[b].succ {
7647            if s == to {
7648                return true;
7649            }
7650            if seen.insert(s) {
7651                q.push_back(s);
7652            }
7653        }
7654    }
7655    false
7656}
7657
7658/// True if `to` is reachable from `from` without stepping on `avoid`.
7659pub fn can_reach_avoiding(
7660    cfg: &Cfg,
7661    exc_succ: &HashMap<usize, Vec<usize>>,
7662    from: usize,
7663    to: usize,
7664    avoid: usize,
7665    budget: usize,
7666) -> bool {
7667    if from == to {
7668        return true;
7669    }
7670    let mut seen = std::collections::HashSet::new();
7671    let mut q = std::collections::VecDeque::new();
7672    if from != avoid {
7673        q.push_back(from);
7674        seen.insert(from);
7675    }
7676    let mut n = 0;
7677    while let Some(b) = q.pop_front() {
7678        n += 1;
7679        if n > budget {
7680            return false;
7681        }
7682        for &s in &cfg.blocks[b].succ {
7683            if s == to {
7684                return true;
7685            }
7686            if s != avoid && seen.insert(s) {
7687                q.push_back(s);
7688            }
7689        }
7690        if let Some(xs) = exc_succ.get(&b) {
7691            for &s in xs {
7692                if s == to {
7693                    return true;
7694                }
7695                if s != avoid && seen.insert(s) {
7696                    q.push_back(s);
7697                }
7698            }
7699        }
7700    }
7701    false
7702}
7703
7704/// Reachability from `from` to `to` that never steps on barrier blocks
7705/// (other than `from` itself). Follows normal AND exception successors
7706/// (`exc_succ`), so a protected block whose normal exits all return/throw is
7707/// still seen to loop back through its handler.
7708pub fn can_reach_cfg_barred(
7709    cfg: &Cfg,
7710    exc_succ: &HashMap<usize, Vec<usize>>,
7711    from: usize,
7712    to: usize,
7713    barriers: &HashSet<usize>,
7714    budget: usize,
7715) -> bool {
7716    if from == to {
7717        return true;
7718    }
7719    let mut seen = std::collections::HashSet::new();
7720    let mut q = std::collections::VecDeque::new();
7721    q.push_back(from);
7722    seen.insert(from);
7723    let mut n = 0;
7724    while let Some(b) = q.pop_front() {
7725        n += 1;
7726        if n > budget {
7727            return false;
7728        }
7729        for &s in &cfg.blocks[b].succ {
7730            if s == to {
7731                return true;
7732            }
7733            if !barriers.contains(&s) && seen.insert(s) {
7734                q.push_back(s);
7735            }
7736        }
7737        if let Some(xs) = exc_succ.get(&b) {
7738            for &s in xs {
7739                if s == to {
7740                    return true;
7741                }
7742                if !barriers.contains(&s) && seen.insert(s) {
7743                    q.push_back(s);
7744                }
7745            }
7746        }
7747    }
7748    false
7749}