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