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        // Zero-clone form: the old code cloned the whole universe TWICE
2490        // (`eff` then `seed`) per call — this runs per arm walk and the
2491        // clones dominated structurer CPU on the large corpora. Membership
2492        // at the retain is `universe ∪ {from} ∪ expansions`, with only the
2493        // (small) group-span EXPANSIONS materialized.
2494        let mut eff: HashSet<usize> = HashSet::default();
2495        // The walk root itself seeds the group expansion even when it lies
2496        // OUTSIDE the passed universe: a protected branch target from a
2497        // smaller group's body walk (jdk11 SocketChannelImpl.finishConnect's
2498        // `if (!isConnected())` then-arm — block pc 39 belongs to the
2499        // readLock/writeLock finally groups (39..174)/(39..167)/(46..105)
2500        // but the If sits inside group (14..23), whose body universe is
2501        // {2,3}; owner_scope then filtered everything and the expansion
2502        // loop had nothing to key off) got sub = {from} alone — the arm
2503        // emitted Goto{pc 46} and the ENTIRE connect-retry body (loop,
2504        // endFinishConnect, return true) vanished, leaving the
2505        // catch(IOException) try body unable to throw (不能抛出异常错误) and
2506        // the blocking/connected locals dangling. The holds_start guard
2507        // still blocks mid-group roots from ballooning (getInputStream0).
2508        for b in universe.iter().copied().chain(std::iter::once(from)) {
2509            if let Some(&gi) = self.body_group.get(&b) {
2510                let g = &self.groups[gi];
2511                // Expand to the full span ONLY when the scope holds the
2512                // group's START block: a walk rooted mid-group (a
2513                // handler-restricted arm at a single continuation block,
2514                // jdk11 getInputStream0's RT-catch arm -> block 33) must
2515                // not balloon to the whole enclosing span — it dragged the
2516                // entire method body into the catch with the handler's
2517                // (nested-less) group visibility, emitting the reflection
2518                // try's call bare (未报告的异常错误NoSuchFieldException).
2519                // Exact mirror of the old `any(|nb| nb.start == g.start
2520                // && seed.contains(&nb.id))` via the pc-sorted index
2521                // (starts are unique — the first block at/after g.start
2522                // is the only candidate); block_at would answer for the
2523                // CONTAINING block, which diverges when a range starts
2524                // mid-block.
2525                let hs_idx = self
2526                    .cfg
2527                    .blocks
2528                    .partition_point(|nb| nb.start < g.start);
2529                let holds_start = self.cfg.blocks.get(hs_idx).is_some_and(|nb| {
2530                    nb.start == g.start && (universe.contains(&nb.id) || nb.id == from)
2531                });
2532                if !holds_start {
2533                    continue;
2534                }
2535                let span_hi = g.end.max(g.start + 1);
2536                let lo = self
2537                    .cfg
2538                    .blocks
2539                    .partition_point(|nb| nb.start < g.start);
2540                for nb in &self.cfg.blocks[lo..] {
2541                    if nb.start >= span_hi {
2542                        break;
2543                    }
2544                    if nb.ins_len != 0 && nb.end <= span_hi {
2545                        eff.insert(nb.id);
2546                    }
2547                }
2548            }
2549        }
2550        sub.retain(|b| {
2551            (universe.contains(b) || *b == from || eff.contains(b))
2552                && !claimed.contains(b)
2553                && !self.is_handler(*b)
2554        });
2555        sub.insert(from);
2556        sub
2557    }
2558
2559    /// `cur` is a loop header if some in-universe, unclaimed predecessor has
2560    /// an edge back to `cur` and `cur` dominates it, or `cur` self-loops.
2561    /// True when the normal edge `p -> cur` closes a cycle at `cur`:
2562    /// either `cur` dominates `p` in the scope's normal-flow dominator
2563    /// tree, or `p` is exception-only reachable (compute_dominators'
2564    /// RPO walks normal succ edges only, so handler-flow blocks keep
2565    /// the idom[p]==p sentinel) and `cur` reaches `p` over the
2566    /// exc-augmented CFG — the catch-and-retry back edge (jdk11
2567    /// AbstractClassLoaderValue.putIfAbsent: the Throwable handler's
2568    /// `goto H` is the retry loop's only inbound edge to H besides the
2569    /// True when `p` is a normally-unreachable handler-flow root in this
2570    /// scope's dominator tree (its idom is itself and it is not the scope
2571    /// entry): every path to it runs through an exception edge — the
2572    /// signature of a catch body's first block (or a handler-only island).
2573    fn dom_is_handler_root(&self, p: usize, dom: &DomInfo, entry: usize) -> bool {
2574        dom.idom[p] == p && p != entry
2575    }
2576
2577    /// True when normal flow from block `p` re-enters the pc span
2578    /// [start_pc, end_pc) — the HP-merge retry check: a handler-flow
2579    /// source whose goto lands back inside the protected span is the
2580    /// catch body's `continue` (loop back edge), not a forward merge.
2581    fn flows_back_into_span(&self, p: usize, start_pc: u32, end_pc: u32) -> bool {
2582        let mut seen: HashSet<usize> = HashSet::default();
2583        let mut q: Vec<usize> = self.cfg.blocks[p].succ.clone();
2584        let mut budget = 512usize;
2585        while let Some(b) = q.pop() {
2586            if budget == 0 || seen.contains(&b) {
2587                if budget == 0 {
2588                    return false;
2589                }
2590                continue;
2591            }
2592            budget -= 1;
2593            seen.insert(b);
2594            let bs = self.cfg.blocks[b].start;
2595            if bs >= start_pc && bs < end_pc {
2596                return true;
2597            }
2598            q.extend(self.cfg.blocks[b].succ.iter().copied());
2599        }
2600        false
2601    }
2602
2603    /// pre-loop init; jdk11/17 ObjectInputStream$1.run's superclass
2604    /// walk update block). The scope's dom root is excluded: its idom
2605    /// is itself too, and root->cur with cur reaching the root is just
2606    /// the enclosing scope's circulation.
2607    fn closes_back_edge(
2608        &self,
2609        cur: usize,
2610        p: usize,
2611        dom: &DomInfo,
2612        entry: usize,
2613        use_exc: bool,
2614    ) -> bool {
2615        if p == cur {
2616            return true;
2617        }
2618        if dom.dominates(cur, p) {
2619            return true;
2620        }
2621        if !use_exc {
2622            return false;
2623        }
2624        // Exclusions: `p == entry` is the scope root's forward edge
2625        // (root→cur with cur reaching the root is the enclosing scope's
2626        // circulation, not a loop at cur), and an exc-only `cur` that is
2627        // not the root is a mid-cycle handler-flow member (jdk11
2628        // Process.waitFor's sleep block — the genuine header sits two
2629        // hops up). The root itself (cur == entry, idom[root]==root by
2630        // construction) stays eligible: jdk26 Future.exceptionNow's
2631        // finally-group body walk is rooted AT the retry header.
2632        if dom.idom[p] == p && p != entry {
2633            let root_walk = cur == entry;
2634            if root_walk {
2635                // A sub-scope root whose sentinel pred is the sub-scope's
2636                // own branch head is an if-ARM walk: the head-to-root
2637                // "cycle" runs out through the ENCLOSING loop's iterator
2638                // (jdk11 FileDescriptor.closeAll: spurious
2639                // while(true){addSuppressed;break} arm wrappers). The
2640                // genuine root-walk retry header (jdk26
2641                // Future.exceptionNow's finally-group body walk rooted AT
2642                // the protected-span start) has its back-edge source as a
2643                // DIRECT exception successor: require that exact edge.
2644                if !self
2645                    .cfg
2646                    .exc_edges
2647                    .iter()
2648                    .any(|e| e.from == cur && e.to == p)
2649                {
2650                    return false;
2651                }
2652            } else if dom.idom[cur] == cur {
2653                // Exc-only mid-cycle member (jdk11 Process.waitFor's
2654                // sleep block): the genuine header sits upstream.
2655                return false;
2656            }
2657            let mut xs: HashMap<usize, Vec<usize>> = HashMap::default();
2658            for e in &self.cfg.exc_edges {
2659                xs.entry(e.from).or_default().push(e.to);
2660            }
2661            return can_reach_cfg_barred(self.cfg, &xs, cur, p, &HashSet::default(), 8192);
2662        }
2663        false
2664    }
2665
2666    fn is_loop_header(
2667        &self,
2668        cur: usize,
2669        universe: &HashSet<usize>,
2670        dom: &DomInfo,
2671        entry: usize,
2672        use_exc: bool,
2673    ) -> bool {
2674        // Enclosing-loop circulation artifact: a walk entry whose only
2675        // successor is an ENCLOSING loop header (a `st = -1; goto head`
2676        // case stub) reaches every in-universe block through that header,
2677        // so the scope-rooted dominator makes `cur` dominate its own
2678        // switch-dispatch preds and the back-edge test fires on a cycle
2679        // that belongs to the enclosing loop — a spurious single-member
2680        // `while (true) { st = -1; continue L1; }` (jdk26 xml
2681        // impl.Parser.xml's default-case wrappers: the switch lost its
2682        // default semantics, sibling cases fell through, and the
2683        // post-switch tail went unreachable — 无法访问的语句). The pred
2684        // only counts when it is reachable from `cur` WITHOUT passing
2685        // through the enclosing header (a genuine loop of `cur`).
2686        let single_enclosing_succ = self.cfg.blocks[cur].succ.len() == 1 && {
2687            let x = self.cfg.blocks[cur].succ[0];
2688            x != cur
2689                && (self.loops_stack.contains(&x)
2690                    || self.sese_loop_headers.contains(&x)
2691                    || self.sese_exc_retry_headers.contains(&x))
2692        };
2693        // Exc-only-reachable preds (handlers and their downstream blocks)
2694        // never get a normal-flow dominator: compute_dominators' RPO walks
2695        // normal succ edges only, so such a block keeps the idom[p]==p
2696        // sentinel and every dominates() query on it is false. When it
2697        // carries a NORMAL edge back into `cur`, that is a genuine back
2698        // edge of a catch-and-retry loop whose cycle runs through a throw
2699        // (jdk11/17 ObjectInputStream$1.run: `for (cl = subcl; cl !=
2700        // ObjectInputStream.class; cl = cl.getSuperclass())` — the update
2701        // block's only inbound edge is try2's exception edge; walk missed
2702        // the loop, copy_walk unrolled 5 nested copies and the tail
2703        // `return TRUE` was lost — 缺少返回语句). Verify the cycle on the
2704        // exc-augmented CFG (same view structure_loop's membership scan
2705        // uses). The scope's dom root is excluded: idom[root]==root too,
2706        // and root→cur with cur⇝root is just the enclosing loop's normal
2707        // circulation, not a loop at `cur`.
2708        for &p in &self.cfg.blocks[cur].pred {
2709            if p == cur {
2710                return true;
2711            }
2712            // In-scope arrivals only: a pred outside this walk's
2713            // universe belongs to a sibling region (a handler arm being
2714            // structured by structure_try). Its back edge is the retry
2715            // loop of the ENCLOSING scope — claiming it here wraps the
2716            // protected block in a spurious inner while(true) whose
2717            // header never lands in loops_stack when the handler arm
2718            // walks, so the retry `goto head` lost its continue (jdk26
2719            // Future.exceptionNow: catch(IE){interrupted=true} fell off
2720            // the method — 缺少返回语句). The dominance branch never saw
2721            // such preds either (out-of-universe preds have sentinel
2722            // idoms); the exc-augmented branch must keep the same view.
2723            if universe.contains(&p) && self.closes_back_edge(cur, p, dom, entry, use_exc) {
2724                if single_enclosing_succ {
2725                    let x = self.cfg.blocks[cur].succ[0];
2726                    let mut barriers: HashSet<usize> = HashSet::default();
2727                    barriers.insert(x);
2728                    if !can_reach_cfg_barred(self.cfg, &HashMap::default(), cur, p, &barriers, 8192) {
2729                        continue;
2730                    }
2731                }
2732                return true;
2733            }
2734        }
2735        false
2736    }
2737
2738    /// Structure the whole method.
2739    pub fn structure_method(&mut self) -> Region {
2740        // From-scratch SESE/dominator-tree structurer (rewrite), gated so the
2741        // default path is the verified `walk` baseline.
2742        let dbg_regions = crate::dbg_flag!("JCDC_DBG_REGIONS");
2743        if crate::dbg_flag!("JCDC_SESE") {
2744            let r = self.structure_method_sese();
2745            if dbg_regions {
2746                eprintln!("REGIONS_SESE {:#?}", r);
2747            }
2748            return r;
2749        }
2750        let r = self.structure_method_walk();
2751        if crate::dbg_flag!("JCDC_DBG_REGIONS") {
2752            eprintln!("REGIONS_WALK {:#?}", r);
2753        }
2754        r
2755    }
2756
2757    /// The verified walk baseline, ungated: the SESE hybrid fallback in
2758    /// method.rs runs it on a fresh Structurer to compare emission counts.
2759    pub fn structure_method_walk(&mut self) -> Region {
2760        let universe: HashSet<usize> = self
2761            .cfg
2762            .blocks
2763            .iter()
2764            .filter(|b| b.ins_len != 0)
2765            .map(|b| b.id)
2766            .collect();
2767
2768        // Exc-mediated retry headers must be known to the walk too: its
2769        // loop-structuring branch and back-edge scans consult
2770        // sese_exc_retry_headers, which only the SESE precompute used to
2771        // fill — in walk-only mode catch-and-retry loops unrolled into
2772        // nested try copies (ois auditSubclass 缺少返回语句 x2 trees).
2773        // sese_loop_headers stays EMPTY here on purpose: feeding walk the
2774        // full natural-header set regressed 6/6/6 (the 1969e0bb lesson —
2775        // walk-side consumers must use the exc-only subset).
2776        {
2777            let idom = compute_dominators(self.cfg, &universe, self.cfg.entry);
2778            let mut lh: HashSet<usize> = HashSet::default();
2779            self.precompute_exc_retry(&universe, &idom, &mut lh);
2780        }
2781
2782        let top_groups: Vec<usize> = (0..self.groups.len())
2783            .filter(|gi| {
2784                let g = &self.groups[*gi];
2785                !self.groups.iter().enumerate().any(|(oj, og)| {
2786                    oj != *gi
2787                        && og.start <= g.start
2788                        && og.end >= g.end
2789                        && (og.start != g.start || og.end != g.end)
2790                })
2791            })
2792            .collect();
2793
2794        let mut claimed = HashSet::default();
2795        self.walk(
2796            self.cfg.entry,
2797            &universe,
2798            &HashSet::default(),
2799            &top_groups,
2800            &mut claimed,
2801            true,
2802        )
2803    }
2804
2805    /// Walk a scope starting at `entry`.
2806    ///
2807    /// * `universe` — blocks eligible for inclusion,
2808    /// * `stop`     — exclusive boundary,
2809    /// * `active`   — try groups that may start inside this scope,
2810    /// * `claimed`  — blocks consumed (in/out; pre-seed for loop bodies),
2811    /// * `allow_claimed_entry` — process `entry` even if already claimed
2812    ///   (loop headers are pre-claimed).
2813    pub(crate) fn walk(
2814        &mut self,
2815        entry: usize,
2816        universe: &HashSet<usize>,
2817        stop: &HashSet<usize>,
2818        active: &[usize],
2819        claimed: &mut HashSet<usize>,
2820        allow_claimed_entry: bool,
2821    ) -> Region {
2822        // Hang guard: pathological methods (e.g. keytool's `doCommands`, with
2823        // thousands of blocks and a long shared-tail chain) can drive unbounded
2824        // `walk` recursion. Cap the depth; beyond it, fall back to a Goto at
2825        // the entry so conversion still emits a valid jump instead of hanging
2826        // or overflowing the stack.
2827        if self.walk_depth >= 256 {
2828            return Region::Goto { target: entry };
2829        }
2830        // Wall-clock guard: bounds the visit-COST (not just the count) of
2831        // pathological explorations — same Goto degradation as the depth
2832        // guard, so conversion stays valid. None = off (jcdc never arms it).
2833        if let Some(dl) = self.walk_deadline {
2834            if std::time::Instant::now() >= dl {
2835                return Region::Goto { target: entry };
2836            }
2837        }
2838        // Visit-COUNT budget: same degradation class once the exploration
2839        // ran long. u64::MAX = off.
2840        {
2841            let left = self.walk_visits_left.get();
2842            if left == 0 {
2843                return Region::Goto { target: entry };
2844            }
2845            self.walk_visits_left.set(left - 1);
2846            #[cfg(feature = "visit-stats")]
2847            {
2848                WALK_VISITS_TOTAL.with(|c| c.set(c.get() + 1));
2849            }
2850        }
2851        // WORK budget: deterministic stand-in for the wall-clock deadline.
2852        // Each visit is charged `universe.len()` — the proxy for its
2853        // dominant cost (`compute_dominators` over the universe). The
2854        // count budget under-bounds per-visit cost (Telegram's
2855        // exponential family: 6ms/visit × 14k visits = minutes), which
2856        // the wall-clock deadline used to catch — but wall-clock is
2857        // nondeterministic under worker contention (a descheduled thread
2858        // crosses any fixed ms bound; reqable's legit 11k-insn enum
2859        // <clinit> flipped `DEBUG(var0)`/`DEBUG(var1)` between identical
2860        // -t 16 runs, stable at -t 1). The work budget bounds the same
2861        // pathologies deterministically: legit corpus work maxes at
2862        // ~200k (4-corpus Σuniverse census), Telegram-scale explosions
2863        // are cut at the budget regardless of scheduling. u64::MAX = off.
2864        {
2865            let left = self.walk_work_left.get();
2866            let cost = universe.len().max(1) as u64;
2867            if left <= cost {
2868                return Region::Goto { target: entry };
2869            }
2870            self.walk_work_left.set(left - cost);
2871        }
2872        self.walk_depth += 1;
2873        let r = self.walk_inner(entry, universe, stop, active, claimed, allow_claimed_entry);
2874        self.walk_depth -= 1;
2875        r
2876    }
2877
2878    fn walk_inner(
2879        &mut self,
2880        entry: usize,
2881        universe: &HashSet<usize>,
2882        stop: &HashSet<usize>,
2883        active: &[usize],
2884        claimed: &mut HashSet<usize>,
2885        allow_claimed_entry: bool,
2886    ) -> Region {
2887        if !universe.contains(&entry) {
2888            return Region::Empty;
2889        }
2890        let dom = compute_dominators(self.cfg, universe, entry);
2891        // When the entry was pre-claimed (loop header), skip the loop-header
2892        // check for the entry block itself to avoid re-structuring the same
2893        // loop recursively.
2894        let entry_preclaimed = claimed.contains(&entry);
2895        if crate::dbg_flag!("JCDC_DBG_IF") {
2896            eprintln!(
2897                "WALK entry={} universe={:?} stop={:?} claimed={:?}",
2898                entry, universe, stop, claimed
2899            );
2900        }
2901        let mut parts: Vec<Region> = Vec::new();
2902        let mut cur = entry;
2903        let mut first = true;
2904        let mut guard = 0usize;
2905        // Groups this scope has already structured: their continuations
2906        // are OWNED by this walk from structure_try's `cont` step on —
2907        // the active-group-continuation guards must no longer defer to
2908        // an owner that IS this scope (jdk11/17 TimeZone.setDefaultZone:
2909        // the post-try fall-out `goto tail` was elided as "the owner
2910        // will emit it" and the shared `return tz` tail vanished from
2911        // the try's normal path — 缺少返回语句 x2 trees).
2912        let mut structured_here: HashSet<usize> = HashSet::default();
2913        let _ = &mut first;
2914        loop {
2915            guard += 1;
2916            if guard > 100_000 || stop.contains(&cur) || !universe.contains(&cur) {
2917                break;
2918            }
2919            if claimed.contains(&cur) && !(first && allow_claimed_entry) {
2920                // Flow re-entered an already structured block.
2921                if crate::dbg_flag!("JCDC_DBG_IF") {
2922                    eprintln!(
2923                        "GOTO-CLAIMED cur={} entry={} parts={}",
2924                        cur,
2925                        entry,
2926                        parts.len()
2927                    );
2928                }
2929                if !parts.is_empty() {
2930                    if self.is_terminator_block(cur) && !stop.contains(&cur) {
2931                        parts.push(Region::CopyStmts { block: cur });
2932                    } else if !stop.contains(&cur) && !self.loops_stack.contains(&cur) {
2933                        match self.copy_walk(cur, stop, active, entry) {
2934                            Some(r) => parts.push(r),
2935                            None => {
2936                                // copy_walk refused (typically the
2937                                // re-entry guard: `cur` flows back into
2938                                // this walk's entry region). When `cur`
2939                                // is a STATEMENT-BEARING back-edge
2940                                // preheader — its flow re-enters an
2941                                // open loop header — a bare Goto
2942                                // resolves to `continue` and SILENTLY
2943                                // DROPS cur's statements at this
2944                                // arrival (jdk26 ClassPrinterImpl
2945                                // toYaml/toXml BLOCK case: the shared
2946                                // toYaml(ternary-indent, ..) merge
2947                                // block was claimed by the sibling
2948                                // arm's copy; the indent+1 arm lost
2949                                // the call and left a dangling
2950                                // `stack0 = indent + 1`). Copy the
2951                                // statements and keep the Goto for the
2952                                // continue (KeyStore backedge-stub
2953                                // pattern).
2954                                if !self.results[cur].stmts.is_empty()
2955                                    && self.cfg.blocks[cur].succ.iter().any(|&s2| {
2956                                        self.loops_stack.contains(&s2)
2957                                            || self.sese_loop_headers.contains(&s2)
2958                                            || can_reach_cfg(self.cfg, s2, entry, 4096)
2959                                    })
2960                                {
2961                                    parts.push(Region::Seq(vec![
2962                                        Region::CopyStmts { block: cur },
2963                                        Region::Goto { target: cur },
2964                                    ]));
2965                                } else if self.results[cur].stmts.is_empty()
2966                                    && matches!(self.results[cur].term, Term::Fallthrough)
2967                                    && self.cfg.blocks[cur].succ.len() == 1
2968                                    && universe.contains(&self.cfg.blocks[cur].succ[0])
2969                                    && !stop.contains(&self.cfg.blocks[cur].succ[0])
2970                                    && parts.iter().any(|p| {
2971                                        region_head_block(p) == self.cfg.blocks[cur].succ[0]
2972                                    })
2973                                {
2974                                    // Fall-into-sibling: `cur` is a
2975                                    // statement-free fallthrough whose
2976                                    // successor is THIS walk's immediate
2977                                    // next part — an if-else chain's
2978                                    // inner block boundary (jdk17
2979                                    // JarFile.getBytes: the readNBytes
2980                                    // A-arm's tail merge block 6 starts
2981                                    // the B-arm copy that follows the
2982                                    // chain; the plain Goto{6} was
2983                                    // elided as a natural fallthrough
2984                                    // and the A-arm ran straight into
2985                                    // readAllBytes — double read +
2986                                    // spurious EOFException). Materialize
2987                                    // the jump so the sibling copy is
2988                                    // skipped.
2989                                    parts.push(Region::Goto { target: cur });
2990                                } else {
2991                                    parts.push(Region::Goto { target: cur });
2992                                }
2993                            }
2994                        }
2995                    } else {
2996                        parts.push(Region::Goto { target: cur });
2997                    }
2998                } else if self.loops_stack.contains(&cur) || self.sese_loop_headers.contains(&cur) {
2999                    // An EMPTY region arriving at an enclosing loop header
3000                    // IS the back edge (jdk26 Future.exceptionNow's
3001                    // `catch (InterruptedException e) { interrupted =
3002                    // true; }` retry — the handler's only out-edge is
3003                    // `goto head`): emit the Goto so conversion resolves
3004                    // it to `continue`. Dropping it lets the handler fall
3005                    // out of the try silently.
3006                    parts.push(Region::Goto { target: cur });
3007                }
3008                break;
3009            }
3010            if !first && self.is_handler(cur) && active.is_empty() {
3011                // Handler head reached by a stray normal edge; it belongs to
3012                // its Try region.
3013                break;
3014            }
3015            first = false;
3016            let b = &self.cfg.blocks[cur];
3017            if b.ins_len == 0 {
3018                match b.succ.first().copied() {
3019                    Some(n) => {
3020                        cur = n;
3021                        continue;
3022                    }
3023                    None => break,
3024                }
3025            }
3026
3027            // Try group starting exactly here? Match by span (the block may
3028            // belong to a more deeply nested group in body_group); active
3029            // lists are ordered outer-first so the first match is the
3030            // outermost group starting here.
3031            let group_here = active
3032                .iter()
3033                .find(|&&gi| {
3034                    self.groups[gi].start == b.start
3035                        && self.groups[gi].end >= b.end
3036                        && !self.handler_group.contains_key(&cur)
3037                })
3038                .copied()
3039                .or_else(|| {
3040                    // A group starting at `cur` that `active` does not
3041                    // carry: under SESE only OUTERMOST groups are active at
3042                    // the top level, and handler-arm scopes inherit the
3043                    // handler's (span-limited) visibility — a nested group
3044                    // reached there was emitted BARE, losing its try
3045                    // (jdk11 HttpURLConnection.getInputStream0: the RT-catch
3046                    // arm re-walked the continuation and the reflection
3047                    // try/catch(IllegalAccessException|NoSuchFieldException)
3048                    // vanished — 未报告的异常错误). Carve it out unless an
3049                    // ACTIVE group strictly enclosing `cur` owns the
3050                    // carve-out (the intact enclosing body walk case).
3051                    let covered_by_active = self.groups.iter().enumerate().any(|(oj, og)| {
3052                        active.contains(&oj)
3053                            && og.start <= b.start
3054                            && og.end > b.start
3055                            && !(og.start == b.start && og.end >= b.end)
3056                    });
3057                    if covered_by_active || self.handler_group.contains_key(&cur) {
3058                        return None;
3059                    }
3060                    self.groups
3061                        .iter()
3062                        .enumerate()
3063                        .filter(|(gi, g)| {
3064                            g.start == b.start
3065                                && g.end >= b.end
3066                                && !self.structuring_groups.borrow().contains(gi)
3067                        })
3068                        .max_by_key(|(_, g)| g.end)
3069                        .map(|(gi, _)| gi)
3070                });
3071            // The group starts at a LOOP HEADER whose back edge lies
3072            // OUTSIDE the protected span (`for (;;) { try { ... } catch
3073            // { ... } ...retry... }` — jdk26 ClassValue.getFromHashMap):
3074            // the loop is the enclosing construct. Structuring the try
3075            // first consumes the header and leaves the back edge to be
3076            // duplicated or dropped (walk: silent lost retry loop +
3077            // missing return; SESE: 3× unrolled copies). Let the
3078            // loop-header branch below win; the body walk re-finds this
3079            // group at its start. try{while} keeps group-first: its back
3080            // edge is INSIDE the protected span.
3081            let group_here = group_here.filter(|&gi| {
3082                entry_preclaimed
3083                    || !(self.is_loop_header(cur, universe, &dom, entry, true)
3084                        || self.sese_loop_headers.contains(&cur)
3085                        // Exc-retry headers count too: the retry loop of
3086                        // `for(;;){try{..}catch{.. goto head}}` wraps the
3087                        // try even when the try's protected span starts
3088                        // AT the header (jdk26 Future.exceptionNow: the
3089                        // finally-group body walk must structure the loop
3090                        // around the inner IE/EE try so the IE handler's
3091                        // `goto head` resolves to `continue` against
3092                        // loops_stack — without it the inner group won,
3093                        // a degenerate while(true){get();throw} absorbed
3094                        // the protected block, and catch(IE){interrupted
3095                        // =true} fell off the method — 缺少返回语句).
3096                        || self.sese_exc_retry_headers.contains(&cur))
3097                    || !self.cfg.blocks[cur].pred.iter().any(|&p| {
3098                        // No universe filter: an exc-mediated retry back
3099                        // edge's source is handler flow — carved OUT of
3100                        // this body walk's universe by construction
3101                        // (Future.exceptionNow's IE handler at pc 57).
3102                        p != cur
3103                            && self.closes_back_edge(cur, p, &dom, entry, true)
3104                            && (self.cfg.blocks[p].end <= self.groups[gi].start
3105                                || self.cfg.blocks[p].start >= self.groups[gi].end
3106                                // HANDLER-PROTECTION topology: the source
3107                                // is normally-unreachable handler flow
3108                                // (its dominator set is just itself) that
3109                                // flows BACK into the protected span —
3110                                // the HP-merge's outer group spans the
3111                                // handler code, so the retry edge is
3112                                // span-internal yet still handler flow
3113                                // (jdk26 javax.crypto.KDF chooseProvider:
3114                                // OUTER=(91,258)[IAPE,NSAE] + INNER=
3115                                // (91,162)[Exception]; the catch body's
3116                                // getNext retry `goto 91` (b30) lives
3117                                // inside the outer span — without the
3118                                // loop-wins veto here the handler walk
3119                                // copy-unrolls the retry loop 4-deep
3120                                // instead of resolving `continue`).
3121                                || (self.dom_is_handler_root(p, &dom, entry)
3122                                    && self.flows_back_into_span(
3123                                        p,
3124                                        self.groups[gi].start,
3125                                        self.groups[gi].end,
3126                                    )))
3127                    })
3128            });
3129            if crate::dbg_flag!("JCDC_DBG_GRP") {
3130                eprintln!("GRPDEC cur={} start={} end={} group_here={:?} active={:?} hg={} structuring={:?}",
3131                    cur, b.start, b.end, group_here, active,
3132                    self.handler_group.contains_key(&cur),
3133                    self.structuring_groups.borrow().clone());
3134            }
3135            if let Some(gi) = group_here {
3136                let outer_universe = universe.clone();
3137                let try_region = self.structure_try(gi, universe, &outer_universe, claimed, stop);
3138                parts.push(try_region);
3139                structured_here.insert(gi);
3140                let gend = self.groups[gi].end;
3141                let hf_next = self.handler_flow_only(gi);
3142                let next = self.cfg.blocks.iter().find(|nb| {
3143                    nb.start >= gend
3144                        && universe.contains(&nb.id)
3145                        && !stop.contains(&nb.id)
3146                        && !claimed.contains(&nb.id)
3147                        && (!self.is_handler(nb.id) || self.is_confluent_tail(gi, nb.id))
3148                        && !hf_next.contains(&nb.id)
3149                });
3150                match next {
3151                    Some(nb) => {
3152                        cur = nb.id;
3153                        continue;
3154                    }
3155                    None => break,
3156                }
3157            }
3158
3159            // Loop header? (skip for a pre-claimed entry: that loop is the
3160            // one we are currently structuring)
3161            let at_preclaimed_entry = entry_preclaimed && cur == entry && parts.is_empty();
3162            // sese_loop_headers carries the exception-mediated retry
3163            // headers (handler-flow back edges invisible to this scope's
3164            // normal dominance): a try-group body walk that reaches the
3165            // header of an enclosing `for(;;){try{..}catch{..continue}}`
3166            // must structure the loop, else the handler's `goto head`
3167            // copy_walks the head into nested duplicate tries (jdk26
3168            // DateTimeFormatter.parseBest unrolled 5 deep, the
3169            // loop-exhausted `throw DateTimeException` lost —
3170            // 缺少返回语句 x3 trees).
3171            if !at_preclaimed_entry
3172                && (self.is_loop_header(cur, universe, &dom, entry, true)
3173                    || self.sese_exc_retry_headers.contains(&cur))
3174            {
3175                let loop_r = self.structure_loop(cur, universe, stop, active, claimed, &dom);
3176                // A body that cannot complete normally (both arms of its
3177                // tail check inline the loop's terminator exits — the
3178                // finally-retry dual-throw shape, jdk11 ObjectInputStream
3179                // .readSerialData) makes any post-loop continuation
3180                // unreachable (无法访问的语句): neither continue at a
3181                // follow nor copy a terminator exit after it.
3182                let body_done = match &loop_r {
3183                    Region::Loop { body, exits, .. } => {
3184                        let handler_exits: Vec<usize> = exits
3185                            .iter()
3186                            .copied()
3187                            .filter(|e| self.is_handler(*e))
3188                            .collect();
3189                        region_terminates_ex(body, self.results, &handler_exits)
3190                            // A terminal Goto to the loop's OWN non-handler
3191                            // exit converts to `break` (the exit binding
3192                            // wins over term-copy inlining), and the break
3193                            // lands on live post-loop flow — the body DOES
3194                            // complete normally even when the exit block is
3195                            // a shared return terminator (jdk11
3196                            // KeyStore$Builder$2$1.run: break → the
3197                            // `getCalled = true; return ks` tail; the
3198                            // term-copy abrupt reading suppressed the
3199                            // continuation and the CBH path fell off the
3200                            // method — 缺少返回语句 x2 trees).
3201                            && !region_ends_at_live_exit(body, exits, self)
3202                    }
3203                    _ => false,
3204                };
3205                let next = if body_done {
3206                    None
3207                } else {
3208                    self.next_after_loop(&loop_r, universe, stop, claimed)
3209                };
3210                let loop_exits = match &loop_r {
3211                    Region::Loop { exits, .. } => exits.clone(),
3212                    _ => Vec::new(),
3213                };
3214                // ROTATED DO-WHILE REBUILD: the loop's continuation is a
3215                // BACKWARD exit — a claimed statement block BEFORE the
3216                // header that this walk already emitted as the pre-loop
3217                // part (parts tail = [Basic{n}, Loop]). javac rotated
3218                // `do { <n stmts> while (c) {..} } while (c2);` into
3219                // `<n stmts>; while(..){.. goto n ..}`: the in-body
3220                // Goto{n} resolved against the inner exits as a `break`
3221                // landing on the post-loop re-copy of n, which dead-ends
3222                // off the method (jdk11/17 URLClassPath$JarLoader
3223                // .getResource: the refetch-true back edge broke out,
3224                // the do-top copy fell off the if — 缺少返回语句).
3225                // Wrap [Basic{n}, Loop] into an OUTER for(;;) region
3226                // headed at n: conversion's header-continue check
3227                // (which precedes the exits-break check) then resolves
3228                // every Goto{n} inside the body to a labeled continue
3229                // of the outer loop, and classify_loop renders the
3230                // Fallthrough-headed outer as while(true). Skip when a
3231                // matexit cascade already materialized n inside the
3232                // body (its copies would duplicate the outer Basic).
3233                if let Some(n) = next {
3234                    if claimed.contains(&n)
3235                        && self.cfg.blocks[n].start < self.cfg.blocks[cur].start
3236                        && !self.is_handler(n)
3237                        && matches!(loop_r, Region::Loop { .. })
3238                        && !region_mentions_block(&loop_r, n)
3239                        && matches!(parts.last(), Some(Region::Basic { block }) if *block == n)
3240                    {
3241                        let do_top = parts.pop().unwrap();
3242                        let mut members = match &loop_r {
3243                            Region::Loop { members, .. } => members.clone(),
3244                            _ => HashSet::default(),
3245                        };
3246                        members.insert(n);
3247                        parts.push(Region::Loop {
3248                            header: n,
3249                            body: Box::new(Region::Seq(vec![do_top, loop_r])),
3250                            members,
3251                            exits: Vec::new(),
3252                        });
3253                        if crate::dbg_flag!("JCDC_DBG_LOOP") {
3254                            eprintln!("ROTATION header={} do-top={}", cur, n);
3255                        }
3256                        break;
3257                    }
3258                }
3259                parts.push(loop_r);
3260                match next {
3261                    Some(n) => {
3262                        cur = n;
3263                        continue;
3264                    }
3265                    None => {
3266                        // The loop exits into a block this walk cannot
3267                        // continue at (claimed by a sibling branch or out
3268                        // of the universe). When that block is a shared
3269                        // terminator (return/throw), copy it here so this
3270                        // path does not silently fall through.
3271                        if !body_done {
3272                            for &e in &loop_exits {
3273                                if !stop.contains(&e) && self.is_terminator_block(e) {
3274                                    parts.push(Region::CopyStmts { block: e });
3275                                    break;
3276                                }
3277                            }
3278                        }
3279                        // An exit that is an ENCLOSING loop's barrier (in
3280                        // `stop`) must still be materialized as a Goto: it
3281                        // converts to the labeled break of the enclosing
3282                        // loop. Dropping it lets the enclosing loop body
3283                        // end in an implicit continue — the normal
3284                        // completion of the inner loop re-entered the
3285                        // outer instead of leaving it (jdk11
3286                        // FutureTask.removeWaiter spun forever; javac:
3287                        // 无法访问的语句 on the tail return once the
3288                        // continue-stub walks were filtered).
3289                        if !body_done
3290                            && parts
3291                                .last()
3292                                .map(|r| !matches!(r, Region::Goto { .. }))
3293                                .unwrap_or(true)
3294                        {
3295                            if let Some(&e) = loop_exits
3296                                .iter()
3297                                .filter(|e| {
3298                                    stop.contains(e)
3299                                        && !self.loops_stack.contains(e)
3300                                        && universe.contains(e)
3301                                })
3302                                .min_by_key(|e| self.cfg.blocks[**e].start)
3303                            {
3304                                parts.push(Region::Goto { target: e });
3305                            }
3306                        }
3307                        break;
3308                    }
3309                }
3310            }
3311
3312            // Folded diamond root: collapse the whole region — claim the
3313            // absorbed blocks and continue at the merge. The folded ternary
3314            // already lives in the merge's input stack.
3315            if let Some(&merge) = self.fold_root_to_merge.get(&cur) {
3316                if let Some((_root, vis)) = self.fold_regions.get(&merge) {
3317                    if !self.results[cur].stmts.is_empty() {
3318                        parts.push(Region::Basic { block: cur });
3319                    }
3320                    claimed.extend(vis.iter().copied());
3321                    claimed.insert(merge);
3322                    claimed.insert(cur);
3323                    if crate::dbg_flag!("JCDC_DBG_IF") {
3324                        eprintln!("FOLD-COLLAPSE root={} merge={}", cur, merge);
3325                    }
3326                    cur = merge;
3327                    // The merge itself must still be processed (it is now
3328                    // claimed, so allow it explicitly on the next iteration).
3329                    first = false;
3330                    // Process the merge block in this same walk: temporarily
3331                    // allowed because we just claimed it; jump back through
3332                    // the loop top with the claimed-entry exception.
3333                    // (Handled below by processing `merge` directly.)
3334                    // Continue the loop; the claimed check at top would
3335                    // break, so instead process merge via a sub-walk splice:
3336                    // simplest is to unclaim merge and let normal flow claim
3337                    // it.
3338                    claimed.remove(&merge);
3339                    continue;
3340                }
3341            }
3342
3343            claimed.insert(cur);
3344            if crate::dbg_flag!("JCDC_DBG_CLAIM") {
3345                eprintln!(
3346                    "CLAIM blk={} entry={} term_is_cond={}",
3347                    cur,
3348                    entry,
3349                    matches!(self.term(cur), Term::Cond { .. })
3350                );
3351            }
3352            match self.term(cur).clone() {
3353                Term::Cond { cond } => {
3354                    let succs = b.succ.clone();
3355                    if succs.len() != 2 {
3356                        // Degenerate conditional (both branches jump to the
3357                        // same block, e.g. javac 7's empty `continue`):
3358                        // emit the statements and walk on into the single
3359                        // successor.
3360                        parts.push(Region::Basic { block: cur });
3361                        match succs.first().copied() {
3362                            Some(nxt)
3363                                if universe.contains(&nxt)
3364                                    && !stop.contains(&nxt)
3365                                    && !claimed.contains(&nxt) =>
3366                            {
3367                                cur = nxt;
3368                                continue;
3369                            }
3370                            _ => break,
3371                        }
3372                    }
3373                    let (fall, taken) = (succs[0], succs[1]);
3374                    let mut pd = self.postdom_ipdom(universe, cur);
3375                    // A shared return/throw block is a poor If-follow: the
3376                    // branch that flows into it should carry its own copy of
3377                    // the terminator, and jumps past it from the other
3378                    // branch stay natural fallthrough. Only treat it as the
3379                    // follow when BOTH branches land on it (the If truly
3380                    // merges there).
3381                    // EXCEPTION — the terminator carries blank-final field
3382                    // assignments: those must execute EXACTLY once on every
3383                    // path, and every copy route (CopyStmts, term copy,
3384                    // SWTAIL) refuses final-writers, so no arm can absorb
3385                    // the tail. Nulling the follow lets the FIRST arm's
3386                    // walk flow into the merge and claim it, and the
3387                    // sibling arms fall off without the assignments
3388                    // (jdk11 LocaleProviderAdapter clinit:
3389                    // adapterPreference/NONEXISTENT_ADAPTER assigned only
3390                    // in the typeList.isEmpty() arm; jdk26
3391                    // ObjectInputFilter$Config clinit:
3392                    // invalidFactoryMessage — 可能尚未初始化变量).
3393                    if let Some(p) = pd {
3394                        if self.is_terminator_block(p)
3395                            && p != taken
3396                            && p != fall
3397                            && !self.terminator_writes_final(p)
3398                        {
3399                            pd = None;
3400                        }
3401                    }
3402
3403                    // Appendix fold: when the post-dominator is unwalkable
3404                    // (None or claimed), the value-diamond continuation
3405                    // between here and the enclosing barrier block M is an
3406                    // "appendix". Fold it into this If's follow so the walk
3407                    // continues at M instead of emitting raw Gotos into
3408                    // already-claimed merge blocks.
3409                    let mut follow = pd;
3410                    let follow_walkable = follow
3411                        .map(|f| {
3412                            universe.contains(&f) && !stop.contains(&f) && !claimed.contains(&f)
3413                        })
3414                        .unwrap_or(false);
3415                    // Never fold an appendix at a pre-claimed loop header:
3416                    // the stop set there is the loop exit, and the header's
3417                    // branch-out must stay a loop exit, not an If follow.
3418                    if !follow_walkable && !at_preclaimed_entry {
3419                        let m = self.appendix_target(cur, universe, stop, claimed);
3420                        if crate::dbg_flag!("JCDC_DBG_IF") {
3421                            eprintln!("COND cur={} pd={:?} unwalkable appendix->{:?}", cur, pd, m);
3422                        }
3423                        if let Some(m) = m {
3424                            follow = Some(m);
3425                        }
3426                    }
3427                    let mut bstop = stop.clone();
3428                    if let Some(f) = follow {
3429                        bstop.insert(f);
3430                    }
3431
3432                    // Chained boolean/ternary: both branch targets lead
3433                    // (through pure value-push blocks) to a common merge
3434                    // whose input stack was folded into ternaries during
3435                    // block building. The true merge is the end of the
3436                    // pure-value tail chain (the post-dominator may be a
3437                    // push block itself when the diamond carries trailing
3438                    // values). Skip the scaffolding and continue at the merge.
3439                    let mut diamond_jump: Option<usize> = None;
3440                    {
3441                        // Candidate merges: nearest block reachable from BOTH
3442                        // branches (true confluence), then the taken-side
3443                        // pure chain end, then the post-dominator.
3444                        let mut cands: Vec<usize> = Vec::new();
3445                        {
3446                            // Deterministic candidate order (HashSet
3447                            // iteration varies per process).
3448                            let mut dms: Vec<usize> = self
3449                                .diamond_merges
3450                                .iter()
3451                                .copied()
3452                                .filter(|dm| {
3453                                    *dm != cur && universe.contains(dm) && !stop.contains(dm)
3454                                })
3455                                .collect();
3456                            dms.sort_unstable_by_key(|b| self.cfg.blocks[*b].start);
3457                            cands.extend(dms);
3458                        }
3459                        if let Some(m) = self.branch_confluence(fall, taken, universe) {
3460                            if !cands.contains(&m) {
3461                                cands.push(m);
3462                            }
3463                        }
3464                        if let Some(m) = self.pure_chain_end(taken, universe, stop, claimed) {
3465                            if !cands.contains(&m) {
3466                                cands.push(m);
3467                            }
3468                        }
3469                        if let Some(m) = pd {
3470                            if !cands.contains(&m) {
3471                                cands.push(m);
3472                            }
3473                        }
3474                        for m in cands {
3475                            if m == cur {
3476                                continue;
3477                            }
3478                            let mut vis_t: HashSet<usize> = HashSet::default();
3479                            let mut vis_f: HashSet<usize> = HashSet::default();
3480                            if self.diamond_side(taken, m, universe, &bstop, claimed, &mut vis_t, 0)
3481                                && self
3482                                    .diamond_side(fall, m, universe, &bstop, claimed, &mut vis_f, 0)
3483                            {
3484                                let mut vis = vis_t;
3485                                vis.extend(vis_f);
3486                                // the merge block itself stays available as
3487                                // the next walk position
3488                                vis.remove(&m);
3489                                claimed.extend(vis.iter().copied());
3490                                diamond_jump = Some(m);
3491                                break;
3492                            }
3493                        }
3494                    }
3495                    if let Some(m) = diamond_jump {
3496                        // The header block's own statements (if any) still
3497                        // execute; only its conditional terminal is folded
3498                        // into the merge value.
3499                        if !self.results[cur].stmts.is_empty() {
3500                            parts.push(Region::Basic { block: cur });
3501                        }
3502                        cur = m;
3503                        continue;
3504                    }
3505                    // No ipdom, no appendix, no value diamond — but when
3506                    // the fall side is a bare statement-free jump STUB
3507                    // and the TAKEN arm's open flow crosses past the
3508                    // stub into its target, that target is the real
3509                    // follow: an if/ELSE pyramid dangles the taken arm's
3510                    // fall-out inside the else arm's territory (jdk26
3511                    // UnixFileSystemProvider.newDirectoryStream x3
3512                    // trees: `catch (UnixException x) { ...;
3513                    // x.rethrowAsIOException(dir); }` falls through
3514                    // into the dfd1/SecureDirectoryStream tail owned by
3515                    // the else arm — the catch fell off the if/else and
3516                    // the method fell off its end — 缺少返回语句).
3517                    // Scoped tight: stub-only fall sides (the sequential-
3518                    // if shape javac emits: `if (c) goto join; goto T`
3519                    // with T the dead stub target), non-terminator
3520                    // target, and a taken-arm route to it that bypasses
3521                    // the stub (barriers exclude the stub, the cond and
3522                    // both branch heads, so the ONLY qualifying route
3523                    // crosses over). Natural diamonds keep their ipdom
3524                    // and never reach this branch.
3525                    if follow.is_none() {
3526                        let fall_is_stub = self.results[fall].stmts.is_empty()
3527                            && matches!(self.results[fall].term, Term::Goto)
3528                            && self.cfg.blocks[fall].succ.len() == 1;
3529                        if fall_is_stub {
3530                            let fh = self.cfg.blocks[fall].succ[0];
3531                            // fh must not START a try group: the follow
3532                            // continuation may be walked in a scope where
3533                            // that (nested) group is not visible, and the
3534                            // carve-out would be lost — jdk11
3535                            // ReflectionFactory.getReplaceResolveFor-
3536                            // Serialization's inner try{setAccessible;
3537                            // unreflect}catch(IAE) dissolved into a bare
3538                            // unreflect (未报告的异常错误 x3 trees).
3539                            let fh_starts_group = self
3540                                .groups
3541                                .iter()
3542                                .any(|g| g.start == self.cfg.blocks[fh].start);
3543                            if fh != cur
3544                                && fh != taken
3545                                && !fh_starts_group
3546                                && !stop.contains(&fh)
3547                                && !claimed.contains(&fh)
3548                                && !self.is_terminator_block(fh)
3549                                && !self.is_handler(fh)
3550                                && !self.loops_stack.contains(&fh)
3551                            {
3552                                let exc_succ: HashMap<usize, Vec<usize>> =
3553                                    self.cfg.exc_edges.iter().fold(HashMap::default(), |mut m, e| {
3554                                        m.entry(e.from).or_default().push(e.to);
3555                                        m
3556                                    });
3557                                let mut barriers: HashSet<usize> = HashSet::default();
3558                                barriers.insert(fh);
3559                                barriers.insert(cur);
3560                                barriers.insert(fall);
3561                                barriers.insert(taken);
3562                                for l in &self.loops_stack {
3563                                    barriers.insert(*l);
3564                                }
3565                                barriers.extend(stop.iter().copied());
3566                                if can_reach_cfg_barred(
3567                                    self.cfg, &exc_succ, taken, fh, &barriers, 8192,
3568                                ) {
3569                                    follow = Some(fh);
3570                                    bstop.insert(fh);
3571                                }
3572                            }
3573                        }
3574                    }
3575                    if crate::dbg_flag!("JCDC_DBG_IF") {
3576                        let tb = self.cfg.block_at(self.cfg.blocks[taken].start);
3577                        eprintln!("COND cur={} pc={} fall_pc={} taken_pc={} pd={:?} follow={:?} f_pc={:?} t_id={:?} t_univ={} t_claim={} t_stop={} bstop={:?}",
3578                            cur, self.cfg.blocks[cur].start,
3579                            self.cfg.blocks[fall].start, self.cfg.blocks[taken].start,
3580                            pd, follow, follow.map(|f| self.cfg.blocks[f].start),
3581                            tb, tb.map(|b| universe.contains(&b)).unwrap_or(false),
3582                            tb.map(|b| claimed.contains(&b)).unwrap_or(false),
3583                            tb.map(|b| stop.contains(&b)).unwrap_or(false),
3584                            { let mut v: Vec<u32> = bstop.iter().map(|b| self.cfg.blocks[*b].start).collect(); v.sort(); v });
3585                    }
3586                    if crate::dbg_flag!("JCDC_DBG_IF") {
3587                        // absorb_pure is &mut and CLAIMS the absorbed block
3588                        // — calling it here for the printout pre-claimed the
3589                        // taken arm and flipped the real decision below
3590                        // (JarFile.getBytes rendered a different shape under
3591                        // JCDC_DBG_IF than in production). Debug must be
3592                        // pure observation.
3593                        eprintln!(
3594                            "THENDECIDE cur={} taken={} fall={} follow={:?} stop_t={} stop_f={} term_t={} term_f={} univ_t={} claim_t={}",
3595                            cur, taken, fall, follow, stop.contains(&taken), stop.contains(&fall),
3596                            self.is_terminator_block(taken), self.is_terminator_block(fall),
3597                            universe.contains(&taken), claimed.contains(&taken)
3598                        );
3599                    }
3600                    let then_r = if Some(taken) == follow && !stop.contains(&taken) {
3601                        Region::Empty
3602                    } else if Some(taken) == follow
3603                        && stop.contains(&taken)
3604                        && self.is_terminator_block(taken)
3605                        && !self.loops_stack.is_empty()
3606                        && self.loops_stack[self.loops_stack.len() - 1] != cur
3607                    {
3608                        // Loop-bottom exit test with a TERMINATOR exit
3609                        // (`if (i >= n) <return-tail>; else <digit; goto
3610                        // head>;`): the then arm must materialize as
3611                        // `break`. Leaving it Empty relies on the tail copy
3612                        // after the loop, but a `while (true)` body without
3613                        // any break is a JLS non-completing loop —
3614                        // prune_unreachable then deletes the tail copy
3615                        // (jdk11/17 InstantPrinterParser.format's second
3616                        // loop copy lost `append('Z'); return true` —
3617                        // 缺少返回语句). classify_loop's split_leading_if
3618                        // still rotates a HEADER test into the while
3619                        // condition (cur != header guard keeps top-tested
3620                        // shapes on the old Empty path).
3621                        Region::Goto { target: taken }
3622                    } else if Some(taken) == follow
3623                        && stop.contains(&taken)
3624                        && !self.is_terminator_block(taken)
3625                    {
3626                        // The "follow" is an enclosing barrier (loop exit):
3627                        // the branch is a jump out, not a fallthrough.
3628                        // Terminator exits stay Empty — the return/throw is
3629                        // emitted after the loop. UNLESS the exit chains into
3630                        // an already-claimed terminator (restart-loop guarded
3631                        // case: breaking lands after the loop where nothing
3632                        // remains; jdk26 DecimalFormat case 6).
3633                        if !self.loops_stack.contains(&taken)
3634                            && self.stop_chain_to_claimed_terminator(taken, claimed)
3635                        {
3636                            // `taken` is the if-follow and sits in bstop;
3637                            // copy_walk's reachable_within would exclude the
3638                            // entry itself — lift it for the copy.
3639                            let mut cstop = bstop.clone();
3640                            cstop.remove(&taken);
3641                            match self.copy_walk(taken, &cstop, active, cur) {
3642                                Some(r) => r,
3643                                None => Region::Goto { target: taken },
3644                            }
3645                        } else {
3646                            Region::Goto { target: taken }
3647                        }
3648                    } else if let Some(absorbed) =
3649                        self.absorb_pure(taken, universe, &bstop, claimed, active)
3650                    {
3651                        absorbed
3652                    } else if universe.contains(&taken)
3653                        && !bstop.contains(&taken)
3654                        && !claimed.contains(&taken)
3655                        && !(self.is_handler(taken) && active.is_empty())
3656                    {
3657                        let branch_universe = self.owner_scope(taken, universe);
3658                        let mut sub = self.sub_scope(taken, &branch_universe, &bstop, claimed);
3659                        self.restrict_handler_branch(&mut sub, entry);
3660                        self.walk(taken, &sub, &bstop, active, claimed, false)
3661                    } else if !stop.contains(&taken)
3662                        && !claimed.contains(&taken)
3663                        && !self.is_terminator_block(taken)
3664                        && self.cfg.exc_edges.iter().any(|e| e.from == taken)
3665                    {
3666                        // A PROTECTED branch target outside this scope's
3667                        // universe (an enclosing try body split it into
3668                        // another owner group): walking it gives the flow
3669                        // its own try carve-out and materializes its
3670                        // statements. Region::Empty here silently deleted
3671                        // the branch AND left the group unstructured —
3672                        // jdk17 HttpURLConnection.getOutputStream's
3673                        // `return getOutputStream0()` arm vanished and the
3674                        // doPrivileged call lost its
3675                        // catch(PrivilegedActionException)
3676                        // (未报告的异常错误 x2 methods, sj17 tree).
3677                        let branch_universe = self.owner_scope(taken, universe);
3678                        let mut sub = self.sub_scope(taken, &branch_universe, &bstop, claimed);
3679                        self.restrict_handler_branch(&mut sub, entry);
3680                        self.walk(taken, &sub, &bstop, active, claimed, false)
3681                    } else {
3682                        // Target is the follow (empty), or already structured
3683                        // (loop header → continue; loop exit → break).
3684                        if self.is_terminator_block(taken)
3685                            && !stop.contains(&taken)
3686                            && !self.terminator_writes_final(taken)
3687                        {
3688                            // Shared return/throw block: inline a copy at
3689                            // this branch (safe — terminators have no
3690                            // outgoing flow). Loop-exit terminators (in
3691                            // `stop`) must stay jumps so they resolve to
3692                            // break/while conditions.
3693                            Region::CopyStmts { block: taken }
3694                        } else if claimed.contains(&taken) {
3695                            if !stop.contains(&taken)
3696                                && !bstop.contains(&taken)
3697                                && !self.loops_stack.contains(&taken)
3698                                && !self.terminator_writes_final(taken)
3699                            {
3700                                match self.copy_walk(taken, &bstop, active, cur) {
3701                                    Some(r) => r,
3702                                    None => Region::Goto { target: taken },
3703                                }
3704                            } else {
3705                                Region::Goto { target: taken }
3706                            }
3707                        } else if stop.contains(&taken) && !self.is_terminator_block(taken) {
3708                            // Jump to an enclosing loop's exit (or other
3709                            // barrier): keep it as a Goto so conversion
3710                            // resolves it to `break` — UNLESS the exit chains
3711                            // into an already-claimed terminator (the
3712                            // restart-loop guarded case: breaking would land
3713                            // after the loop where nothing remains and the
3714                            // body is lost; jdk26 DecimalFormat case 6).
3715                            if !self.loops_stack.contains(&taken)
3716                                && self.stop_chain_to_claimed_terminator(taken, claimed)
3717                            {
3718                                let mut cstop = bstop.clone();
3719                                cstop.remove(&taken);
3720                                match self.copy_walk(taken, &cstop, active, cur) {
3721                                    Some(r) => r,
3722                                    None => Region::Goto { target: taken },
3723                                }
3724                            } else {
3725                                Region::Goto { target: taken }
3726                            }
3727                        } else if self.is_terminator_block(taken)
3728                            && stop.contains(&taken)
3729                            && self.is_terminator_block(fall)
3730                            && stop.contains(&fall)
3731                        {
3732                            self.dual_terminator_branch(taken, active)
3733                        } else if self.is_terminator_block(taken)
3734                            && stop.contains(&taken)
3735                            && !self.loops_stack.is_empty()
3736                            && !self.is_terminator_block(fall)
3737                            && stop.contains(&fall)
3738                            && can_reach_cfg(self.cfg, fall, taken, 4096)
3739                        {
3740                            // A TERMINATOR loop-exit target as a mid-body
3741                            // branch whose FALL sibling also flows to that
3742                            // same terminator (both arms are alternate
3743                            // routes to one shared exit): Empty would let
3744                            // the then-arm fall into the REST OF THE BODY
3745                            // instead of the exit emission (jdk11/17/26
3746                            // ConcurrentLinkedQueue.poll: the p==h
3747                            // cas-success arm targeting the shared
3748                            // `return item` block rendered empty and fell
3749                            // into the advance code — the dequeued item was
3750                            // silently lost until the next poll; offer's
3751                            // TAIL-cas arm looped instead of returning
3752                            // true). Emit the jump; conversion resolves it
3753                            // against the loop exits (break) exactly like
3754                            // the sibling arm's chain-end goto. The
3755                            // fall-reaches-taken convergence requirement
3756                            // keeps single-exit body tails (Locale/Pattern
3757                            // hair-trigger shapes) on the historical Empty
3758                            // path.
3759                            Region::Goto { target: taken }
3760                        } else {
3761                            Region::Empty
3762                        }
3763                    };
3764                    let else_r = if Some(fall) == follow && !stop.contains(&fall) {
3765                        if crate::dbg_flag!("JCDC_DBG_IF") {
3766                            eprintln!("IF cur={} else Empty (fall==follow {})", cur, fall);
3767                        }
3768                        Region::Empty
3769                    } else if Some(fall) == follow
3770                        && stop.contains(&fall)
3771                        && self.is_terminator_block(fall)
3772                        && !self.loops_stack.is_empty()
3773                        && self.loops_stack[self.loops_stack.len() - 1] != cur
3774                    {
3775                        // Loop-bottom exit test, inverted orientation: the
3776                        // FALL side is the terminator exit (see the taken
3777                        // side).
3778                        Region::Goto { target: fall }
3779                    } else if Some(fall) == follow
3780                        && stop.contains(&fall)
3781                        && !self.is_terminator_block(fall)
3782                    {
3783                        // The "follow" is an enclosing barrier (loop exit):
3784                        // the branch is a jump out, not a fallthrough.
3785                        // Terminator exits stay Empty — the return/throw is
3786                        // emitted after the loop. See the taken side for the
3787                        // claimed-terminator-chain exception.
3788                        if !self.loops_stack.contains(&fall)
3789                            && self.stop_chain_to_claimed_terminator(fall, claimed)
3790                        {
3791                            let mut cstop = bstop.clone();
3792                            cstop.remove(&fall);
3793                            match self.copy_walk(fall, &cstop, active, cur) {
3794                                Some(r) => r,
3795                                None => Region::Goto { target: fall },
3796                            }
3797                        } else {
3798                            Region::Goto { target: fall }
3799                        }
3800                    } else if let Some(absorbed) =
3801                        self.absorb_pure(fall, universe, &bstop, claimed, active)
3802                    {
3803                        absorbed
3804                    } else if Some(taken) == follow
3805                        && !stop.contains(&taken)
3806                        && universe.contains(&taken)
3807                        && !claimed.contains(&taken)
3808                        && !self.is_terminator_block(taken)
3809                        && !self.results[taken].stmts.is_empty()
3810                        && !self.loops_stack.contains(&taken)
3811                        && !(self.is_handler(taken) && active.is_empty())
3812                        && !bstop.contains(&fall)
3813                        && !claimed.contains(&fall)
3814                        && universe.contains(&fall)
3815                        && !(self.is_handler(fall) && active.is_empty())
3816                    {
3817                        // PARKED-ELSE COMPLETION (the systemic empty-arm
3818                        // family): `taken` is the If's follow AND a
3819                        // statement-bearing in-universe block — the
3820                        // if-else-if chain shape javac emits for a shared
3821                        // else body (`if (t1) goto B; if (t2) goto B; A;
3822                        // goto M; B: ...; M: ...`). The then-arm renders
3823                        // Region::Empty (correct: it falls into B's
3824                        // statements parked after the chain), but the ELSE
3825                        // arm's flow continues PAST B — its terminal
3826                        // Goto{M} is elided at conversion (goto_is_last)
3827                        // and the arm falls INTO the parked B statements:
3828                        // double execution on the else path and B skipped
3829                        // on its real predecessors' path (jdk17
3830                        // JarFile.getBytes: the readNBytes A-arm ran
3831                        // straight into readAllBytes; Pattern.clazz's
3832                        // Bound arm, DatagramChannelImpl.receive,
3833                        // SocketChannelImpl, HttpURLConnection carry the
3834                        // same latent shape). Complete the else arm with
3835                        // its OWN copy of the parked chain — exactly what
3836                        // the GOTO-FT/GOTO-CLAIMED shared-tail arrivals
3837                        // already do for claimed targets: per-arrival
3838                        // copies are the bytecode semantics.
3839                        let branch_universe = self.owner_scope(fall, universe);
3840                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
3841                        self.restrict_handler_branch(&mut sub, entry);
3842                        let arm = self.walk(fall, &sub, &bstop, active, claimed, false);
3843                        // BYPASS DISCRIMINATOR: completion is needed only
3844                        // when some arm path's terminal flow skips the
3845                        // parked follow (a trailing Goto targeting
3846                        // something other than `taken`, at any nesting
3847                        // depth — JarFile's A-arm ends at Goto{epilogue
3848                        // 11}, not the parked B 6). Exempt targets are
3849                        // TRUE JUMPS, never fallthrough bypasses: loop
3850                        // headers/exits and switch follows embedded in
3851                        // the arm (resolve to continue/break), the
3852                        // enclosing loops_stack and case_arm_ctx
3853                        // follows, and this scope's barriers (bstop —
3854                        // enclosing loop exits resolve to break at
3855                        // conversion; Files.walkFileTree's TERMINATE
3856                        // arm ends at Goto{loop-exit} and inverted when
3857                        // wrongly completed). NOT exempt: an active
3858                        // group's post-try continuation when the parked
3859                        // follow's statements still sit between the arm
3860                        // and it at render time (JarFile's b11 — the
3861                        // owner emits the follow chain BEFORE the cont,
3862                        // so the bare fallthrough would cross the parked
3863                        // B statements). The ordinary empty-then `if`
3864                        // (every arm path flows into the follow
3865                        // naturally) needs no copy — completing it would
3866                        // duplicate the tail through every such if in
3867                        // the corpus (25-class matrix churn).
3868                        let mut jump_exempt: HashSet<usize> = HashSet::default();
3869                        Region::jump_targets(&arm, &mut jump_exempt);
3870                        jump_exempt.extend(self.loops_stack.iter().copied());
3871                        jump_exempt.extend(self.case_arm_ctx.iter().filter_map(|c| c.1));
3872                        jump_exempt.extend(bstop.iter().copied());
3873                        let bypass = Region::bypasses_exempt(&arm, taken, &jump_exempt);
3874                        // The parked chain: statement blocks flowing from
3875                        // `taken` up to the blocks this arm already copied
3876                        // (their per-arrival copies render inside the arm;
3877                        // the sibling emits the rest).
3878                        let mut chain: HashSet<usize> = HashSet::default();
3879                        if bypass {
3880                            let mut b = taken;
3881                            for _ in 0..64 {
3882                                if chain.contains(&b)
3883                                    || b == cur
3884                                    // bstop holds the follow == the chain
3885                                    // head itself; only deeper barriers
3886                                    // (loop exits) end the chain.
3887                                    || (b != taken && bstop.contains(&b))
3888                                    || self.loops_stack.contains(&b)
3889                                    || self.is_handler(b)
3890                                    || claimed.contains(&b)
3891                                    || self.terminator_writes_final(b)
3892                                {
3893                                    break;
3894                                }
3895                                if arm.iter_parts().iter().any(|p| region_head_block(p) == b) {
3896                                    break;
3897                                }
3898                                let term = self.results[b].term.clone();
3899                                let stmts_ok = !self.results[b].stmts.is_empty()
3900                                    || matches!(term, Term::Fallthrough | Term::Goto);
3901                                if !stmts_ok {
3902                                    break;
3903                                }
3904                                chain.insert(b);
3905                                match term {
3906                                    Term::Fallthrough | Term::Goto
3907                                        if self.cfg.blocks[b].succ.len() == 1 =>
3908                                    {
3909                                        b = self.cfg.blocks[b].succ[0];
3910                                    }
3911                                    _ => break,
3912                                }
3913                            }
3914                        }
3915                        if chain.is_empty() {
3916                            if crate::dbg_flag!("JCDC_DBG_IF") {
3917                                eprintln!(
3918                                    "PARKCHAIN cur={} chain=EMPTY arm={}",
3919                                    cur,
3920                                    region_shape(&arm)
3921                                );
3922                            }
3923                            arm
3924                        } else {
3925                            // The chain walk must stay a SIMPLE tail
3926                            // reproduction: universe = chain + forward
3927                            // flow closure under the FULL parent
3928                            // barriers (stop ∪ bstop minus the chain
3929                            // itself, plus every enclosing loop header —
3930                            // back edges into open loops belong to the
3931                            // loop's own structuring, never to an arm
3932                            // copy: huc getInputStream0's follow 41
3933                            // flows around the auth retry loop and the
3934                            // unbarriered closure restructured
3935                            // Loop(41)+5 regions inside the else arm).
3936                            let mut cu: HashSet<usize> = chain.clone();
3937                            {
3938                                let mut q: Vec<usize> = chain.iter().copied().collect();
3939                                let mut seen: HashSet<usize> = chain.clone();
3940                                while let Some(b) = q.pop() {
3941                                    for &s2 in &self.cfg.blocks[b].succ {
3942                                        if bstop.contains(&s2)
3943                                            || stop.contains(&s2)
3944                                            || self.loops_stack.contains(&s2)
3945                                        {
3946                                            continue;
3947                                        }
3948                                        if seen.insert(s2) {
3949                                            cu.insert(s2);
3950                                            q.push(s2);
3951                                        }
3952                                    }
3953                                }
3954                            }
3955                            let mut cstop: HashSet<usize> = bstop.union(stop).copied().collect();
3956                            cstop.extend(self.loops_stack.iter().copied());
3957                            cstop.retain(|x| !chain.contains(x));
3958                            // SCRATCH claimed set: a rejected completion
3959                            // must leave no claims behind (an abandoned
3960                            // exploratory walk claiming the follow would
3961                            // degrade the parent's parked-sibling arrival
3962                            // to a bare elided Goto — the very statement
3963                            // loss the completion exists to prevent).
3964                            // copied_tails must roll back too: conversion's
3965                            // reaches_copy_tail elides RawGotos targeting
3966                            // copy-walked tails, and the exploratory walk's
3967                            // copy_walks poisoned that set for the parent's
3968                            // own (identical-shape) walk.
3969                            let saved_claims = claimed.clone();
3970                            let mut scratch = claimed.clone();
3971                            let saved_tails = self.copied_tails.clone();
3972                            let saved_groups = self.structuring_groups.borrow().clone();
3973                            let saved_loops = self.loops_stack.clone();
3974                            let saved_switch_depth = self.switch_depth;
3975                            let saved_case_ctx = self.case_arm_ctx.clone();
3976                            let saved_walk_depth = self.walk_depth;
3977                            let taken_r = if self.take_copy_ticket() {
3978                                self.walk(taken, &cu, &cstop, active, &mut scratch, false)
3979                            } else {
3980                                Region::Empty
3981                            };
3982                            if crate::dbg_flag!("JCDC_DBG_IF") {
3983                                eprintln!(
3984                                    "PARKCHAIN cur={} chain={:?} cu={} cstop={:?} taken_r={}",
3985                                    cur,
3986                                    chain,
3987                                    cu.len(),
3988                                    cstop,
3989                                    region_shape(&taken_r)
3990                                );
3991                            }
3992                            // Accept only SIMPLE completions (linear
3993                            // blocks, copied tails, elision-safe gotos,
3994                            // plain if-diamonds). A Loop/Try/Switch in
3995                            // the result means the parked flow is
3996                            // structural territory the PARENT walk must
3997                            // own — back off and keep the historical
3998                            // shape (adding a copy anyway lost huc's
3999                            // serverAuthentication.addToCache: the arm
4000                            // claimed the follow, the parent's arrival
4001                            // degraded to a bare elided Goto).
4002                            fn simple_completion(r: &Region) -> bool {
4003                                match r {
4004                                    Region::Basic { .. }
4005                                    | Region::CopyStmts { .. }
4006                                    | Region::Empty
4007                                    | Region::Goto { .. } => true,
4008                                    Region::Seq(v) => v.iter().all(simple_completion),
4009                                    Region::If { then_r, else_r, .. } => {
4010                                        simple_completion(then_r) && simple_completion(else_r)
4011                                    }
4012                                    _ => false,
4013                                }
4014                            }
4015                            // CONTINUATION COHERENCE: the chain copy's
4016                            // own walk must end where the bypassing arm
4017                            // path jumps (its continuation walk starts at
4018                            // the bypass target). When they diverge (huc
4019                            // getInputStream0: bypass Goto{117} but the
4020                            // parked 109-chain continues into the auth
4021                            // retry loop territory), splicing the copy
4022                            // into the arm flips conv's goto_is_last for
4023                            // the arm's internal gotos (a nested elided
4024                            // Goto{108} materialized as Continue) and
4025                            // prune_unreachable then ate the tail copy
4026                            // (serverAuthentication.addToCache lost).
4027                            let mut bts: Vec<usize> = Vec::new();
4028                            let coherent = {
4029                                fn chain_cont(r: &Region) -> Option<usize> {
4030                                    match r {
4031                                        Region::Goto { target } => Some(*target),
4032                                        Region::Seq(v) => v.last().and_then(chain_cont),
4033                                        _ => None,
4034                                    }
4035                                }
4036                                fn bypass_targets(r: &Region, out: &mut Vec<usize>) {
4037                                    match r {
4038                                        Region::Goto { target } => out.push(*target),
4039                                        Region::Seq(v) => {
4040                                            if let Some(l) = v.last() {
4041                                                bypass_targets(l, out);
4042                                            }
4043                                        }
4044                                        Region::If { then_r, else_r, .. } => {
4045                                            bypass_targets(then_r, out);
4046                                            bypass_targets(else_r, out);
4047                                        }
4048                                        Region::Try { body, catches, .. } => {
4049                                            bypass_targets(body, out);
4050                                            for (_, _, h) in catches {
4051                                                bypass_targets(h, out);
4052                                            }
4053                                        }
4054                                        _ => {}
4055                                    }
4056                                }
4057                                bypass_targets(&arm, &mut bts);
4058                                // A copy ending in a Goto to somewhere
4059                                // OTHER than the bypass target re-routes
4060                                // the path away from the arm's real
4061                                // continuation (huc). A copy with NO
4062                                // trailing Goto (it ends inside a
4063                                // terminator or a full tail walk past
4064                                // the target — JarFile's chain flows
4065                                // through b11 into the close/return
4066                                // epilogue) preserves it.
4067                                //
4068                                // REACHABILITY RELAXATION: a bypass
4069                                // target that flows to the copy's own
4070                                // continuation WITHOUT crossing the
4071                                // chain barriers shares that
4072                                // continuation — coherent when the arm
4073                                // gets a per-arrival fill (routed2
4074                                // below). jdk11 HttpURLConnection
4075                                // getInputStream0: arm-tail Goto{105}
4076                                // flows 105→106→114 == chain_cont; the
4077                                // strict equality refused the copy and
4078                                // the short-clone arm fell through the
4079                                // sibling else into getRootPath (the
4080                                // source skips it on the path-
4081                                // shortening branch).
4082                                match chain_cont(&taken_r) {
4083                                    Some(c) => bts.iter().all(|t| {
4084                                        *t == c || bypass_flows_to(self.cfg, *t, c, &cstop)
4085                                    }),
4086                                    None => true,
4087                                }
4088                            };
4089                            // PER-BYPASS-TARGET ROUTING (the dci receive
4090                            // misroute cure): when the arm holds exit
4091                            // Gotos to chain-MID blocks (b12/b13's
4092                            // `goto 15` = the SKIP path around the parked
4093                            // `sender = sourceSocketAddress()` block 14),
4094                            // those Gotos elide at conversion (15 is an
4095                            // inner If's follow) and fall into the APPENDED
4096                            // copy's head — the skip path executed the
4097                            // parked SET. Slice the copy at each bypass
4098                            // target's block and splice it over that
4099                            // target's Goto, so every arm path enters the
4100                            // chain exactly where its bytecode jumped:
4101                            // skip paths get the from-15 suffix, the
4102                            // fall-into path keeps the full from-14 copy.
4103                            // Only offered when the appended copy has no
4104                            // trailing Goto of its own (chain_cont=None —
4105                            // a trailing-Goto copy must keep the strict
4106                            // all-bts-equal-cont coherence above).
4107                            if crate::dbg_flag!("JCDC_DBG_IF") {
4108                                eprintln!(
4109                                    "PARKC-ARM cur={} taken={} bts={:?} arm={}",
4110                                    cur,
4111                                    taken,
4112                                    bts,
4113                                    region_shape(&arm)
4114                                );
4115                            }
4116                            #[allow(clippy::too_many_arguments)] // structuring context travels as one bundle
4117                            fn fill_bypass(
4118                                st: &mut Structurer,
4119                                r: &mut Region,
4120                                copy: &Region,
4121                                taken: usize,
4122                                cu2: &HashSet<usize>,
4123                                cstop: &HashSet<usize>,
4124                                active: &[usize],
4125                                claimed: &HashSet<usize>,
4126                                filled: &mut usize,
4127                                failed: &mut bool,
4128                                top: bool,
4129                            ) {
4130                                match r {
4131                                    Region::Seq(v) => {
4132                                        let n = v.len();
4133                                        for i in 0..n {
4134                                            let is_last = i + 1 == n;
4135                                            if is_last {
4136                                                let goto_t = match &v[i] {
4137                                                    Region::Goto { target } => Some(*target),
4138                                                    _ => None,
4139                                                };
4140                                                if let Some(t) = goto_t {
4141                                                    if t == taken && !top {
4142                                                        // Deep If-arm tail jumping to the
4143                                                        // chain head: own copy of the chain.
4144                                                        if st.take_copy_ticket() {
4145                                                            v[i] = copy.clone();
4146                                                            *filled += 1;
4147                                                        }
4148                                                        continue;
4149                                                    }
4150                                                    if t != taken
4151                                                        && bypass_flows_to(st.cfg, t, taken, cstop)
4152                                                    {
4153                                                        // Arm tail flowing into the chain:
4154                                                        // fresh walk from t; drop a
4155                                                        // preceding CopyStmts{t} (the fresh
4156                                                        // Basic(t) supersedes it).
4157                                                        let mut sc = claimed.clone();
4158                                                        let sub = if st.take_copy_ticket() {
4159                                                            st.walk(
4160                                                                t, cu2, cstop, active, &mut sc,
4161                                                                false,
4162                                                            )
4163                                                        } else {
4164                                                            Region::Empty
4165                                                        };
4166                                                        if !matches!(sub, Region::Empty)
4167                                                            && simple_completion_local2(&sub)
4168                                                            && region_terminates_ex(
4169                                                                &sub,
4170                                                                st.results,
4171                                                                &[],
4172                                                            )
4173                                                        {
4174                                                            v[i] = sub;
4175                                                            if n >= 2 {
4176                                                                if let Region::CopyStmts { block } =
4177                                                                    &v[i - 1]
4178                                                                {
4179                                                                    if *block == t {
4180                                                                        v[i - 1] = Region::Empty;
4181                                                                    }
4182                                                                }
4183                                                            }
4184                                                            *filled += 1;
4185                                                        } else {
4186                                                            *failed = true;
4187                                                        }
4188                                                        continue;
4189                                                    }
4190                                                }
4191                                            }
4192                                            fill_bypass(
4193                                                st, &mut v[i], copy, taken, cu2, cstop, active,
4194                                                claimed, filled, failed, false,
4195                                            );
4196                                        }
4197                                    }
4198                                    Region::If { then_r, else_r, .. } => {
4199                                        fill_bypass(
4200                                            st, then_r, copy, taken, cu2, cstop, active, claimed,
4201                                            filled, failed, false,
4202                                        );
4203                                        fill_bypass(
4204                                            st, else_r, copy, taken, cu2, cstop, active, claimed,
4205                                            filled, failed, false,
4206                                        );
4207                                    }
4208                                    Region::Try { body, catches, .. } => {
4209                                        fill_bypass(
4210                                            st, body, copy, taken, cu2, cstop, active, claimed,
4211                                            filled, failed, false,
4212                                        );
4213                                        for (_, _, h) in catches.iter_mut() {
4214                                            fill_bypass(
4215                                                st, h, copy, taken, cu2, cstop, active, claimed,
4216                                                filled, failed, false,
4217                                            );
4218                                        }
4219                                    }
4220                                    _ => {}
4221                                }
4222                            }
4223                            fn simple_completion_local2(r: &Region) -> bool {
4224                                match r {
4225                                    Region::Basic { .. }
4226                                    | Region::CopyStmts { .. }
4227                                    | Region::Empty
4228                                    | Region::Goto { .. } => true,
4229                                    Region::Seq(v) => v.iter().all(simple_completion_local2),
4230                                    Region::If { then_r, else_r, .. } => {
4231                                        simple_completion_local2(then_r)
4232                                            && simple_completion_local2(else_r)
4233                                    }
4234                                    _ => false,
4235                                }
4236                            }
4237                            let routed = {
4238                                coherent
4239                                    && !matches!(taken_r, Region::Empty)
4240                                    // The copy must end abruptly (its own
4241                                    // return/throw): a filled skip arm must
4242                                    // not fall out of the copy into
4243                                    // whatever follows the If.
4244                                    && region_terminates_ex(&taken_r, self.results, &[])
4245                                    && {
4246                                        fn has_deep_empty(r: &Region, top: bool) -> bool {
4247                                            match r {
4248                                                Region::Empty => !top,
4249                                                Region::Seq(v) => {
4250                                                    let n = v.len();
4251                                                    v.iter().enumerate().any(|(k, x)| {
4252                                                        has_deep_empty(x, top && k + 1 == n)
4253                                                    })
4254                                                }
4255                                                Region::If { then_r, else_r, .. } => {
4256                                                    has_deep_empty(then_r, false)
4257                                                        || has_deep_empty(else_r, false)
4258                                                }
4259                                                Region::Try { body, catches, .. } => {
4260                                                    has_deep_empty(body, false)
4261                                                        || catches.iter().any(|(_, _, h)| {
4262                                                            has_deep_empty(h, false)
4263                                                        })
4264                                                }
4265                                                _ => false,
4266                                            }
4267                                        }
4268                                        has_deep_empty(&arm, true)
4269                                    }
4270                            };
4271                            if matches!(taken_r, Region::Empty)
4272                                || !simple_completion(&taken_r)
4273                                || !coherent
4274                            {
4275                                // Full state rollback: the exploratory walk
4276                                // mutates Structurer state beyond `claimed`
4277                                // (copied_tails feeds conversion's
4278                                // reaches_copy_tail elision; the group/loop/
4279                                // switch stacks feed scope decisions) — huc
4280                                // getInputStream0 lost addToCache with only
4281                                // claimed+tails restored.
4282                                self.copied_tails = saved_tails;
4283                                *claimed = saved_claims;
4284                                *self.structuring_groups.borrow_mut() = saved_groups;
4285                                self.loops_stack = saved_loops;
4286                                self.switch_depth = saved_switch_depth;
4287                                self.case_arm_ctx = saved_case_ctx;
4288                                self.walk_depth = saved_walk_depth;
4289                                arm
4290                            } else {
4291                                claimed.extend(scratch.iter().copied());
4292                                // EMPTY-ARM ROUTING (the dci receive
4293                                // misroute cure): the bypass paths render
4294                                // as EMPTY If-arms (their Goto to the
4295                                // skip target elides at conversion because
4296                                // the target is an inner If's follow) and
4297                                // fall through the arm tail into the
4298                                // appended copy's head — executing the
4299                                // parked SET they must skip (jdk26
4300                                // DatagramChannelImpl.receive: n<0 and
4301                                // n==0&&!isOpen returned
4302                                // sourceSocketAddress() instead of the
4303                                // null sender). Give every arm-level
4304                                // Empty its own copy of the parked
4305                                // follow: the fall-into path then takes
4306                                // the appended copy and each skip path
4307                                // takes its in-arm copy — per-arrival
4308                                // copies, the codebase's standard
4309                                // shared-tail discipline. Gated: the
4310                                // follow must be claimed or parked (its
4311                                // canonical emission already exists, so
4312                                // the copies are per-arrival
4313                                // materializations, not double emission).
4314                                let mut arm = arm;
4315                                // SIBLING-INTERIOR ROUTING (the huc
4316                                // short-clone-arm cure): with relaxed
4317                                // coherence the arm may hold deep Goto
4318                                // tails whose target is the chain head
4319                                // (an If-arm's last statement jumping to
4320                                // `taken`) or flows to the chain's
4321                                // continuation (the arm's own back-edge-
4322                                // stub tail). Those Gotos would elide and
4323                                // fall into the WRONG sibling emission —
4324                                // give each its own per-arrival copy:
4325                                // Goto{taken} gets the chain copy clone;
4326                                // a flow-to-continuation tail gets a fresh
4327                                // scratch walk from its target (replacing
4328                                // a preceding CopyStmts pair) validated by
4329                                // simple completion + abrupt termination.
4330                                let routed2 = coherent
4331                                    && !routed
4332                                    && !matches!(taken_r, Region::Empty)
4333                                    && simple_completion(&taken_r);
4334                                if routed2 {
4335                                    let mut cu2 = cu.clone();
4336                                    for t in bts.iter() {
4337                                        cu2.insert(*t);
4338                                        for x in reachable_within(self.cfg, *t, &cstop) {
4339                                            cu2.insert(x);
4340                                        }
4341                                    }
4342                                    let mut filled2 = 0usize;
4343                                    let mut failed2 = false;
4344                                    fill_bypass(
4345                                        self,
4346                                        &mut arm,
4347                                        &taken_r,
4348                                        taken,
4349                                        &cu2,
4350                                        &cstop,
4351                                        active,
4352                                        claimed,
4353                                        &mut filled2,
4354                                        &mut failed2,
4355                                        true,
4356                                    );
4357                                    if crate::dbg_flag!("JCDC_DBG_IF") {
4358                                        eprintln!(
4359                                            "PARKCHAIN-ROUTE2 cur={} filled={} failed={}",
4360                                            cur, filled2, failed2
4361                                        );
4362                                    }
4363                                }
4364                                if routed {
4365                                    // SKIP-PATH ROUTING: a follow-EMPTY If
4366                                    // arm whose target is NOT the parked
4367                                    // chain head is a skip path around the
4368                                    // head (dci receive: `if (n != 0) {}`
4369                                    // and `else if (!isOpen()) {}` must
4370                                    // reach `return sender` WITHOUT
4371                                    // executing `sender =
4372                                    // sourceSocketAddress()`). Give each
4373                                    // such Empty its own per-arrival copy:
4374                                    // slice the chain copy from that
4375                                    // target's part when the copy's parts
4376                                    // expose it, else re-walk the target
4377                                    // on a scratch claim set. The
4378                                    // fall-INTO Empty (target == chain
4379                                    // head) stays empty and drops into the
4380                                    // appended copy.
4381                                    #[allow(clippy::too_many_arguments)] // structuring context travels as one bundle
4382                                    fn fill_skip_empties(
4383                                        st: &mut Structurer,
4384                                        r: &mut Region,
4385                                        copy: &Region,
4386                                        taken: usize,
4387                                        cu: &HashSet<usize>,
4388                                        cstop: &HashSet<usize>,
4389                                        active: &[usize],
4390                                        claimed: &HashSet<usize>,
4391                                        filled: &mut usize,
4392                                    ) {
4393                                        match r {
4394                                            Region::Seq(v) => {
4395                                                let n = v.len();
4396                                                for (i, x) in v.iter_mut().enumerate() {
4397                                                    // A Seq's LAST element
4398                                                    // is the fall-through
4399                                                    // tail into the
4400                                                    // appended copy — never
4401                                                    // replace it.
4402                                                    if i + 1 == n {
4403                                                        if !matches!(x, Region::Goto { .. }) {
4404                                                            fill_skip_empties(
4405                                                                st, x, copy, taken, cu, cstop,
4406                                                                active, claimed, filled,
4407                                                            );
4408                                                        }
4409                                                        continue;
4410                                                    }
4411                                                    fill_skip_empties(
4412                                                        st, x, copy, taken, cu, cstop, active,
4413                                                        claimed, filled,
4414                                                    );
4415                                                }
4416                                            }
4417                                            Region::Goto { target } if *target != taken => {
4418                                                // An elided follow-merge
4419                                                // jump to a skip target
4420                                                // (b13's `goto 15` rendered
4421                                                // empty because 15 is the
4422                                                // inner If's follow): give
4423                                                // it the suffix copy too.
4424                                                let sl = match copy {
4425                                                    Region::Seq(v) => v
4426                                                        .iter()
4427                                                        .position(|p| {
4428                                                            crate::structure::region_head_block(p)
4429                                                                == *target
4430                                                        })
4431                                                        .map(|i| {
4432                                                            if v.len() - i == 1 {
4433                                                                v[i].clone()
4434                                                            } else {
4435                                                                Region::Seq(v[i..].to_vec())
4436                                                            }
4437                                                        }),
4438                                                    other => {
4439                                                        if crate::structure::region_head_block(
4440                                                            other,
4441                                                        ) == *target
4442                                                        {
4443                                                            Some(other.clone())
4444                                                        } else {
4445                                                            None
4446                                                        }
4447                                                    }
4448                                                };
4449                                                let suffix = match sl {
4450                                                    Some(x) => Some(x),
4451                                                    None => {
4452                                                        let mut sc = claimed.clone();
4453                                                        let sub = if st.take_copy_ticket() {
4454                                                            st.walk(
4455                                                                *target, cu, cstop, active,
4456                                                                &mut sc, false,
4457                                                            )
4458                                                        } else {
4459                                                            Region::Empty
4460                                                        };
4461                                                        if !matches!(sub, Region::Empty)
4462                                                            && simple_completion_local(&sub)
4463                                                            && region_terminates_ex(
4464                                                                &sub,
4465                                                                st.results,
4466                                                                &[],
4467                                                            )
4468                                                        {
4469                                                            Some(sub)
4470                                                        } else {
4471                                                            None
4472                                                        }
4473                                                    }
4474                                                };
4475                                                if let Some(sfx) = suffix {
4476                                                    *r = sfx;
4477                                                    *filled += 1;
4478                                                }
4479                                            }
4480                                            Region::If {
4481                                                block,
4482                                                then_r,
4483                                                else_r,
4484                                                ..
4485                                            } => {
4486                                                let succ = st.cfg.blocks[*block].succ.clone();
4487                                                if succ.len() == 2 {
4488                                                    let pairs = [
4489                                                        (succ[1], then_r.as_mut()),
4490                                                        (succ[0], else_r.as_mut()),
4491                                                    ];
4492                                                    for (tgt, armr) in pairs {
4493                                                        if matches!(*armr, Region::Empty) {
4494                                                            if tgt == taken {
4495                                                                continue; // fall-into path
4496                                                            }
4497                                                            // suffix copy from tgt
4498                                                            let sl = match copy {
4499                                                                Region::Seq(v) => v
4500                                                                    .iter()
4501                                                                    .position(|p| {
4502                                                                        crate::structure::region_head_block(p) == tgt
4503                                                                    })
4504                                                                    .map(|i| {
4505                                                                        if v.len() - i == 1 {
4506                                                                            v[i].clone()
4507                                                                        } else {
4508                                                                            Region::Seq(v[i..].to_vec())
4509                                                                        }
4510                                                                    }),
4511                                                                other => {
4512                                                                    if crate::structure::region_head_block(other) == tgt {
4513                                                                        Some(other.clone())
4514                                                                    } else {
4515                                                                        None
4516                                                                    }
4517                                                                }
4518                                                            };
4519                                                            let suffix = match sl {
4520                                                                Some(x) => Some(x),
4521                                                                None => {
4522                                                                    let mut sc = claimed.clone();
4523                                                                    let sub = if st
4524                                                                        .take_copy_ticket()
4525                                                                    {
4526                                                                        st.walk(
4527                                                                            tgt, cu, cstop, active,
4528                                                                            &mut sc, false,
4529                                                                        )
4530                                                                    } else {
4531                                                                        Region::Empty
4532                                                                    };
4533                                                                    if !matches!(sub, Region::Empty)
4534                                                                        && simple_completion_local(
4535                                                                            &sub,
4536                                                                        )
4537                                                                        && region_terminates_ex(
4538                                                                            &sub,
4539                                                                            st.results,
4540                                                                            &[],
4541                                                                        )
4542                                                                    {
4543                                                                        Some(sub)
4544                                                                    } else {
4545                                                                        None
4546                                                                    }
4547                                                                }
4548                                                            };
4549                                                            if let Some(sfx) = suffix {
4550                                                                *armr = sfx;
4551                                                                *filled += 1;
4552                                                            }
4553                                                        } else {
4554                                                            fill_skip_empties(
4555                                                                st, armr, copy, taken, cu, cstop,
4556                                                                active, claimed, filled,
4557                                                            );
4558                                                        }
4559                                                    }
4560                                                }
4561                                            }
4562                                            Region::Try { body, catches, .. } => {
4563                                                fill_skip_empties(
4564                                                    st, body, copy, taken, cu, cstop, active,
4565                                                    claimed, filled,
4566                                                );
4567                                                for (_, _, h) in catches.iter_mut() {
4568                                                    fill_skip_empties(
4569                                                        st, h, copy, taken, cu, cstop, active,
4570                                                        claimed, filled,
4571                                                    );
4572                                                }
4573                                            }
4574                                            _ => {}
4575                                        }
4576                                    }
4577                                    fn simple_completion_local(r: &Region) -> bool {
4578                                        match r {
4579                                            Region::Basic { .. }
4580                                            | Region::CopyStmts { .. }
4581                                            | Region::Empty
4582                                            | Region::Goto { .. } => true,
4583                                            Region::Seq(v) => v.iter().all(simple_completion_local),
4584                                            Region::If { then_r, else_r, .. } => {
4585                                                simple_completion_local(then_r)
4586                                                    && simple_completion_local(else_r)
4587                                            }
4588                                            _ => false,
4589                                        }
4590                                    }
4591                                    let mut filled = 0usize;
4592                                    fill_skip_empties(
4593                                        self,
4594                                        &mut arm,
4595                                        &taken_r,
4596                                        taken,
4597                                        &cu,
4598                                        &cstop,
4599                                        active,
4600                                        claimed,
4601                                        &mut filled,
4602                                    );
4603                                    if crate::dbg_flag!("JCDC_DBG_IF") {
4604                                        eprintln!(
4605                                            "PARKCHAIN-ROUTE cur={} filled={} skip-empties",
4606                                            cur, filled
4607                                        );
4608                                    }
4609                                }
4610                                Region::Seq(vec![arm, taken_r])
4611                            }
4612                        }
4613                    } else if universe.contains(&fall)
4614                        && !bstop.contains(&fall)
4615                        && !claimed.contains(&fall)
4616                        && !(self.is_handler(fall) && active.is_empty())
4617                    {
4618                        let branch_universe = self.owner_scope(fall, universe);
4619                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
4620                        self.restrict_handler_branch(&mut sub, entry);
4621                        if crate::dbg_flag!("JCDC_DBG_IF") {
4622                            eprintln!("IF cur={} else walk fall={} sub={:?}", cur, fall, sub);
4623                        }
4624                        self.walk(fall, &sub, &bstop, active, claimed, false)
4625                    } else if self.is_terminator_block(fall)
4626                        && !stop.contains(&fall)
4627                        && !self.terminator_writes_final(fall)
4628                    {
4629                        Region::CopyStmts { block: fall }
4630                    } else if claimed.contains(&fall) {
4631                        if !stop.contains(&fall)
4632                            && !bstop.contains(&fall)
4633                            && !self.loops_stack.contains(&fall)
4634                            && !self.terminator_writes_final(fall)
4635                            && !self.shared_tail_confluence(fall)
4636                        {
4637                            if let Some(r) = self.copy_walk(fall, &bstop, active, cur) {
4638                                r
4639                            } else {
4640                                if crate::dbg_flag!("JCDC_DBG_IF") {
4641                                    eprintln!("IF cur={} else Goto(claimed) fall={}", cur, fall);
4642                                }
4643                                Region::Goto { target: fall }
4644                            }
4645                        } else {
4646                            if crate::dbg_flag!("JCDC_DBG_IF") {
4647                                eprintln!("IF cur={} else Goto(claimed) fall={}", cur, fall);
4648                            }
4649                            Region::Goto { target: fall }
4650                        }
4651                    } else if self.is_terminator_block(fall)
4652                        && !stop.contains(&fall)
4653                        && !self.terminator_writes_final(fall)
4654                    {
4655                        Region::CopyStmts { block: fall }
4656                    } else if stop.contains(&fall) && !self.is_terminator_block(fall) {
4657                        // Jump to an enclosing loop's exit (or other
4658                        // barrier): keep it as a Goto so conversion
4659                        // resolves it to `break` — unless the exit chains
4660                        // into an already-claimed terminator (see the
4661                        // taken side; jdk26 DecimalFormat guarded case).
4662                        if !self.loops_stack.contains(&fall)
4663                            && self.stop_chain_to_claimed_terminator(fall, claimed)
4664                        {
4665                            let mut cstop = bstop.clone();
4666                            cstop.remove(&fall);
4667                            match self.copy_walk(fall, &cstop, active, cur) {
4668                                Some(r) => r,
4669                                None => Region::Goto { target: fall },
4670                            }
4671                        } else {
4672                            Region::Goto { target: fall }
4673                        }
4674                    } else if self.is_terminator_block(fall)
4675                        && stop.contains(&fall)
4676                        && self.is_terminator_block(taken)
4677                        && stop.contains(&taken)
4678                    {
4679                        // Dual-terminator stop exits: see the taken side
4680                        // (jdk11 ObjectInputStream.readSerialData).
4681                        self.dual_terminator_branch(fall, active)
4682                    } else if !stop.contains(&fall)
4683                        && !claimed.contains(&fall)
4684                        && self.cfg.exc_edges.iter().any(|e| e.from == fall)
4685                    {
4686                        // Protected branch target outside the scope — see
4687                        // the taken side (getOutputStream p==null arm).
4688                        let branch_universe = self.owner_scope(fall, universe);
4689                        let mut sub = self.sub_scope(fall, &branch_universe, &bstop, claimed);
4690                        self.restrict_handler_branch(&mut sub, entry);
4691                        self.walk(fall, &sub, &bstop, active, claimed, false)
4692                    } else {
4693                        if crate::dbg_flag!("JCDC_DBG_IF") {
4694                            eprintln!(
4695                                "IF cur={} else Empty fall={} univ={} bstop={} handler={}",
4696                                cur,
4697                                fall,
4698                                universe.contains(&fall),
4699                                bstop.contains(&fall),
4700                                self.is_handler(fall)
4701                            );
4702                        }
4703                        Region::Empty
4704                    };
4705                    // Value-diamond detection: both branches are pure
4706                    // (no statements, no exits) and leave exactly one value.
4707                    // A branch may be `Basic` or `Seq[Basic, Goto{follow}]`
4708                    // (javac often uses an explicit goto to the merge).
4709                    fn pure_block(
4710                        r: &crate::ir::build::BlockResult,
4711                        succs: &[usize],
4712                        follow: Option<usize>,
4713                    ) -> bool {
4714                        r.stmts.is_empty()
4715                            && r.out_stack.len() == 1
4716                            && match &r.term {
4717                                crate::ir::build::Term::Fallthrough => true,
4718                                crate::ir::build::Term::Goto => {
4719                                    follow.map(|f| succs == [f]).unwrap_or(false)
4720                                }
4721                                _ => false,
4722                            }
4723                    }
4724                    fn region_pure_block(
4725                        rg: &Region,
4726                        results: &[BlockResult],
4727                        cfg: &Cfg,
4728                        follow: Option<usize>,
4729                    ) -> Option<usize> {
4730                        match rg {
4731                            Region::Basic { block } => {
4732                                if pure_block(&results[*block], &cfg.blocks[*block].succ, follow) {
4733                                    Some(*block)
4734                                } else {
4735                                    None
4736                                }
4737                            }
4738                            Region::Seq(v) if v.len() == 2 => {
4739                                if let (Region::Basic { block }, Region::Goto { target }) =
4740                                    (&v[0], &v[1])
4741                                {
4742                                    if follow == Some(*target)
4743                                        && pure_block(
4744                                            &results[*block],
4745                                            &cfg.blocks[*block].succ,
4746                                            follow,
4747                                        )
4748                                    {
4749                                        return Some(*block);
4750                                    }
4751                                }
4752                                None
4753                            }
4754                            _ => None,
4755                        }
4756                    }
4757                    let ternary = if follow.is_some() {
4758                        match (
4759                            region_pure_block(&then_r, self.results, self.cfg, follow),
4760                            region_pure_block(&else_r, self.results, self.cfg, follow),
4761                        ) {
4762                            (Some(tb), Some(fb)) => Some((
4763                                self.results[tb].out_stack[0].clone(),
4764                                self.results[fb].out_stack[0].clone(),
4765                            )),
4766                            _ => None,
4767                        }
4768                    } else {
4769                        None
4770                    };
4771                    if ternary.is_some() {
4772                        // consume the branch blocks (Basic or Seq[Basic,Goto])
4773                        for rg in [&then_r, &else_r] {
4774                            match rg {
4775                                Region::Basic { block } => {
4776                                    claimed.insert(*block);
4777                                }
4778                                Region::Seq(v) => {
4779                                    for x in v {
4780                                        if let Region::Basic { block } = x {
4781                                            claimed.insert(*block);
4782                                        }
4783                                    }
4784                                }
4785                                _ => {}
4786                            }
4787                        }
4788                    }
4789                    parts.push(Region::If {
4790                        block: cur,
4791                        cond,
4792                        then_r: Box::new(then_r),
4793                        else_r: Box::new(else_r),
4794                        follow,
4795                        ternary,
4796                    });
4797                    match follow {
4798                        Some(f)
4799                            if universe.contains(&f)
4800                                && !stop.contains(&f)
4801                                && !claimed.contains(&f) =>
4802                        {
4803                            cur = f;
4804                            continue;
4805                        }
4806                        Some(f)
4807                            if universe.contains(&f)
4808                                && !stop.contains(&f)
4809                                && !self.loops_stack.contains(&f)
4810                                && !can_reach_cfg(self.cfg, f, cur, 4096) =>
4811                        {
4812                            // The merge block was already structured inside
4813                            // one branch (a shared tail). Every branch exit
4814                            // re-executes the whole tail, so re-walk it
4815                            // with a fresh claimed set (a structural copy)
4816                            // and splice the region in here.
4817                            //
4818                            // UNLESS the follow is a loop BACKEDGE STUB
4819                            // (`reset(); goto head`): the fresh walk would
4820                            // re-walk the ENTIRE loop from the header
4821                            // (fresh claimed knows nothing) and paste the
4822                            // loop body into this branch (jdk11 KeyStore
4823                            // .getInstance(File)'s IOException catch
4824                            // swallowed the provider loop with an
4825                            // unprotected Security.getImpl — 未报告的异常
4826                            // 错误NoSuchProviderException). Copy the stub's
4827                            // own statements and attach an explicit Goto
4828                            // to the header — conversion resolves it to
4829                            // `continue`; a fall-through must NOT reach
4830                            // the head (it would re-run the loop test).
4831                            let stub_header = if matches!(self.results[f].term, Term::Goto)
4832                                && self.cfg.blocks[f].succ.len() == 1
4833                            {
4834                                let t = self.cfg.blocks[f].succ[0];
4835                                self.loops_stack
4836                                    .iter()
4837                                    .chain(self.sese_loop_headers.iter())
4838                                    .find(|&&h| h == t || self.is_stmt_free_chain_to_block(t, h))
4839                                    .copied()
4840                            } else {
4841                                None
4842                            };
4843                            if let Some(h) = stub_header {
4844                                parts.push(Region::Seq(vec![
4845                                    Region::CopyStmts { block: f },
4846                                    Region::Goto { target: h },
4847                                ]));
4848                                break;
4849                            }
4850                            let mut barriers = stop.clone();
4851                            barriers.insert(cur);
4852                            let tail_universe = reachable_within(self.cfg, f, &barriers);
4853                            let mut fresh: HashSet<usize> = HashSet::default();
4854                            let r = self.walk(f, &tail_universe, stop, active, &mut fresh, true);
4855                            self.copied_tails.insert(f);
4856                            parts.push(r);
4857                            break;
4858                        }
4859                        _ => break,
4860                    }
4861                }
4862                Term::Switch {
4863                    selector,
4864                    targets,
4865                    default: _,
4866                } => {
4867                    // A switch's follow is the confluence of the cases that
4868                    // do NOT terminate (return/throw). `immediate_postdom`
4869                    // would yield None whenever any case always exits (e.g.
4870                    // a throwing default), losing break resolution for the
4871                    // remaining cases.
4872                    let follow = self
4873                        .postdom_ipdom(universe, cur)
4874                        .or_else(|| self.switch_follow(cur, universe))
4875                        // Inside a copied/shared-tail region the confluence
4876                        // sits in `stop` (the enclosing flow owns it), so
4877                        // the universe-bound searches miss it and every
4878                        // case's terminal goto degrades to fall-through
4879                        // (jdk11 Calendar.createCalendar catch-copy switch
4880                        // lost its breaks). A stop block that ALL live
4881                        // cases escape to is still the logical follow for
4882                        // break resolution.
4883                        .or_else(|| self.switch_stop_confluence(cur, universe, stop));
4884                    // CROSSING follow: a stop-confluence of a NESTED
4885                    // switch belongs to an enclosing construct (jdk26 xml
4886                    // impl.Parser.bappend: the inner switch(ch)'s cases
4887                    // break to the OUTER switch(mode)'s follow — passing
4888                    // it as the inner follow resolved the default's goto
4889                    // to a bare `break` binding the inner switch, case
4890                    // 105 fell through into case 99 and the outer tail
4891                    // went unreachable). With follow=None the conversion's
4892                    // labeled-break logic binds it to the outer switch
4893                    // (`break L1`); the confluence still bounds the case
4894                    // walks via `stop`. Calendar (depth 0) keeps Some(f).
4895                    // EXCEPTION — when every route past the built switch
4896                    // lands on the confluence anyway (the switch is the
4897                    // last construct of an abruptly-completing outer case
4898                    // arm), the plain inner-break binding is equivalent
4899                    // AND renderable: the labeled one makes the inner
4900                    // switch non-completing and the outer case's appended
4901                    // `break;` unreachable (ClassPrinterImpl.toYaml/toXml
4902                    // 无法访问的语句 x2 trees). Rebuild with the follow.
4903                    let crossing = match follow {
4904                        Some(f) if stop.contains(&f) && self.switch_depth > 0 => Some(f),
4905                        _ => None,
4906                    };
4907                    let region_follow = match crossing {
4908                        Some(_) => None,
4909                        None => follow,
4910                    };
4911                    self.switch_depth += 1;
4912                    let claimed_save = claimed.clone();
4913                    let mut sw = self.structure_switch(
4914                        cur,
4915                        selector.clone(),
4916                        &targets,
4917                        universe,
4918                        stop,
4919                        region_follow,
4920                        active,
4921                        claimed,
4922                    );
4923                    if let Some(f) = crossing {
4924                        // What this scope does RIGHT AFTER the switch must
4925                        // already land on `f`: either the scope's earlier
4926                        // parts complete abruptly (nothing can fall through
4927                        // the switch), or the walk's own next-step rule
4928                        // continues exactly at `f` (an in-universe follow).
4929                        // Anything else means the bare-break binding drops
4930                        // flow into the enclosing continuation, re-running
4931                        // code the bytecode skipped (bappend's outer tail,
4932                        // bkeyword's case fall-through losing `return '?'`).
4933                        let cont_dead = parts
4934                            .last()
4935                            .map(|p| region_terminates(p, self.results))
4936                            .unwrap_or(false);
4937                        let cont_is_f = universe.contains(&f)
4938                            && !stop.contains(&f)
4939                            && !claimed_save.contains(&f);
4940                        // This walk IS a pattern-switch case arm and the
4941                        // confluence is that switch's follow: the arm
4942                        // renderer appends the case's terminal `break;`
4943                        // (restore_one_switch), landing on the outer
4944                        // follow == f — the plain-break binding is
4945                        // equivalent and keeps the appended break
4946                        // reachable (ClassPrinterImpl.toYaml/toXml).
4947                        let cont_via_case_break = self
4948                            .case_arm_ctx
4949                            .last()
4950                            .map(|&(head, ef, pat)| head == entry && pat && ef == Some(f))
4951                            .unwrap_or(false);
4952                        let bindable = (cont_dead || cont_is_f || cont_via_case_break)
4953                            && self.switch_follow_bindable(&sw, cur, &targets, f);
4954                        if crate::dbg_flag!("JCDC_DBG_SWF") {
4955                            eprintln!("SWBIND cur={} f={} cont_dead={} cont_is_f={} cont_case={} bindable={}",
4956                                cur, f, cont_dead, cont_is_f, cont_via_case_break, bindable);
4957                        }
4958                        if bindable {
4959                            *claimed = claimed_save;
4960                            sw = self.structure_switch(
4961                                cur,
4962                                selector,
4963                                &targets,
4964                                universe,
4965                                stop,
4966                                Some(f),
4967                                active,
4968                                claimed,
4969                            );
4970                        }
4971                    }
4972                    self.switch_depth -= 1;
4973                    // A follow already CLAIMED by a sibling arm (a shared
4974                    // terminator tail copied there first) still binds this
4975                    // switch's `break`s — but nothing after the switch
4976                    // renders the tail, so a normally-completing switch
4977                    // fell off the enclosing arm (jdk11 MethodTypeForm
4978                    // .canonicalize: the Void.TYPE lookupswitch's default
4979                    // `goto return-null` became `break` with the tail
4980                    // claimed by the tableswitch arm — 缺少返回语句). Copy
4981                    // the terminator at the confluence, mirroring the
4982                    // GOTO-CLAIMED shared-tail rule. Only when the switch
4983                    // can complete normally (JLS 14.11: an open default
4984                    // route, a non-terminating default, or any
4985                    // non-terminating case) — else the copy is unreachable
4986                    // (无法访问的语句).
4987                    if let Some(f) = follow {
4988                        if crate::dbg_flag!("JCDC_DBG_SWF") {
4989                            eprintln!("SWTAIL cur={} f={} claimed={} stop={} loops={} term={} loophdr={} finw={} completes={}",
4990                                cur, f, claimed.contains(&f), stop.contains(&f),
4991                                self.loops_stack.contains(&f), self.is_terminator_block(f),
4992                                Self::ctx_is_loop_header(self, f), self.terminator_writes_final(f),
4993                                switch_completes_normally(&sw, f, self.results));
4994                        }
4995                        if claimed.contains(&f)
4996                            && !stop.contains(&f)
4997                            && !self.loops_stack.contains(&f)
4998                            && self.is_terminator_block(f)
4999                            && !Self::ctx_is_loop_header(self, f)
5000                            && !self.terminator_writes_final(f)
5001                            && switch_completes_normally(&sw, f, self.results)
5002                        {
5003                            parts.push(sw);
5004                            parts.push(Region::CopyStmts { block: f });
5005                            break;
5006                        }
5007                    }
5008                    parts.push(sw);
5009                    match follow {
5010                        Some(f)
5011                            if universe.contains(&f)
5012                                && !stop.contains(&f)
5013                                && !claimed.contains(&f) =>
5014                        {
5015                            cur = f;
5016                            continue;
5017                        }
5018                        _ => break,
5019                    }
5020                }
5021                Term::Goto => {
5022                    let t = b.succ.first().copied();
5023                    match t {
5024                        Some(t)
5025                            if universe.contains(&t)
5026                                && !stop.contains(&t)
5027                                && !claimed.contains(&t)
5028                                && !(self.is_handler(t) && active.is_empty()) =>
5029                        {
5030                            // Forward jump within scope: keep this block's
5031                            // statements, continue walking at the target.
5032                            parts.push(Region::Basic { block: cur });
5033                            cur = t;
5034                            continue;
5035                        }
5036                        Some(t)
5037                            if universe.contains(&t)
5038                                && !stop.contains(&t)
5039                                && claimed.contains(&t)
5040                                && dom.dominates(t, cur) =>
5041                        {
5042                            // Back edge into an already-structured block:
5043                            // emit statements + goto (→ continue/break).
5044                            parts.push(Region::Basic { block: cur });
5045                            if self.is_terminator_block(t)
5046                                && !stop.contains(&t)
5047                                && !self.loops_stack.contains(&t)
5048                                && !Self::ctx_is_loop_header(self, t)
5049                            {
5050                                // A shared TERMINATOR tail (return/throw) is
5051                                // copied at each arrival — but NOT when the
5052                                // target is a LOOP HEADER: the back edge is
5053                                // a `continue`, and copying a header whose
5054                                // body ends in a throw inlines the body into
5055                                // the handler unprotected (jdk26 Future
5056                                // .exceptionNow: the InterruptedException
5057                                // retry `goto 40` copied `get(); throw ISE`
5058                                // into the catch — 未报告的异常错误
5059                                // InterruptedException).
5060                                parts.push(Region::CopyStmts { block: t });
5061                            } else if !stop.contains(&t)
5062                                && !self.loops_stack.contains(&t)
5063                                && !Self::ctx_is_loop_header(self, t)
5064                            {
5065                                match self.copy_walk(t, stop, active, cur) {
5066                                    Some(r) => parts.push(r),
5067                                    None => parts.push(Region::Goto { target: t }),
5068                                }
5069                            } else {
5070                                parts.push(Region::Goto { target: t });
5071                            }
5072                            break;
5073                        }
5074                        Some(t) => {
5075                            if crate::dbg_flag!("JCDC_DBG_IF") {
5076                                eprintln!(
5077                                    "GOTO-FALL cur={} t={} univ={} stop={} claimed={} entry={}",
5078                                    cur,
5079                                    t,
5080                                    universe.contains(&t),
5081                                    stop.contains(&t),
5082                                    claimed.contains(&t),
5083                                    entry
5084                                );
5085                            }
5086
5087                            parts.push(Region::Basic { block: cur });
5088                            if self.is_terminator_block(t)
5089                                && !stop.contains(&t)
5090                                && !Self::ctx_is_loop_header(self, t)
5091                                && !self.terminator_writes_final(t)
5092                            {
5093                                // Shared terminator tail — but NOT a retry
5094                                // loop header (a handler-entry `goto head`
5095                                // re-executes the protected body; copying a
5096                                // throw-terminated header inlines it
5097                                // unprotected: jdk26 Future.exceptionNow
5098                                // catch(IE) got `get(); throw ISE` —
5099                                // 未报告的异常错误 InterruptedException).
5100                                parts.push(Region::CopyStmts { block: t });
5101                            } else if self.walk_depth == 1
5102                                && claimed.contains(&t)
5103                                && self.copied_tails.contains(&t)
5104                                && self.cfg_all_paths_terminate(t)
5105                                && !stop.contains(&t)
5106                                && !Self::ctx_is_loop_header(self, t)
5107                                && !self.terminator_writes_final(t)
5108                            {
5109                                // Method-final epilogue recovery: the
5110                                // target was claimed by copy/handler walks
5111                                // whose owner scopes are FINISHED (the
5112                                // active-group deferral below would strand
5113                                // it — jdk11/17 Resource.getBytes: the
5114                                // shared `if (interrupted) interrupt();
5115                                // return b;` selector was claimed inside
5116                                // the finally renders, the method-level
5117                                // stub arrival deferred to long-finished
5118                                // inner groups, and the method ended right
5119                                // after the finally — 缺少返回语句). Only at
5120                                // method depth (keytool's per-case return
5121                                // tails arrive inside switch-arm walks),
5122                                // only at an already-copied tail (fresh
5123                                // tails stay under the deferral discipline
5124                                // — SSLEngineImpl's try-containing arrival
5125                                // completes normally and fails the
5126                                // all-paths-terminate probe anyway).
5127                                match self.copy_walk(t, stop, active, cur) {
5128                                    Some(r) => parts.push(r),
5129                                    None => parts.push(Region::Goto { target: t }),
5130                                }
5131                            } else if !stop.contains(&t)
5132                                && !self.loops_stack.contains(&t)
5133                                && !Self::ctx_is_loop_header(self, t)
5134                                && !self.shared_tail_confluence(t)
5135                                && !self.is_active_group_continuation(
5136                                    cur,
5137                                    t,
5138                                    claimed,
5139                                    &active_filtered(active, &structured_here),
5140                                )
5141                            {
5142                                match self.copy_walk(t, stop, active, cur) {
5143                                    Some(r) => parts.push(r),
5144                                    None => parts.push(Region::Goto { target: t }),
5145                                }
5146                            } else {
5147                                parts.push(Region::Goto { target: t });
5148                            }
5149                            break;
5150                        }
5151                        None => {
5152                            parts.push(Region::Basic { block: cur });
5153                            break;
5154                        }
5155                    }
5156                }
5157                Term::Return(_) | Term::Throw(_) | Term::Ret | Term::Jsr => {
5158                    parts.push(Region::Basic { block: cur });
5159                    break;
5160                }
5161                Term::Fallthrough => {
5162                    parts.push(Region::Basic { block: cur });
5163                    match b.succ.first().copied() {
5164                        Some(n)
5165                            if universe.contains(&n)
5166                                && !stop.contains(&n)
5167                                && !(self.is_handler(n) && active.is_empty()) =>
5168                        {
5169                            cur = n;
5170                            continue;
5171                        }
5172                        Some(n) if self.is_handler(n) && active.is_empty() => break,
5173                        Some(n) => {
5174                            if crate::dbg_flag!("JCDC_DBG_IF") {
5175                                eprintln!(
5176                                    "GOTO-FT cur={} n={} univ={} stop={} claimed={} entry={}",
5177                                    cur,
5178                                    n,
5179                                    universe.contains(&n),
5180                                    stop.contains(&n),
5181                                    claimed.contains(&n),
5182                                    entry
5183                                );
5184                            }
5185                            // Shared-tail arrival: `n` is CLAIMED (hence
5186                            // out of this walk's universe — sub_scope
5187                            // retains unclaimed blocks only). A bare Goto
5188                            // here is elided at conversion as the walk's
5189                            // last part (goto_is_last), silently severing
5190                            // this path's flow into the shared tail (jdk26
5191                            // DatagramChannelImpl.innerJoin's IPv4 arm:
5192                            // `key = new Type4(..)` fell off the method
5193                            // without the `registry.add(key); return key;`
5194                            // copy — 缺少返回语句 x3 trees). Per-arrival
5195                            // copies are exactly the bytecode semantics.
5196                            // Shared-tail arrival: `n` is CLAIMED, so no
5197                            // walk here will ever emit it again. A bare
5198                            // Goto is elided at conversion as the walk's
5199                            // last part (goto_is_last), silently severing
5200                            // this path's flow into the tail (jdk26
5201                            // DatagramChannelImpl.innerJoin's IPv4 arm:
5202                            // `key = new Type4(..)` fell off the method
5203                            // without the `registry.add(key); return key;`
5204                            // copy — 缺少返回语句 x3 trees) — UNLESS `n`
5205                            // is an ACTIVE group's post-try continuation:
5206                            // the owner walk emits it at the right level
5207                            // after the Try region (jdk11 Module
5208                            // .loadModuleInfoClass's in.close() block is
5209                            // gi=1's cont — copying it here duplicated the
5210                            // close+return chain into the body, defeated
5211                            // twr_j11's try-with-resources fold, and the
5212                            // pending-rethrow scaffolding leaked —
5213                            // 未报告的异常错误Throwable x2 trees). The
5214                            // conversion's strip_trailing_goto(try_follow)
5215                            // renders the Goto as the natural fallthrough.
5216                            let owned_cont = self.is_cont_of_active_group(
5217                                n,
5218                                stop,
5219                                claimed,
5220                                &active_filtered(active, &structured_here),
5221                            );
5222                            if claimed.contains(&n)
5223                                && !owned_cont
5224                                && !stop.contains(&n)
5225                                && !self.loops_stack.contains(&n)
5226                                && !self.is_handler(n)
5227                                && !self.terminator_writes_final(n)
5228                            {
5229                                if self.is_terminator_block(n) {
5230                                    parts.push(Region::CopyStmts { block: n });
5231                                    break;
5232                                }
5233                                if let Some(r) = self.copy_walk(n, stop, active, cur) {
5234                                    parts.push(r);
5235                                    break;
5236                                }
5237                            }
5238                            parts.push(Region::Goto { target: n });
5239                            break;
5240                        }
5241                        None => break,
5242                    }
5243                }
5244            }
5245        }
5246        if crate::dbg_flag!("JCDC_DBG_IF") {
5247            eprintln!(
5248                "WALK END entry={} nparts={} claimed={:?}",
5249                entry,
5250                parts.len(),
5251                claimed
5252            );
5253        }
5254        match parts.len() {
5255            0 => Region::Empty,
5256            1 => parts.pop().unwrap(),
5257            _ => Region::Seq(parts),
5258        }
5259    }
5260
5261    /// Scope for a branch starting at `b`: when `b` belongs to a try group
5262    /// body, exclude blocks owned by OTHER (sibling) groups — they are
5263    /// structured by their own Try regions — but keep unowned blocks (the
5264    /// post-try continuation flow) and nested groups of `gi`.
5265    fn owner_scope(&self, b: usize, universe: &HashSet<usize>) -> HashSet<usize> {
5266        match self.body_group.get(&b) {
5267            Some(gi) => {
5268                let g = &self.groups[*gi];
5269                let mut out: HashSet<usize> = universe
5270                    .iter()
5271                    .copied()
5272                    .filter(|x| match self.body_group.get(x) {
5273                        None => true,
5274                        Some(og) => {
5275                            *og == *gi
5276                                || (self.groups[*og].start >= g.start
5277                                    && self.groups[*og].end <= g.end)
5278                                // Blocks owned by a group ENCLOSING `gi`
5279                                // are the arm's post-try continuation, not
5280                                // a sibling's territory: a nested try whose
5281                                // protected span ends before the enclosing
5282                                // monitor/group span leaves its monitorexit
5283                                // + return tail owned by the outer group,
5284                                // and filtering it stranded the tail in no
5285                                // region (jdk26 ZipFile.getComment: the
5286                                // `return zipCoder.toString(comment)` value
5287                                // block walked alone, its Goto into the
5288                                // tail elided at conversion — empty try
5289                                // body AND a missing return).
5290                                || (self.groups[*og].start <= g.start
5291                                    && self.groups[*og].end >= g.end)
5292                                // Blocks owned by a LATER sibling group
5293                                // that the arm's own flow runs INTO are
5294                                // not foreign territory either: the arm
5295                                // walk structures that group when flow
5296                                // arrives (structure_try fires at its
5297                                // start). Filtering them severs the flow
5298                                // mid-chain — every arrival at the
5299                                // dropped block dangles as an
5300                                // exits-resolved break (jdk11/17
5301                                // SeedGenerator ThreadedSeedGenerator.run:
5302                                // the spin arm's scope was rooted at the
5303                                // thread-create try; the spin body's
5304                                // `synchronized(this){}` monitorexit
5305                                // block is owned by its own tiny sync
5306                                // group starting INSIDE the spin loop, so
5307                                // it was stripped — the latch increment
5308                                // block past it left the spin's member
5309                                // set, the body's back edge resolved to
5310                                // `break`, and the 250ms entropy spin
5311                                // rendered as `while (cond) { break; }`
5312                                // with the latch lost). A later sibling
5313                                // whose start the arm cannot reach stays
5314                                // filtered (no arrival ever dangles).
5315                                || (self.groups[*og].start >= g.end
5316                                    && self.arm_flow_reaches(
5317                                        b,
5318                                        *og,
5319                                        universe,
5320                                    ))
5321                        }
5322                    })
5323                    .collect();
5324                self.expand_orphan_group_tails(&mut out, b);
5325                out
5326            }
5327            None => {
5328                let mut out = universe.clone();
5329                self.expand_orphan_group_tails(&mut out, b);
5330                out
5331            }
5332        }
5333    }
5334
5335    /// True when normal flow from `from` (within `universe`) reaches the
5336    /// START block of `target_gi`: the arm walk will arrive there and
5337    /// structure the group itself.
5338    fn arm_flow_reaches(&self, from: usize, target_gi: usize, universe: &HashSet<usize>) -> bool {
5339        let gstart = self.groups[target_gi].start;
5340        let mut start_blk: Option<usize> = None;
5341        for nb in &self.cfg.blocks {
5342            if nb.start == gstart {
5343                start_blk = Some(nb.id);
5344                break;
5345            }
5346        }
5347        let Some(target) = start_blk else {
5348            return false;
5349        };
5350        let mut seen: HashSet<usize> = HashSet::default();
5351        let mut q: VecDeque<usize> = VecDeque::new();
5352        q.push_back(from);
5353        seen.insert(from);
5354        let mut guard = 0;
5355        while let Some(b) = q.pop_front() {
5356            guard += 1;
5357            if guard > 1024 {
5358                return false;
5359            }
5360            for &s in &self.cfg.blocks[b].succ {
5361                if s == target {
5362                    return true;
5363                }
5364                if seen.contains(&s) || !universe.contains(&s) {
5365                    continue;
5366                }
5367                // Plain normal-flow BFS: handler blocks are unreachable
5368                // here by construction (exception-only in-edges), and a
5369                // kept-but-never-arrived block is harmless (the arm walk
5370                // only structures what its flow reaches).
5371                seen.insert(s);
5372                q.push_back(s);
5373            }
5374        }
5375        false
5376    }
5377
5378    /// A sibling-group-owned block that is the normal-flow target of a
5379    /// kept in-scope block, lies PAST every group active over that block,
5380    /// and whose group has no owner in scope, is an ORPHAN TAIL: no walk
5381    /// can ever structure it (the owner_scope filter strips it from every
5382    /// arm universe, so every arrival dangles as an elided raw Goto).
5383    /// Adopt it — with its whole group span and that group's own tail
5384    /// closure — so the arriving arm walks and structures it (jdk26
5385    /// StructuredTaskScopeImpl.join: both timeoutExpired arms ended in
5386    /// Goto{tail}, the tail's `try { return joiner.result(); } catch
5387    /// (Throwable e) { throw new FailedException(e); }` was owned by a
5388    /// sibling group present in NO arm's scope — the epilogue vanished
5389    /// from the render entirely, 缺少返回语句).
5390    fn expand_orphan_group_tails(&self, out: &mut HashSet<usize>, from: usize) {
5391        let structuring = self.structuring_groups.borrow().clone();
5392        // Group-presence sets over `out`, refreshed as adoptions grow it.
5393        // The per-node rescans below used to walk ALL of out × groups (and
5394        // all of blocks) — O(G·|out|·nodes) per call; once the
5395        // handler-parse fix surfaced the real group count, that alone
5396        // burned ~5× wall on weibo's synchronized monsters.
5397        let mut present_body: crate::fx::FxHashSet<usize> = crate::fx::FxHashSet::default();
5398        let mut present_handler: crate::fx::FxHashSet<usize> = crate::fx::FxHashSet::default();
5399        for &x in out.iter() {
5400            if let Some(&gi) = self.body_group.get(&x) {
5401                present_body.insert(gi);
5402            }
5403            if let Some(&gi) = self.handler_group.get(&x) {
5404                present_handler.insert(gi);
5405            }
5406        }
5407        let mut frontier: Vec<usize> = vec![from];
5408        let mut seen: crate::fx::FxHashSet<usize> = crate::fx::FxHashSet::default();
5409        let mut guard = 0;
5410        while let Some(b) = frontier.pop() {
5411            guard += 1;
5412            if guard > 512 || !seen.insert(b) {
5413                continue;
5414            }
5415            if !out.contains(&b) {
5416                continue;
5417            }
5418            for &t in &self.cfg.blocks[b].succ {
5419                if out.contains(&t) {
5420                    frontier.push(t);
5421                    continue;
5422                }
5423                let Some(&ogi) = self.body_group.get(&t) else {
5424                    continue;
5425                };
5426                let og = &self.groups[ogi];
5427                // The owner group is STILL PENDING in this scope — its
5428                // start block is held by `out` and structure_try has not
5429                // consumed it yet: that walk structures the group when
5430                // flow arrives and its post-try continuation emits the
5431                // tail at the right level. Adopting it into an arm
5432                // universe claims the shared tail early and the
5433                // continuation search then finds it claimed (jdk17/26
5434                // PrintStream.format x2 trees: the else arm adopted the
5435                // synchronized group's monitorexit chain plus the
5436                // post-try `return this`, cont resolved to None inside
5437                // BOTH nested tries — 缺少返回语句 x2 methods). Groups
5438                // already structured by an ancestor (STS join's tail
5439                // try, structured by the sibling arm) keep adopting:
5440                // their tail will never be emitted elsewhere.
5441                let start_held = self
5442                    .cfg
5443                    .block_at(og.start)
5444                    .is_some_and(|bid| {
5445                        self.cfg.blocks[bid].start == og.start && out.contains(&bid)
5446                    });
5447                if structuring.contains(&ogi) || start_held {
5448                    continue;
5449                }
5450                // Past every group active over `b`: a target inside an
5451                // active group's span is that group's own continuation —
5452                // the owner walk handles it (ZipFile.getComment's
5453                // enclosing-group tail; Module.loadModuleInfoClass's
5454                // post-try cont) — never an orphan.
5455                if present_body.contains(&ogi) || present_handler.contains(&ogi) {
5456                    continue;
5457                }
5458                let spanned = present_body.iter().any(|&gi| {
5459                    let g = &self.groups[gi];
5460                    g.start <= og.start && g.end >= og.end
5461                });
5462                if spanned {
5463                    continue;
5464                }
5465                // Adopt the full span + handler heads (structure_try
5466                // rebuilds handlers itself) + the group's own tail
5467                // closure beyond the span end.
5468                let lo = self
5469                    .cfg
5470                    .blocks
5471                    .partition_point(|nb| nb.start < og.start);
5472                let mut adopt: Vec<usize> = self.cfg.blocks[lo..]
5473                    .iter()
5474                    .take_while(|nb| nb.end <= og.end.max(og.start + 1))
5475                    .filter(|nb| nb.ins_len != 0)
5476                    .map(|nb| nb.id)
5477                    .collect();
5478                for (&hb, &hg) in self.handler_group.iter() {
5479                    if hg == ogi {
5480                        adopt.push(hb);
5481                    }
5482                }
5483                let mut x = og.end;
5484                for _ in 0..8 {
5485                    let Some(bid) = self.cfg.block_at(x) else {
5486                        break;
5487                    };
5488                    if self.body_group.contains_key(&bid) {
5489                        break;
5490                    }
5491                    adopt.push(bid);
5492                    let nxt = match self.results[bid].term {
5493                        Term::Fallthrough | Term::Goto if self.cfg.blocks[bid].succ.len() == 1 => {
5494                            self.cfg.blocks[bid].succ[0]
5495                        }
5496                        _ => break,
5497                    };
5498                    x = self.cfg.blocks[nxt].start;
5499                }
5500                for id in adopt {
5501                    if out.insert(id) {
5502                        if let Some(&gi) = self.body_group.get(&id) {
5503                            present_body.insert(gi);
5504                        }
5505                        if let Some(&gi) = self.handler_group.get(&id) {
5506                            present_handler.insert(gi);
5507                        }
5508                        frontier.push(id);
5509                    }
5510                }
5511            }
5512        }
5513    }
5514
5515    /// If `blk` is a pure value block (no statements, one stack value out,
5516    /// single successor) and its successor is not walkable here (stop,
5517    /// claimed, or out of universe), absorb the block into this scope:
5518    /// return Basic(blk) and mark it claimed. This materializes chained
5519    /// boolean diamonds (`a && b || c`) whose shared push blocks would
5520    /// otherwise be consumed silently by the first reaching branch.
5521    /// Confluence block of a switch's non-terminating case flows.
5522    /// The single stop/out-of-universe block that every live (non
5523    /// return/throw) case target escapes to. Used as a switch follow for
5524    /// break resolution when the real confluence lies beyond the current
5525    /// region's universe.
5526    pub(crate) fn switch_stop_confluence(
5527        &self,
5528        cur: usize,
5529        universe: &HashSet<usize>,
5530        stop: &HashSet<usize>,
5531    ) -> Option<usize> {
5532        let mut common: Option<usize> = None;
5533        let mut saw_live = false;
5534        for &t in &self.cfg.blocks[cur].succ {
5535            if self.is_terminator_block(t) {
5536                continue;
5537            }
5538            saw_live = true;
5539            // Forward BFS within the universe; collect the first blocks
5540            // that leave it (stop members or out-of-universe succs).
5541            let mut escapes: HashSet<usize> = HashSet::default();
5542            let mut seen: HashSet<usize> = HashSet::default();
5543            let mut q: Vec<usize> = vec![t];
5544            seen.insert(t);
5545            while let Some(b) = q.pop() {
5546                for &s2 in &self.cfg.blocks[b].succ {
5547                    if !universe.contains(&s2) || stop.contains(&s2) {
5548                        escapes.insert(s2);
5549                    } else if seen.insert(s2) {
5550                        q.push(s2);
5551                    }
5552                }
5553            }
5554            // A live target that IS itself a stop/out-of-universe block
5555            // (the default jumping straight to the confluence) escapes to
5556            // itself.
5557            if !universe.contains(&t) || stop.contains(&t) {
5558                escapes.clear();
5559                escapes.insert(t);
5560            }
5561            if escapes.is_empty() {
5562                // The case flow terminates ENTIRELY inside the universe
5563                // (every exit is a return/throw leaf — no edge leaves it):
5564                // an always-terminating case contributes no escape and
5565                // must not veto the confluence of the others (jdk26 xml
5566                // impl.Parser.bappend: the whitespace case's
5567                // if/else-if/else chain all-returns zeroed the escape set
5568                // and the whole-switch None made the inner switch's
5569                // `break`-to-outer-follow default unresolvable — the case
5570                // fell through into `case 99`, corrupting the outer
5571                // switch's completion and stranding its follow —
5572                // 无法访问的语句).
5573                continue;
5574            }
5575            if escapes.len() != 1 {
5576                return None;
5577            }
5578            let e = *escapes.iter().next().unwrap();
5579            match common {
5580                None => common = Some(e),
5581                Some(c) if c == e => {}
5582                Some(_) => return None,
5583            }
5584        }
5585        if saw_live {
5586            common
5587        } else {
5588            None
5589        }
5590    }
5591
5592    /// For a NESTED switch whose confluence `f` is a stop block owned by
5593    /// an enclosing construct (the CROSSING shape), decide whether `f` may
5594    /// still be passed as the switch's OWN follow (case arms then resolve
5595    /// to plain `break`s) or must be nulled (labeled breaks bind the
5596    /// enclosing switch). The plain-break binding is safe ONLY when every
5597    /// way flow could naturally continue past this switch inside the
5598    /// enclosing region lands on `f` anyway: otherwise a case the bytecode
5599    /// sends straight to `f` would fall out of the switch into that
5600    /// continuation and re-execute code the bytecode skipped (jdk26 xml
5601    /// impl.Parser.bappend: the inner switch(ch)'s cases break to the
5602    /// OUTER switch(mode)'s follow, jumping over the outer case's tail —
5603    /// nulling keeps `break L1`). When the switch is the LAST construct of
5604    /// an abruptly-completing case arm both bindings land at `f` and the
5605    /// plain inner break is the renderable one — binding the outer label
5606    /// makes the inner switch unable to complete normally and the
5607    /// pattern-switch restoration's appended outer-case `break;` becomes
5608    /// unreachable (jdk26 ClassPrinterImpl.toYaml/toXml: the MapNode/List
5609    /// inner ordinal switches have NO default stub — every arm `goto`s the
5610    /// shared return directly — so the inner switch rendered `default:
5611    /// break L1` + outer case `break;` — 无法访问的语句 x2 trees).
5612    pub(crate) fn switch_follow_bindable(
5613        &self,
5614        sw: &Region,
5615        block: usize,
5616        _targets: &SwitchTargets,
5617        f: usize,
5618    ) -> bool {
5619        let (cases, default) = match sw {
5620            Region::Switch { cases, default, .. } => (cases, default.as_deref()),
5621            _ => return false,
5622        };
5623        // The default arm's target comes from the terminator (the JVM
5624        // records it in the switch payload, DEX in the payload table).
5625        let default_pc = match self.results.get(block).map(|r| &r.term) {
5626            Some(Term::Switch {
5627                targets: SwitchTargets::Table { .. },
5628                default: Some(d),
5629                ..
5630            })
5631            | Some(Term::Switch {
5632                targets: SwitchTargets::Lookup { .. },
5633                default: Some(d),
5634                ..
5635            }) => *d,
5636            _ => return false,
5637        };
5638        // The default route: the rendered default region must itself bind
5639        // to `f`. `default_pc == f` is NOT automatically bindable: with
5640        // the follow passed, structure_switch filters the default region
5641        // away and unmatched-selector flow falls out of the switch into
5642        // the ENCLOSING continuation (bkeyword's outer case then fell
5643        // through into the next case, dropping the shared `return '?'`).
5644        let default_ok = match default {
5645            Some(d) => self.arm_binds_to(d, f),
5646            None => false,
5647        };
5648        // Case arms in emission order. An Empty group is a fallthrough
5649        // label: its route is the NEXT group's (checked in its own
5650        // iteration), or the default's when it is the last group.
5651        let n = cases.len();
5652        let cases_ok = cases.iter().enumerate().all(|(i, (_, r))| {
5653            if matches!(r, Region::Empty) {
5654                return i + 1 < n || default_ok;
5655            }
5656            let ok = self.arm_binds_to(r, f);
5657            if crate::dbg_flag!("JCDC_DBG_SWF") {
5658                eprintln!(
5659                    "SWBIND-CASE i={} block={} f={} ok={} region={:?}",
5660                    i,
5661                    region_head_block(r),
5662                    f,
5663                    ok,
5664                    region_shape(r)
5665                );
5666            }
5667            ok
5668        });
5669        if crate::dbg_flag!("JCDC_DBG_SWF") {
5670            eprintln!(
5671                "SWBIND-DEF block={} default_pc={} f={} default_ok={} cases_ok={}",
5672                block, default_pc, f, default_ok, cases_ok
5673            );
5674        }
5675        default_ok && cases_ok
5676    }
5677
5678    /// Every completion path of this arm region lands on `f`: a trailing
5679    /// goto to it, a block whose terminal edge is that goto, an if whose
5680    /// both branches bind, a loop whose every exit IS `f`, or a nested
5681    /// switch that is itself bindable.
5682    fn arm_binds_to(&self, r: &Region, f: usize) -> bool {
5683        match r {
5684            Region::Basic { .. } => true,
5685            Region::Goto { target } => *target == f,
5686            Region::Empty => false,
5687            Region::Seq(v) => v.last().map(|x| self.arm_binds_to(x, f)).unwrap_or(false),
5688            Region::If { then_r, else_r, .. } => {
5689                self.arm_binds_to(then_r, f) && self.arm_binds_to(else_r, f)
5690            }
5691            // The loop arm binds when ALL its exits are `f`: loop
5692            // completion falls out of the switch (follow == `f`) and any
5693            // in-body break targets an exit == `f` (ClassPrinterImpl's
5694            // BLOCK-case iterator loop exits straight to the shared
5695            // return — no default stub).
5696            Region::Loop { exits, .. } => !exits.is_empty() && exits.iter().all(|&e| e == f),
5697            Region::Switch { block, .. } => {
5698                if let Term::Switch { targets, .. } = &self.results[*block].term {
5699                    self.switch_follow_bindable(r, *block, targets, f)
5700                } else {
5701                    false
5702                }
5703            }
5704            _ => false,
5705        }
5706    }
5707
5708    /// True when `cur` flows through single-successor blocks to an
5709    /// already-claimed terminator: the chain is safe to copy inline because
5710    /// its endpoint was structured elsewhere (the restart-loop switch: the
5711    /// guard-pass body flows ONLY to the shared areturn every case region
5712    /// absorbed) and the copy is this path's sole continuation. Normal loop
5713    /// exits chain to UNCLAIMED continuations (the post-loop follow) and
5714    /// must stay jumps — inlining those rewrites breaks into returns and
5715    /// degrades while-cond loops (jdk26 ThreadPoolExecutor regression).
5716    /// True when a loop-exit target heads real CONTENT: a statement or a
5717    /// branch cascade, not a statement-free stub chain into a barrier
5718    /// (loop header / another exit / out of scope). Breaking to a stub
5719    /// exit loses nothing (Pattern.clazz's switch-case goto stubs must
5720    /// stay bare breaks). Breaking to a content exit drops its whole
5721    /// flow: jdk17 AQS.cleanQueue's `q.status < 0` arm breaks to the
5722    /// CAS-unlink cascade (casTail/casPrev/casNext/signalNext flowing to
5723    /// the outer restart stub) and the bare break silently deleted the
5724    /// phase (semantic, compile-clean).
5725    pub(crate) fn exit_has_content(&self, e: usize, stop: &HashSet<usize>) -> bool {
5726        let mut b = e;
5727        for _ in 0..16 {
5728            if !self.results[b].stmts.is_empty() {
5729                return true;
5730            }
5731            match &self.results[b].term {
5732                Term::Cond { .. } | Term::Switch { .. } => return true,
5733                Term::Return(_) | Term::Throw(_) => return false,
5734                Term::Fallthrough | Term::Goto if self.cfg.blocks[b].succ.len() == 1 => {
5735                    let n = self.cfg.blocks[b].succ[0];
5736                    if n == e || stop.contains(&n) || self.loops_stack.contains(&n) {
5737                        return false;
5738                    }
5739                    b = n;
5740                }
5741                _ => return false,
5742            }
5743        }
5744        false
5745    }
5746
5747    fn stop_chain_to_claimed_terminator(&self, cur: usize, claimed: &HashSet<usize>) -> bool {
5748        let mut x = cur;
5749        for _ in 0..8 {
5750            if matches!(self.results[x].term, Term::Return(_) | Term::Throw(_)) {
5751                // The restart-loop guarded case is a chain of EMPTY jump
5752                // stubs into an absorbed terminator — breaking would land
5753                // where nothing remains. A STATEMENT-BEARING claimed
5754                // terminator is the post-loop continuation tail (a shared
5755                // clinit tail claimed early by the first arrival): the
5756                // enclosing walk structures it after the loop and the
5757                // barrier Goto must stay a `break`. Copying the tail into
5758                // the exit test put the blank-final assignments INSIDE the
5759                // for-each loop (javac: 可能在 loop 中分配了变量 x18,
5760                // SecurityProviderConstants) and duplicated them into the
5761                // catch.
5762                return claimed.contains(&x) && self.results[x].stmts.is_empty();
5763            }
5764            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
5765                return false;
5766            }
5767            // An UNCLAIMED statement-bearing link means the chain is real
5768            // post-loop code, not absorbed scaffolding (see above).
5769            if !self.results[x].stmts.is_empty() && !claimed.contains(&x) {
5770                return false;
5771            }
5772            let succs = &self.cfg.blocks[x].succ;
5773            if succs.len() != 1 {
5774                return false;
5775            }
5776            let n = succs[0];
5777            if n == x || self.loops_stack.contains(&n) {
5778                return false;
5779            }
5780            x = n;
5781        }
5782        false
5783    }
5784
5785    fn switch_follow(&self, cur: usize, universe: &HashSet<usize>) -> Option<usize> {
5786        let mut dists: Vec<HashMap<usize, u32>> = Vec::new();
5787        for &s0 in &self.cfg.blocks[cur].succ {
5788            if !universe.contains(&s0) {
5789                continue;
5790            }
5791            let mut d: HashMap<usize, u32> = HashMap::default();
5792            let mut q: VecDeque<(usize, u32)> = VecDeque::new();
5793            d.insert(s0, 0);
5794            q.push_back((s0, 0));
5795            while let Some((b, db)) = q.pop_front() {
5796                for &s in &self.cfg.blocks[b].succ {
5797                    if s != cur && universe.contains(&s) && !d.contains_key(&s) {
5798                        d.insert(s, db + 1);
5799                        q.push_back((s, db + 1));
5800                    }
5801                }
5802            }
5803            // Cases that always terminate (return/throw with no flow
5804            // onward) contribute no reconvergence point.
5805            let terminates = d.len() <= 1
5806                && self.cfg.blocks[s0]
5807                    .succ
5808                    .iter()
5809                    .all(|s| !universe.contains(s));
5810            if !terminates {
5811                dists.push(d);
5812            }
5813        }
5814        if dists.is_empty() {
5815            return None;
5816        }
5817        if dists.len() == 1 {
5818            // Single non-terminating case flow: its exit out of the case
5819            // region is the switch follow.
5820            let d = &dists[0];
5821            // Multi-pred blocks INSIDE a nested switch's span are that
5822            // switch's own merge, not this switch's follow (jdk26 xml
5823            // impl.Parser.dtdsub: switch(ch)'s case '!' flow merges the
5824            // nested switch(bkeyword) at the shared `st = 1; goto head`
5825            // stub — taking it as the follow put `st = 1` after the inner
5826            // switch in case '<' and left the arm without its continue,
5827            // falling through into case '%' — unreachable code past the
5828            // all-continue arms plus a corrupted state machine, 无法访问
5829            // 的语句 x5 methods x3 trees). A nested-switch merge is
5830            // claimed by that switch's own follow resolution.
5831            let in_nested_switch = |c: usize, d: &HashMap<usize, u32>| -> bool {
5832                let preds = &self.cfg.blocks[c].pred;
5833                if preds.len() < 2 {
5834                    return false;
5835                }
5836                self.cfg.blocks.iter().any(|b| {
5837                    if b.id == cur
5838                        || !matches!(self.results[b.id].term, Term::Switch { .. })
5839                        || !d.contains_key(&b.id)
5840                    {
5841                        return false;
5842                    }
5843                    preds.iter().all(|&p| {
5844                        b.succ.iter().any(|&t| {
5845                            let mut seen: HashSet<usize> = HashSet::default();
5846                            let mut q: VecDeque<usize> = VecDeque::new();
5847                            q.push_back(t);
5848                            while let Some(x) = q.pop_front() {
5849                                if x == p {
5850                                    return true;
5851                                }
5852                                if !seen.insert(x) || !d.contains_key(&x) {
5853                                    continue;
5854                                }
5855                                for &sx in &self.cfg.blocks[x].succ {
5856                                    q.push_back(sx);
5857                                }
5858                            }
5859                            false
5860                        })
5861                    })
5862                })
5863            };
5864            // A RETURN/THROW merge whose preds all live inside this one
5865            // flow is the case's private terminator confluence, not the
5866            // switch's follow (jdk26 xml impl.Parser.bappend: the
5867            // whitespace case's if-chain arms all converge on the shared
5868            // `return` leaf; electing it follow made the switch "complete"
5869            // into a bogus `return;` and the case group fall through into
5870            // `case 99` — 无法访问的语句 on the outer tail). A shared
5871            // return that IS the lexical follow keeps preds from the
5872            // sibling (terminator-excluded) case flows and stays elected.
5873            // A candidate whose every flow-internal pred is a VALUE-STACK
5874            // diamond arm (statement-free, one stack value out, single
5875            // successor) is a folded-ternary merge INSIDE the case body,
5876            // not the switch follow (jdk26 xml impl.Parser.xml: case
5877            // 0xFEFF's `ch = val < 0 ? 0xFFFF : val` and `st = ch != '<'
5878            // ? -1 : 1` diamonds — electing the first merge split the
5879            // case body in half, the front fell through into `default`
5880            // and the back half went unreachable after the all-continue
5881            // switch — 无法访问的语句 x2 trees). In the single-flow
5882            // branch every other case terminates, so the true follow is
5883            // the flow's own exit; an interior diamond merge never is.
5884            let diamond_merge = |c: usize, d: &HashMap<usize, u32>| -> bool {
5885                let internal: Vec<usize> = self.cfg.blocks[c]
5886                    .pred
5887                    .iter()
5888                    .copied()
5889                    .filter(|p| d.contains_key(p))
5890                    .collect();
5891                !internal.is_empty()
5892                    && internal.iter().all(|&p| {
5893                        self.results[p].stmts.is_empty()
5894                            && self.results[p].out_stack.len() == 1
5895                            && self.cfg.blocks[p].succ.len() == 1
5896                    })
5897            };
5898            let private_terminator_merge = |c: usize, d: &HashMap<usize, u32>| -> bool {
5899                if !matches!(self.results[c].term, Term::Return(_) | Term::Throw(_)) {
5900                    return false;
5901                }
5902                if !self.cfg.blocks[c]
5903                    .pred
5904                    .iter()
5905                    .all(|p| d.contains_key(p) || *p == cur)
5906                {
5907                    return false;
5908                }
5909                // A SINGLE case target whose flow ends at this return IS
5910                // the lexical switch tail (`switch (x) { default: g(); }
5911                // return v;` — the default completes the switch into the
5912                // return): keep it as the follow. With two or more live
5913                // targets the return is one case's private confluence
5914                // (bappend's whitespace if-chain), not the follow.
5915                self.cfg.blocks[cur].succ.len() > 1
5916            };
5917            let mut best: Option<usize> = None;
5918            for &cand in d.keys() {
5919                if self.cfg.blocks[cand].pred.len() >= 2
5920                    && !in_nested_switch(cand, d)
5921                    && !diamond_merge(cand, d)
5922                    && !private_terminator_merge(cand, d)
5923                    && best
5924                        .map(|b| self.cfg.blocks[cand].start < self.cfg.blocks[b].start)
5925                        .unwrap_or(true)
5926                {
5927                    best = Some(cand);
5928                }
5929            }
5930            return best;
5931        }
5932        let mut best: Option<(u32, usize)> = None;
5933        for (&cand, &d0) in dists[0].iter() {
5934            if cand == cur {
5935                continue;
5936            }
5937            let mut total = d0;
5938            let mut ok = true;
5939            for d in &dists[1..] {
5940                match d.get(&cand) {
5941                    Some(x) => total += x,
5942                    None => {
5943                        ok = false;
5944                        break;
5945                    }
5946                }
5947            }
5948            if ok {
5949                let better = match best {
5950                    None => true,
5951                    Some((bd, bc)) => {
5952                        total < bd
5953                            || (total == bd
5954                                && self.cfg.blocks[cand].start < self.cfg.blocks[bc].start)
5955                    }
5956                };
5957                if better {
5958                    best = Some((total, cand));
5959                }
5960            }
5961        }
5962        best.map(|(_, c)| c)
5963    }
5964
5965    /// Re-walk an already-claimed region with a throwaway claimed set,
5966    /// producing a structural copy. Used when flow re-enters a shared
5967    /// region that Java cannot express with a jump (no labelable target):
5968    /// re-executing the blocks matches the bytecode's per-arrival
5969    /// semantics. Bounded by depth and a no-reentry check.
5970    /// Take one copy ticket; false when the per-method budget is spent
5971    /// (copy mechanisms must then decline and leave a Goto).
5972    fn take_copy_ticket(&self) -> bool {
5973        let b = self.copy_budget.get();
5974        if b == 0 {
5975            return false;
5976        }
5977        self.copy_budget.set(b - 1);
5978        true
5979    }
5980
5981    pub(crate) fn copy_walk(
5982        &mut self,
5983        t: usize,
5984        stop: &HashSet<usize>,
5985        active: &[usize],
5986        guard_against: usize,
5987    ) -> Option<Region> {
5988        use std::cell::Cell;
5989        thread_local! {
5990            static COPY_DEPTH: Cell<u32> = const { Cell::new(0) };
5991        }
5992        if COPY_DEPTH.with(|c| c.get()) >= 4 {
5993            return None;
5994        }
5995        if !self.take_copy_ticket() {
5996            return None;
5997        }
5998        if can_reach_cfg(self.cfg, t, guard_against, 4096) {
5999            return None;
6000        }
6001        // Targets that flow back into an enclosing loop (or are loop
6002        // exits) resolve to continue/break at conversion; never copy them.
6003        for h in &self.loops_stack {
6004            if *h == t || can_reach_cfg(self.cfg, t, *h, 4096) {
6005                return None;
6006            }
6007        }
6008        let mut barriers = stop.clone();
6009        barriers.extend(self.loops_stack.iter().copied());
6010        barriers.insert(guard_against);
6011        // Shared tail-confluence heads inside the closure become dynamic
6012        // barriers: the copy stops before them and defers to the owner
6013        // walk's canonical render (keytool doCommands' load epilogue was
6014        // re-rendered by every password-section copy whose closure flowed
6015        // into it — 12 inline survivors blew the 64K try-codegen limit).
6016        // Skip the pre-pass when t is itself a terminator (legit shared
6017        // return-tail copies never traverse a confluence head anyway).
6018        //
6019        // COPY_ALLOW_CONFLUENCE (SESE consumed-arrival copies: the copy IS
6020        // the arm's only emission route) crosses a barred confluence only
6021        // when its own forward closure is SMALL (≤8 blocks): jdk11/17/26
6022        // Pattern.family's stage-2 switch head and DecimalFormat.equals'
6023        // chain merge must ride along or the copied arm falls off the
6024        // method end (缺少返回语句), while keytool doCommands' 28-pred
6025        // load epilogue — a 22-statement TWR finish chain every case arm
6026        // flows into — stays barred: crossing it duplicated the epilogue
6027        // into every consumed-arrival copy (75 → 329 FileOutputStream
6028        // sites, 953KB → 3.2MB render, try 语句的代码过长 ×652 unmasked
6029        // the moment OCSPResponse's FLOW error stopped masking javac's
6030        // GENERATE phase).
6031        if !self.is_terminator_block(t) {
6032            let allow = COPY_ALLOW_CONFLUENCE.with(|c| c.get());
6033            let probe = reachable_within(self.cfg, t, &barriers);
6034            let extra: Vec<usize> = probe
6035                .iter()
6036                .copied()
6037                .filter(|&x| x != t && self.shared_tail_confluence(x))
6038                .filter(|&x| {
6039                    !allow
6040                        || !(matches!(self.results[x].term, crate::ir::build::Term::Switch { .. })
6041                            || self.confluence_closure_small(x, &barriers))
6042                })
6043                .collect();
6044            barriers.extend(extra);
6045        }
6046        let tu = reachable_within(self.cfg, t, &barriers);
6047        if !tu.contains(&t) {
6048            return None;
6049        }
6050        #[allow(unused_mut)]
6051        let mut tu = tu;
6052        COPY_DEPTH.with(|c| c.set(c.get() + 1));
6053        let mut fresh: HashSet<usize> = HashSet::default();
6054        // Try groups that START (and whose handler heads live) inside the
6055        // copied universe must fire inside the copy: SESE passes only the
6056        // top-level groups, so a copied loop-top try head re-walked from a
6057        // catch back-edge lost its try wrapper and emitted bare statements
6058        // (jdk11 ObjectStreamClass.getInheritableMethod: the retry
6059        // getDeclaredMethod call landed in the catch WITHOUT its
6060        // try/catch — "unreported exception NoSuchMethodException").
6061        // For walk callers `active` already covers the scope, so this is
6062        // a no-op there.
6063        let mut active2: Vec<usize> = active.to_vec();
6064        for gi in 0..self.groups.len() {
6065            if active2.contains(&gi) {
6066                continue;
6067            }
6068            let gs = self.groups[gi].start;
6069            let start_in = self
6070                .cfg
6071                .blocks
6072                .iter()
6073                .any(|b| b.start == gs && tu.contains(&b.id));
6074            if !start_in {
6075                continue;
6076            }
6077            // Pull the handler heads (and their forward flow up to the
6078            // group's end) into the copy universe: exception edges are
6079            // not followed by reachable_within, but structure_try needs
6080            // the handler blocks present to rebuild the catch.
6081            let heads: Vec<usize> = self
6082                .handler_group
6083                .iter()
6084                .filter(|(_, &g)| g == gi)
6085                .map(|(&hb, _)| hb)
6086                .collect();
6087            for h in heads {
6088                tu.insert(h);
6089            }
6090            active2.push(gi);
6091        }
6092        let r = self.walk(t, &tu, stop, &active2, &mut fresh, true);
6093        COPY_DEPTH.with(|c| c.set(c.get() - 1));
6094        self.copied_tails.insert(t);
6095        Some(r)
6096    }
6097
6098    /// True when the block ends in a return/throw (a shared terminator that
6099    /// can be safely duplicated at each arrival site).
6100    pub(crate) fn is_terminator_block(&self, b: usize) -> bool {
6101        matches!(self.results[b].term, Term::Return(_) | Term::Throw(_))
6102    }
6103
6104    /// True when `b` reaches some method Return through normal edges
6105    /// (bounded BFS): the block sits on a live route to the method end,
6106    /// i.e. it heads a method-tail region rather than a self-contained
6107    /// abrupt epilogue.
6108    fn reaches_any_return(&self, b: usize) -> bool {
6109        let mut seen: HashSet<usize> = HashSet::default();
6110        let mut q: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
6111        q.push_back(b);
6112        let mut budget = 4096;
6113        while let Some(x) = q.pop_front() {
6114            if budget == 0 {
6115                return false;
6116            }
6117            budget -= 1;
6118            if !seen.insert(x) {
6119                continue;
6120            }
6121            if matches!(self.results[x].term, Term::Return(_)) {
6122                return true;
6123            }
6124            for &sx in &self.cfg.blocks[x].succ {
6125                q.push_back(sx);
6126            }
6127        }
6128        false
6129    }
6130
6131    /// A SHARED TAIL CONFLUENCE: a many-pred normal merge head that sits
6132    /// on a live route to the method's final return (jdk11/26 keytool
6133    /// doCommands' keystore-load epilogue, block 376 `if (!token)` with
6134    /// 28 preds: every password-section arm/handler exit jumps to it, and
6135    /// the canonical owner walk renders it once after the dispatch
6136    /// chain). Per-arrival COPY contexts must not swallow such a head in
6137    /// their universe closures: each copy re-renders the whole ~92-line
6138    /// epilogue (12 survivors inflated the method past javac's 64K
6139    /// try-codegen limit — try 语句的代码过长 ×64/87). The copy's exit
6140    /// Goto resolves at conversion like every deferred shared merge.
6141    /// Terminator heads stay copyable (the established per-arrival shared
6142    /// RETURN-tail discipline — small, no outgoing flow, nothing to
6143    /// duplicate); handler heads and group-span starts are structural
6144    /// territory, excluded; the high pred floor keeps ordinary merges
6145    /// (loop selectors, small diamonds) out — copy-local merges have
6146    /// their preds inside the closure and close normally.
6147    pub(crate) fn shared_tail_confluence(&self, b: usize) -> bool {
6148        if self.is_terminator_block(b) || self.is_handler(b) {
6149            return false;
6150        }
6151        let bstart = self.cfg.blocks[b].start;
6152        if self.groups.iter().any(|g| g.start == bstart) {
6153            return false;
6154        }
6155        let normal_preds = self.cfg.blocks[b]
6156            .pred
6157            .iter()
6158            .filter(|p| !self.is_handler(**p))
6159            .count();
6160        normal_preds >= 8 && self.reaches_any_return(b)
6161    }
6162
6163    /// True when the forward closure of confluence `b` (within the given
6164    /// barriers) is small — ≤8 blocks. The COPY_ALLOW_CONFLUENCE crossing
6165    /// budget: small closures are completeness-critical merge stubs /
6166    /// stage heads whose loss strands the copied arm (Pattern.family,
6167    /// DecimalFormat.equals); big closures are shared finish chains whose
6168    /// per-arrival duplication overflows javac's 64K try-codegen limit
6169    /// (keytool doCommands' 22-statement TWR epilogue).
6170    fn confluence_closure_small(&self, b: usize, barriers: &HashSet<usize>) -> bool {
6171        let reach = reachable_within(self.cfg, b, barriers);
6172        reach.len() <= 8
6173    }
6174
6175    /// True when EVERY normal flow path out of `b` ends in a
6176    /// return/throw within a small budget: the block heads an abrupt
6177    /// completion chain (possibly via a cond whose arms both terminate,
6178    /// Resource.getBytes' `if (interrupted)` epilogue selector). Used by
6179    /// the post-try recovery scan — a tail that can complete normally is
6180    /// live fall-through flow some walk emits itself, and copying it
6181    /// duplicates statements or whole try-containing tails.
6182    fn cfg_all_paths_terminate(&self, b: usize) -> bool {
6183        let mut visited: HashSet<usize> = HashSet::default();
6184        let mut q: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
6185        q.push_back(b);
6186        while let Some(x) = q.pop_front() {
6187            if !visited.insert(x) {
6188                continue;
6189            }
6190            if visited.len() > 64 {
6191                return false;
6192            }
6193            match self.results[x].term {
6194                Term::Return(_) | Term::Throw(_) => {}
6195                Term::Fallthrough | Term::Goto => {
6196                    let succs = self.cfg.blocks[x].succ.clone();
6197                    if succs.len() != 1 {
6198                        return false;
6199                    }
6200                    q.push_back(succs[0]);
6201                }
6202                _ => {
6203                    let succs = self.cfg.blocks[x].succ.clone();
6204                    if succs.is_empty() {
6205                        return false;
6206                    }
6207                    for sx in succs {
6208                        q.push_back(sx);
6209                    }
6210                }
6211            }
6212        }
6213        true
6214    }
6215
6216    /// True when the block's statements assign a FINAL field of the
6217    /// emitting class: copying such a shared terminator at an extra
6218    /// arrival site duplicates the final's assignment (javac:
6219    /// 可能在 loop 中分配了变量 / 可能已分配变量 — jdk11
6220    /// SecurityProviderConstants clinit tail assigned its blank-final
6221    /// key sizes in the loop-exit copy AND the handler copy). Such
6222    /// arrivals emit a Goto instead — conversion's term_copy refuses it
6223    /// for the same final_fields reason and elides to the real tail the
6224    /// enclosing flow emits next.
6225    fn terminator_writes_final(&self, b: usize) -> bool {
6226        if self.final_fields.is_empty() {
6227            return false;
6228        }
6229        // Walk the LINEAR tail chain (single-succ Fall/Goto hops, bounded):
6230        // a shared tail whose blank-final write sits PAST the head block
6231        // still double-assigns when copied per-arrival (jdk11/26
6232        // URICertStore clinit: head `CA_ISS_ALLOW_ANY = allowAny` chains
6233        // into `certStoreCache = newSoftMemoryCache(185)` — the head-only
6234        // probe missed the second final and the tail copy landed inside
6235        // the debug-println branch alongside the canonical render —
6236        // 可能已分配变量 ×2). Branching chains stop (a final write on only
6237        // one route is not a guaranteed duplicate).
6238        let writes = |x: usize| {
6239            self.results[x].stmts.iter().any(|s| {
6240                matches!(
6241                    s,
6242                    crate::ir::stmt::Stmt::ExprStmt(crate::ir::expr::Expr::Assign { target, .. })
6243                        if matches!(
6244                            target.as_ref(),
6245                            crate::ir::expr::Expr::Field { name, .. }
6246                                if self.final_fields.contains(name.as_ref())
6247                        )
6248                )
6249            })
6250        };
6251        let mut x = b;
6252        let mut seen: HashSet<usize> = HashSet::default();
6253        for _ in 0..8 {
6254            if !seen.insert(x) {
6255                return false;
6256            }
6257            if writes(x) {
6258                return true;
6259            }
6260            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6261                return false;
6262            }
6263            let succs = &self.cfg.blocks[x].succ;
6264            if succs.len() != 1 {
6265                return false;
6266            }
6267            x = succs[0];
6268        }
6269        false
6270    }
6271
6272    /// Unique enclosing barrier block reachable from `cur`'s branch region.
6273    /// Barriers = stop ∪ claimed. Used by the appendix fold: when exactly
6274    /// one *stop* block is reachable, it is the enclosing merge M.
6275    fn appendix_target(
6276        &self,
6277        cur: usize,
6278        universe: &HashSet<usize>,
6279        stop: &HashSet<usize>,
6280        claimed: &HashSet<usize>,
6281    ) -> Option<usize> {
6282        if stop.is_empty() {
6283            return None;
6284        }
6285        let mut barriers: HashSet<usize> = stop.union(claimed).copied().collect();
6286        barriers.remove(&cur);
6287        let mut hits: HashSet<usize> = HashSet::default();
6288        let mut seen: HashSet<usize> = HashSet::default();
6289        let _ = universe;
6290        let mut q: VecDeque<usize> = self.cfg.blocks[cur].succ.iter().copied().collect();
6291        while let Some(b) = q.pop_front() {
6292            if !seen.insert(b) {
6293                continue;
6294            }
6295            if barriers.contains(&b) {
6296                hits.insert(b);
6297                continue;
6298            }
6299            q.extend(self.cfg.blocks[b].succ.iter().copied());
6300        }
6301        let mut stop_hits = hits.iter().copied().filter(|h| stop.contains(h));
6302        let m = stop_hits.next()?;
6303        if stop_hits.next().is_some() {
6304            return None; // ambiguous
6305        }
6306        // Non-stop hits (claimed blocks) are fine: the appendix region
6307        // merges into them, and folding makes this If their owner.
6308        Some(m)
6309    }
6310
6311    fn absorb_pure(
6312        &mut self,
6313        blk: usize,
6314        universe: &HashSet<usize>,
6315        bstop: &HashSet<usize>,
6316        claimed: &mut HashSet<usize>,
6317        active: &[usize],
6318    ) -> Option<Region> {
6319        if !universe.contains(&blk) {
6320            return None;
6321        }
6322        // A block that STARTS a try group visible to this walk must be
6323        // WALKED, not absorbed: absorption inlines its statements into
6324        // the branch and silently drops the group's exception protection
6325        // (jdk17 HttpURLConnection.getOutputStream: the
6326        // doPrivilegedWithCombiner call absorbed out of its
6327        // try/catch(PrivilegedActionException) into the `if (p == null)`
6328        // else arm — 未报告的异常错误PrivilegedActionException).
6329        let blk_start = self.cfg.blocks[blk].start;
6330        if self
6331            .groups
6332            .iter()
6333            .enumerate()
6334            .any(|(gi, g)| g.start == blk_start && active.contains(&gi))
6335        {
6336            return None;
6337        }
6338        let dbg_absorb = crate::dbg_flag!("JCDC_DBG_ABSORB");
6339        let succ_walkable;
6340        {
6341            let b = &self.cfg.blocks[blk];
6342            let r = &self.results[blk];
6343            if b.ins_len == 0 || b.succ.len() != 1 {
6344                if dbg_absorb {
6345                    eprintln!(
6346                        "absorb EARLY blk={} ins={} succ={} out={}",
6347                        blk,
6348                        b.ins_len == 0,
6349                        b.succ.len(),
6350                        r.out_stack.len()
6351                    );
6352                }
6353                return None;
6354            }
6355            // Statements must be empty or only stack-merge stores.
6356            let stmts_ok = r.stmts.iter().all(|st| match st {
6357                crate::ir::stmt::Stmt::LocalDef { init: Some(_), .. } => true,
6358                crate::ir::stmt::Stmt::ExprStmt(crate::ir::expr::Expr::Assign {
6359                    target, ..
6360                }) => {
6361                    matches!(&**target, crate::ir::expr::Expr::Local { .. })
6362                }
6363                _ => false,
6364            });
6365            if !stmts_ok {
6366                return None;
6367            }
6368            // Only fallthrough blocks may be absorbed: an explicit goto to
6369            // a barrier is a real jump (loop continue / break) that must
6370            // stay a Region::Goto so it resolves to a jump statement.
6371            if !matches!(r.term, crate::ir::build::Term::Fallthrough) {
6372                return None;
6373            }
6374            let succ = b.succ[0];
6375            succ_walkable = universe.contains(&succ) && !bstop.contains(&succ);
6376        }
6377        if succ_walkable {
6378            if crate::dbg_flag!("JCDC_DBG_ABSORB") {
6379                eprintln!(
6380                    "absorb REJECT blk={} succ walkable (univ={}, bstop={})",
6381                    blk,
6382                    universe.contains(&self.cfg.blocks[blk].succ[0]),
6383                    bstop.contains(&self.cfg.blocks[blk].succ[0])
6384                );
6385            }
6386            return None; // normal flow continues; not our case
6387        }
6388        if crate::dbg_flag!("JCDC_DBG_ABSORB") {
6389            eprintln!(
6390                "absorb ACCEPT blk={} claimed={}",
6391                blk,
6392                claimed.contains(&blk)
6393            );
6394        }
6395        // The unwalkable successor is an already-CLAIMED block this arm's
6396        // normal walk would inline-copy (the shared `registry.add(key);
6397        // return key;` tail): absorbing blk ends the arm AT blk and the
6398        // path to the tail is silently severed — the enclosing walk
6399        // breaks at the if with follow=None and the method falls off
6400        // without a return on this path (jdk26
6401        // DatagramChannelImpl.innerJoin's IPv4 arm lost the tail after
6402        // `key = new Type4(..)` — 缺少返回语句 x3 trees). Refuse: the
6403        // normal walk branch emits Basic{blk} and, arriving at the
6404        // claimed successor, copies it per arrival — exactly the bytecode
6405        // semantics. Final-writing successors stay absorbed (copying
6406        // would double-assign the blank final — the UntrustedCertificates
6407        // discipline). NOTE: Module.loadModuleInfoClass's TWR fold needs
6408        // this refusal ABSENT when handler_flow_only carries the
6409        // ownership check — the working combination is this refusal +
6410        // the ORIGINAL f4472a5f fixpoint + the sole-entry private-tail
6411        // cont==hb strip (gate-matrix verified).
6412        {
6413            let succ = self.cfg.blocks[blk].succ[0];
6414            let succ_owned_cont = self.is_cont_of_active_group(succ, bstop, claimed, active);
6415            if claimed.contains(&succ)
6416                && !succ_owned_cont
6417                && !bstop.contains(&succ)
6418                && !self.loops_stack.contains(&succ)
6419                && !self.terminator_writes_final(succ)
6420            {
6421                if dbg_absorb {
6422                    eprintln!(
6423                        "absorb REJECT blk={} succ={} claimed inline-copy tail",
6424                        blk, succ
6425                    );
6426                }
6427                return None;
6428            }
6429        }
6430        // Shared pure blocks (reached from several branches) get their
6431        // statements duplicated into each branch — exactly the bytecode
6432        // semantics, since the block re-executes per arrival.
6433        if claimed.contains(&blk) {
6434            let r = &self.results[blk];
6435            if r.stmts.is_empty() {
6436                return Some(Region::Empty);
6437            }
6438            return Some(Region::CopyStmts { block: blk });
6439        }
6440        claimed.insert(blk);
6441        Some(Region::Basic { block: blk })
6442    }
6443
6444    /// Nearest block reachable from both `a` and `b` (min total BFS dist).
6445    fn branch_confluence(&self, a: usize, b: usize, universe: &HashSet<usize>) -> Option<usize> {
6446        let bfs = |s0: usize| -> HashMap<usize, u32> {
6447            let mut d: HashMap<usize, u32> = HashMap::default();
6448            let mut q: VecDeque<(usize, u32)> = VecDeque::new();
6449            d.insert(s0, 0);
6450            q.push_back((s0, 0));
6451            while let Some((x, dx)) = q.pop_front() {
6452                for &s in &self.cfg.blocks[x].succ {
6453                    if universe.contains(&s) && !d.contains_key(&s) {
6454                        d.insert(s, dx + 1);
6455                        q.push_back((s, dx + 1));
6456                    }
6457                }
6458            }
6459            d
6460        };
6461        let da = bfs(a);
6462        let db = bfs(b);
6463        let mut best: Option<(u32, usize)> = None;
6464        for (&c, &x) in &da {
6465            if let Some(&y) = db.get(&c) {
6466                let t = x + y;
6467                let better = match best {
6468                    None => true,
6469                    Some((bt, bc)) => {
6470                        t < bt || (t == bt && self.cfg.blocks[c].start < self.cfg.blocks[bc].start)
6471                    }
6472                };
6473                if better {
6474                    best = Some((t, c));
6475                }
6476            }
6477        }
6478        best.map(|(_, c)| c)
6479    }
6480
6481    /// Follow a chain of pure value-producing blocks (no statements, one
6482    /// stack value out, fallthrough/goto terminals) and return the first
6483    /// block that is NOT pure — the real merge point of a value diamond.
6484    fn pure_chain_end(
6485        &self,
6486        start: usize,
6487        universe: &HashSet<usize>,
6488        stop: &HashSet<usize>,
6489        claimed: &HashSet<usize>,
6490    ) -> Option<usize> {
6491        let mut cur = start;
6492        let mut steps = 0;
6493        loop {
6494            steps += 1;
6495            if steps > 10_000 || !universe.contains(&cur) || stop.contains(&cur) {
6496                return None;
6497            }
6498            let b = &self.cfg.blocks[cur];
6499            let r = &self.results[cur];
6500            let pure = b.ins_len != 0
6501                && r.stmts.is_empty()
6502                && !r.out_stack.is_empty()
6503                && matches!(
6504                    r.term,
6505                    crate::ir::build::Term::Fallthrough | crate::ir::build::Term::Goto
6506                )
6507                && b.succ.len() == 1;
6508            if !pure {
6509                return Some(cur);
6510            }
6511            if claimed.contains(&cur) {
6512                return None;
6513            }
6514            cur = b.succ[0];
6515        }
6516    }
6517
6518    /// Check that both `a` and `b` flow to `merge` through blocks that are
6519    /// pure value producers (no statements; terminals are fallthrough or
6520    /// goto), possibly via nested diamond headers. All traversed blocks are
6521    /// added to `visited` so the caller can claim them.
6522    #[allow(dead_code)]
6523    #[allow(clippy::too_many_arguments)] // structuring context travels as one bundle
6524    fn branches_are_diamond(
6525        &self,
6526        a: usize,
6527        b: usize,
6528        merge: usize,
6529        universe: &HashSet<usize>,
6530        stop: &HashSet<usize>,
6531        claimed: &HashSet<usize>,
6532        visited: &mut HashSet<usize>,
6533    ) -> bool {
6534        self.diamond_side(a, merge, universe, stop, claimed, visited, 0)
6535            && self.diamond_side(b, merge, universe, stop, claimed, visited, 0)
6536    }
6537
6538    #[allow(clippy::too_many_arguments)] // structuring context travels as one bundle
6539    fn diamond_side(
6540        &self,
6541        blk: usize,
6542        merge: usize,
6543        universe: &HashSet<usize>,
6544        stop: &HashSet<usize>,
6545        claimed: &HashSet<usize>,
6546        visited: &mut HashSet<usize>,
6547        depth: usize,
6548    ) -> bool {
6549        if depth > 32 {
6550            return false;
6551        }
6552        let mut cur = blk;
6553        let mut steps = 0;
6554        loop {
6555            steps += 1;
6556            if steps > 10_000 {
6557                return false;
6558            }
6559            if cur == merge {
6560                return true;
6561            }
6562            if stop.contains(&cur) || claimed.contains(&cur) || !universe.contains(&cur) {
6563                return false;
6564            }
6565            let b = &self.cfg.blocks[cur];
6566            if b.ins_len == 0 {
6567                match b.succ.first() {
6568                    Some(&n) => {
6569                        visited.insert(cur);
6570                        cur = n;
6571                        continue;
6572                    }
6573                    None => return false,
6574                }
6575            }
6576            match self.term(cur) {
6577                Term::Fallthrough | Term::Goto => {
6578                    let r = &self.results[cur];
6579                    // pure = no statements and exactly one successor (the
6580                    // value it leaves may be any stack depth >= 0)
6581                    if !r.stmts.is_empty() || b.succ.len() != 1 {
6582                        return false;
6583                    }
6584                    let n = b.succ[0];
6585                    visited.insert(cur);
6586                    cur = n;
6587                }
6588                Term::Cond { .. } if b.succ.len() == 2 => {
6589                    // nested diamond header: both sides must reach merge
6590                    if !self.results[cur].stmts.is_empty() {
6591                        return false;
6592                    }
6593                    visited.insert(cur);
6594                    let s0 = b.succ[0];
6595                    let s1 = b.succ[1];
6596                    let mut v2 = HashSet::default();
6597                    let ok =
6598                        self.diamond_side(s0, merge, universe, stop, claimed, &mut v2, depth + 1)
6599                            && self.diamond_side(
6600                                s1,
6601                                merge,
6602                                universe,
6603                                stop,
6604                                claimed,
6605                                &mut v2,
6606                                depth + 1,
6607                            );
6608                    if ok {
6609                        visited.extend(v2);
6610                        return true;
6611                    }
6612                    return false;
6613                }
6614                _ => return false,
6615            }
6616        }
6617    }
6618
6619    fn next_after_loop(
6620        &self,
6621        loop_r: &Region,
6622        universe: &HashSet<usize>,
6623        stop: &HashSet<usize>,
6624        claimed: &HashSet<usize>,
6625    ) -> Option<usize> {
6626        let exits = match loop_r {
6627            Region::Loop { exits, .. } => exits.clone(),
6628            _ => return None,
6629        };
6630        let _ = claimed;
6631        // Pre-stub-filter candidate set: the shared-collector test in
6632        // stub_chain_claims_continuation checks chain intermediates
6633        // against it.
6634        let pre: HashSet<usize> = exits
6635            .iter()
6636            .copied()
6637            .filter(|e| universe.contains(e) && !stop.contains(e))
6638            .collect();
6639        exits
6640            .into_iter()
6641            .filter(|e| universe.contains(e) && !stop.contains(e))
6642            // An exit that is a statement-free jump chain back into an
6643            // ENCLOSING loop is a `continue outer` stub — one of the inner
6644            // loop's conditional break arms, already resolved to a break
6645            // inside the body. Walking it as the straight-line continuation
6646            // re-emits the jump unconditionally after the inner loop
6647            // (jdk11 FutureTask.removeWaiter: the pred.thread/CAS-fail
6648            // `continue retry` stubs became an unconditional `continue`
6649            // after the inner while, the outer while(true) never completed
6650            // and the tail `return` went unreachable — 无法访问的语句 x3
6651            // trees). Real fall-out exits (post-loop code) are kept.
6652            .filter(|e| {
6653                !self.loops_stack.iter().any(|&h| {
6654                    h != *e
6655                        && self.is_stmt_free_chain_to_block(*e, h)
6656                        && !self.stub_chain_claims_continuation(*e, h, &pre)
6657                })
6658            })
6659            .min_by_key(|e| self.cfg.blocks[*e].start)
6660    }
6661
6662    /// True when `from` reaches `to` through statement-free
6663    /// Fallthrough/Goto blocks (walk-side twin of the strip helper).
6664    pub(crate) fn is_stmt_free_chain_to_block(&self, from: usize, to: usize) -> bool {
6665        let mut x = from;
6666        let mut seen: HashSet<usize> = HashSet::default();
6667        for _ in 0..8 {
6668            if x == to {
6669                return true;
6670            }
6671            if !seen.insert(x) {
6672                return false;
6673            }
6674            if !self.results[x].stmts.is_empty() {
6675                return false;
6676            }
6677            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6678                return false;
6679            }
6680            let succs = self.cfg.blocks[x].succ.clone();
6681            if succs.len() != 1 {
6682                return false;
6683            }
6684            x = succs[0];
6685        }
6686        false
6687    }
6688
6689    /// True when `from`'s statement-free jump chain into loop header `to`
6690    /// passes through a block that is ITSELF a continuation candidate for
6691    /// the just-structured loop (in-universe, non-stop, non-stub exit with
6692    /// multiple predecessors — a shared continue-collector). Such a chain
6693    /// is the loop's real backedge merge, not a disposable conditional
6694    /// break arm: filtering it lets a lower-priority candidate (a bare
6695    /// `return` escape) become the continuation and the loop body loses
6696    /// its trailing re-iteration (jdk17 AQS.cleanQueue: the traversal
6697    /// loop's exits are the return block and the `goto 171` stub; block
6698    /// 171 (`goto 0`) has 10 preds and is a candidate — dropping the stub
6699    /// picked the return, the outer for(;;) body lost its `continue`, and
6700    /// the do-while rotation relocated the CAS tail past the loop with
6701    /// bare continues — continue 在 loop 外部). Direct-header backedges
6702    /// (jdk11 FutureTask.removeWaiter's `goto retry` stubs, chain = the
6703    /// exit block alone) have no intermediate shared block and stay
6704    /// filtered: walking them re-emitted the conditional retry jump as an
6705    /// unconditional continue after the inner loop.
6706    pub(crate) fn stub_chain_claims_continuation(
6707        &self,
6708        from: usize,
6709        to: usize,
6710        cands: &HashSet<usize>,
6711    ) -> bool {
6712        let mut x = from;
6713        let mut seen: HashSet<usize> = HashSet::default();
6714        while x != to {
6715            if !seen.insert(x) {
6716                return false;
6717            }
6718            if x != from && cands.contains(&x) && self.cfg.blocks[x].pred.len() > 1 {
6719                return true;
6720            }
6721            if !self.results[x].stmts.is_empty() {
6722                return false;
6723            }
6724            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
6725                return false;
6726            }
6727            let succs = &self.cfg.blocks[x].succ;
6728            if succs.len() != 1 {
6729                return false;
6730            }
6731            x = succs[0];
6732        }
6733        false
6734    }
6735
6736    fn structure_loop(
6737        &mut self,
6738        header: usize,
6739        universe: &HashSet<usize>,
6740        stop: &HashSet<usize>,
6741        active: &[usize],
6742        claimed: &mut HashSet<usize>,
6743        dom: &DomInfo,
6744    ) -> Region {
6745        // Dominators rooted at the loop header (the incoming dom is rooted
6746        // at the enclosing walk entry and would misjudge inner loops).
6747        let dom_owned = compute_dominators(self.cfg, universe, header);
6748        let _dom = &dom_owned;
6749        // Loop body = blocks forward-reachable from the header without
6750        // re-entering it, EXCLUDING:
6751        // * blocks not dominated by the header,
6752        // * the header's own exiting branch (direct successor that cannot
6753        //   loop back),
6754        // * break-outer targets (cannot loop back and the branching block
6755        //   only escapes through them),
6756        // * blocks whose way back to the header requires an ENCLOSING loop's
6757        //   back edge (e.g. the outer increment block of nested loops).
6758        let mut barriers: HashSet<usize> = HashSet::default();
6759        for l in &self.loops_stack {
6760            barriers.insert(*l);
6761        }
6762        barriers.insert(header);
6763        // Exception successors (NOT filtered by `universe`): the handler that
6764        // loops back is carved out of this region's universe because it is
6765        // structured separately by structure_try, yet it is real control flow.
6766        // A protected block whose normal successors all return/throw can still
6767        // reach the header through that handler. Without these edges the
6768        // membership reachability below misclassifies the try head as a loop
6769        // EXIT and hoists the whole try/catch out of the loop body.
6770        let mut exc_succ: HashMap<usize, Vec<usize>> = HashMap::default();
6771        for e in &self.cfg.exc_edges {
6772            exc_succ.entry(e.from).or_default().push(e.to);
6773        }
6774        let mut members: HashSet<usize> = HashSet::default();
6775        members.insert(header);
6776        {
6777            let mut q: VecDeque<usize> = VecDeque::new();
6778            q.push_back(header);
6779            while let Some(b) = q.pop_front() {
6780                for &s in &self.cfg.blocks[b].succ {
6781                    let dbg_lmem = crate::dbg_flag!("JCDC_DBG_LMEM");
6782                    if barriers.contains(&s) && s != header {
6783                        // enclosing loop header: never absorb it
6784                        if dbg_lmem {
6785                            eprintln!("LMEM h={} from={} s={} why=barrier", header, b, s);
6786                        }
6787                        continue;
6788                    }
6789                    if s == header || !universe.contains(&s) || stop.contains(&s) {
6790                        if dbg_lmem {
6791                            eprintln!(
6792                                "LMEM h={} from={} s={} why=hdr{} univ{} stop{} hndlr={} succ={:?}",
6793                                header,
6794                                b,
6795                                s,
6796                                s == header,
6797                                universe.contains(&s),
6798                                stop.contains(&s),
6799                                self.is_handler(s),
6800                                self.cfg.blocks[s].succ
6801                            );
6802                        }
6803                        continue;
6804                    }
6805                    if !dom.dominates(header, s) {
6806                        if dbg_lmem {
6807                            eprintln!(
6808                                "LMEM h={} from={} s={} why=dom preds={:?}",
6809                                header, b, s, self.cfg.blocks[s].pred
6810                            );
6811                        }
6812                        continue;
6813                    }
6814                    let reaches =
6815                        can_reach_cfg_barred(self.cfg, &exc_succ, s, header, &barriers, 8192);
6816                    if dbg_lmem && !reaches {
6817                        eprintln!(
6818                            "LMEM h={} from={} s={} reaches=false succ={:?}",
6819                            header, b, s, self.cfg.blocks[s].succ
6820                        );
6821                    }
6822                    if !reaches {
6823                        // `s` cannot loop back on its own. It is the loop's
6824                        // exit merge when the header branches to it directly
6825                        // or some predecessor reaches it as its dedicated
6826                        // escape jump. A body-internal terminator tail
6827                        // (return/throw whose predecessor only falls into
6828                        // it) stays a member.
6829                        let preds = self.cfg.blocks[s].pred.clone();
6830                        // A pred OUTSIDE this walk's universe is a dedicated
6831                        // escape by definition: its flow belongs to the
6832                        // enclosing scope, so `s` is a shared merge the loop
6833                        // must not own. Without this, a copy_walk scope
6834                        // rooted at a branch target (entry 24, single succ =
6835                        // the loop header) makes the header dominate the
6836                        // shared tail, and every in-scope pred reaches the
6837                        // header around it -- the tail was absorbed as a
6838                        // member, exits emptied, and the post-loop
6839                        // `append('Z'); return true` landed INSIDE the
6840                        // while(true) (jdk11/17 DateTimeFormatterBuilder
6841                        // InstantPrinterParser.format: 缺少返回语句).
6842                        let mut exit_edge = preds.iter().any(|&p| {
6843                            p == header
6844                                || !universe.contains(&p)
6845                                || !can_reach_avoiding(self.cfg, &exc_succ, p, header, s, 8192)
6846                        });
6847                        if !exit_edge {
6848                            // Confluence of dedicated escape jumps: every
6849                            // predecessor also branches to another exit
6850                            // candidate that cannot reach the header.
6851                            exit_edge = !preds.is_empty()
6852                                && preds.iter().all(|&p| {
6853                                    self.cfg.blocks[p].succ.iter().any(|&q| {
6854                                        q != s
6855                                            && !can_reach_cfg_barred(
6856                                                self.cfg, &exc_succ, q, header, &barriers, 8192,
6857                                            )
6858                                    })
6859                                });
6860                        }
6861                        if exit_edge || preds.is_empty() {
6862                            // Last chance: a protected terminator tail. The
6863                            // JVM splits `try { return f(); }` into a call
6864                            // block (inside the range, has an exc edge) and a
6865                            // bare `return` block starting exactly at the
6866                            // range end (no exc edge of its own). Such a
6867                            // return is body-internal — keep it when its
6868                            // predecessors are already members sharing a try
6869                            // range. Without this, the in-loop try's arm
6870                            // region ends at the call block and the return is
6871                            // lost.
6872                            let protected_tail = self.cfg.blocks[s].succ.is_empty()
6873                                && !preds.is_empty()
6874                                && preds.iter().all(|&p| {
6875                                    members.contains(&p)
6876                                        && self.cfg.exc_ranges.iter().any(|r| {
6877                                            r.start <= self.cfg.blocks[p].start
6878                                                && self.cfg.blocks[p].start < r.end
6879                                                && self.cfg.blocks[s].start <= r.end
6880                                                && self.cfg.blocks[s].start
6881                                                    > self.cfg.blocks[p].start
6882                                        })
6883                                });
6884                            if !protected_tail {
6885                                continue;
6886                            }
6887                        }
6888                    }
6889                    if members.insert(s) {
6890                        q.push_back(s);
6891                    }
6892                }
6893            }
6894        }
6895        let mut exits: Vec<usize> = Vec::new();
6896        for &m in &members {
6897            for &s in &self.cfg.blocks[m].succ {
6898                if !members.contains(&s) && !exits.contains(&s) {
6899                    exits.push(s);
6900                }
6901            }
6902        }
6903        exits.sort_by_key(|e| self.cfg.blocks[*e].start);
6904
6905        let mut inner_stop: HashSet<usize> = stop
6906            .iter()
6907            .copied()
6908            .filter(|s| members.contains(s))
6909            .collect();
6910        inner_stop.extend(exits.iter().copied());
6911        // NOTE: header is NOT in inner_stop — the walk starts there and the
6912        // claimed-guard stops re-entry (back edges become Goto{header}).
6913        if crate::dbg_flag!("JCDC_DBG_IF") {
6914            eprintln!(
6915                "structure_loop header={} members={:?} exits={:?}",
6916                header, members, exits
6917            );
6918        }
6919        if crate::dbg_flag!("JCDC_DBG_LOOP") {
6920            eprintln!(
6921                "LOOP header={} members={:?} inner_stop={:?}",
6922                header, members, inner_stop
6923            );
6924        }
6925        claimed.insert(header);
6926        self.loops_stack.push(header);
6927        let mut body = self.walk(header, &members, &inner_stop, active, claimed, true);
6928        if crate::dbg_flag!("JCDC_DBG_LOOP") {
6929            eprintln!("LOOP body region: {:#?}", body);
6930        }
6931        // Walk-side twin of the SESE loop-site call: orphaned
6932        // statement-bearing exits materialized into the breaking arms
6933        // (jdk26 java.nio.Bits.reserveMemory's phase-1 success path —
6934        // `tryReserveOrClean` true falls to the interrupted-check +
6935        // return epilogue (blocks 16-18) which the bare-break rendering
6936        // dropped entirely: the success path RE-LOOPED, re-reserving
6937        // memory forever; javac can't see it, the walk path structures
6938        // this method, so the SESE-only pass never ran).
6939        if !crate::dbg_flag!("JCDC_NO_MATEXIT") {
6940            let natural: HashSet<usize> = self.cfg.blocks[header]
6941                .succ
6942                .iter()
6943                .copied()
6944                .filter(|x| !members.contains(x) && *x != header)
6945                .collect();
6946            let reach_all: HashSet<usize> = (0..self.cfg.blocks.len()).collect();
6947            let lh: HashSet<usize> = HashSet::default();
6948            self.materialize_content_exits(
6949                &mut body,
6950                header,
6951                &exits,
6952                &members,
6953                &natural,
6954                &members,
6955                &self.loops_stack.clone(),
6956                &lh,
6957                active,
6958                stop,
6959                &reach_all,
6960                claimed,
6961                false,
6962            );
6963        }
6964        self.loops_stack.pop();
6965        claimed.extend(members.iter().copied());
6966        Region::Loop {
6967            header,
6968            body: Box::new(body),
6969            members,
6970            exits,
6971        }
6972    }
6973
6974    #[allow(clippy::too_many_arguments)] // structuring context travels as one bundle
6975    pub(crate) fn structure_switch(
6976        &mut self,
6977        block: usize,
6978        selector: Expr,
6979        targets: &SwitchTargets,
6980        universe: &HashSet<usize>,
6981        stop: &HashSet<usize>,
6982        follow: Option<usize>,
6983        active: &[usize],
6984        claimed: &mut HashSet<usize>,
6985    ) -> Region {
6986        if crate::dbg_flag!("JCDC_DBG_GOTO") {
6987            eprintln!(
6988                "SWITCHENTRY block={} start={} follow={:?} universe={} stop={:?} claimed={:?}",
6989                block,
6990                self.cfg.blocks[block].start,
6991                follow,
6992                universe.len(),
6993                stop,
6994                claimed
6995            );
6996        }
6997        let (pairs, default_pc): (Vec<(i64, u32)>, u32) =
6998            match (targets, self.results.get(block).map(|r| &r.term)) {
6999                (
7000                    SwitchTargets::Table { .. },
7001                    Some(Term::Switch {
7002                        targets: SwitchTargets::Table { low, targets: ts },
7003                        default,
7004                        ..
7005                    }),
7006                ) => (
7007                    ts.iter()
7008                        .enumerate()
7009                        .map(|(i, &t)| (*low as i64 + i as i64, t))
7010                        .collect(),
7011                    default.unwrap_or(0),
7012                ),
7013                (
7014                    SwitchTargets::Lookup { .. },
7015                    Some(Term::Switch {
7016                        targets: SwitchTargets::Lookup { pairs: ps },
7017                        default,
7018                        ..
7019                    }),
7020                ) => (
7021                    ps.iter().map(|(m, t)| (*m as i64, *t)).collect(),
7022                    default.unwrap_or(0),
7023                ),
7024                _ => (Vec::new(), 0),
7025            };
7026        // If the default target is the switch follow (confluence), there is
7027        // no explicit default region — flow just continues after the switch.
7028        let default_block = self.cfg.block_at(default_pc).filter(|d| Some(*d) != follow);
7029
7030        let mut case_groups: Vec<(Vec<i64>, usize, bool)> = Vec::new(); // (vals, block, is_follow)
7031        for (v, t) in &pairs {
7032            let Some(tb) = self.cfg.block_at(*t) else {
7033                continue;
7034            };
7035            if Some(tb) == default_block {
7036                continue;
7037            }
7038            if Some(tb) == follow {
7039                // Case that jumps straight to the confluence: empty body + break.
7040                if let Some(g) = case_groups.iter_mut().find(|(_, gb, f)| *gb == tb && *f) {
7041                    g.0.push(*v);
7042                } else {
7043                    case_groups.push((vec![*v], tb, true));
7044                }
7045                continue;
7046            }
7047            if let Some(g) = case_groups.iter_mut().find(|(_, gb, f)| *gb == tb && !*f) {
7048                g.0.push(*v);
7049            } else {
7050                case_groups.push((vec![*v], tb, false));
7051            }
7052        }
7053        case_groups.sort_by_key(|(_, b, _)| self.cfg.blocks[*b].start);
7054
7055        // Java switch case groups fall through when their body completes
7056        // normally: an arm region whose flow does NOT provably land on the
7057        // switch follow (a dangling Goto to a claimed sibling block that
7058        // conversion elides or inlines without its jump — jdk17/26
7059        // AbstractValidatingLambdaMetafactory ctor: case-7's
7060        // `if (targetClass == implClass && isPrivate)` middle branch jumped
7061        // to the already-claimed `implKind = 7` block whose Goto{follow}
7062        // the RawGoto stmt-inline consumed, so the arm fell through into
7063        // case 6/8 and re-assigned the blank finals implClass/implKind/
7064        // implIsInstanceMethod — 可能已分配变量 ×3) gets an explicit
7065        // trailing Goto{follow}, which conversion materializes as `break`.
7066        // Arms that bind already (trailing Goto{follow}, terminators,
7067        // both-branch if binds) and abrupt arms are untouched; when no
7068        // follow is known the historical shape is kept.
7069        fn bind_arm(st: &Structurer, r: Region, follow: Option<usize>) -> Region {
7070            let Some(f) = follow else { return r };
7071            if st.arm_binds_to(&r, f) || region_terminates(&r, st.results) {
7072                return r;
7073            }
7074            // A nested switch whose groups are ALL abrupt cannot complete
7075            // normally by JLS 14.11.1 (only the LAST group's normal
7076            // completion and reachable unlabeled breaks count — arm_binds
7077            // _to's nested-switch rule is deliberately conservative and
7078            // says "not bindable" there): an appended break after it is an
7079            // 无法访问的语句 (jdk internal xml Parser's `switch (wsskip())
7080            // { case: continue; case: panic(); default: break L11; }`).
7081            // The arm already cannot fall through — append nothing.
7082            fn abrupt_switch_at_end(r: &Region, results: &[crate::ir::build::BlockResult]) -> bool {
7083                fn all_abrupt(rr: &Region, results: &[crate::ir::build::BlockResult]) -> bool {
7084                    match rr {
7085                        Region::Switch { cases, default, .. } => {
7086                            // JLS 14.11.1: a switch CANNOT complete normally
7087                            // when its LAST rendered group is abrupt (earlier
7088                            // groups either end abruptly or fall THROUGH into
7089                            // their successor, which is not normal completion
7090                            // of the switch) and no unlabeled break targets
7091                            // it — those surface as Goto{this switch's own
7092                            // follow}, which structure_switch never leaves
7093                            // dangling inside an arm. default renders last.
7094                            match default {
7095                                Some(d) => all_abrupt(d, results),
7096                                None => cases
7097                                    .last()
7098                                    .map(|c| all_abrupt(&c.1, results))
7099                                    .unwrap_or(false),
7100                            }
7101                        }
7102                        Region::Seq(v) => match v.last() {
7103                            Some(last) => all_abrupt(last, results),
7104                            None => false,
7105                        },
7106                        Region::If { then_r, else_r, .. } => {
7107                            all_abrupt(then_r, results) && all_abrupt(else_r, results)
7108                        }
7109                        Region::Loop { .. } => true,
7110                        Region::Goto { .. } => true,
7111                        Region::Basic { block } => {
7112                            matches!(results[*block].term, Term::Return(_) | Term::Throw(_))
7113                        }
7114                        Region::CopyStmts { .. } => false,
7115                        Region::Empty => false,
7116                        // Try and any other shape: conservatively NOT
7117                        // all-abrupt — the appended break stays reachable.
7118                        _ => false,
7119                    }
7120                }
7121                // ONLY a nested switch at the arm's end qualifies — an
7122                // arm ending in a bare Goto to a non-follow block is the
7123                // dangling-jump case the append exists for (the Goto is
7124                // elided/inlined at conversion, so the arm falls through
7125                // in the rendered layout despite looking abrupt here).
7126                match r {
7127                    Region::Seq(v) => {
7128                        matches!(v.last(), Some(Region::Switch { .. }) if all_abrupt(v.last().unwrap(), results))
7129                    }
7130                    Region::Switch { .. } => all_abrupt(r, results),
7131                    _ => false,
7132                }
7133            }
7134            if abrupt_switch_at_end(&r, st.results) {
7135                return r;
7136            }
7137            Region::Seq(vec![r, Region::Goto { target: f }])
7138        }
7139        let mut case_stop = stop.clone();
7140        if let Some(f) = follow {
7141            case_stop.insert(f);
7142        }
7143        for (_, b, is_follow) in &case_groups {
7144            if !is_follow {
7145                case_stop.insert(*b);
7146            }
7147        }
7148        if let Some(d) = default_block {
7149            case_stop.insert(d);
7150        }
7151
7152        // All case/default head blocks: a trailing Goto to one of them is a
7153        // switch fallthrough (no statement in Java).
7154        let mut head_set: HashSet<usize> = HashSet::default();
7155        for (_, b, is_follow) in &case_groups {
7156            if !is_follow {
7157                head_set.insert(*b);
7158            }
7159        }
7160        if let Some(d) = default_block {
7161            head_set.insert(d);
7162        }
7163
7164        let mut cases = Vec::new();
7165        // Java 21+ pattern switch: the restoration pass re-appends each
7166        // labelled case's terminal `break;` — a case arm ending at a
7167        // nested switch lands on THIS switch's follow (see case_arm_ctx).
7168        let is_pattern = matches!(&selector, Expr::Invokedynamic { name, args, .. }
7169            if name == "typeSwitch" && !args.is_empty());
7170        for (vals, b, is_follow) in case_groups {
7171            if is_follow {
7172                // Empty case that breaks out to the confluence.
7173                cases.push((vals, Region::Goto { target: b }));
7174                continue;
7175            }
7176            if !universe.contains(&b) || claimed.contains(&b) {
7177                // A case jumping into an already-structured terminator tail
7178                // gets its own copy (per-case epilogues are javac's normal
7179                // return-path duplication).
7180                let copied = if !stop.contains(&b) && !self.loops_stack.contains(&b) {
7181                    let mut cstop = case_stop.clone();
7182                    cstop.remove(&b);
7183                    self.copy_walk(b, &cstop, active, block)
7184                } else {
7185                    None
7186                };
7187                if crate::dbg_flag!("JCDC_DBG_GOTO") {
7188                    eprintln!(
7189                        "SWCASE b={} univ={} claimed={} stop={} follow={:?} loops={:?} copied={}",
7190                        b,
7191                        universe.contains(&b),
7192                        claimed.contains(&b),
7193                        case_stop.contains(&b),
7194                        follow,
7195                        self.loops_stack,
7196                        copied.is_some()
7197                    );
7198                }
7199                cases.push((vals, copied.unwrap_or(Region::Goto { target: b })));
7200                continue;
7201            }
7202            // The case's own head must not be in its stop set.
7203            let mut cstop = case_stop.clone();
7204            cstop.remove(&b);
7205            let sub = self.sub_scope(b, universe, &cstop, claimed);
7206            self.case_arm_ctx.push((b, follow, is_pattern));
7207            let r = self.walk(b, &sub, &cstop, active, claimed, false);
7208            self.case_arm_ctx.pop();
7209            let r = strip_fallthrough_goto(r, &head_set);
7210            cases.push((vals, bind_arm(self, r, follow)));
7211        }
7212        let default = default_block.map(|d| {
7213            if !universe.contains(&d) || claimed.contains(&d) {
7214                let copied = if !stop.contains(&d) && !self.loops_stack.contains(&d) {
7215                    let mut cstop = case_stop.clone();
7216                    cstop.remove(&d);
7217                    self.copy_walk(d, &cstop, active, block)
7218                } else {
7219                    None
7220                };
7221                return Box::new(copied.unwrap_or(Region::Goto { target: d }));
7222            }
7223            let mut cstop = case_stop.clone();
7224            cstop.remove(&d);
7225            let sub = reachable_within(self.cfg, d, &cstop);
7226            self.case_arm_ctx.push((d, follow, is_pattern));
7227            let r = self.walk(d, &sub, &cstop, active, claimed, false);
7228            self.case_arm_ctx.pop();
7229            Box::new(bind_arm(self, strip_fallthrough_goto(r, &head_set), follow))
7230        });
7231        Region::Switch {
7232            block,
7233            selector,
7234            cases,
7235            default,
7236            follow,
7237        }
7238    }
7239
7240    /// True when block `b` is the CONFLUENT continuation of group `gi`:
7241    /// the handler pc IS the span-end block and the try body falls
7242    /// through to it on the normal path (d8 $SwitchMap clinits — the
7243    /// NoSuchFieldError handler resumes at the body's own continuation,
7244    /// no move-exception). Such a catch renders EMPTY and the block
7245    /// belongs to the post-try walk.
7246    fn is_confluent_tail(&self, gi: usize, b: usize) -> bool {
7247        let g = &self.groups[gi];
7248        self.cfg.block_at(g.end) == Some(b)
7249            && self.cfg.blocks[b]
7250                .pred
7251                .iter()
7252                .any(|p| self.body_group.get(p) == Some(&gi))
7253    }
7254
7255    pub(crate) fn structure_try(
7256        &mut self,
7257        gi: usize,
7258        universe: &HashSet<usize>,
7259        outer_universe: &HashSet<usize>,
7260        claimed: &mut HashSet<usize>,
7261        stop: &HashSet<usize>,
7262    ) -> Region {
7263        let g = self.groups[gi].clone();
7264        self.structuring_groups.borrow_mut().push(gi);
7265        let nested: Vec<usize> = (0..self.groups.len())
7266            .filter(|&j| j != gi && self.groups[j].start >= g.start && self.groups[j].end <= g.end)
7267            .collect();
7268        // Body universe includes nested groups' blocks so the walk reaches
7269        // their start and carves out inner Try regions.
7270        let body_universe: HashSet<usize> = universe
7271            .iter()
7272            .copied()
7273            .filter(|b| match self.body_group.get(b) {
7274                Some(ogi) => *ogi == gi || nested.contains(ogi),
7275                None => false,
7276            })
7277            .collect();
7278        if crate::dbg_flag!("JCDC_DBG_IF") {
7279            eprintln!(
7280                "structure_try gi={} span=({},{}) body_universe={:?} nested={:?}",
7281                gi, g.start, g.end, body_universe, nested
7282            );
7283        }
7284        // javac often excludes the final `areturn` from the protected span
7285        // (it cannot throw). When that trailing terminator's only entry is
7286        // the protected flow, it semantically belongs to the try body —
7287        // absorb it so `return f();` stays inside the try and the catches
7288        // remain legal.
7289        let mut body_universe = body_universe;
7290        if let Some(cb) = self.cfg.block_at(g.end) {
7291            // The absorbing tail must belong to THIS group's flow: a
7292            // loop-exit block parked at exactly g.end (jdk26
7293            // Future.resultNow's normal-path finally-if at pc 28, the
7294            // retry loop's break target) has the protected block as its
7295            // only pred but continues to the post-loop tail — absorbing it
7296            // hides the exit from the enclosing loop's continuation
7297            // (exits/natural_follow lose it) and strands `interrupt();
7298            // return result;` (缺少返回语句). Preds may be body blocks,
7299            // this group's handler heads, or handler-flow-only blocks
7300            // (the pending-rethrow copy shapes). Do NOT gate on
7301            // !stop.contains(cb): a shared return tail that is ALSO an
7302            // enclosing loop's exit must still absorb when its preds are
7303            // all group flow — refusing left ReflectionFactory
7304            // .getReplaceResolveForSerialization's copied inner-try
7305            // return as a Goto that resolved to `break` (the
7306            // IllegalAccessException catch lost its throwing call).
7307            // Future's exit block is ruled out by the pred test alone
7308            // (its succs continue past the group, so it is not a
7309            // terminator).
7310            let hf_own = self.handler_flow_only(gi);
7311            let pred_in_group_flow = |p: &usize| {
7312                self.body_group.get(p) == Some(&gi)
7313                    || self.handler_group.get(p) == Some(&gi)
7314                    || hf_own.contains(p)
7315                    || body_universe.contains(p)
7316            };
7317            if !body_universe.contains(&cb)
7318                && self.is_terminator_block(cb)
7319                && self.results[cb].stmts.is_empty()
7320                && !self.handler_group.contains_key(&cb)
7321                && !self.cfg.blocks[cb].pred.is_empty()
7322                && self.cfg.blocks[cb].pred.iter().all(pred_in_group_flow)
7323            {
7324                body_universe.insert(cb);
7325            }
7326        }
7327        // A protected VALUE PUSH whose consumer sits just past the span
7328        // must stay inside the try: javac excludes the monitorexit+areturn
7329        // tail from the protected range (jdk26 ZipFile.getComment's
7330        // `return zipCoder.toString(comment)`), so the body block ends in
7331        // Fallthrough with the value on the out-stack and the Return that
7332        // names the call lives outside — converting that split either
7333        // empties the try (checked catch illegal) or emits the throwing
7334        // call UNPROTECTED. Absorb the linear statements-free fallthrough
7335        // chain plus the single Return consumer. The return only runs
7336        // after the chain completes, so it is exception-equivalent to the
7337        // protected call; MonitorExit markers ride into the body where
7338        // reconstruct_synchronized strips them again.
7339        {
7340            let entry_blk = self
7341                .cfg
7342                .blocks
7343                .iter()
7344                .find(|b| b.start == g.start && body_universe.contains(&b.id))
7345                .map(|b| b.id);
7346            if let Some(eb) = entry_blk {
7347                let spans = |gi: usize, og: usize| {
7348                    self.groups[og].start <= self.groups[gi].start
7349                        && self.groups[og].end >= self.groups[gi].end
7350                };
7351                let ownable = |n: usize| match self.body_group.get(&n) {
7352                    None => true,
7353                    Some(&og) => {
7354                        og == gi
7355                            || (self.groups[og].start >= self.groups[gi].start
7356                                && self.groups[og].end <= self.groups[gi].end)
7357                            || spans(gi, og)
7358                    }
7359                };
7360                if matches!(self.results[eb].term, Term::Fallthrough)
7361                    && self.results[eb].out_stack.len() == 1
7362                {
7363                    let mut x = eb;
7364                    for _ in 0..4 {
7365                        let succs = self.cfg.blocks[x].succ.clone();
7366                        if succs.len() != 1 {
7367                            break;
7368                        }
7369                        let n = succs[0];
7370                        if body_universe.contains(&n)
7371                            || self.handler_group.contains_key(&n)
7372                            || !ownable(n)
7373                        {
7374                            break;
7375                        }
7376                        let r = &self.results[n];
7377                        // Monitor markers ride along (they are stripped by
7378                        // reconstruct_synchronized); any OTHER statement
7379                        // stops the chain — absorbing real code past the
7380                        // span would newly protect it (a checked catch
7381                        // would become illegal).
7382                        if !r.stmts.is_empty()
7383                            && !r.stmts.iter().all(|s| {
7384                                matches!(
7385                                    s,
7386                                    crate::ir::stmt::Stmt::MonitorExit(_)
7387                                        | crate::ir::stmt::Stmt::MonitorEnter(_)
7388                                )
7389                            })
7390                        {
7391                            break;
7392                        }
7393                        match &r.term {
7394                            Term::Fallthrough => {
7395                                body_universe.insert(n);
7396                                x = n;
7397                            }
7398                            Term::Return(_) => {
7399                                body_universe.insert(n);
7400                                break;
7401                            }
7402                            _ => break,
7403                        }
7404                    }
7405                }
7406            }
7407        }
7408        let mut outer_cont_some = false;
7409        let body = match self.cfg.block_at(g.start) {
7410            Some(entry) if body_universe.contains(&entry) => {
7411                // A group whose protected span starts AT an enclosing
7412                // loop's header arrives here with the header already
7413                // claimed (structure_loop / the SESE loop branch claim it
7414                // before walking the body, and the group-yield filter
7415                // deliberately lets the body walk re-find the group at
7416                // its start). The body walk must then tolerate the
7417                // claimed entry like structure_loop does — refusing it
7418                // collapsed the whole protected body into the back-edge
7419                // Goto and the try emitted as `try { continue; }`
7420                // (jdk11 HttpURLConnection$ErrorStream.getErrorStream:
7421                // the `len = is.read(..)` do-while body lost —
7422                // 在相应的try语句主体中不能抛出异常错误SocketTimeoutException).
7423                let allow = claimed.contains(&entry);
7424                let mut r = self.walk(
7425                    entry,
7426                    &body_universe,
7427                    &HashSet::default(),
7428                    &nested,
7429                    claimed,
7430                    allow,
7431                );
7432                // Flow leaving the try body to the post-try continuation is
7433                // natural fallthrough (the outer walk picks it up there).
7434                let cont = self
7435                    .continuation_after(g.end, outer_universe, claimed, stop)
7436                    .filter(|c| {
7437                        !self.handler_flow_only(gi).contains(c) && !body_universe.contains(c)
7438                    });
7439                outer_cont_some = cont.is_some();
7440                if crate::dbg_flag!("JCDC_DBG_IF") {
7441                    eprintln!("try gi={} cont={:?} universe_has_blocks_after_end={} outer_universe={:?} stop={:?}", gi, cont,
7442                        universe.iter().filter(|b| self.cfg.blocks[**b].start >= g.end).count(),
7443                        outer_universe, stop);
7444                }
7445                if let Some(c) = cont {
7446                    self.strip_trailing_goto_to(&mut r, c);
7447                }
7448                r
7449            }
7450            _ => Region::Empty,
7451        };
7452
7453        // Merge multi-catch: consecutive handlers with the same handler block
7454        // become one catch with multiple types.
7455        if crate::dbg_flag!("JCDC_DBG_CATCH") {
7456            eprintln!(
7457                "[grp] gi={} span=({},{}) handlers={:?} exc_ranges={:?}",
7458                gi,
7459                g.start,
7460                g.end,
7461                g.handlers,
7462                self.cfg.exc_ranges.iter().map(|r| (r.start, r.end, r.handler)).collect::<Vec<_>>()
7463            );
7464        }
7465        let mut merged_handlers: Vec<(Vec<std::sync::Arc<str>>, u32, usize)> = Vec::new(); // types, hpc, hb
7466        for (hpc, ty) in &g.handlers {
7467            let Some(hb) = self.cfg.block_at(*hpc) else {
7468                continue;
7469            };
7470            if self.handler_group.get(&hb) != Some(&gi) {
7471                continue;
7472            }
7473            if let Some(last) = merged_handlers.last_mut() {
7474                if last.2 == hb {
7475                    if let Some(t) = ty {
7476                        last.0.push(t.clone());
7477                    }
7478                    continue;
7479                }
7480            }
7481            merged_handlers.push((ty.clone().into_iter().collect(), *hpc, hb));
7482        }
7483        let mut catches = Vec::new();
7484        for (tys, _hpc, hb) in &merged_handlers {
7485            // Confluent handler: exception flow resumes exactly where
7486            // the body's normal fallthrough goes — an empty catch plus
7487            // the shared tail emitted ONCE by the post-try walk (the
7488            // caller relaxes its is_handler filter via
7489            // is_confluent_tail). Walking the tail as the catch body
7490            // pulled it off the normal path and bound the exception
7491            // param into its first register read (`a =
7492            // noSuchFieldError;` with `static int[] a`, weixin sp2/i2).
7493            if self.is_confluent_tail(gi, *hb) {
7494                catches.push((tys.clone(), *hb, Box::new(Region::Seq(Vec::new()))));
7495                continue;
7496            }
7497            let mut hstop: HashSet<usize> = HashSet::default();
7498            for b in universe.iter().copied() {
7499                if self.body_group.get(&b) == Some(&gi) && b != *hb {
7500                    hstop.insert(b);
7501                }
7502            }
7503            for (h2, _) in &g.handlers {
7504                if let Some(hb2) = self.cfg.block_at(*h2) {
7505                    if hb2 != *hb {
7506                        hstop.insert(hb2);
7507                    }
7508                }
7509            }
7510            let mut huniverse = reachable_within(self.cfg, *hb, &hstop);
7511            if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7512                eprintln!(
7513                    "HUNIV0 gi={} hb={} hstop={:?} reach={:?}",
7514                    gi,
7515                    hb,
7516                    {
7517                        let mut v: Vec<usize> = hstop.iter().copied().collect();
7518                        v.sort();
7519                        v
7520                    },
7521                    {
7522                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7523                        v.sort();
7524                        v
7525                    }
7526                );
7527            }
7528            // Keep the handler walk out of try-body blocks that start before
7529            // this group's end (real protected code), and out of other
7530            // handlers' heads. Blocks past the group end may legitimately
7531            // belong to an enclosing group's span while being part of this
7532            // handler's flow (nested try-with-resources).
7533            huniverse.retain(|b| {
7534                // The handler head rides inside the group span when d8's
7535                // self-protection range over the handler's own monitorexit
7536                // merged into this group — it must stay (it IS the walk
7537                // start; excluding it emptied the universe and the walk
7538                // never reached the rethrow tail, swallowing the
7539                // exception: reqable s2/w.a `catch (Throwable) {
7540                // monitorexit }` with the bytecode's throw gone).
7541                let body_before_end = *b != *hb
7542                    && self
7543                        .body_group
7544                        .get(b)
7545                        .map(|_ogi| self.cfg.blocks[*b].start < g.end)
7546                        .unwrap_or(false);
7547                !body_before_end && (!self.handler_group.contains_key(b) || *b == *hb)
7548            });
7549            // Also exclude the post-try continuation flow: blocks reachable
7550            // from the group's end are shared with the normal path and must
7551            // not be absorbed into the handler. `block_at(g.end)` misses the
7552            // merge when the handler itself sits at the span end (javac
7553            // excludes the try body's trailing return from the protected
7554            // range: jdk26 AlgorithmId.getName span (27,62), areturn at 62,
7555            // handler astore at 63 -> block_at(62) resolves to the areturn,
7556            // and the `goto merge` tail from 63 drags the WHOLE shared
7557            // `if (o != null) return o.stdName(); else ...` merge into the
7558            // catch, leaving every other path without its return --
7559            // 缺少返回语句). Gate on the merge shape directly: an unclaimed
7560            // non-group block whose normal preds are already claimed by the
7561            // surrounding flow is the post-try merge -- the handler reaches
7562            // it only by jumping out, which strip_handler_exit_goto already
7563            // renders as the natural fallthrough.
7564            let hf = self.handler_flow_only(gi);
7565            // An enclosing loop's BACKEDGE STUB (a Goto block whose
7566            // single target is/leads to an open loop header — the
7567            // handler's `dataStream.reset(); goto head` continue tail)
7568            // is NOT a post-try merge: stripping it from the handler
7569            // universe leaves the handler's branch arms dangling
7570            // (Region::Empty) and the flow re-copies the whole loop body
7571            // into the catch (jdk11 KeyStore.getInstance(File)'s
7572            // IOException catch swallowed the provider loop with an
7573            // unprotected Security.getImpl — 未报告的异常错误
7574            // NoSuchProviderException; regression of the loop-header
7575            // circulation guard exposing this path).
7576            let backedge_stub = |b: usize| -> bool {
7577                if !matches!(self.results[b].term, Term::Goto) {
7578                    return false;
7579                }
7580                let succs = &self.cfg.blocks[b].succ;
7581                if succs.len() != 1 {
7582                    return false;
7583                }
7584                let t = succs[0];
7585                self.loops_stack
7586                    .iter()
7587                    .chain(self.sese_loop_headers.iter())
7588                    .any(|&h| h == t || self.is_stmt_free_chain_to_block(t, h))
7589            };
7590            let mut shared_merge: Vec<usize> = huniverse
7591                .iter()
7592                .copied()
7593                .filter(|b| {
7594                    *b != *hb
7595                        && !hf.contains(b)
7596                        && !claimed.contains(b)
7597                        && !backedge_stub(*b)
7598                        && self.body_group.get(b) != Some(&gi)
7599                        && self.handler_group.get(b) != Some(&gi)
7600                        && self.cfg.blocks[*b].pred.iter().any(|p| {
7601                            claimed.contains(p)
7602                                && !self.handler_group.contains_key(p)
7603                                && self.body_group.get(p) != Some(&gi)
7604                        })
7605                })
7606                .collect();
7607            // Deterministic pick: huniverse is a HashSet whose iteration
7608            // order varies per process, and shared_merge[0] selects the
7609            // post-try tail to strip. Without the sort, jdk8
7610            // JMXConnectorFactory.connect flip-flopped between the
7611            // correct nested-if shape and a broken else-if chain that
7612            // trapped the shared getProvider tail inside the IOException
7613            // catch (losing it on the null-classloader and normal paths).
7614            // Earliest-pc first: it is the flow confluence, and its
7615            // reachable tail is the superset covering later candidates.
7616            shared_merge.sort_by_key(|b| self.cfg.blocks[*b].start);
7617            if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7618                eprintln!(
7619                    "HUNIV1 gi={} hb={} shared_merge={:?} huniverse={:?}",
7620                    gi,
7621                    hb,
7622                    shared_merge,
7623                    {
7624                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7625                        v.sort();
7626                        v
7627                    }
7628                );
7629            }
7630            if !shared_merge.is_empty() {
7631                let tail = reachable_within(self.cfg, shared_merge[0], &HashSet::default());
7632                huniverse.retain(|b| !tail.contains(b) || *b == *hb || hf.contains(b));
7633                if crate::dbg_flag!("JCDC_DBG_HUNIV") {
7634                    eprintln!("HUNIV2 gi={} after-strip={:?}", gi, {
7635                        let mut v: Vec<usize> = huniverse.iter().copied().collect();
7636                        v.sort();
7637                        v
7638                    });
7639                }
7640            }
7641            // The cont-tail exclusion must not fire when the block at the
7642            // span end IS the handler flow (javac excludes the body's
7643            // trailing return from the span, so the handler entry sits AT
7644            // g.end: jdk11 AQS.acquireQueued span (2,57) merged from
7645            // (2,37)+(38,57), handler at 57). Excluding reachable(handler)
7646            // there strips the handler's OWN tail (`selfInterrupt();
7647            // throw t;`) out of its universe -- the catch loses it and the
7648            // outer continuation emits it as a top-level sibling
7649            // (未报告的异常错误Throwable).
7650            if let Some(cont) = self.cfg.block_at(g.end) {
7651                if cont == *hb {
7652                    // The handler entry sits AT the span end (merged spans,
7653                    // javac excluding the body's trailing terminator). Keep
7654                    // the handler's PRIVATE tail — successors owned by no
7655                    // group and no other group's handler (AQS.acquireQueued
7656                    // `if (interrupted) selfInterrupt(); throw t;`) — and
7657                    // strip the rest: successors OWNED by an enclosing try
7658                    // are the shared post-try flow / inline finally copies
7659                    // (feat Exceptions nestedTry: the try body always
7660                    // throws, so the d/e/g/return chain is structurally
7661                    // handler-only but semantically the continuation —
7662                    // swallowing it into the catch snapshotted the return
7663                    // before the real finally g ran, output lost its g).
7664                    // Private-tail keep set: successors not owned by
7665                    // ANOTHER group (nor another group's handler) whose
7666                    // preds stay within the handler's own flow. Only for
7667                    // SOLE-ENTRY handlers (the body always completes
7668                    // abruptly — nestedTry); a multi-entry handler's
7669                    // reachable set mixes in the genuine shared
7670                    // continuation and stripping it starves the post-try
7671                    // walk (jdk Module TWR).
7672
7673                    // Body-flow reachability per enclosing group: a block
7674                    // owned by group og that og's own body flow REACHES
7675                    // (from its span-start block, through blocks it owns,
7676                    // without stepping on handler heads) will be re-emitted
7677                    // by og's body walk — stripping it from this handler's
7678                    // universe is safe (feat Exceptions nestedTry: the
7679                    // d/e/g continuation chain is gi=0 body flow). A block
7680                    // merely GEOGRAPHICALLY inside og's span but reachable
7681                    // only through handler flow is this handler's private
7682                    // tail — stripping loses it entirely (jdk11
7683                    // ServerSocketAdaptor.accept: the catch(Exception)'s
7684                    // assert-throw / `return sc.socket()` tail sits inside
7685                    // the enclosing sync group's span but its body flow
7686                    // never reaches it — stripped, the catch fell off the
7687                    // method end: 缺少返回语句).
7688                    let mut og_body_reach: HashMap<usize, HashSet<usize>> = HashMap::default();
7689                    {
7690                        let mut by_og: HashMap<usize, Vec<usize>> = HashMap::default();
7691                        for (b, og) in self.body_group.iter() {
7692                            by_og.entry(*og).or_default().push(*b);
7693                        }
7694                        for (og, _) in by_og {
7695                            // Normal-flow reachability from the group's
7696                            // body entry, bounded by the group's span and
7697                            // barred at handler heads: nested groups'
7698                            // blocks lie on the body flow (the walk
7699                            // carves them out but continues after), so
7700                            // the BFS must pass through them.
7701                            let g = &self.groups[og];
7702                            let mut reach: HashSet<usize> = HashSet::default();
7703                            if let Some(entry) = self.cfg.block_at(g.start) {
7704                                let mut q: VecDeque<usize> = VecDeque::new();
7705                                q.push_back(entry);
7706                                reach.insert(entry);
7707                                // Groups carved out of this span: the
7708                                // body walk resumes at their post-try
7709                                // continuation, so the BFS hops the same
7710                                // way — BUT never onto handler-flow-only
7711                                // blocks of THIS structure_try's group
7712                                // (accept's gi=0 body cont would land on
7713                                // the catch's private assert-throw tail;
7714                                // the hf_after filter skips it there, and
7715                                // letting the BFS see it would mark the
7716                                // tail re-emittable and strip it).
7717                                let mut conts: HashMap<usize, usize> = HashMap::default();
7718                                for (j, gj) in self.groups.iter().enumerate() {
7719                                    if j == og {
7720                                        continue;
7721                                    }
7722                                    if gj.start < g.start || gj.end > g.end.max(g.start + 1) {
7723                                        continue;
7724                                    }
7725                                    // The exclusion set must be the
7726                                    // CARVED group's own hf (that is what
7727                                    // the body walk's continuation_after
7728                                    // filter consults), not this
7729                                    // structure_try's hf.
7730                                    let hf_j = self.handler_flow_only(j);
7731                                    let mut cont: Option<usize> = None;
7732                                    for nb in self.cfg.blocks.iter() {
7733                                        if nb.start < gj.end || nb.ins_len == 0 {
7734                                            continue;
7735                                        }
7736                                        if self.handler_group.contains_key(&nb.id) {
7737                                            continue;
7738                                        }
7739                                        if hf_j.contains(&nb.id) {
7740                                            continue;
7741                                        }
7742                                        cont = Some(nb.id);
7743                                        break;
7744                                    }
7745                                    if let Some(c) = cont {
7746                                        for nb in self.cfg.blocks.iter() {
7747                                            if nb.ins_len != 0
7748                                                && nb.start >= g.start
7749                                                && nb.end <= gj.end.max(gj.start + 1)
7750                                                && !self.handler_group.contains_key(&nb.id)
7751                                            {
7752                                                conts.entry(nb.id).or_insert(c);
7753                                            }
7754                                        }
7755                                    }
7756                                }
7757                                while let Some(x) = q.pop_front() {
7758                                    let mut edges: Vec<usize> = self.cfg.blocks[x].succ.clone();
7759                                    if let Some(&c) = conts.get(&x) {
7760                                        edges.push(c);
7761                                    }
7762                                    for sx in edges {
7763                                        let sb = &self.cfg.blocks[sx];
7764                                        if sb.start >= g.start
7765                                            && sb.end <= g.end.max(g.start + 1)
7766                                            && !self.handler_group.contains_key(&sx)
7767                                            && reach.insert(sx)
7768                                        {
7769                                            q.push_back(sx);
7770                                        }
7771                                    }
7772                                }
7773                            }
7774                            og_body_reach.insert(og, reach);
7775                        }
7776                    }
7777                    let mut private: HashSet<usize> = HashSet::default();
7778                    {
7779                        let mut pq: VecDeque<usize> = VecDeque::new();
7780                        for &s0 in &self.cfg.blocks[*hb].succ {
7781                            pq.push_back(s0);
7782                        }
7783                        while let Some(x) = pq.pop_front() {
7784                            if private.contains(&x) || x == *hb {
7785                                continue;
7786                            }
7787                            match self.body_group.get(&x) {
7788                                Some(og) if *og != gi
7789                                    // Owned by another group: private only
7790                                    // when that group's body flow cannot
7791                                    // re-emit it.
7792                                    && og_body_reach
7793                                        .get(og)
7794                                        .map(|r| r.contains(&x))
7795                                        .unwrap_or(false)
7796                                    => {
7797                                        continue;
7798                                    }
7799                                _ => {}
7800                            }
7801                            if self
7802                                .handler_group
7803                                .get(&x)
7804                                .map(|og| *og != gi)
7805                                .unwrap_or(false)
7806                            {
7807                                continue;
7808                            }
7809                            if self.cfg.blocks[x].pred.iter().all(|p| {
7810                                *p == *hb
7811                                    || private.contains(p)
7812                                    || self.handler_group.get(p) == Some(&gi)
7813                            }) {
7814                                private.insert(x);
7815                                for &s1 in &self.cfg.blocks[x].succ {
7816                                    pq.push_back(s1);
7817                                }
7818                            }
7819                        }
7820                    }
7821                    let sole_entry_hb = self.cfg.blocks[*hb]
7822                        .pred
7823                        .iter()
7824                        .all(|p| self.handler_group.contains_key(p));
7825                    let tail = reachable_within(self.cfg, cont, &HashSet::default());
7826                    if sole_entry_hb {
7827                        huniverse.retain(|b| !tail.contains(b) || private.contains(b));
7828                    }
7829                    // Multi-entry cont==hb: no strip (the f4472a5f shape —
7830                    // the shared_merge machinery and outer walks handle
7831                    // it; the confluent fallthrough case short-circuits
7832                    // to an empty catch before the universe is built).
7833                } else if outer_cont_some
7834                    && !self.handler_group.contains_key(&cont)
7835                    && !hf.contains(&cont)
7836                {
7837                    // Strip the span-end block's reachable tail from the
7838                    // handler universe ONLY when the outer walk will emit
7839                    // a post-try continuation: the tail is shared with
7840                    // the normal path then. When cont is None (every
7841                    // block past the span end is already claimed — the
7842                    // body walk's follow chain absorbed the span-end
7843                    // goto and the method-final tail), stripping orphans
7844                    // the tail entirely: SSLSessionContextImpl
7845                    // getDefaults' body walk claimed the span-end
7846                    // `goto 234` (pc 205) and the final `return 20480`
7847                    // (pc 234); the catch-else arm's term-copy route was
7848                    // the only emitter left, and the strip starved it —
7849                    // 缺少返回语句, sj17 walk-only since HEAD (SESE renders
7850                    // the tail; javac masked it behind OCSP's DA error
7851                    // until the rejection suite fixed that).
7852                    let tail = reachable_within(self.cfg, cont, &HashSet::default());
7853                    huniverse.retain(|b| !tail.contains(b) || hf.contains(b));
7854                }
7855            }
7856            huniverse.insert(*hb);
7857            // Exception groups nested inside the handler region (e.g. a
7858            // synchronized block within a catch) must be visible to the
7859            // handler walk so they get carved out as their own Try regions.
7860            let hmin = huniverse
7861                .iter()
7862                .map(|b| self.cfg.blocks[*b].start)
7863                .min()
7864                .unwrap_or(u32::MAX);
7865            let hmax = huniverse
7866                .iter()
7867                .map(|b| self.cfg.blocks[*b].end)
7868                .max()
7869                .unwrap_or(0);
7870            let h_active: Vec<usize> = (0..self.groups.len())
7871                .filter(|&j| {
7872                    if j == gi {
7873                        return false;
7874                    }
7875                    let gj = &self.groups[j];
7876                    if gj.start < hmin || gj.end > hmax {
7877                        return false;
7878                    }
7879                    // Handler self-protection ranges (the handler guards its
7880                    // own monitorexit) are not separate regions.
7881                    !gj.handlers.iter().all(|(h, _)| *h == gj.start)
7882                })
7883                .collect();
7884            let mut r = self.walk(*hb, &huniverse, &HashSet::default(), &h_active, claimed, false);
7885            // Handler exits into the post-try flow: any forward target that
7886            // is not part of a try body is a natural merge (the outer walk
7887            // emits those blocks after this region).
7888            self.strip_handler_exit_goto(&mut r, g.end);
7889            catches.push((tys.clone(), *hb, Box::new(r)));
7890        }
7891        self.structuring_groups.borrow_mut().retain(|&x| x != gi);
7892        Region::Try {
7893            group_idx: gi,
7894            body: Box::new(body),
7895            catches,
7896        }
7897    }
7898}
7899
7900/// Reachability from `from` to `to` via normal edges, with a work budget.
7901pub fn can_reach_cfg(cfg: &Cfg, from: usize, to: usize, budget: usize) -> bool {
7902    if from == to {
7903        return true;
7904    }
7905    let mut seen = HashSet::default();
7906    let mut q = std::collections::VecDeque::new();
7907    q.push_back(from);
7908    seen.insert(from);
7909    let mut n = 0;
7910    while let Some(b) = q.pop_front() {
7911        n += 1;
7912        if n > budget {
7913            return false;
7914        }
7915        for &s in &cfg.blocks[b].succ {
7916            if s == to {
7917                return true;
7918            }
7919            if seen.insert(s) {
7920                q.push_back(s);
7921            }
7922        }
7923    }
7924    false
7925}
7926
7927/// True if `to` is reachable from `from` without stepping on `avoid`.
7928pub fn can_reach_avoiding(
7929    cfg: &Cfg,
7930    exc_succ: &HashMap<usize, Vec<usize>>,
7931    from: usize,
7932    to: usize,
7933    avoid: usize,
7934    budget: usize,
7935) -> bool {
7936    if from == to {
7937        return true;
7938    }
7939    let mut seen = HashSet::default();
7940    let mut q = std::collections::VecDeque::new();
7941    if from != avoid {
7942        q.push_back(from);
7943        seen.insert(from);
7944    }
7945    let mut n = 0;
7946    while let Some(b) = q.pop_front() {
7947        n += 1;
7948        if n > budget {
7949            return false;
7950        }
7951        for &s in &cfg.blocks[b].succ {
7952            if s == to {
7953                return true;
7954            }
7955            if s != avoid && seen.insert(s) {
7956                q.push_back(s);
7957            }
7958        }
7959        if let Some(xs) = exc_succ.get(&b) {
7960            for &s in xs {
7961                if s == to {
7962                    return true;
7963                }
7964                if s != avoid && seen.insert(s) {
7965                    q.push_back(s);
7966                }
7967            }
7968        }
7969    }
7970    false
7971}
7972
7973/// Reachability from `from` to `to` that never steps on barrier blocks
7974/// (other than `from` itself). Follows normal AND exception successors
7975/// (`exc_succ`), so a protected block whose normal exits all return/throw is
7976/// still seen to loop back through its handler.
7977pub fn can_reach_cfg_barred(
7978    cfg: &Cfg,
7979    exc_succ: &HashMap<usize, Vec<usize>>,
7980    from: usize,
7981    to: usize,
7982    barriers: &HashSet<usize>,
7983    budget: usize,
7984) -> bool {
7985    if from == to {
7986        return true;
7987    }
7988    let mut seen = HashSet::default();
7989    let mut q = std::collections::VecDeque::new();
7990    q.push_back(from);
7991    seen.insert(from);
7992    let mut n = 0;
7993    while let Some(b) = q.pop_front() {
7994        n += 1;
7995        if n > budget {
7996            return false;
7997        }
7998        for &s in &cfg.blocks[b].succ {
7999            if s == to {
8000                return true;
8001            }
8002            if !barriers.contains(&s) && seen.insert(s) {
8003                q.push_back(s);
8004            }
8005        }
8006        if let Some(xs) = exc_succ.get(&b) {
8007            for &s in xs {
8008                if s == to {
8009                    return true;
8010                }
8011                if !barriers.contains(&s) && seen.insert(s) {
8012                    q.push_back(s);
8013                }
8014            }
8015        }
8016    }
8017    false
8018}