Skip to main content

jdc_core/
structure.rs

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