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