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